/** * Boundary schema tests for SignalingMessageSchema and its variants. * * Asserts (a) every variant parses, (b) malformed payloads are * rejected, (c) unknown discriminators are rejected. */ import { describe, expect, it } from "@suaveplan/testing/runner"; import { AnnounceChunksMessageSchema, AnswerMessageSchema, ChunkBroadcastRequestSchema, ChunkBroadcastResponseSchema, ChunkDataMessageSchema, ChunkDataPartMessageSchema, ChunkQueryMessageSchema, ChunkQueryResponseSchema, ChunkRequestMessageSchema, ChunksUpdatedMessageSchema, DataChannelChunkRequestSchema, IceCandidateMessageSchema, OfferMessageSchema, PeerJoinedMessageSchema, PeerLeftMessageSchema, RegisteredMessageSchema, RegisterMessageSchema, SignalingMessageSchema, } from "../src/index.js"; const validVariants: Array<{ name: string; value: unknown }> = [ { name: "offer", value: { type: "offer", from: "a", to: "b", payload: { type: "offer", sdp: "v=0" }, }, }, { name: "answer", value: { type: "answer", payload: { type: "answer", sdp: "v=0" }, }, }, { name: "ice-candidate", value: { type: "ice-candidate", payload: { candidate: "candidate:1 1 UDP 1 1.1.1.1 1 typ host" }, }, }, { name: "register", value: { type: "register", payload: { peerId: "p1", peerName: "Alice", authToken: "tok", chunks: ["1:0", "1:1"], }, }, }, { name: "registered", value: { type: "registered", payload: { peerId: "p1", peers: [{ id: "p2", name: "Bob", chunks: ["2:0"] }], }, }, }, { name: "peer-joined", value: { type: "peer-joined", payload: { id: "p3", name: "Carol", chunks: [] }, }, }, { name: "peer-left", value: { type: "peer-left", payload: { peerId: "p3" } }, }, { name: "announce-chunks", value: { type: "announce-chunks", payload: { chunks: ["1:0", "1:1"] }, }, }, { name: "peer-chunks-updated", value: { type: "peer-chunks-updated", payload: { peerId: "p1", chunks: ["1:0"] }, }, }, { name: "request-chunk (signaling)", value: { type: "request-chunk", payload: { requestId: "r1", fileId: "1", chunkId: "2" }, }, }, { name: "chunk-data", value: { type: "chunk-data", payload: { requestId: "r1", fileId: "1", chunkId: "2", data: "AAAA", }, }, }, { name: "chunk-data-part", value: { type: "chunk-data-part", requestId: "r1", partIndex: 0, totalParts: 4, data: "AAAA", }, }, { name: "request-chunk (data channel)", value: { type: "request-chunk", requestId: "r1", fileId: "1", chunkId: "2", }, }, { name: "chunk-query", value: { type: "chunk-query", requestId: "r1", fileId: "1", chunkIds: ["0", "1"], }, }, { name: "chunk-query-response", value: { type: "chunk-query-response", requestId: "r1", fileId: "1", availableChunkIds: ["0", "1"], }, }, { name: "chunk-broadcast-request", value: { type: "chunk-broadcast-request", requestId: "r1", fileId: "1", neededChunkIds: ["0", "1"], }, }, { name: "chunk-broadcast-response", value: { type: "chunk-broadcast-response", requestId: "r1", fileId: "1", peerId: "p1", availableChunkIds: ["0", "1"], }, }, ]; describe("SignalingMessageSchema (union)", () => { for (const { name, value } of validVariants) { it(`accepts the ${name} variant`, { timeout: 5000 }, () => { const r = SignalingMessageSchema.safeParse(value); expect(r.success).toBe(true); }); } it("rejects unknown discriminator", { timeout: 5000 }, () => { const r = SignalingMessageSchema.safeParse({ type: "totally-unknown", payload: {}, }); expect(r.success).toBe(false); }); it("rejects fileId that is not a decimal string", { timeout: 5000 }, () => { const r = SignalingMessageSchema.safeParse({ type: "chunk-query", requestId: "r1", fileId: "0xff", chunkIds: ["0"], }); expect(r.success).toBe(false); }); it("rejects negative partIndex on chunk-data-part", { timeout: 5000, }, () => { const r = SignalingMessageSchema.safeParse({ type: "chunk-data-part", requestId: "r1", partIndex: -1, totalParts: 4, data: "AA", }); expect(r.success).toBe(false); }); it("rejects zero totalParts on chunk-data-part", { timeout: 5000 }, () => { const r = SignalingMessageSchema.safeParse({ type: "chunk-data-part", requestId: "r1", partIndex: 0, totalParts: 0, data: "AA", }); expect(r.success).toBe(false); }); it("rejects empty requestId", { timeout: 5000 }, () => { const r = SignalingMessageSchema.safeParse({ type: "request-chunk", requestId: "", fileId: "1", chunkId: "0", }); expect(r.success).toBe(false); }); }); describe("per-variant schemas", () => { it("OfferMessageSchema rejects an answer", { timeout: 5000 }, () => { expect( OfferMessageSchema.safeParse({ type: "answer", payload: { type: "answer" }, }).success ).toBe(false); }); it("AnswerMessageSchema rejects missing payload", { timeout: 5000 }, () => { expect(AnswerMessageSchema.safeParse({ type: "answer" }).success).toBe( false ); }); it("IceCandidateMessageSchema accepts minimum payload", { timeout: 5000, }, () => { expect( IceCandidateMessageSchema.safeParse({ type: "ice-candidate", payload: {}, }).success ).toBe(true); }); it("RegisterMessageSchema requires peerId", { timeout: 5000 }, () => { expect( RegisterMessageSchema.safeParse({ type: "register", payload: { peerName: "n", chunks: [] }, }).success ).toBe(false); }); it("RegisteredMessageSchema requires peers array", { timeout: 5000, }, () => { expect( RegisteredMessageSchema.safeParse({ type: "registered", payload: { peerId: "p1" }, }).success ).toBe(false); }); it("PeerJoinedMessageSchema requires id", { timeout: 5000 }, () => { expect( PeerJoinedMessageSchema.safeParse({ type: "peer-joined", payload: { name: "n", chunks: [] }, }).success ).toBe(false); }); it("PeerLeftMessageSchema rejects missing peerId", { timeout: 5000, }, () => { expect( PeerLeftMessageSchema.safeParse({ type: "peer-left", payload: {}, }).success ).toBe(false); }); it("AnnounceChunksMessageSchema accepts empty chunks array", { timeout: 5000, }, () => { expect( AnnounceChunksMessageSchema.safeParse({ type: "announce-chunks", payload: { chunks: [] }, }).success ).toBe(true); }); it("ChunksUpdatedMessageSchema rejects empty peerId", { timeout: 5000, }, () => { expect( ChunksUpdatedMessageSchema.safeParse({ type: "peer-chunks-updated", payload: { peerId: "", chunks: [] }, }).success ).toBe(false); }); it("ChunkRequestMessageSchema rejects empty data base64", { timeout: 5000, }, () => { expect( ChunkDataMessageSchema.safeParse({ type: "chunk-data", payload: { requestId: "r", fileId: "1", chunkId: "0", data: "", }, }).success ).toBe(false); }); it("ChunkRequestMessageSchema requires nested payload", { timeout: 5000, }, () => { expect( ChunkRequestMessageSchema.safeParse({ type: "request-chunk", requestId: "r", fileId: "1", chunkId: "0", }).success ).toBe(false); }); it("DataChannelChunkRequestSchema requires flat fields", { timeout: 5000, }, () => { expect( DataChannelChunkRequestSchema.safeParse({ type: "request-chunk", payload: { requestId: "r", fileId: "1", chunkId: "0" }, }).success ).toBe(false); }); it("ChunkDataPartMessageSchema rejects float partIndex", { timeout: 5000, }, () => { expect( ChunkDataPartMessageSchema.safeParse({ type: "chunk-data-part", requestId: "r", partIndex: 0.5, totalParts: 4, data: "x", }).success ).toBe(false); }); it("ChunkQueryMessageSchema accepts missing chunkIds", { timeout: 5000, }, () => { expect( ChunkQueryMessageSchema.safeParse({ type: "chunk-query", requestId: "r", fileId: "1", }).success ).toBe(true); }); it("ChunkQueryResponseSchema requires availableChunkIds", { timeout: 5000, }, () => { expect( ChunkQueryResponseSchema.safeParse({ type: "chunk-query-response", requestId: "r", fileId: "1", }).success ).toBe(false); }); it("ChunkBroadcastRequestSchema requires neededChunkIds", { timeout: 5000, }, () => { expect( ChunkBroadcastRequestSchema.safeParse({ type: "chunk-broadcast-request", requestId: "r", fileId: "1", }).success ).toBe(false); }); it("ChunkBroadcastResponseSchema requires peerId", { timeout: 5000, }, () => { expect( ChunkBroadcastResponseSchema.safeParse({ type: "chunk-broadcast-response", requestId: "r", fileId: "1", availableChunkIds: [], }).success ).toBe(false); }); });