#include #include #include #include #include #include #include namespace { std::array zeros() { return {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; } fesa::ResultDatabase valid_database() { fesa::NodalFrame nodal{ {fesa::NodeId{0}, fesa::NodeId{1}}, { fesa::EntityOrigin{"BeamPart", "Beam-1", 10}, fesa::EntityOrigin{"BeamPart", "Beam-1", 20}, }, { zeros(), {1.0, 2.0, 3.0, 0.1, 0.2, 0.3}, }, { {-10.0, -20.0, -30.0, -1.0, -2.0, -3.0}, zeros(), }, }; fesa::BeamElementFrame beam{ fesa::ElementId{3}, fesa::EntityOrigin{"BeamPart", "Beam-1", 30}, { fesa::Vec3{1.0, 0.0, 0.0}, fesa::Vec3{0.0, 1.0, 0.0}, fesa::Vec3{0.0, 0.0, 1.0}, }, {{ { -1.0, fesa::NodeId{0}, zeros(), zeros(), 0.0, {1.0}, }, { 1.0, fesa::NodeId{1}, zeros(), zeros(), 0.0, {2.0}, }, }}, }; fesa::ResultFrame frame{ 1.0, std::move(nodal), {{std::move(beam)}}, {}, }; fesa::ResultStep step{"Load", {std::move(frame)}}; return {"1.0.0", {std::move(step)}}; } bool has_diagnostic( const fesa::Status& status, const std::string_view code) { return std::ranges::any_of( status.diagnostics, [code](const fesa::Diagnostic& diagnostic) { return diagnostic.code == code && diagnostic.stage == fesa::DiagnosticStage::results && diagnostic.severity == fesa::Severity::error; }); } TEST(NodalFrame, AcceptsFiniteSixComponentFieldsWithMatchingNodeIds) { const auto database = valid_database(); const auto status = fesa::validate_result_database(database); EXPECT_TRUE(status.succeeded); EXPECT_TRUE(status.diagnostics.empty()); } TEST(NodalFrame, RejectsDisplacementSizeMismatch) { auto database = valid_database(); database.steps[0].frames[0].nodal.displacement.pop_back(); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.nodal_size_mismatch")); } TEST(NodalFrame, RejectsReactionSizeMismatch) { auto database = valid_database(); database.steps[0].frames[0].nodal.reaction.pop_back(); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.nodal_size_mismatch")); } TEST(CompleteResultContract, RejectsNodeOriginSizeMismatch) { auto database = valid_database(); database.steps[0].frames[0].nodal.origins.pop_back(); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic( status, "results.nodal_origin_size_mismatch")); } TEST(NodalFrame, RejectsDuplicateNodeId) { auto database = valid_database(); database.steps[0].frames[0].nodal.node_ids[1] = fesa::NodeId{0}; const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.duplicate_node_id")); } TEST(NodalFrame, RejectsNonfiniteDisplacement) { auto database = valid_database(); database.steps[0].frames[0].nodal.displacement[1][2] = std::numeric_limits::quiet_NaN(); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value")); } TEST(NodalFrame, RejectsNonfiniteReaction) { auto database = valid_database(); database.steps[0].frames[0].nodal.reaction[0][4] = std::numeric_limits::infinity(); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value")); } TEST(ResultDatabase, RejectsDuplicateStepName) { auto database = valid_database(); database.steps.push_back(database.steps[0]); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.duplicate_step_name")); } TEST(ResultDatabase, RejectsDuplicateFrameTimeWithinStep) { auto database = valid_database(); database.steps[0].frames.push_back(database.steps[0].frames[0]); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.duplicate_frame_time")); } TEST(ResultDatabase, RejectsNonfiniteFrameTime) { auto database = valid_database(); database.steps[0].frames[0].step_time = std::numeric_limits::quiet_NaN(); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value")); } TEST(CompleteResultContract, DeclaresCoordinatesAndComponentOrdering) { EXPECT_EQ( fesa::NodalFrame::coordinate_system, fesa::FieldCoordinateSystem::global); EXPECT_EQ( fesa::NodalFrame::displacement_components, (std::array{ "Ux", "Uy", "Uz", "Rx", "Ry", "Rz"})); EXPECT_EQ( fesa::NodalFrame::reaction_components, (std::array{ "RFx", "RFy", "RFz", "RMx", "RMy", "RMz"})); EXPECT_EQ( fesa::BeamElementFrame::coordinate_system, fesa::FieldCoordinateSystem::element_local); EXPECT_EQ( fesa::BeamElementFrame::section_strain_components, (std::array{ "epsilon", "gamma_y", "gamma_z", "kappa_x", "kappa_y", "kappa_z"})); EXPECT_EQ( fesa::BeamElementFrame::section_force_components, (std::array{ "N", "Vy", "Vz", "T", "My", "Mz"})); EXPECT_EQ( fesa::BeamElementFrame::axial_stress_component, std::string_view{"sigma_xx"}); } TEST(ElementFrame, AcceptsFiniteConnectedBeamResults) { const auto status = fesa::validate_result_database(valid_database()); EXPECT_TRUE(status.succeeded); EXPECT_TRUE(status.diagnostics.empty()); } TEST(ElementFrame, RejectsDuplicateElementId) { auto database = valid_database(); auto& beams = database.steps[0].frames[0].element.beams; beams.push_back(beams[0]); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.duplicate_element_id")); } TEST(ElementFrame, RejectsDuplicateEndNode) { auto database = valid_database(); auto& ends = database.steps[0].frames[0] .element.beams[0].end_results; ends[1].end_node = ends[0].end_node; const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.duplicate_beam_end_node")); } TEST(ElementFrame, RejectsWrongEndConnectivity) { auto database = valid_database(); database.steps[0].frames[0] .element.beams[0].end_results[0].xi = 1.0; const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.invalid_beam_connectivity")); } TEST(ElementFrame, RejectsEndNodeAbsentFromNodalFrame) { auto database = valid_database(); database.steps[0].frames[0] .element.beams[0].end_results[1].end_node = fesa::NodeId{99}; const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.invalid_beam_connectivity")); } TEST(ElementFrame, RejectsNonfiniteLocalFrameAndSectionValue) { auto database = valid_database(); auto& beam = database.steps[0].frames[0].element.beams[0]; beam.local_frame.ey.y = std::numeric_limits::infinity(); beam.end_results[1].section_force[4] = std::numeric_limits::quiet_NaN(); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic(status, "results.nonfinite_value")); } TEST(ElementFrame, RejectsMismatchedRecoveryPointCount) { auto database = valid_database(); database.steps[0].frames[0] .element.beams[0].end_results[1].sigma_xx.push_back(3.0); const auto status = fesa::validate_result_database(database); EXPECT_FALSE(status.succeeded); EXPECT_TRUE(has_diagnostic( status, "results.recovery_point_count_mismatch")); } } // namespace