import { describe, expect, it } from "vitest"; import { classifyProtocolFamily, decodeGrpcFrame, decodeJsonRpc, decodeMessagePack, inferMessageSchema, looksLikeCredential, protocolCaptureSchema, type ProtocolMessage, type ProtocolCapture, } from "../src/domain/protocolCapture.js"; describe("protocol capture", () => { it("classifies gRPC from content type", () => { expect(classifyProtocolFamily("application/grpc+proto", null)).toBe("grpc"); }); it("classifies protobuf from content type", () => { expect(classifyProtocolFamily("application/x-protobuf", null)).toBe( "protobuf", ); }); it("classifies json-rpc from content type", () => { expect(classifyProtocolFamily("application/json-rpc", null)).toBe( "json-rpc", ); }); it("classifies msgpack from content type", () => { expect(classifyProtocolFamily("application/msgpack", null)).toBe( "messagepack", ); }); it("classifies from path when content type is null", () => { expect(classifyProtocolFamily(null, "/some/grpc/method")).toBe("grpc"); expect(classifyProtocolFamily(null, "/api/json-rpc/2.0")).toBe("json-rpc"); }); it("returns null for unknown protocols", () => { expect(classifyProtocolFamily("text/plain", null)).toBeNull(); expect(classifyProtocolFamily(null, null)).toBeNull(); }); it("detects credential-like field names", () => { expect(looksLikeCredential("password")).toBe(true); expect(looksLikeCredential("api_key")).toBe(true); expect(looksLikeCredential("authorization")).toBe(true); expect(looksLikeCredential("username")).toBe(false); }); }); describe("JSON-RPC decoding", () => { it("decodes a simple JSON-RPC request", () => { const payload = new TextEncoder().encode( JSON.stringify({ jsonrpc: "2.0", method: "add", params: [1, 2], id: 1, }), ); const result = decodeJsonRpc(payload); expect(result.decoded_fields.length).toBe(4); expect(result.truncated).toBe(false); }); it("redacts credential-like fields", () => { const payload = new TextEncoder().encode( JSON.stringify({ password: "secret123", username: "admin", }), ); const result = decodeJsonRpc(payload); const passwordField = result.decoded_fields.find( (f) => f.path === "password", ); expect(passwordField?.value).toBe("[REDACTED]"); const usernameField = result.decoded_fields.find( (f) => f.path === "username", ); expect(usernameField?.value).toBe("admin"); }); }); describe("MessagePack decoding", () => { it("detects map type", () => { const payload = new Uint8Array([0x82]); const result = decodeMessagePack(payload); expect(result.decoded_fields[0]?.value).toBe("map"); }); it("detects array type", () => { const payload = new Uint8Array([0x92]); const result = decodeMessagePack(payload); expect(result.decoded_fields[0]?.value).toBe("array"); }); it("reports unknown type for other tags", () => { const payload = new Uint8Array([0xff]); const result = decodeMessagePack(payload); expect(result.decoded_fields[0]?.value).toBe("unknown"); expect(result.decoded_fields[0]?.inferred).toBe(true); }); }); describe("gRPC frame decoding", () => { it("decodes a valid gRPC frame header", () => { const payload = new Uint8Array([ 0x00, 0x00, 0x00, 0x00, 0x05, 1, 2, 3, 4, 5, ]); const result = decodeGrpcFrame(payload); expect(result.decoded_fields).toHaveLength(2); expect(result.decoded_fields[0]?.path).toBe("compressed"); expect(result.decoded_fields[0]?.value).toBe(false); expect(result.decoded_fields[1]?.path).toBe("message_length"); expect(result.decoded_fields[1]?.value).toBe(5); }); it("detects compressed flag", () => { const payload = new Uint8Array([0x01, 0x00, 0x00, 0x00, 0x0a]); const result = decodeGrpcFrame(payload); expect(result.decoded_fields[0]?.value).toBe(true); }); it("handles too-short frames", () => { const payload = new Uint8Array([0x00, 0x01]); const result = decodeGrpcFrame(payload); expect(result.decoded_fields[0]?.path).toBe("error"); }); }); describe("schema inference", () => { it("infers schema from repeated messages", () => { const messages: ProtocolMessage[] = [ { sequence: 0, at_ms: 0, family: "json-rpc", direction: "request", endpoint: "/api", content_type: "application/json", raw_payload: null, decoded_fields: [ { path: "method", wire_type: "json", value: "add", inferred: false }, { path: "id", wire_type: "json", value: 1, inferred: false }, ], schema_hypotheses: [], truncated: false, credentials_redacted: false, }, { sequence: 1, at_ms: 10, family: "json-rpc", direction: "response", endpoint: "/api", content_type: "application/json", raw_payload: null, decoded_fields: [ { path: "result", wire_type: "json", value: 3, inferred: false }, { path: "id", wire_type: "json", value: 1, inferred: false }, ], schema_hypotheses: [], truncated: false, credentials_redacted: false, }, ]; const hypotheses = inferMessageSchema(messages); expect(hypotheses).toHaveLength(3); const idHypothesis = hypotheses.find((h) => h.field_name === "id"); expect(idHypothesis).toBeDefined(); expect(idHypothesis!.evidence_count).toBe(2); expect(idHypothesis!.confidence).toBe(1); }); }); describe("uses ProtocolCapture type", () => { it("validates a well-formed capture typed value", () => { const capture: ProtocolCapture = { family: "grpc", messages: [], inferred_schema: [], has_truncated: false, credentials_detected: false, }; expect(capture.family).toBe("grpc"); }); }); describe("protocol capture schema validation", () => { it("validates a well-formed capture", () => { const capture = { family: "grpc" as const, messages: [], inferred_schema: [], has_truncated: false, credentials_detected: false, }; const result = protocolCaptureSchema.safeParse(capture); expect(result.success).toBe(true); }); });