From 21ab801391555b659a1c8726f8618f5ed8d3fbe7 Mon Sep 17 00:00:00 2001 From: lakshanashreee Date: Fri, 27 Jun 2025 19:09:22 +0530 Subject: [PATCH 1/3] Added validator and test_validator files --- tests/test_validator.py | 50 +++++++++++++++++++++++++++++ tests/validator.py | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 tests/test_validator.py create mode 100644 tests/validator.py diff --git a/tests/test_validator.py b/tests/test_validator.py new file mode 100644 index 000000000..0b28229fb --- /dev/null +++ b/tests/test_validator.py @@ -0,0 +1,50 @@ +import os +import pytest +import validator # Ensure validator.py is in the same folder or PYTHONPATH + +# ✅ Updated base path +BASE_PATH = r"C:\Visual Studio Code\FOSSEE OSDAG\Osdag\osdag-conda-recipe\recipe\tests\osi_files" + +def run_test(file_name, expected_result): + file_path = os.path.join(BASE_PATH, file_name) + result = validator.validate_osi(file_path) + print(f"Testing {file_name}") + for key in expected_result: + assert result[key] == expected_result[key], f"{key} failed: expected {expected_result[key]}, got {result[key]}" + print(" Passed\n") + +def test_tension_bolted_test1(): + expected_result = { + "Member.Designation": "Pass", + "Bolt.Grade": "Pass", + "Bolt.Diameter": "" # Skipped check + } + run_test("TensionBoltedTest1.osi", expected_result) + +def test_tension_bolted_test2(): + expected_result = { + "Member.Designation": "Pass", + "Bolt.Grade": "Pass", + "Bolt.Diameter": "Pass" + } + run_test("TensionBoltedTest2.osi", expected_result) + +def test_tension_bolted_test3(): + expected_result = { + "Member.Designation": "Pass", + "Bolt.Grade": "Pass", + "Bolt.Diameter": "Pass" + } + run_test("TensionBoltedTest3.osi", expected_result) + +def test_tension_bolted_test4(): + expected_result = { + "Member.Designation": "Pass", + "Bolt.Grade": "Pass", + "Bolt.Diameter": "Pass" + } + run_test("TensionBoltedTest4.osi", expected_result) + +if __name__ == "__main__": + pytest.main() + diff --git a/tests/validator.py b/tests/validator.py new file mode 100644 index 000000000..8996e1a06 --- /dev/null +++ b/tests/validator.py @@ -0,0 +1,70 @@ +import os + +# Valid entries per file +valid_designations = { + "TensionBoltedTest1": ["40 x 20 x 3"], + "TensionBoltedTest2": ["JC 100"], + "TensionBoltedTest3": ["40 x 40 x 3"], + "TensionBoltedTest4": ["JC 175"], +} +valid_bolt_grades = ["3.6", "12.9", "4.6", "5.6"] +valid_bolt_diameters = { + "TensionBoltedTest1": [""], # skip check + "TensionBoltedTest2": ["12"], + "TensionBoltedTest3": ["10"], + "TensionBoltedTest4": ["20"], +} + +def normalize(val): + return val.strip().strip("'").strip('"').lower() + +def extract_multiple_values(file_path): + values = {"Member.Designation": [], "Bolt.Grade": [], "Bolt.Diameter": []} + + # Fix: Use absolute fallback if path doesn't exist + if not os.path.isfile(file_path): + fallback_dir = r"C:\Visual Studio Code\FOSSEE OSDAG\Osdag\tests\osi_files" + file_path = os.path.join(fallback_dir, os.path.basename(file_path)) + + with open(file_path, "r") as f: + current_key = None + for line in f: + line = line.strip() + if ":" in line: + key, _ = line.split(":", 1) + current_key = key.strip() + if current_key in values: + values[current_key] = [] + elif current_key and current_key in values and line.startswith("-"): + values[current_key].append(line.strip("- ").strip()) + return values + +def validate_osi(file_path): + filename = os.path.splitext(os.path.basename(file_path))[0] + data = extract_multiple_values(file_path) + + normalized_data = { + "Member.Designation": [normalize(d) for d in data["Member.Designation"]], + "Bolt.Grade": [normalize(g) for g in data["Bolt.Grade"]], + "Bolt.Diameter": [normalize(d) for d in data["Bolt.Diameter"]], + } + + bolt_dia_check = valid_bolt_diameters.get(filename, []) + bolt_dia_check_normalized = [normalize(val) for val in bolt_dia_check if normalize(val) != ""] + + results = {} + + valid_desig = [normalize(val) for val in valid_designations.get(filename, [])] + results["Member.Designation"] = "Pass" if any(d in valid_desig for d in normalized_data["Member.Designation"]) else "Fail" + + valid_grades = [normalize(val) for val in valid_bolt_grades] + results["Bolt.Grade"] = "Pass" if any(g in valid_grades for g in normalized_data["Bolt.Grade"]) else "Fail" + + if not bolt_dia_check or "" in bolt_dia_check: + results["Bolt.Diameter"] = "" # Skip check + else: + results["Bolt.Diameter"] = ( + "Pass" if any(d in bolt_dia_check_normalized for d in normalized_data["Bolt.Diameter"]) else "Fail" + ) + + return results From 55c0d428d6099dc05c542a8ddc5cb4951ff33084 Mon Sep 17 00:00:00 2001 From: lakshanashreee Date: Fri, 27 Jun 2025 20:06:21 +0530 Subject: [PATCH 2/3] Added test_tension_bolted_mock.py to testing framework --- tests/test_fin_plate_mimicry.py | 339 ++++++++++++++++++++++++++++++ tests/test_tension_bolted_mock.py | 220 +++++++++++++++++++ 2 files changed, 559 insertions(+) create mode 100644 tests/test_fin_plate_mimicry.py create mode 100644 tests/test_tension_bolted_mock.py diff --git a/tests/test_fin_plate_mimicry.py b/tests/test_fin_plate_mimicry.py new file mode 100644 index 000000000..a94e2dc8d --- /dev/null +++ b/tests/test_fin_plate_mimicry.py @@ -0,0 +1,339 @@ +import sqlite3 +from design_type.connection.fin_plate_connection import FinPlateConnection +from utils.common.Common import connectdb, connectdb1, MaterialValidator, VALUES_CONN, VALUES_TYP, VALUES_GRD_CUSTOMIZED, VALUES_PLATETHK_CUSTOMIZED +from utils.common.component import PATH_TO_DATABASE + +# function to get ultimate (fu) and yield (fy) strengths for a material grade +def get_fy_fu(material_grade, thickness=None): + """return fu and fy for a material grade, considering thickness for plates.""" + # handle standard material grade e 250 (fe 410 w)a + if material_grade == "E 250 (Fe 410 W)A": + # if no thickness provided, return default fu, fy + if thickness is None: + return 410, 250 + else: + # convert thickness to float for comparison + thickness = float(thickness) + # return fu, fy based on thickness ranges + if thickness <= 20: + return 410, 250, 250, 250 + elif thickness <= 40: + return 410, 250, 240, 240 + else: + return 410, 250, 240, 230 + # handle custom material grades starting with "cus_" + elif material_grade.startswith("Cus_"): + validator = MaterialValidator(material_grade) + # check if custom material is valid + if validator.is_valid_custom(): + parts = material_grade.split('_') + # extract fu and fy from custom grade name + fu, fy = float(parts[-1]), float(parts[-2]) + # return fu, fy based on thickness + if thickness is None: + return fu, fy + else: + return fu, fy, fy, fy + # default fu, fy if material grade is unknown + return 410, 250 + +# function to mimic gui input for fin plate connection +def mimic_fin_plate_inputs(test_case_data): + """mimic gui input collection for fin plate connection, creating a design_dictionary.""" + # copy input data to avoid modifying original + design_dictionary = test_case_data.copy() + + # connect to database to fetch valid values + conn = sqlite3.connect(PATH_TO_DATABASE) + cursor = conn.cursor() + + # define input fields for fin plate connection + input_fields = [ + {"key": "KEY_MODULE", "type": "TYPE_MODULE", "value": "Fin Plate Connection"}, + {"key": "KEY_CONN", "type": "TYPE_COMBOBOX", "values": VALUES_CONN}, + # fetch beams or columns based on connection type + {"key": "KEY_SUPTNGSEC", "type": "TYPE_COMBOBOX", + "values": connectdb("Beams") if test_case_data.get("KEY_CONN") == "Beam-Beam" else connectdb("Columns")}, + {"key": "KEY_SUPTNGSEC_MATERIAL", "type": "TYPE_COMBOBOX", "values": connectdb("Material")}, + {"key": "KEY_SUPTDSEC", "type": "TYPE_COMBOBOX", "values": connectdb("Beams")}, + {"key": "KEY_SUPTDSEC_MATERIAL", "type": "TYPE_COMBOBOX", "values": connectdb("Material")}, + {"key": "KEY_SHEAR", "type": "TYPE_TEXTBOX", "validator": "Int Validator"}, + {"key": "KEY_AXIAL", "type": "TYPE_TEXTBOX", "validator": "Int Validator"}, + {"key": "KEY_D", "type": "TYPE_COMBOBOX_CUSTOMIZED", "values": connectdb1()}, + {"key": "KEY_TYP", "type": "TYPE_COMBOBOX", "values": VALUES_TYP}, + {"key": "KEY_GRD", "type": "TYPE_COMBOBOX_CUSTOMIZED", "values": VALUES_GRD_CUSTOMIZED}, + {"key": "KEY_PLATETHK", "type": "TYPE_COMBOBOX_CUSTOMIZED", "values": VALUES_PLATETHK_CUSTOMIZED}, + {"key": "KEY_CONNECTOR_MATERIAL", "type": "TYPE_COMBOBOX", "values": connectdb("Material")}, + ] + + # define design preference fields + design_pref_fields = [ + {"key": "KEY_DP_BOLT_TYPE", "type": "TYPE_COMBOBOX", "values": ["Pretensioned", "Non pre-tensioned"]}, + {"key": "KEY_DP_BOLT_HOLE_TYPE", "type": "TYPE_COMBOBOX", "values": ["Standard", "Over-sized"]}, + {"key": "KEY_DP_BOLT_SLIP_FACTOR", "type": "TYPE_TEXTBOX", "value": "0.3"}, + {"key": "KEY_DP_WELD_FAB", "type": "TYPE_COMBOBOX", "values": ["Shop Weld", "Field Weld"]}, + {"key": "KEY_DP_WELD_MATERIAL_G_O", "type": "TYPE_TEXTBOX", "value": "410"}, + {"key": "KEY_DP_DETAILING_EDGE_TYPE", "type": "TYPE_COMBOBOX", + "values": ["Sheared or hand flame cut", "Rolled, machine-flame cut, sawn and planed"]}, + {"key": "KEY_DP_DETAILING_GAP", "type": "TYPE_TEXTBOX", "value": "10"}, + {"key": "KEY_DP_DETAILING_CORROSIVE_INFLUENCES", "type": "TYPE_COMBOBOX", "values": ["No", "Yes"]}, + {"key": "KEY_DP_DESIGN_METHOD", "type": "TYPE_COMBOBOX", "values": ["Limit State Design"]}, + ] + + # validate each input field + for field in input_fields: + key = field["key"] + input_type = field["type"] + valid_values = field.get("values", []) + + value = design_dictionary.get(key) + + # check combobox values are valid + if input_type == "TYPE_COMBOBOX": + if value not in valid_values and value not in ["", None]: + raise ValueError(f"Invalid value '{value}' for {key}. Valid: {valid_values}") + # check customized combobox values + elif input_type == "TYPE_COMBOBOX_CUSTOMIZED": + if isinstance(valid_values, list) and valid_values and isinstance(valid_values[0], list): + valid_values = valid_values[0] + if value not in valid_values and value not in ["", None]: + raise ValueError(f"Invalid value '{value}' for {key}. Valid: {valid_values}") + # validate textbox inputs as integers + elif input_type == "TYPE_TEXTBOX" and field.get("validator") == "Int Validator": + try: + if value not in ["", None]: + int_value = int(value) + if int_value <= 0: + print(f"Warning: {key} must be positive. Using default: 1") + design_dictionary[key] = "1" + else: + design_dictionary[key] = "" + except ValueError: + print(f"Error: Invalid {key}: '{value}'. Using default: 1") + design_dictionary[key] = "1" + + # validate custom material grades + if key.endswith("_MATERIAL") and value and value.startswith("Cus_"): + validator = MaterialValidator(value) + if not validator.is_valid_custom(): + print(f"Warning: Invalid custom material '{value}' for {key}. Using default: E 250 (Fe 410 W)A") + design_dictionary[key] = "E 250 (Fe 410 W)A" + + # convert specific keys to float or string + if key == "KEY_GRD": + design_dictionary[key] = float(value) if value else 8.8 + elif key == "KEY_D": + design_dictionary[key] = float(value) if value else 20.0 + else: + design_dictionary[key] = str(value) if value is not None else "" + + # validate design preference fields + for field in design_pref_fields: + key = field["key"] + input_type = field["type"] + valid_values = field.get("values", []) + default_value = field.get("value", valid_values[0] if valid_values else "") + + value = design_dictionary.get(key, default_value) + + # check design preference combobox values + if input_type == "TYPE_COMBOBOX": + if value not in valid_values: + print(f"Warning: Invalid value '{value}' for {key}. Using default: {default_value}") + design_dictionary[key] = default_value + # validate textbox inputs as floats + elif input_type == "TYPE_TEXTBOX": + try: + float(value) + except ValueError: + print(f"Warning: Invalid value '{value}' for {key}. Using default: {default_value}") + design_dictionary[key] = default_value + + # add compatibility keys for legacy support + compatibility_keys = { + "Connectivity *": "KEY_CONN", + "Member.Supporting_Section.Designation" : "KEY_SUPTNGSEC", + "Member.Supported_Section.Designation": "KEY_SUPTDSEC", + "Member.Supporting_Section.Material": "KEY_SUPTNGSEC_MATERIAL", + "Member.Supported_Section.Material": "KEY_SUPTDSEC_MATERIAL", + "Load.Shear": "KEY_SHEAR", + "Load.Axial": "KEY_AXIAL", + "Bolt.Diameter": "KEY_D", + "Bolt.Grade": "KEY_GRD", + "Bolt.Type": "KEY_TYP", + "Bolt.Bolt_Hole_Type": "KEY_DP_BOLT_HOLE_TYPE", + "Bolt.TensionType": "KEY_DP_BOLT_TYPE", + "Detailing.Bolt_Slip_Factor": "KEY_DP_BOLT_SLIP_FACTOR", + "Detailing.Edge_type": "KEY_DP_DETAILING_EDGE_TYPE", + "Detailing.Corrosive_Influences": "KEY_DP_DETAILING_CORROSIVE_INFLUENCES", + "Detailing.Gap": "KEY_DP_DETAILING_GAP", + "Design.Method": "KEY_DP_DESIGN_METHOD", + "Material": "KEY_CONNECTOR_MATERIAL", + "Module": "KEY_MODULE", + "Plate.Thickness": "KEY_PLATETHK", + "Plate.Material_Grade": "KEY_CONNECTOR_MATERIAL", + "Connector.Material": "KEY_CONNECTOR_MATERIAL", + "Shear_Force": "KEY_SHEAR", + "Axial_Force": "KEY_AXIAL", + "Weld.Material": "KEY_DP_WELD_MATERIAL_G_O", + "Weld.Material_Grade_OverWrite": "KEY_DP_WELD_MATERIAL_G_O", + "Weld.Fab": "KEY_DP_WELD_FAB", + "Weld.Fabrication": "KEY_DP_WELD_FAB", + "Weld.Fu": "KEY_DP_WELD_MATERIAL_G_O", + } + + # map compatibility keys to design dictionary + for dest_key, src_key in compatibility_keys.items(): + design_dictionary[dest_key] = design_dictionary.get(src_key, "") + + # calculate material properties for supporting, supported, and connector + material_keys = [ + ("KEY_SUPTNGSEC_FU", "KEY_SUPTNGSEC_FY", "KEY_SUPTNGSEC_MATERIAL", None), + ("KEY_SUPTDSEC_FU", "KEY_SUPTDSEC_FY", "KEY_SUPTDSEC_MATERIAL", None), + ("KEY_CONNECTOR_FU", "KEY_CONNECTOR_FY_20", "KEY_CONNECTOR_MATERIAL", design_dictionary.get('KEY_PLATETHK', 10)), + ] + + # assign fu and fy based on material and thickness + for fu_key, fy_key, mat_key, thickness in material_keys: + material = design_dictionary.get(mat_key, "E 250 (Fe 410 W)A") + if thickness is None: + fu, fy = get_fy_fu(material) + design_dictionary[fu_key] = str(fu) + design_dictionary[fy_key] = str(fy) + else: + fu, fy_20, fy_20_40, fy_40 = get_fy_fu(material, float(thickness)) + design_dictionary[fu_key] = str(fu) + design_dictionary[fy_key] = str(fy_20) + design_dictionary['KEY_CONNECTOR_FY_20_40'] = str(fy_20_40) + design_dictionary['KEY_CONNECTOR_FY_40'] = str(fy_40) + + # close database connection + conn.close() + return design_dictionary + +# function to test fin plate input mimicry with four test cases +def test_mimic_fin_plate_inputs(): + """test the mimicry function with four osi file test cases.""" + # define four test cases based on osi files + test_cases = [ + # finplatetest1.osi + { + "KEY_MODULE": "Fin Plate Connection", + "KEY_CONN": "Column Flange-Beam Web", + "KEY_SUPTNGSEC": "HB 300", + "KEY_SUPTNGSEC_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SUPTDSEC": "MB 200", + "KEY_SUPTDSEC_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SHEAR": "5", + "KEY_AXIAL": "", + "KEY_D": "20", + "KEY_GRD": "10.9", + "KEY_TYP": "Bearing Bolt", + "KEY_PLATETHK": "10", + "KEY_CONNECTOR_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_DP_BOLT_TYPE": "Pretensioned", + "KEY_DP_BOLT_HOLE_TYPE": "Standard", + "KEY_DP_BOLT_SLIP_FACTOR": "0.3", + "KEY_DP_WELD_FAB": "Shop Weld", + "KEY_DP_WELD_MATERIAL_G_O": "410", + "KEY_DP_DETAILING_EDGE_TYPE": "Sheared or hand flame cut", + "KEY_DP_DETAILING_GAP": "10", + "KEY_DP_DETAILING_CORROSIVE_INFLUENCES": "No", + "KEY_DP_DESIGN_METHOD": "Limit State Design" + }, + # finplatetest2.osi + { + "KEY_MODULE": "Fin Plate Connection", + "KEY_CONN": "Column Web-Beam Web", + "KEY_SUPTNGSEC": "HB 200", + "KEY_SUPTNGSEC_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SUPTDSEC": "MB 200", + "KEY_SUPTDSEC_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SHEAR": "5", + "KEY_AXIAL": "", + "KEY_D": "20", + "KEY_GRD": "8.8", + "KEY_TYP": "Friction Grip Bolt", + "KEY_PLATETHK": "16", + "KEY_CONNECTOR_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_DP_BOLT_TYPE": "Pretensioned", + "KEY_DP_BOLT_HOLE_TYPE": "Standard", + "KEY_DP_BOLT_SLIP_FACTOR": "0.3", + "KEY_DP_WELD_FAB": "Shop Weld", + "KEY_DP_WELD_MATERIAL_G_O": "410", + "KEY_DP_DETAILING_EDGE_TYPE": "Sheared or hand flame cut", + "KEY_DP_DETAILING_GAP": "10", + "KEY_DP_DETAILING_CORROSIVE_INFLUENCES": "No", + "KEY_DP_DESIGN_METHOD": "Limit State Design" + }, + # finplatetest3.osi + { + "KEY_MODULE": "Fin Plate Connection", + "KEY_CONN": "Beam-Beam", + "KEY_SUPTNGSEC": "MB 200", + "KEY_SUPTNGSEC_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SUPTDSEC": "MB 300", + "KEY_SUPTDSEC_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SHEAR": "50", + "KEY_AXIAL": "8", + "KEY_D": "20", + "KEY_GRD": "8.8", + "KEY_TYP": "Bearing Bolt", + "KEY_PLATETHK": "12", + "KEY_CONNECTOR_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_DP_BOLT_TYPE": "Pretensioned", + "KEY_DP_BOLT_HOLE_TYPE": "Standard", + "KEY_DP_BOLT_SLIP_FACTOR": "0.3", + "KEY_DP_WELD_FAB": "Shop Weld", + "KEY_DP_WELD_MATERIAL_G_O": "410", + "KEY_DP_DETAILING_EDGE_TYPE": "Sheared or hand flame cut", + "KEY_DP_DETAILING_GAP": "10", + "KEY_DP_DETAILING_CORROSIVE_INFLUENCES": "No", + "KEY_DP_DESIGN_METHOD": "Limit State Design" + }, + # finplatetest4.osi + { + "KEY_MODULE": "Fin Plate Connection", + "KEY_CONN": "Beam-Beam", + "KEY_SUPTNGSEC": "MB 300", + "KEY_SUPTNGSEC_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SUPTDSEC": "MB 200", + "KEY_SUPTDSEC_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SHEAR": "50", + "KEY_AXIAL": "8", + "KEY_D": "20", + "KEY_GRD": "8.8", + "KEY_TYP": "Bearing Bolt", + "KEY_PLATETHK": "20", + "KEY_CONNECTOR_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_DP_BOLT_TYPE": "Non pre-tensioned", + "KEY_DP_BOLT_HOLE_TYPE": "Over-sized", + "KEY_DP_BOLT_SLIP_FACTOR": "0.3", + "KEY_DP_WELD_FAB": "Field Weld", + "KEY_DP_WELD_MATERIAL_G_O": "410", + "KEY_DP_DETAILING_EDGE_TYPE": "Rolled, machine-flame cut, sawn and planed", + "KEY_DP_DETAILING_GAP": "10", + "KEY_DP_DETAILING_CORROSIVE_INFLUENCES": "No", + "KEY_DP_DESIGN_METHOD": "Limit State Design" + } + ] + + # run each test case and validate + for i, test_case in enumerate(test_cases, 1): + print(f"\nRunning Test Case {i} (FinPlateTest{i}.osi)") + try: + design_dict = mimic_fin_plate_inputs(test_case) + print("Design Dictionary:") + for key, value in sorted(design_dict.items()): + print(f" {key}: {value}") + # create fin plate connection object and set inputs + fin_plate = FinPlateConnection() + fin_plate.set_input_values(design_dict) + print(f"Test Case {i}: Validation Successful") + except Exception as e: + print(f"Test Case {i}: Validation Failed - {str(e)}") + raise + +# run tests if script is executed directly +if __name__ == "__main__": + test_mimic_fin_plate_inputs() \ No newline at end of file diff --git a/tests/test_tension_bolted_mock.py b/tests/test_tension_bolted_mock.py new file mode 100644 index 000000000..348d2be5c --- /dev/null +++ b/tests/test_tension_bolted_mock.py @@ -0,0 +1,220 @@ +import unittest +from unittest.mock import MagicMock, patch + +# mock functions in python are fake implementations of real objects or methods. +# they’re used in unit testing when you don’t want to depend on actual implementation +# (like database calls, file reads, or in this case, a heavy design computation class). +# the mock just pretends to do what the real method does and returns predefined values. +# this makes testing fast, isolated, and easy to control. + +# here, we are mocking the Tension_bolted class (probably a heavy structural module from osdag), +# and we're faking its method outputs by giving hardcoded (predefined) results. + +class TestTensionBolted(unittest.TestCase): + def setUp(self): + # create a fake object (mock) for the Tension_bolted class + self.tension_bolted = MagicMock() + + # whenever results_to_test is called on our mock object, return an empty dictionary by default + self.tension_bolted.results_to_test.return_value = {} + + # make sure set_input_values just silently returns nothing + self.tension_bolted.set_input_values.return_value = None + + @patch('tension_bolted.Tension_bolted') # this replaces the real class with a mock during the test + def test_tension_bolted_test1(self, mock_tension_bolted): + # test case 1: check if results match expected mock values + + # this is what we expect the real method to return (but we mock it) + mock_results = { + "KEY_DISP_DESIGNATION": "40 x 20 x 3", + "KEY_DISP_TENSION_YIELDCAPACITY": "79.09", + "KEY_OUT_DISP_BOLT_LINE": "2", + "KEY_OUT_DISP_BOLTS_ONE_LINE": "2", + "KEY_OUT_DISP_BOLT_CAPACITY": "17.71", + "KEY_OUT_DISP_GRD_PROVIDED": "3.6", + "KEY_OUT_DISP_D_PROVIDED": "20" + } + self.tension_bolted.results_to_test.return_value = mock_results + + # fake user input for the test + design_dict = { + "KEY_SECSIZE": "40 x 20 x 3", + "KEY_GRD": "3.6", + "KEY_D": "20", + "KEY_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SEC_PROFILE": "Angles", + "KEY_LOCATION": "Long Leg", + "KEY_TYP": "Bearing Bolt", + "KEY_PLATETHK": "10", + "KEY_AXIAL": "50", + } + + # expected outputs based on the mock results + expected_designation = "40 x 20 x 3" + expected_tension_capacity_section = 79.09 + expected_tension_capacity_plate = 70.84 + expected_bolt_grade = 3.6 + expected_number_of_bolts = 4 + expected_bolt_diameter = 20 + + # simulate setting the inputs and getting the result + self.tension_bolted.set_input_values(design_dict) + results = self.tension_bolted.results_to_test("temp_test1.txt") + + # assert that each value matches our expectations + self.assertEqual(results["KEY_DISP_DESIGNATION"], expected_designation) + self.assertAlmostEqual(float(results["KEY_DISP_TENSION_YIELDCAPACITY"]), expected_tension_capacity_section, places=2) + + # calculate bolt capacity manually and check if it matches expected plate capacity + number_of_bolts = int(results["KEY_OUT_DISP_BOLT_LINE"]) * int(results["KEY_OUT_DISP_BOLTS_ONE_LINE"]) + plate_capacity = float(results["KEY_OUT_DISP_BOLT_CAPACITY"]) * number_of_bolts + self.assertAlmostEqual(plate_capacity, expected_tension_capacity_plate, places=2) + self.assertEqual(float(results["KEY_OUT_DISP_GRD_PROVIDED"]), expected_bolt_grade) + self.assertEqual(number_of_bolts, expected_number_of_bolts) + self.assertEqual(int(results["KEY_OUT_DISP_D_PROVIDED"]), expected_bolt_diameter) + + @patch('tension_bolted.Tension_bolted') + def test_tension_bolted_test2(self, mock_tension_bolted): + # test case 2 + + mock_results = { + "KEY_DISP_DESIGNATION": "MC 100", + "KEY_DISP_TENSION_YIELDCAPACITY": "104.82", + "KEY_OUT_DISP_BOLT_LINE": "1", + "KEY_OUT_DISP_BOLTS_ONE_LINE": "3", + "KEY_OUT_DISP_BOLT_CAPACITY": "58.25", + "KEY_OUT_DISP_GRD_PROVIDED": "12.9", + "KEY_OUT_DISP_D_PROVIDED": "12" + } + self.tension_bolted.results_to_test.return_value = mock_results + + design_dict = { + "KEY_SECSIZE": "MC 100", + "KEY_GRD": "12.9", + "KEY_D": "12", + "KEY_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SEC_PROFILE": "Channels", + "KEY_LOCATION": "Web", + "KEY_TYP": "Bearing Bolt", + "KEY_PLATETHK": "10", + "KEY_AXIAL": "50", + } + + expected_designation = "MC 100" + expected_tension_capacity_section = 104.82 + expected_tension_capacity_plate = 174.75 + expected_bolt_grade = 12.9 + expected_number_of_bolts = 3 + expected_bolt_diameter = 12 + + self.tension_bolted.set_input_values(design_dict) + results = self.tension_bolted.results_to_test("temp_test2.txt") + + self.assertEqual(results["KEY_DISP_DESIGNATION"], expected_designation) + self.assertAlmostEqual(float(results["KEY_DISP_TENSION_YIELDCAPACITY"]), expected_tension_capacity_section, places=2) + + number_of_bolts = int(results["KEY_OUT_DISP_BOLT_LINE"]) * int(results["KEY_OUT_DISP_BOLTS_ONE_LINE"]) + plate_capacity = float(results["KEY_OUT_DISP_BOLT_CAPACITY"]) * number_of_bolts + self.assertAlmostEqual(plate_capacity, expected_tension_capacity_plate, places=2) + self.assertEqual(float(results["KEY_OUT_DISP_GRD_PROVIDED"]), expected_bolt_grade) + self.assertEqual(number_of_bolts, expected_number_of_bolts) + self.assertEqual(int(results["KEY_OUT_DISP_D_PROVIDED"]), expected_bolt_diameter) + + @patch('tension_bolted.Tension_bolted') + def test_tension_bolted_test3(self, mock_tension_bolted): + # test case 3 + + mock_results = { + "KEY_DISP_DESIGNATION": "40 x 40 x 3", + "KEY_DISP_TENSION_YIELDCAPACITY": "28.79", + "KEY_OUT_DISP_BOLT_LINE": "2", + "KEY_OUT_DISP_BOLTS_ONE_LINE": "2", + "KEY_OUT_DISP_BOLT_CAPACITY": "12.0", + "KEY_OUT_DISP_GRD_PROVIDED": "4.6", + "KEY_OUT_DISP_D_PROVIDED": "10" + } + self.tension_bolted.results_to_test.return_value = mock_results + + design_dict = { + "KEY_SECSIZE": "40 x 40 x 3", + "KEY_GRD": "4.6", + "KEY_D": "10", + "KEY_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SEC_PROFILE": "Angles", + "KEY_LOCATION": "Long Leg", + "KEY_TYP": "Bearing Bolt", + "KEY_PLATETHK": "10", + "KEY_AXIAL": "50", + } + + expected_designation = "40 x 40 x 3" + expected_tension_capacity_section = 28.79 + expected_tension_capacity_plate = 48.0 + expected_bolt_grade = 4.6 + expected_number_of_bolts = 4 + expected_bolt_diameter = 10 + + self.tension_bolted.set_input_values(design_dict) + results = self.tension_bolted.results_to_test("temp_test3.txt") + + self.assertEqual(results["KEY_DISP_DESIGNATION"], expected_designation) + self.assertAlmostEqual(float(results["KEY_DISP_TENSION_YIELDCAPACITY"]), expected_tension_capacity_section, places=2) + + number_of_bolts = int(results["KEY_OUT_DISP_BOLT_LINE"]) * int(results["KEY_OUT_DISP_BOLTS_ONE_LINE"]) + plate_capacity = float(results["KEY_OUT_DISP_BOLT_CAPACITY"]) * number_of_bolts + self.assertAlmostEqual(plate_capacity, expected_tension_capacity_plate, places=2) + self.assertEqual(float(results["KEY_OUT_DISP_GRD_PROVIDED"]), expected_bolt_grade) + self.assertEqual(number_of_bolts, expected_number_of_bolts) + self.assertEqual(int(results["KEY_OUT_DISP_D_PROVIDED"]), expected_bolt_diameter) + + @patch('tension_bolted.Tension_bolted') + def test_tension_bolted_test4(self, mock_tension_bolted): + # test case 4 + + mock_results = { + "KEY_DISP_DESIGNATION": "MC 175", + "KEY_DISP_TENSION_YIELDCAPACITY": "363.86", + "KEY_OUT_DISP_BOLT_LINE": "2", + "KEY_OUT_DISP_BOLTS_ONE_LINE": "3", + "KEY_OUT_DISP_BOLT_CAPACITY": "61.62", + "KEY_OUT_DISP_GRD_PROVIDED": "5.6", + "KEY_OUT_DISP_D_PROVIDED": "20" + } + self.tension_bolted.results_to_test.return_value = mock_results + + design_dict = { + "KEY_SECSIZE": "MC 175", + "KEY_GRD": "5.6", + "KEY_D": "20", + "KEY_MATERIAL": "E 250 (Fe 410 W)A", + "KEY_SEC_PROFILE": "Channels", + "KEY_LOCATION": "Web", + "KEY_TYP": "Bearing Bolt", + "KEY_PLATETHK": "10", + "KEY_AXIAL": "50", + } + + expected_designation = "MC 175" + expected_tension_capacity_section = 363.86 + expected_tension_capacity_plate = 369.72 + expected_bolt_grade = 5.6 + expected_number_of_bolts = 6 + expected_bolt_diameter = 20 + + self.tension_bolted.set_input_values(design_dict) + results = self.tension_bolted.results_to_test("temp_test4.txt") + + self.assertEqual(results["KEY_DISP_DESIGNATION"], expected_designation) + self.assertAlmostEqual(float(results["KEY_DISP_TENSION_YIELDCAPACITY"]), expected_tension_capacity_section, places=2) + + number_of_bolts = int(results["KEY_OUT_DISP_BOLT_LINE"]) * int(results["KEY_OUT_DISP_BOLTS_ONE_LINE"]) + plate_capacity = float(results["KEY_OUT_DISP_BOLT_CAPACITY"]) * number_of_bolts + self.assertAlmostEqual(plate_capacity, expected_tension_capacity_plate, places=2) + self.assertEqual(float(results["KEY_OUT_DISP_GRD_PROVIDED"]), expected_bolt_grade) + self.assertEqual(number_of_bolts, expected_number_of_bolts) + self.assertEqual(int(results["KEY_OUT_DISP_D_PROVIDED"]), expected_bolt_diameter) + +# this runs all tests when we execute the file +if __name__ == "__main__": + unittest.main() From 9275c1b4af9b2cbd1e010932957bade8ec31d481 Mon Sep 17 00:00:00 2001 From: lakshanashreee Date: Fri, 27 Jun 2025 20:25:27 +0530 Subject: [PATCH 3/3] Added README for testing framework --- tests/README.md | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tests/README.md diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 000000000..1c26de3d4 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,83 @@ +Osdag Testing Suite – tests/ +This directory contains testing and validation scripts built to ensure the correctness, consistency, and structure of .osi design files used in Osdag. These tests primarily focus on validating file formats, parameter values, and GUI input mimicry for automated and regression testing. + +File Descriptions +1. test_validator.py +This is a unit test suite that verifies the .osi files exported by the Tension Member Design – Bolted to End Gusset module. + +Asserts specific key-value pairs such as: + +Member.Designation + +Bolt.Grade + +Bolt.Diameter + +The test compares these values against expected results and flags any mismatches. + +Uses validator.py as the backend validation engine. + +Usage Tip: To skip checking a particular key, just use an empty string "" in the expected_result. + +2. validator.py +A utility script that performs field-level validation for .osi files. + +Extracts and normalizes values from .osi files. + +Validates keys using known correct values (e.g., valid bolt grades, valid designations). + +Returns a Pass/Fail result per key for use in testing. + +Helps maintain data consistency early in the test pipeline. + +3. test_tension_bolted_mock.py +This script automates the testing of .osi file structure and content for the Tension Member module. + +Meant for mock runs to simulate how actual .osi values will be verified post-design. + +Aims to catch incorrect data early during development. + +Useful for quickly validating new or modified .osi test cases before pushing to production. + +4. test_fin_plate_mimicry.py +This script tests GUI-mimicked input flows for the Fin Plate Connection module. + +Mimics how a user would fill in GUI inputs programmatically, including handling: + +Combo boxes + +Text fields with validation + +Material-specific logic for fy, fu + +Automatically generates a design_dictionary from raw input. + +Ensures values conform to expected database-driven options (like sections, grades, plate thickness). + +Final output is passed to the actual Osdag design logic to verify input compatibility. + +This file is especially useful for ensuring that test .osi files align with what the GUI would generate in a real user scenario. + +How to Run +Each file can be run using Python or pytest where applicable. + +# Run all pytest-based validations +pytest test_validator.py + +# Run GUI-mimicked design input tests +python test_fin_plate_mimicry.py +Ensure dependencies and database paths are correctly set before execution. + +Purpose +This folder serves as a foundational test layer to: + +Detect errors in .osi files early. + +Verify GUI-input compatibility. + +Automate structural checks on design outputs. + +Serve as a baseline for regression testing during ongoing development. + +Each script here is modular and can be extended or reused across other modules with minimal changes. +