mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-23 02:26:37 +00:00
234 lines
10 KiB
Python
234 lines
10 KiB
Python
import pytest
|
|
import time
|
|
from unittest.mock import Mock, patch
|
|
from datetime import datetime, timedelta
|
|
|
|
from f02_1_flight_lifecycle_manager import GPSPoint
|
|
from f03_flight_database import FrameResult as F03FrameResult
|
|
from f14_result_manager import ResultManager, FrameResult, RefinedFrameResult
|
|
|
|
@pytest.fixture
|
|
def f03():
|
|
db = Mock()
|
|
store = {}
|
|
|
|
def mock_execute(ops):
|
|
for op in ops:
|
|
op()
|
|
return True
|
|
db.execute_transaction.side_effect = mock_execute
|
|
|
|
def save_fr(fid, fr):
|
|
store[fr.frame_id] = fr
|
|
return True
|
|
db.save_frame_result.side_effect = save_fr
|
|
|
|
def get_fr(fid):
|
|
return list(store.values())
|
|
db.get_frame_results.side_effect = get_fr
|
|
|
|
return db
|
|
|
|
@pytest.fixture
|
|
def f15():
|
|
return Mock()
|
|
|
|
@pytest.fixture
|
|
def rm(f03, f15):
|
|
return ResultManager(f03_database=f03, f15_streamer=f15)
|
|
|
|
@pytest.fixture
|
|
def sample_result():
|
|
return FrameResult(
|
|
frame_id=10,
|
|
gps_center=GPSPoint(lat=48.0, lon=37.0),
|
|
altitude=400.0,
|
|
heading=90.0,
|
|
confidence=0.8,
|
|
timestamp=datetime.utcnow()
|
|
)
|
|
|
|
class TestResultManager:
|
|
|
|
# --- 14.01 Feature: Frame Result Persistence ---
|
|
|
|
def test_update_frame_result_new_frame(self, rm, f03, sample_result):
|
|
res = rm.update_frame_result("flight_1", 10, sample_result)
|
|
assert res is True
|
|
f03.save_frame_result.assert_called_once()
|
|
|
|
def test_update_frame_result_updates_waypoint(self, rm, f03, sample_result):
|
|
rm.update_frame_result("flight_1", 10, sample_result)
|
|
f03.insert_waypoint.assert_called_once()
|
|
|
|
def test_update_frame_result_transaction_atomic(self, rm, f03, f15, sample_result):
|
|
f03.execute_transaction.side_effect = None
|
|
f03.execute_transaction.return_value = False
|
|
res = rm.update_frame_result("flight_1", 10, sample_result)
|
|
assert res is False # Because atomic transaction failed
|
|
f15.send_frame_result.assert_not_called()
|
|
|
|
def test_update_frame_result_triggers_sse(self, rm, f15, sample_result):
|
|
rm.update_frame_result("flight_1", 10, sample_result)
|
|
f15.send_frame_result.assert_called_once()
|
|
|
|
def test_update_frame_result_refined_flag(self, rm, f03, sample_result):
|
|
sample_result.refined = True
|
|
rm.update_frame_result("flight_1", 10, sample_result)
|
|
|
|
# Grab the saved F03 object
|
|
args, _ = f03.save_frame_result.call_args
|
|
assert args[1].refined is True
|
|
|
|
def test_publish_waypoint_fetches_latest(self, rm, f03, f15, sample_result):
|
|
rm.update_frame_result("flight_1", 10, sample_result)
|
|
rm.publish_waypoint_update("flight_1", 10)
|
|
f03.get_frame_results.assert_called()
|
|
f15.send_frame_result.assert_called()
|
|
|
|
def test_publish_waypoint_handles_transient_error(self, rm, f03, f15, sample_result):
|
|
rm.update_frame_result("flight_1", 10, sample_result)
|
|
|
|
# Fail twice, succeed third time
|
|
f15.send_frame_result.side_effect = [Exception("Net Err"), Exception("Net Err"), True]
|
|
assert rm.publish_waypoint_update("flight_1", 10) is True
|
|
assert f15.send_frame_result.call_count == 4 # 1 from update + 3 retries
|
|
|
|
def test_publish_waypoint_logs_on_db_unavailable(self, rm, f03):
|
|
f03.get_frame_results.side_effect = Exception("DB Down")
|
|
assert rm.publish_waypoint_update("flight_1", 10) is False
|
|
|
|
# --- 14.03 Feature: Batch Refinement Updates ---
|
|
|
|
def test_mark_refined_updates_all_frames(self, rm, f03, sample_result):
|
|
rm.update_frame_result("flight_ref", 10, sample_result)
|
|
|
|
ref = RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=48.1, lon=37.1), confidence=0.99)
|
|
assert rm.mark_refined("flight_ref", [ref]) is True
|
|
|
|
# Verify it was updated in the DB
|
|
f03_res = f03.get_frame_results("flight_ref")[0]
|
|
assert f03_res.gps_center.lat == 48.1
|
|
assert f03_res.confidence == 0.99
|
|
|
|
def test_mark_refined_sets_refined_flag(self, rm, f03, sample_result):
|
|
rm.update_frame_result("flight_ref", 10, sample_result)
|
|
rm.mark_refined("flight_ref", [RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=0, lon=0), confidence=0.9)])
|
|
|
|
assert f03.get_frame_results("flight_ref")[0].refined is True
|
|
|
|
def test_mark_refined_updates_gps_coordinates(self, rm, f03, sample_result):
|
|
rm.update_frame_result("flight_ref", 10, sample_result)
|
|
rm.mark_refined("flight_ref", [RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=99.0, lon=99.0), confidence=0.9)])
|
|
|
|
assert f03.get_frame_results("flight_ref")[0].gps_center.lat == 99.0
|
|
|
|
def test_mark_refined_triggers_sse_per_frame(self, rm, f15, sample_result):
|
|
rm.update_frame_result("flight_ref", 10, sample_result)
|
|
f15.reset_mock()
|
|
|
|
rm.mark_refined("flight_ref", [RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=0, lon=0), confidence=0.9)])
|
|
f15.send_refinement.assert_called_once()
|
|
|
|
def test_mark_refined_updates_waypoints(self, rm, f03, sample_result):
|
|
rm.update_frame_result("flight_ref", 10, sample_result)
|
|
f03.insert_waypoint.reset_mock()
|
|
|
|
rm.mark_refined("flight_ref", [RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=0, lon=0), confidence=0.9)])
|
|
f03.insert_waypoint.assert_called_once()
|
|
|
|
def test_chunk_merge_updates_all_frames(self, rm, f03, sample_result):
|
|
rm.update_frame_result("flight_chk", 10, sample_result)
|
|
ref = RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=48.5, lon=37.5), confidence=0.9)
|
|
|
|
assert rm.update_results_after_chunk_merge("flight_chk", [ref]) is True
|
|
assert f03.get_frame_results("flight_chk")[0].gps_center.lat == 48.5
|
|
|
|
def test_chunk_merge_sets_refined_flag(self, rm, f03, sample_result):
|
|
rm.update_frame_result("flight_chk", 10, sample_result)
|
|
ref = RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=48.5, lon=37.5), confidence=0.9)
|
|
rm.update_results_after_chunk_merge("flight_chk", [ref])
|
|
assert f03.get_frame_results("flight_chk")[0].refined is True
|
|
|
|
def test_chunk_merge_triggers_sse(self, rm, f15, sample_result):
|
|
rm.update_frame_result("flight_chk", 10, sample_result)
|
|
f15.reset_mock()
|
|
rm.update_results_after_chunk_merge("flight_chk", [RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=0, lon=0), confidence=0.9)])
|
|
f15.send_refinement.assert_called_once()
|
|
|
|
def test_batch_transaction_atomic(self, rm, f03, sample_result):
|
|
rm.update_frame_result("flight_chk", 10, sample_result)
|
|
f03.execute_transaction.side_effect = None
|
|
f03.execute_transaction.return_value = False
|
|
res = rm.mark_refined("flight_chk", [RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=0, lon=0), confidence=0.9)])
|
|
assert res is False
|
|
|
|
# --- 14.02 Feature: Result Retrieval ---
|
|
|
|
def test_get_flight_results_returns_all_frames(self, rm, sample_result):
|
|
rm.update_frame_result("flight_all", 10, sample_result)
|
|
|
|
res2 = sample_result.model_copy(update={'frame_id': 11})
|
|
rm.update_frame_result("flight_all", 11, res2)
|
|
results = rm.get_flight_results("flight_all")
|
|
assert len(results.frames) == 2
|
|
|
|
def test_get_flight_results_includes_statistics(self, rm, sample_result):
|
|
rm.update_frame_result("flight_stats", 10, sample_result)
|
|
results = rm.get_flight_results("flight_stats")
|
|
assert results.statistics.total_frames == 1
|
|
assert results.statistics.mean_confidence == 0.8
|
|
|
|
def test_get_flight_results_empty_flight(self, rm):
|
|
results = rm.get_flight_results("flight_empty")
|
|
assert len(results.frames) == 0
|
|
assert results.statistics.total_frames == 0
|
|
|
|
def test_get_flight_results_performance(self, rm, f03):
|
|
f03.get_frame_results.side_effect = None
|
|
f03.get_frame_results.return_value = [F03FrameResult(frame_id=i, gps_center=GPSPoint(lat=0, lon=0), altitude=0.0, heading=0.0, confidence=1.0, timestamp=datetime.utcnow(), updated_at=datetime.utcnow()) for i in range(2000)]
|
|
start = time.time()
|
|
res = rm.get_flight_results("flight_perf")
|
|
assert time.time() - start < 0.200
|
|
assert len(res.frames) == 2000
|
|
|
|
def test_get_changed_frames_returns_modified(self, rm, sample_result):
|
|
rm.update_frame_result("flight_change", 10, sample_result)
|
|
t1 = datetime.utcnow()
|
|
time.sleep(0.01)
|
|
res2 = sample_result.model_copy(update={'frame_id': 11, 'updated_at': datetime.utcnow()})
|
|
rm.update_frame_result("flight_change", 11, res2)
|
|
|
|
changed = rm.get_changed_frames("flight_change", t1)
|
|
assert len(changed) == 1
|
|
assert changed[0] == 11
|
|
|
|
def test_get_changed_frames_empty_result(self, rm, sample_result):
|
|
rm.update_frame_result("flight_change", 10, sample_result)
|
|
changed = rm.get_changed_frames("flight_change", datetime.utcnow() + timedelta(days=1))
|
|
assert len(changed) == 0
|
|
|
|
def test_get_changed_frames_includes_refined(self, rm, sample_result):
|
|
rm.update_frame_result("flight_change", 10, sample_result)
|
|
t1 = datetime.utcnow()
|
|
time.sleep(0.01)
|
|
|
|
rm.mark_refined("flight_change", [RefinedFrameResult(frame_id=10, gps_center=GPSPoint(lat=0, lon=0), confidence=0.9)])
|
|
changed = rm.get_changed_frames("flight_change", t1)
|
|
assert len(changed) == 1
|
|
|
|
def test_export_results_json(self, rm, sample_result):
|
|
rm.update_frame_result("flight_exp", 10, sample_result)
|
|
out = rm.export_results("flight_exp", "json")
|
|
assert "flight_exp" in out
|
|
assert "48.0" in out
|
|
|
|
def test_export_results_csv(self, rm, sample_result):
|
|
rm.update_frame_result("flight_exp", 10, sample_result)
|
|
out = rm.export_results("flight_exp", "csv")
|
|
assert "image,sequence,lat,lon" in out
|
|
assert "AD000010.jpg,10,48.0,37.0" in out
|
|
|
|
def test_export_results_kml(self, rm, sample_result):
|
|
out = rm.export_results("flight_exp", "kml")
|
|
assert "<kml" in out and "</kml>" in out |