Initial Commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { WebRTCChunkSource } from "@suaveplan/drive-webrtc/chunk-source/chunk-source";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/chunk-source/chunk-source bundle size", () => {
|
||||
it("does not reach into signalling or low-level WebRTC", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = WebRTCChunkSource.toString();
|
||||
expect(src).not.toMatch(/RTCPeerConnection/);
|
||||
expect(src).not.toMatch(/WebSocket/);
|
||||
expect(src).not.toMatch(/SignalingClient/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { WebRTCClient } from "@suaveplan/drive-webrtc/client/webrtc-client";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/client/webrtc-client bundle size", () => {
|
||||
it("does not drag in signalling or discovery code", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = WebRTCClient.toString();
|
||||
expect(src).not.toMatch(/SignalingClient/);
|
||||
expect(src).not.toMatch(/ChunkDiscoveryManager/);
|
||||
expect(src).not.toMatch(/WebSocket/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { DataChannelManager } from "@suaveplan/drive-webrtc/data-channel/data-channel-manager";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/data-channel/data-channel-manager bundle size", () => {
|
||||
it("does not pull signalling or discovery code", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = DataChannelManager.toString();
|
||||
expect(src).not.toMatch(/WebSocket/);
|
||||
expect(src).not.toMatch(/SignalingClient/);
|
||||
expect(src).not.toMatch(/ChunkDiscoveryManager/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ChunkIntegrityManager } from "@suaveplan/drive-webrtc/integrity/chunk-integrity";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/integrity/chunk-integrity bundle size", () => {
|
||||
it("does not reach into signalling or peer code", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = ChunkIntegrityManager.toString();
|
||||
expect(src).not.toMatch(/RTCPeerConnection/);
|
||||
expect(src).not.toMatch(/WebSocket/);
|
||||
expect(src).not.toMatch(/SignalingClient/);
|
||||
expect(src).not.toMatch(/ChunkDiscoveryManager/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
import { LRUCache } from "@suaveplan/drive-webrtc/lru-cache/lru-cache";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/lru-cache bundle size", () => {
|
||||
it("does not reach into WebRTC / WebSocket primitives", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = LRUCache.toString();
|
||||
expect(src).not.toMatch(/RTCPeerConnection/);
|
||||
expect(src).not.toMatch(/WebSocket/);
|
||||
expect(src).not.toMatch(/ChunkDiscoveryManager/);
|
||||
expect(src).not.toMatch(/SignalingClient/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { PeerManager } from "@suaveplan/drive-webrtc/peer-manager/peer-manager";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/peer-manager/peer-manager bundle size", () => {
|
||||
it("does not reach into low-level WebRTC primitives", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = PeerManager.toString();
|
||||
expect(src).not.toMatch(/RTCPeerConnection/);
|
||||
expect(src).not.toMatch(/WebSocket/);
|
||||
expect(src).not.toMatch(/ChunkDiscoveryManager/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { WebRTCDrivePlugin } from "@suaveplan/drive-webrtc/plugin/webrtc-plugin";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/plugin/webrtc-plugin bundle size", () => {
|
||||
it("loads the orchestrator class", { timeout: 5000 }, () => {
|
||||
const src = WebRTCDrivePlugin.toString();
|
||||
// Orchestrator is allowed to reference both signalling and
|
||||
// WebRTC primitives because it wires them. Verify it does not
|
||||
// pull in unrelated rate-limiter or LRU internals.
|
||||
expect(src).not.toMatch(/RateLimiter/);
|
||||
expect(src).not.toMatch(/class LRUCache/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { RateLimiter } from "@suaveplan/drive-webrtc/rate-limiter/rate-limiter";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/rate-limiter/rate-limiter bundle size", () => {
|
||||
it("is a leaf module — pulls no other subsystem", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = RateLimiter.toString();
|
||||
expect(src).not.toMatch(/RTCPeerConnection/);
|
||||
expect(src).not.toMatch(/WebSocket/);
|
||||
expect(src).not.toMatch(/ChunkDiscoveryManager/);
|
||||
expect(src).not.toMatch(/SignalingClient/);
|
||||
expect(src).not.toMatch(/PeerManager/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
generateSecurePeerId,
|
||||
generateSecureRequestId,
|
||||
validatePeerSecurity,
|
||||
validateSignalingUrl,
|
||||
} from "@suaveplan/drive-webrtc/security/security-validator";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/security/security-validator bundle size", () => {
|
||||
it("is a leaf module — pulls no other subsystem", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const combined =
|
||||
generateSecurePeerId.toString() +
|
||||
generateSecureRequestId.toString() +
|
||||
validatePeerSecurity.toString() +
|
||||
validateSignalingUrl.toString();
|
||||
expect(combined).not.toMatch(/RTCPeerConnection/);
|
||||
expect(combined).not.toMatch(/WebSocket/);
|
||||
expect(combined).not.toMatch(/SignalingClient/);
|
||||
expect(combined).not.toMatch(/PeerManager/);
|
||||
expect(combined).not.toMatch(/ChunkDiscoveryManager/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { SignalingClient } from "@suaveplan/drive-webrtc/signaling/signaling-client";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/signaling/signaling-client bundle size", () => {
|
||||
it("does not pull RTCPeerConnection or discovery code", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = SignalingClient.toString();
|
||||
expect(src).not.toMatch(/RTCPeerConnection/);
|
||||
expect(src).not.toMatch(/ChunkDiscoveryManager/);
|
||||
expect(src).not.toMatch(/DataChannelManager/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
SubChunkReassembler,
|
||||
sendSubChunks,
|
||||
splitIntoSubChunks,
|
||||
} from "@suaveplan/drive-webrtc/sub-chunk/sub-chunk-splitter";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
describe("@suaveplan/drive-webrtc/sub-chunk/sub-chunk-splitter bundle size", () => {
|
||||
it("does not drag in higher-level subsystems", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src =
|
||||
SubChunkReassembler.toString() +
|
||||
splitIntoSubChunks.toString() +
|
||||
sendSubChunks.toString();
|
||||
expect(src).not.toMatch(/RTCPeerConnection/);
|
||||
expect(src).not.toMatch(/WebSocket/);
|
||||
expect(src).not.toMatch(/SignalingClient/);
|
||||
expect(src).not.toMatch(/ChunkDiscoveryManager/);
|
||||
expect(src).not.toMatch(/PeerManager/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* Boundary schema tests for WebRTCPluginConfigSchema and friends.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import {
|
||||
IceServerSchema,
|
||||
P2PConfigSchema,
|
||||
SignalingConfigSchema,
|
||||
SubChunkConfigSchema,
|
||||
WebRTCPluginConfigSchema,
|
||||
} from "../src/index.js";
|
||||
|
||||
describe("IceServerSchema", () => {
|
||||
it("accepts a single-URL form", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
IceServerSchema.safeParse({
|
||||
urls: "stun:stun.l.google.com:19302",
|
||||
}).success
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts a non-empty URL array with credentials", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(
|
||||
IceServerSchema.safeParse({
|
||||
urls: ["turn:turn.example.com:3478"],
|
||||
username: "u",
|
||||
credential: "p",
|
||||
credentialType: "password",
|
||||
}).success
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects an empty URL array", { timeout: 5000 }, () => {
|
||||
expect(IceServerSchema.safeParse({ urls: [] }).success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects unknown credentialType", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
IceServerSchema.safeParse({
|
||||
urls: "stun:x",
|
||||
credentialType: "oauth",
|
||||
}).success
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SignalingConfigSchema", () => {
|
||||
it("accepts minimal valid config", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
SignalingConfigSchema.safeParse({
|
||||
url: "wss://signal.example.com",
|
||||
}).success
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects empty url", { timeout: 5000 }, () => {
|
||||
expect(SignalingConfigSchema.safeParse({ url: "" }).success).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects non-positive reconnectDelay", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
SignalingConfigSchema.safeParse({
|
||||
url: "wss://x",
|
||||
reconnectDelay: 0,
|
||||
}).success
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("P2PConfigSchema", () => {
|
||||
it("accepts empty config", { timeout: 5000 }, () => {
|
||||
expect(P2PConfigSchema.safeParse({}).success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts a populated config", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
P2PConfigSchema.safeParse({
|
||||
priority: 10,
|
||||
timeout: 30_000,
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||
maxConcurrentDownloads: 5,
|
||||
}).success
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects negative priority", { timeout: 5000 }, () => {
|
||||
expect(P2PConfigSchema.safeParse({ priority: -1 }).success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects zero maxConcurrentDownloads", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
P2PConfigSchema.safeParse({ maxConcurrentDownloads: 0 }).success
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects float timeout", { timeout: 5000 }, () => {
|
||||
expect(P2PConfigSchema.safeParse({ timeout: 1.5 }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SubChunkConfigSchema", () => {
|
||||
it("accepts empty config", { timeout: 5000 }, () => {
|
||||
expect(SubChunkConfigSchema.safeParse({}).success).toBe(true);
|
||||
});
|
||||
|
||||
it("accepts a populated rateLimit", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
SubChunkConfigSchema.safeParse({
|
||||
size: 16_000,
|
||||
backpressure: 262_144,
|
||||
rateLimit: { delayMs: 5, everyNParts: 10 },
|
||||
}).success
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects zero everyNParts", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
SubChunkConfigSchema.safeParse({
|
||||
rateLimit: { delayMs: 0, everyNParts: 0 },
|
||||
}).success
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects negative delayMs", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
SubChunkConfigSchema.safeParse({
|
||||
rateLimit: { delayMs: -1, everyNParts: 1 },
|
||||
}).success
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("WebRTCPluginConfigSchema", () => {
|
||||
it("accepts minimal valid config", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
WebRTCPluginConfigSchema.safeParse({
|
||||
signaling: { url: "wss://signal.example.com" },
|
||||
}).success
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects missing signaling", { timeout: 5000 }, () => {
|
||||
expect(WebRTCPluginConfigSchema.safeParse({}).success).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects invalid nested p2p config", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
WebRTCPluginConfigSchema.safeParse({
|
||||
signaling: { url: "wss://x" },
|
||||
p2p: { timeout: -5 },
|
||||
}).success
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts a fully populated config", { timeout: 5000 }, () => {
|
||||
expect(
|
||||
WebRTCPluginConfigSchema.safeParse({
|
||||
signaling: {
|
||||
url: "wss://signal.example.com",
|
||||
peerId: "peer-a",
|
||||
peerName: "Alice",
|
||||
authToken: "tok",
|
||||
reconnect: true,
|
||||
reconnectDelay: 1000,
|
||||
},
|
||||
p2p: {
|
||||
priority: 5,
|
||||
timeout: 30_000,
|
||||
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
|
||||
maxConcurrentDownloads: 5,
|
||||
},
|
||||
subChunks: {
|
||||
size: 16_000,
|
||||
backpressure: 262_144,
|
||||
rateLimit: { delayMs: 5, everyNParts: 10 },
|
||||
},
|
||||
}).success
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,430 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user