diff --git a/fixtures/golden/navigation_gps_request.json b/fixtures/golden/navigation_gps_request.json new file mode 100644 index 0000000..666f070 --- /dev/null +++ b/fixtures/golden/navigation_gps_request.json @@ -0,0 +1,71 @@ +{ + "description": "Golden wire-format fixtures for CarServer.Action.vehicleAction (navigation GPS request, latitude/longitude pair). Pins NavigationGpsRequest.lat and .lon as little-endian IEEE-754 doubles at tags 1 and 2. Coordinates below are synthetic, and every case leaves order (tag 3) at its default so it is absent from the wire. Consumed identically by the TypeScript and Python test suites: both encoders must produce these exact bytes, and both decoders must round-trip them back to the same field values.", + "outer_message": "CarServer.Action", + "outer_field": { + "name": "vehicleAction", + "tag": 2, + "type": "CarServer.VehicleAction" + }, + "cases": [ + { + "name": "navigation_gps_northern_western", + "vehicle_action_field": { + "name": "navigationGpsRequest", + "tag": 53, + "type": "CarServer.NavigationGpsRequest" + }, + "payload": { + "lat": 37.5, + "lon": -122.25, + "order": "REMOTE_NAV_TRIP_ORDER_UNKNOWN", + "order_number": 0 + }, + "hex": "1215aa0312090000000000c04240110000000000905ec0" + }, + { + "name": "navigation_gps_origin_all_defaults", + "vehicle_action_field": { + "name": "navigationGpsRequest", + "tag": 53, + "type": "CarServer.NavigationGpsRequest" + }, + "payload": { + "lat": 0.0, + "lon": 0.0, + "order": "REMOTE_NAV_TRIP_ORDER_UNKNOWN", + "order_number": 0 + }, + "hex": "1203aa0300" + }, + { + "name": "navigation_gps_southern_eastern", + "vehicle_action_field": { + "name": "navigationGpsRequest", + "tag": 53, + "type": "CarServer.NavigationGpsRequest" + }, + "payload": { + "lat": -33.875, + "lon": 151.25, + "order": "REMOTE_NAV_TRIP_ORDER_UNKNOWN", + "order_number": 0 + }, + "hex": "1215aa0312090000000000f040c0110000000000e86240" + }, + { + "name": "navigation_gps_positive_extremes", + "vehicle_action_field": { + "name": "navigationGpsRequest", + "tag": 53, + "type": "CarServer.NavigationGpsRequest" + }, + "payload": { + "lat": 90.0, + "lon": 180.0, + "order": "REMOTE_NAV_TRIP_ORDER_UNKNOWN", + "order_number": 0 + }, + "hex": "1215aa0312090000000000805640110000000000806640" + } + ] +} diff --git a/packages/python/tests/test_navigation_gps_request.py b/packages/python/tests/test_navigation_gps_request.py new file mode 100644 index 0000000..071a694 --- /dev/null +++ b/packages/python/tests/test_navigation_gps_request.py @@ -0,0 +1,44 @@ +import json +import unittest +from pathlib import Path + +from tesla_protocol.command.car_server_pb2 import Action, NavigationGpsRequest + +FIXTURE = json.loads( + ( + Path(__file__).parents[3] / "fixtures" / "golden" / "navigation_gps_request.json" + ).read_text() +) + + +class GoldenFixtureTests(unittest.TestCase): + def test_outer_vehicle_action_wrapper_is_tag_2(self): + self.assertEqual(FIXTURE["outer_field"]["tag"], 2) + self.assertEqual(FIXTURE["outer_field"]["name"], "vehicleAction") + + def test_fixtures_round_trip_to_golden_bytes(self): + for case in FIXTURE["cases"]: + with self.subTest(case=case["name"]): + self.assertEqual(case["vehicle_action_field"]["tag"], 53) + action = Action( + vehicleAction={ + "navigationGpsRequest": NavigationGpsRequest( + lat=case["payload"]["lat"], + lon=case["payload"]["lon"], + order=case["payload"]["order_number"], + ) + } + ) + + encoded = action.SerializeToString() + self.assertEqual(encoded.hex(), case["hex"]) + + decoded = Action.FromString(encoded) + request = decoded.vehicleAction.navigationGpsRequest + self.assertEqual(request.lat, case["payload"]["lat"]) + self.assertEqual(request.lon, case["payload"]["lon"]) + self.assertEqual(request.order, case["payload"]["order_number"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/packages/typescript/test/navigation-gps-request.test.ts b/packages/typescript/test/navigation-gps-request.test.ts new file mode 100644 index 0000000..75fbbce --- /dev/null +++ b/packages/typescript/test/navigation-gps-request.test.ts @@ -0,0 +1,43 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +import { Action } from "../dist/command/car_server.mjs"; + +const fixturePath = fileURLToPath( + new URL("../../../fixtures/golden/navigation_gps_request.json", import.meta.url), +); +const fixture = JSON.parse(readFileSync(fixturePath, "utf8")); + +function hex(bytes: Uint8Array): string { + return Buffer.from(bytes).toString("hex"); +} + +test("Action.vehicleAction outer wrapper is tag 2", () => { + assert.equal(fixture.outer_field.tag, 2); + assert.equal(fixture.outer_field.name, "vehicleAction"); +}); + +for (const c of fixture.cases) { + test(`golden fixture: ${c.name}`, () => { + assert.equal(c.vehicle_action_field.tag, 53); + const action = Action.fromJSON({ + vehicleAction: { + navigationGpsRequest: { + lat: c.payload.lat, + lon: c.payload.lon, + order: c.payload.order_number, + }, + }, + }); + + const encoded = Action.encode(action).finish(); + assert.equal(hex(encoded), c.hex, `encode mismatch for ${c.name}`); + + const decoded = Action.decode(encoded); + assert.equal(decoded.vehicleAction?.navigationGpsRequest?.lat ?? 0, c.payload.lat); + assert.equal(decoded.vehicleAction?.navigationGpsRequest?.lon ?? 0, c.payload.lon); + assert.equal(decoded.vehicleAction?.navigationGpsRequest?.order ?? 0, c.payload.order_number); + }); +}