import importlib.util import json from pathlib import Path import sys import types import unittest from unittest.mock import patch MODULE_PATH = Path(__file__).resolve().parents[1] / "core" / "identification.py" def load_identification_module(): get_v = types.ModuleType("get_V") get_v.measure_volume = lambda *args, **kwargs: None ind_collector = types.ModuleType("ind_collector") ind_collector.collect_data_with_prbs = lambda *args, **kwargs: {} api = types.ModuleType("api") api.base_url = "https://cloud.example" api.data_record_url = "https://cloud.example/data_record" api.the_folder = "customer-a" requests = types.ModuleType("requests") spec = importlib.util.spec_from_file_location( "identification_under_test", MODULE_PATH ) module = importlib.util.module_from_spec(spec) with patch.dict(sys.modules, { "get_V": get_v, "ind_collector": ind_collector, "api": api, "requests": requests, }): spec.loader.exec_module(module) return module IDENTIFICATION = load_identification_module() class FakeClock: def __init__(self): self.now = 0.0 def monotonic(self): self.now += 0.001 return self.now def sleep(self, duration): self.now += max(0.0, duration) class FakeConnectionManager: def __init__(self): self.distance = 0 def set_motor_position(self, distance): self.distance = int(distance) return True def read_pressure(self): return self.distance / 100.0 class InitialTravelScanTests(unittest.TestCase): def test_uploads_distance_and_pressure_json_without_time_fields(self): manager = IDENTIFICATION.IdentificationManager() manager._identifying = True captured = {} def capture_upload(body, filename, folder): captured.update(body=body, filename=filename, folder=folder) return True manager._upload_to_cos = capture_upload clock = FakeClock() with patch.object(IDENTIFICATION.time, "monotonic", clock.monotonic), \ patch.object(IDENTIFICATION.time, "sleep", clock.sleep): payload = manager._run_initial_travel_scan( FakeConnectionManager() ) records = payload["stable_pressures"] expected_distances = list(range(1000, -1, -100)) self.assertEqual( [record["distance"] for record in records], expected_distances ) self.assertEqual( [record["pressure"] for record in records], [distance / 100.0 for distance in expected_distances], ) self.assertTrue(captured["filename"].endswith(".json")) self.assertEqual(captured["folder"], "customer-a/ind_data") self.assertEqual(json.loads(captured["body"]), payload) self.assertTrue(all( set(record) == {"distance", "pressure"} for record in records )) def test_identification_uploads_collector_csv_and_notifies_filename(self): manager = IDENTIFICATION.IdentificationManager() manager._run_initial_travel_scan = lambda conn_mgr: {} uploaded = {} callbacks = [] csv_data = b"t,u,p,q_in,V\n0.0,10.0,20.0,50.0,5.0\n" csv_filename = "identification_data_test.csv" manager._upload_to_cos = lambda content, filename, folder: ( uploaded.update( content=content, filename=filename, folder=folder ) or True ) manager.set_identification_upload_callback( lambda success, filename, error: callbacks.append((success, filename, error)) ) class ConnectedManager: def is_connected(self): return True collector_result = { "success": True, "csv_data": csv_data, "filename": csv_filename, } with patch.object( IDENTIFICATION, "collect_data_with_prbs", return_value=collector_result): started = manager.start_identification( conn_mgr=ConnectedManager(), running_flag_check=lambda: False, q_in_val=50.0, dt=0.1, n_order=6, t_c=2.5, levels=[10, 20, 30, 40, 50, 60, 70, 80], dead_area=240.0, xa_full=1000.0, V_val=5.0, repeat=2, ) manager._task_thread.join(timeout=2) self.assertTrue(started) self.assertFalse(manager._task_thread.is_alive()) self.assertEqual(uploaded["content"], csv_data) self.assertEqual(uploaded["filename"], csv_filename) self.assertEqual(uploaded["folder"], "customer-a/ind_data") self.assertEqual(callbacks, [(True, csv_filename, None)]) if __name__ == "__main__": unittest.main()