tiandytmd
This commit is contained in:
1 parent
f7a782dcef
commit
ddea6d0578
6 files changed
+389
-34
No files matched your search
@@ -51,6 +51,29 @@ class ApiRecoveryTests(unittest.TestCase):
|
||||
self.assertEqual(payload["scan"]["state"], "idle")
|
||||
wait_mock.assert_called_once_with()
|
||||
|
||||
def test_move_not_reached_error_is_explained_for_operator(self):
|
||||
result = completed(
|
||||
22,
|
||||
stdout=json.dumps({
|
||||
"error": {
|
||||
"code": "sdk_error",
|
||||
"message": (
|
||||
"ERR move_not_reached pan_error=-25 tilt_error=56 "
|
||||
"zoom_error=0 target=25892,2044,4620 "
|
||||
"actual=25917,1988,4620"
|
||||
),
|
||||
}
|
||||
}),
|
||||
stderr="curl: (22) The requested URL returned error: 500",
|
||||
)
|
||||
|
||||
detail = core.api_error_detail(result)
|
||||
|
||||
self.assertIn("没有完全到位", detail)
|
||||
self.assertIn("tilt=2044 (20.44°)", detail)
|
||||
self.assertIn("实际", detail)
|
||||
self.assertIn("现场处理", detail)
|
||||
|
||||
@mock.patch("scan_calibrator.wait_for_scan_http")
|
||||
@mock.patch("scan_calibrator.subprocess.run")
|
||||
def test_non_transient_http_error_is_not_retried(self, run_mock, wait_mock):
|
||||
@@ -111,8 +134,104 @@ class ApiRecoveryTests(unittest.TestCase):
|
||||
self.assertIn("恢复起点失败", stderr.getvalue())
|
||||
self.assertEqual(move_mock.call_count, 2)
|
||||
|
||||
@mock.patch("scan_calibrator.api")
|
||||
def test_calibration_ptz_requests_identify_their_control_source(self, api_mock):
|
||||
api_mock.return_value = {"ptz": {"pan": 1, "tilt": 2, "zoom": 3}}
|
||||
|
||||
self.assertEqual(core.get_ptz(), {"pan": 1, "tilt": 2, "zoom": 3})
|
||||
api_mock.assert_called_with(
|
||||
"GET", "/api/v1/ptz?source=scan_calibrator", timeout=50
|
||||
)
|
||||
|
||||
core.move({"pan": 4, "tilt": 5, "zoom": 6})
|
||||
api_mock.assert_called_with(
|
||||
"POST", "/api/v1/ptz/absolute",
|
||||
{"pan": 4, "tilt": 5, "zoom": 6,
|
||||
"source": "scan_calibrator"},
|
||||
timeout=50,
|
||||
)
|
||||
|
||||
def test_hikvision_control_contract_is_supported(self):
|
||||
result = core.validate_control_status({
|
||||
"vendor": "hikvision",
|
||||
"driver_ready": True,
|
||||
"capabilities": ["get_position", "move_absolute", "jog"],
|
||||
"camera": {"sdk_host": "192.168.2.10", "sdk_port": 8000},
|
||||
})
|
||||
|
||||
self.assertEqual(result["vendor"], "hikvision")
|
||||
self.assertIn("move_absolute", result["capabilities"])
|
||||
|
||||
def test_tiandy_control_contract_is_supported(self):
|
||||
result = core.validate_control_status({
|
||||
"vendor": "tiandy",
|
||||
"driver_ready": True,
|
||||
"capabilities": ["get_position", "move_absolute", "presets"],
|
||||
"camera": {"sdk_host": "192.168.100.64", "sdk_port": 3000},
|
||||
})
|
||||
|
||||
self.assertEqual(result["vendor"], "tiandy")
|
||||
self.assertIn("get_position", result["capabilities"])
|
||||
|
||||
def test_control_contract_rejects_unready_driver(self):
|
||||
with self.assertRaisesRegex(SystemExit, "login failed"):
|
||||
core.validate_control_status({
|
||||
"vendor": "tiandy",
|
||||
"driver_ready": False,
|
||||
"driver_error": "login failed",
|
||||
"capabilities": [],
|
||||
})
|
||||
|
||||
def test_control_contract_rejects_missing_absolute_move(self):
|
||||
with self.assertRaisesRegex(SystemExit, "move_absolute"):
|
||||
core.validate_control_status({
|
||||
"vendor": "hikvision",
|
||||
"driver_ready": True,
|
||||
"capabilities": ["get_position", "jog"],
|
||||
})
|
||||
|
||||
def test_ptz_response_must_use_normalized_contract(self):
|
||||
with self.assertRaisesRegex(SystemExit, "pan超出"):
|
||||
core.parse_ptz_payload({
|
||||
"ptz": {"pan": 40000, "tilt": 100, "zoom": 200}
|
||||
})
|
||||
|
||||
|
||||
class InteractiveWizardTests(unittest.TestCase):
|
||||
def test_guided_steps_use_actual_motion_not_requested_motion(self):
|
||||
self.assertEqual(
|
||||
wizard.fov_guided_deltas(62, 0.802, "tilt"),
|
||||
[56, 88, 125, 163, 204],
|
||||
)
|
||||
|
||||
def test_guided_steps_wait_for_measurable_image_shift(self):
|
||||
self.assertEqual(wizard.fov_guided_deltas(33, 0.952, "tilt"), [])
|
||||
|
||||
def test_probe_cap_uses_camera_reported_motion(self):
|
||||
self.assertEqual(wizard.safe_probe_cap(33, 0.952), 584)
|
||||
self.assertEqual(wizard.safe_probe_cap(62, 0.802), 266)
|
||||
|
||||
@mock.patch("interactive_calibrator.core.get_ptz")
|
||||
@mock.patch("interactive_calibrator.core.api")
|
||||
def test_calibration_pauses_active_patrol_before_ptz_barrier(
|
||||
self, api_mock, get_ptz_mock
|
||||
):
|
||||
status = {"control": {"scan_active": False, "patrol_active": True,
|
||||
"patrol_paused_by_operator": False}}
|
||||
camera = {"vendor": "tiandy", "camera": {}, "capabilities": set()}
|
||||
api_mock.return_value = {"success": True}
|
||||
|
||||
with mock.patch(
|
||||
"interactive_calibrator.core.get_control_status",
|
||||
return_value=(status, camera),
|
||||
):
|
||||
self.assertTrue(wizard.acquire_calibration_control())
|
||||
api_mock.assert_called_once_with(
|
||||
"POST", "/api/v1/patrol/stop",
|
||||
{"source": "scan_calibrator"}, timeout=15,
|
||||
)
|
||||
get_ptz_mock.assert_called_once_with()
|
||||
|
||||
def test_manual_ptz_accepts_annotated_display_format(self):
|
||||
self.assertEqual(
|
||||
wizard.parse_ptz_input(
|
||||
|
||||
Reference in new issue
Block a user