Files
suaveplan/packages/networking/drive/drive-webrtc/tests/signaling.schemas.test.ts
2026-05-27 23:47:53 +01:00

431 lines
11 KiB
TypeScript

/**
* 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);
});
});