from __future__ import annotations import unittest from datetime import datetime from app import create_app from app.config import Config from app.extensions import db from app.models import Asset, AssetLogEntry, DockingEvent class TestConfig(Config): TESTING = True SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:" WTF_CSRF_ENABLED = False class AssetEntryTests(unittest.TestCase): def setUp(self) -> None: self.app = create_app(TestConfig) self.client = self.app.test_client() with self.app.app_context(): db.create_all() asset = Asset( name="Codex Test Asset", asset_type="Station", program="Codex", home_region="LEO", ) db.session.add(asset) db.session.commit() self.asset_id = asset.id def tearDown(self) -> None: with self.app.app_context(): db.session.remove() db.drop_all() def _latest_entry(self) -> AssetLogEntry: with self.app.app_context(): return ( db.session.query(AssetLogEntry) .filter_by(asset_id=self.asset_id) .order_by(AssetLogEntry.created_at.desc()) .first() ) def test_form_state_nodes_save_all_location_and_state_fields(self) -> None: response = self.client.post( f"/assets/{self.asset_id}/entries/new", data={ "entry_kind": "state", "title": "Mars transfer", "start_at": "2060-03-12T09:00", "end_at": "2060-03-13T09:00", "state_node_id": [""], "state_node_title": ["Arrival burn"], "state_node_at": ["2060-03-12T10:00"], "state_node_detail": ["Inserted into Mars orbit"], "state_node_state_label": ["transit-complete"], "state_node_previous_location": ["LEO"], "state_node_transit_location": ["Transfer"], "state_node_target_location": ["Mars Orbit"], }, ) self.assertEqual(response.status_code, 302) entry = self._latest_entry() self.assertEqual(len(entry.state_nodes), 1) node = entry.state_nodes[0] self.assertEqual(node.state_label, "transit-complete") self.assertEqual(node.previous_location, "LEO") self.assertEqual(node.transit_location, "Transfer") self.assertEqual(node.target_location, "Mars Orbit") def test_form_edit_state_nodes_updates_all_location_and_state_fields(self) -> None: self.client.post( f"/assets/{self.asset_id}/entries/new", data={ "entry_kind": "state", "title": "Initial interval", "start_at": "2060-03-12T09:00", "end_at": "2060-03-13T09:00", "state_node_id": [""], "state_node_title": ["Initial node"], "state_node_at": ["2060-03-12T10:00"], "state_node_detail": ["Before edit"], "state_node_state_label": ["Docked"], "state_node_previous_location": ["LEO"], "state_node_transit_location": ["Transfer"], "state_node_target_location": ["Mars Orbit"], }, ) entry = self._latest_entry() node_id = entry.state_nodes[0].id response = self.client.post( f"/assets/{self.asset_id}/entries/{entry.id}/edit", data={ "entry_kind": "state", "title": "Edited interval", "start_at": "2060-03-12T09:00", "end_at": "2060-03-13T09:00", "state_node_id": [str(node_id)], "state_node_title": ["Edited node"], "state_node_at": ["2060-03-12T11:00"], "state_node_detail": ["After edit"], "state_node_state_label": ["Exploration"], "state_node_previous_location": ["Mars Orbit"], "state_node_transit_location": ["Transfer"], "state_node_target_location": ["Mars Surface"], }, ) self.assertEqual(response.status_code, 302) updated = self._latest_entry() node = updated.state_nodes[0] self.assertEqual(node.title, "Edited node") self.assertEqual(node.state_label, "Exploration") self.assertEqual(node.previous_location, "Mars Orbit") self.assertEqual(node.transit_location, "Transfer") self.assertEqual(node.target_location, "Mars Surface") def test_json_state_nodes_save_all_location_and_state_fields(self) -> None: response = self.client.post( f"/assets/{self.asset_id}/entries/new", json={ "entry_kind": "state", "title": "Quick state", "start_at": "2060-03-12T09:00", "end_at": "2060-03-13T09:00", "state_label": "Exploration", "location": "Mars Surface", "state_nodes": [ { "title": "Landed", "at_time": "2060-03-12T10:00", "detail": "Surface operations", "state_label": "Exploration", "previous_location": "Mars Orbit", "transit_location": "Transfer", "target_location": "Mars Surface", } ], }, ) self.assertEqual(response.status_code, 200) entry = self._latest_entry() self.assertEqual(len(entry.state_nodes), 1) node = entry.state_nodes[0] self.assertEqual(node.state_label, "Exploration") self.assertEqual(node.previous_location, "Mars Orbit") self.assertEqual(node.transit_location, "Transfer") self.assertEqual(node.target_location, "Mars Surface") def test_json_event_without_start_at_uses_sim_time(self) -> None: response = self.client.post( f"/assets/{self.asset_id}/entries/new", json={ "entry_kind": "event", "title": "Quick event", "start_at": None, "sim_time": "2060-03-12T09:00", "location": "LEO", "summary": "Created from the quick modal", }, ) self.assertEqual(response.status_code, 200, response.get_data(as_text=True)) entry = self._latest_entry() self.assertEqual(entry.entry_kind, "event") self.assertEqual(entry.start_at, datetime(2060, 3, 12, 9, 0)) self.assertEqual(entry.state_nodes[0].target_location, "LEO") def test_form_event_location_is_saved_for_display(self) -> None: response = self.client.post( f"/assets/{self.asset_id}/entries/new", data={ "entry_kind": "event", "title": "Quick sighting", "start_at": "2060-03-12T09:00", "location": "Mars Orbit", "summary": "Point event with a location", }, ) self.assertEqual(response.status_code, 302) entry = self._latest_entry() self.assertEqual(entry.entry_kind, "event") self.assertEqual(len(entry.state_nodes), 1) self.assertEqual(entry.state_nodes[0].target_location, "Mars Orbit") def test_dock_existing_vehicle_creates_active_parent_child_relation_and_logs(self) -> None: with self.app.app_context(): child = Asset(name="Codex Test Shuttle", asset_type="Vehicle", home_region="LEO") db.session.add(child) db.session.commit() child_id = child.id response = self.client.post( f"/api/v1/assets/{self.asset_id}/dock", json={ "mode": "dock_vehicle", "vehicle_id": str(child_id), "vehicle_name": "Codex Test Shuttle", "sim_time": "2060-03-12T09:00", "note": "Docked in test", }, ) self.assertEqual(response.status_code, 200, response.get_data(as_text=True)) with self.app.app_context(): event = db.session.query(DockingEvent).one() self.assertEqual(event.parent_asset_id, self.asset_id) self.assertEqual(event.child_asset_id, child_id) self.assertIsNone(event.undocked_at) parent = db.session.get(Asset, self.asset_id) child = db.session.get(Asset, child_id) parent_snapshot = self.app.view_functions["web.asset_detail"].__globals__["_build_asset_snapshot"]( parent, datetime(2060, 3, 12, 9, 0) ) child_snapshot = self.app.view_functions["web.asset_detail"].__globals__["_build_asset_snapshot"]( child, datetime(2060, 3, 12, 9, 0) ) self.assertEqual(parent_snapshot["docked_vehicles"][0]["id"], child_id) self.assertEqual(child_snapshot["docking_target"]["id"], self.asset_id) self.assertEqual(len(parent.log_entries), 1) self.assertEqual(len(child.log_entries), 1) def test_dock_to_target_creates_reverse_relation(self) -> None: with self.app.app_context(): target = Asset(name="Codex Test Station", asset_type="Station", home_region="Mars Orbit") db.session.add(target) db.session.commit() target_id = target.id response = self.client.post( f"/api/v1/assets/{self.asset_id}/dock", json={ "mode": "dock_to_target", "target_asset_id": str(target_id), "sim_time": "2060-03-12T09:00", }, ) self.assertEqual(response.status_code, 200, response.get_data(as_text=True)) with self.app.app_context(): event = db.session.query(DockingEvent).one() self.assertEqual(event.parent_asset_id, target_id) self.assertEqual(event.child_asset_id, self.asset_id) current = db.session.get(Asset, self.asset_id) target = db.session.get(Asset, target_id) current_snapshot = self.app.view_functions["web.asset_detail"].__globals__["_build_asset_snapshot"]( current, datetime(2060, 3, 12, 9, 0) ) target_snapshot = self.app.view_functions["web.asset_detail"].__globals__["_build_asset_snapshot"]( target, datetime(2060, 3, 12, 9, 0) ) self.assertEqual(current_snapshot["docking_target"]["id"], target_id) self.assertEqual(target_snapshot["docked_vehicles"][0]["id"], self.asset_id) def test_dock_custom_vehicle_creates_label_relation(self) -> None: response = self.client.post( f"/api/v1/assets/{self.asset_id}/dock", json={ "mode": "dock_vehicle", "vehicle_id": None, "vehicle_name": "External Cargo Pod", "sim_time": "2060-03-12T09:00", }, ) self.assertEqual(response.status_code, 200, response.get_data(as_text=True)) with self.app.app_context(): event = db.session.query(DockingEvent).one() self.assertEqual(event.parent_asset_id, self.asset_id) self.assertIsNone(event.child_asset_id) self.assertEqual(event.child_label, "External Cargo Pod") if __name__ == "__main__": unittest.main()