mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-23 02:26:37 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
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
|
||||
Reference in New Issue
Block a user