// Verifies the orchestration order in `GalaxyClient.executeCommand`: // // core.signRequest(fields) // → signer(canonicalBytes) // → edge.executeCommand({ envelope, signature }) // → core.verifyPayloadHash(response payload, hash) // → core.verifyResponse(envelope, signature) // → return response.payloadBytes // // Tests use a hand-rolled mock `Core` and a Connect router transport // from `@connectrpc/connect`, which lets us assert what the gateway // would have received without standing up a real Connect server. import { create } from "@bufbuild/protobuf"; import { createClient, createRouterTransport } from "@connectrpc/connect"; import { describe, expect, test, vi } from "vitest"; import { GalaxyClient } from "../src/api/galaxy-client"; import { EdgeGateway, ExecuteCommandResponseSchema, type ExecuteCommandRequest, } from "../src/proto/galaxy/gateway/v1/edge_gateway_pb"; import type { Core, RequestSigningFields, ResponseSigningFields, } from "../src/platform/core/index"; const FIXED_REQUEST_ID = "req-test-1"; const FIXED_TIMESTAMP = 1_700_000_000_000n; describe("GalaxyClient.executeCommand", () => { test("orchestrates sign → fetch → verify and returns the payload", async () => { const canonicalBytes = new Uint8Array([0xca, 0xfe]); const signature = new Uint8Array(64).fill(0x55); const responsePayload = new TextEncoder().encode("hello-from-server"); const responseHash = new Uint8Array(32).fill(0x77); const responseSignature = new Uint8Array(64).fill(0x99); const responsePublicKey = new Uint8Array(32).fill(0x11); const core = mockCore({ signRequestImpl: () => canonicalBytes, verifyResponseImpl: () => true, verifyPayloadHashImpl: () => true, }); const signer = vi.fn(async () => signature); const sha256 = vi.fn(async () => new Uint8Array(32).fill(0x33)); let captured: ExecuteCommandRequest | undefined; const transport = createRouterTransport(({ service }) => { service(EdgeGateway, { executeCommand(req) { captured = req; return create(ExecuteCommandResponseSchema, { protocolVersion: "v1", requestId: FIXED_REQUEST_ID, timestampMs: FIXED_TIMESTAMP, resultCode: "ok", payloadBytes: responsePayload, payloadHash: responseHash, signature: responseSignature, }); }, subscribeEvents() { throw new Error("not used in this test"); }, }); }); const edge = createClient(EdgeGateway, transport); const client = new GalaxyClient({ core, edge, signer, sha256, deviceSessionId: "device-session-1", gatewayResponsePublicKey: responsePublicKey, clock: () => FIXED_TIMESTAMP, requestIdFactory: () => FIXED_REQUEST_ID, }); const out = await client.executeCommand( "user.account.get", new TextEncoder().encode("client-payload"), ); expect(out.resultCode).toBe("ok"); expect(Array.from(out.payloadBytes)).toEqual(Array.from(responsePayload)); expect(signer).toHaveBeenCalledWith(canonicalBytes); expect(sha256).toHaveBeenCalledTimes(1); expect(core.signRequest).toHaveBeenCalledTimes(1); const verifyHashCall = vi.mocked(core.verifyPayloadHash).mock.calls[0]!; expect(Array.from(verifyHashCall[0])).toEqual(Array.from(responsePayload)); expect(Array.from(verifyHashCall[1])).toEqual(Array.from(responseHash)); const verifyRespCall = vi.mocked(core.verifyResponse).mock.calls[0]!; expect(Array.from(verifyRespCall[0])).toEqual(Array.from(responsePublicKey)); expect(Array.from(verifyRespCall[1])).toEqual(Array.from(responseSignature)); expect(verifyRespCall[2]).toMatchObject({ protocolVersion: "v1", requestId: FIXED_REQUEST_ID, resultCode: "ok", }); expect(captured?.deviceSessionId).toBe("device-session-1"); expect(captured?.messageType).toBe("user.account.get"); expect(captured?.requestId).toBe(FIXED_REQUEST_ID); expect(Array.from(captured?.signature ?? [])).toEqual( Array.from(signature), ); }); test("throws when the response signature fails verification", async () => { const core = mockCore({ signRequestImpl: () => new Uint8Array(), verifyResponseImpl: () => false, verifyPayloadHashImpl: () => true, }); const transport = createRouterTransport(({ service }) => { service(EdgeGateway, { executeCommand: () => create(ExecuteCommandResponseSchema, { protocolVersion: "v1", requestId: FIXED_REQUEST_ID, timestampMs: FIXED_TIMESTAMP, resultCode: "ok", payloadBytes: new Uint8Array(), payloadHash: new Uint8Array(32), signature: new Uint8Array(64), }), subscribeEvents() { throw new Error("not used in this test"); }, }); }); const client = new GalaxyClient({ core, edge: createClient(EdgeGateway, transport), signer: async () => new Uint8Array(64), sha256: async () => new Uint8Array(32), deviceSessionId: "device-session-1", gatewayResponsePublicKey: new Uint8Array(32), clock: () => FIXED_TIMESTAMP, requestIdFactory: () => FIXED_REQUEST_ID, }); await expect( client.executeCommand("user.account.get", new Uint8Array()), ).rejects.toThrow(/invalid response signature/); }); test("throws when the response payload_hash does not match", async () => { const core = mockCore({ signRequestImpl: () => new Uint8Array(), verifyResponseImpl: () => true, verifyPayloadHashImpl: () => false, }); const transport = createRouterTransport(({ service }) => { service(EdgeGateway, { executeCommand: () => create(ExecuteCommandResponseSchema, { protocolVersion: "v1", requestId: FIXED_REQUEST_ID, timestampMs: FIXED_TIMESTAMP, resultCode: "ok", payloadBytes: new Uint8Array(), payloadHash: new Uint8Array(32), signature: new Uint8Array(64), }), subscribeEvents() { throw new Error("not used in this test"); }, }); }); const client = new GalaxyClient({ core, edge: createClient(EdgeGateway, transport), signer: async () => new Uint8Array(64), sha256: async () => new Uint8Array(32), deviceSessionId: "device-session-1", gatewayResponsePublicKey: new Uint8Array(32), clock: () => FIXED_TIMESTAMP, requestIdFactory: () => FIXED_REQUEST_ID, }); await expect( client.executeCommand("user.account.get", new Uint8Array()), ).rejects.toThrow(/payload_hash mismatch/); }); }); interface MockCoreOptions { signRequestImpl: (fields: RequestSigningFields) => Uint8Array; verifyResponseImpl: ( publicKey: Uint8Array, signature: Uint8Array, fields: ResponseSigningFields, ) => boolean; verifyPayloadHashImpl: ( payload: Uint8Array, hash: Uint8Array, ) => boolean; } function mockCore(opts: MockCoreOptions): Core & { signRequest: ReturnType; verifyResponse: ReturnType; verifyPayloadHash: ReturnType; verifyEvent: ReturnType; } { return { signRequest: vi.fn(opts.signRequestImpl), verifyResponse: vi.fn(opts.verifyResponseImpl), verifyEvent: vi.fn(() => true), verifyPayloadHash: vi.fn(opts.verifyPayloadHashImpl), // `GalaxyClient` does not exercise the Phase 18 calc bridge, // so these stubs only need to satisfy the `Core` interface. driveEffective: () => 0, emptyMass: () => 0, weaponsBlockMass: () => 0, fullMass: () => 0, speed: () => 0, cargoCapacity: () => 0, carryingMass: () => 0, blockUpgradeCost: () => 0, }; }