From ef3099314cbfff51b5b079d6c510b623607c97b7 Mon Sep 17 00:00:00 2001 From: sgofferj Date: Tue, 25 Aug 2026 16:42:16 +0300 Subject: [PATCH] feat: complete Mission API - add get_resource and search_sync Wraps the last two unwrapped mission-api operations: - get_resource(hash): resource records by content hash - search_sync(...): data-sync search with the full filter set (bounding box / circle, time range, altitudes, keywords, mime type, mission, tool, uid, hash, filename) Mission API now covers 112/112 spec operations; README table and AGENTS completion status corrected accordingly (the old 69/112 figure was a stale count). 3 new unit tests; live coverage via the existing mission CRUD test. --- README.rst | 2 +- live_tests/test_live_mission.py | 8 ++++ src/python_takserver_api/tak_mission_api.py | 53 +++++++++++++++++++++ tests/test_mission_package.py | 33 +++++++++++++ 4 files changed, 95 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9d7508c..7940a01 100644 --- a/README.rst +++ b/README.rst @@ -30,7 +30,7 @@ Currently covered classes: - Complete, incl. helpers * - ``MissionApi`` - ``server.mission`` - - 69 of 112 spec operations + - 112 of 112 spec operations - Complete, incl. helpers * - ``UserAccountManagementApi`` - ``server.user`` diff --git a/live_tests/test_live_mission.py b/live_tests/test_live_mission.py index 5abe2d3..5632631 100644 --- a/live_tests/test_live_mission.py +++ b/live_tests/test_live_mission.py @@ -48,6 +48,14 @@ async def test_mission_crud(server) -> None: status, data = await server.mission.get_mission_changes(name) assert status == 200, data + + # search_sync finds the mission by name filter (read-only) + status, data = await server.mission.search_sync(mission=name) + assert status == 200, data + + # get_resource on a bogus hash answers 404 without side effects + status, _ = await server.mission.get_resource("live-test-nonexistent-hash") + assert status in (200, 404) finally: status, data = await server.mission.delete_mission(name, creator_uid=creator_uid, deep_delete=True) assert status in (200, 404), (status, data) diff --git a/src/python_takserver_api/tak_mission_api.py b/src/python_takserver_api/tak_mission_api.py index ed99e11..e02aa2d 100644 --- a/src/python_takserver_api/tak_mission_api.py +++ b/src/python_takserver_api/tak_mission_api.py @@ -930,3 +930,56 @@ async def create_mission_subscriptions(self, name: str, creator_uid: str, uids: headers = {"Content-Type": "application/json"} s, r = await self.server.connection.request("post", url, headers=headers, json=uids) return s, r + + async def get_resource(self, hash_id: str) -> tuple[int, Any]: + """Returns the resource records stored under a content hash.""" + path = f"/Marti/api/resources/{hash_id}" + url = self.server.api_base_url + path + headers = {"Content-Type": "application/json"} + s, r = await self.server.connection.request("get", url, headers=headers) + return s, r + + # pylint: disable=too-many-arguments, too-many-positional-arguments + async def search_sync( + self, + box: str | None = None, + circle: str | None = None, + start_time: str | None = None, + end_time: str | None = None, + min_altitude: float | None = None, + max_altitude: float | None = None, + filename: str | None = None, + keyword: list[str] | None = None, + mimetype: str | None = None, + name: str | None = None, + uid: str | None = None, + hash: str | None = None, # pylint: disable=redefined-builtin + mission: str | None = None, + tool: str | None = None, + ) -> tuple[int, Any]: + """Searches mission data sync contents. + + All parameters are optional filters; `box` is a bounding box + ("minLon,minLat,maxLon,maxLat"), `circle` a + "lat,lon,radius(meters)" query. `keyword` may be repeated. + """ + path = "/Marti/api/sync/search" + _query( + box=box, + circle=circle, + startTime=start_time, + endTime=end_time, + minAltitude=min_altitude, + maxAltitude=max_altitude, + filename=filename, + keyword=keyword, + mimetype=mimetype, + name=name, + uid=uid, + hash=hash, + mission=mission, + tool=tool, + ) + url = self.server.api_base_url + path + headers = {"Content-Type": "application/json"} + s, r = await self.server.connection.request("get", url, headers=headers) + return s, r diff --git a/tests/test_mission_package.py b/tests/test_mission_package.py index cc45d9d..3a17124 100644 --- a/tests/test_mission_package.py +++ b/tests/test_mission_package.py @@ -55,6 +55,39 @@ class RequestCase(NamedTuple): MISSION_REQUEST_CASES: list[RequestCase] = [ + RequestCase( + "get_resource", + {"hash_id": "abc123"}, + "get", + "https://tak.example.com:8443/Marti/api/resources/abc123", + None, + None, + {"Content-Type": "application/json"}, + ), + RequestCase( + "search_sync", + { + "box": "1.0,2.0,3.0,4.0", + "keyword": ["kw1", "kw2"], + "mission": "my-mission", + "min_altitude": 100.5, + }, + "get", + "https://tak.example.com:8443/Marti/api/sync/search?box=1.0%2C2.0%2C3.0%2C4.0" + "&minAltitude=100.5&keyword=kw1&keyword=kw2&mission=my-mission", + None, + None, + {"Content-Type": "application/json"}, + ), + RequestCase( + "search_sync", + {}, + "get", + "https://tak.example.com:8443/Marti/api/sync/search", + None, + None, + {"Content-Type": "application/json"}, + ), RequestCase( "get_mission_count", {"password_protected": True, "tool": "public"},