import csv import importlib.util import tempfile import unittest from pathlib import Path MODULE_PATH = Path(__file__).with_name("unified_simulator.py") def load_simulator_module(): spec = importlib.util.spec_from_file_location("unified_simulator_contract", MODULE_PATH) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) return module class UnifiedSimulatorContractTests(unittest.TestCase): def setUp(self): self.simulator = load_simulator_module() def test_modbus_contract_constants_are_explicit(self): self.assertEqual(self.simulator.MODBUS_PORT, 5020) self.assertEqual(self.simulator.HTTP_PORT, 8085) self.assertEqual(self.simulator.REQUIRED_PYMODBUS_VERSION, "3.6.9") self.assertEqual(sorted(self.simulator.BANKS), [1, 2, 3, 4, 5]) def test_pymodbus_version_guard_accepts_only_supported_version(self): compatible = self.simulator.validate_pymodbus_version("3.6.9") incompatible = self.simulator.validate_pymodbus_version("3.11.1") self.assertTrue(compatible.is_compatible) self.assertFalse(incompatible.is_compatible) self.assertIn("pymodbus==3.6.9", incompatible.install_hint) self.assertIn("live register updates", incompatible.reason) def test_generated_csv_matches_backend_import_contract(self): expected_headers = [ "WellCode", "DeviceName", "Host", "Port", "UnitId", "VariableAlias", "Address", "DataType", "LastCalibrationDate", "NextCalibrationDate", "CalibrationCertificateUrl", ] with tempfile.TemporaryDirectory() as temp_dir: original_path = self.simulator.CSV_PATH self.simulator.CSV_PATH = str(Path(temp_dir) / "variables_import.csv") try: csv_path = self.simulator.write_import_csv() finally: self.simulator.CSV_PATH = original_path with open(csv_path, newline="", encoding="utf-8") as csv_file: reader = csv.DictReader(csv_file) rows = list(reader) self.assertEqual(reader.fieldnames, expected_headers) self.assertEqual(len(rows), 1000) self.assertEqual({int(row["UnitId"]) for row in rows}, {1, 2, 3, 4, 5}) self.assertIn("HR:0", {row["Address"] for row in rows}) self.assertIn("IR:0", {row["Address"] for row in rows}) self.assertEqual({row["DataType"] for row in rows}, {"uint16"}) def test_api_payload_exposes_documented_shape(self): payload = self.simulator.build_api_payload() self.assertEqual(set(payload), {"timestamp", "summary", "wells", "sample"}) self.assertEqual(payload["summary"]["total"], 1000) self.assertEqual(payload["summary"]["wells"], 5) self.assertEqual(len(payload["wells"]), 5) self.assertGreater(len(payload["sample"]), 0) self.assertIn("address", payload["sample"][0]) if __name__ == "__main__": unittest.main()