import { describe, expect, it, vi } from "vitest"; import { createClientRecord, type ClientRecord } from "../plugin/core/persistence.js"; import { createServerRuleRegistry, ServerRuleRegistryError } from "../plugin/core/rules.js"; import { createPairingService } from "../plugin/services/pairing.js"; // Inline protocol helpers (to avoid submodule dependency in tests) function createAuthRequestSigningInput(input: { secret: string; nonce: string; proofTimestamp: number; }): string { return JSON.stringify({ secret: input.secret, nonce: input.nonce, timestamp: input.proofTimestamp }); } function isTimestampFresh( proofTimestamp: number, now: number, maxDriftSeconds: number = 10 ): { ok: true } | { ok: false; reason: "stale_timestamp" | "future_timestamp" } { const drift = proofTimestamp - now; if (Math.abs(drift) < maxDriftSeconds) { return { ok: true }; } return { ok: false, reason: drift < 0 ? "stale_timestamp" : "future_timestamp" }; } describe("Yonexus.Server PairingService", () => { it("creates a pending pairing request with ttl metadata", () => { const record = createClientRecord("client-a"); const pairing = createPairingService({ now: () => 1_710_000_000 }); const request = pairing.createPairingRequest(record, { ttlSeconds: 180 }); expect(request.identifier).toBe("client-a"); expect(request.ttlSeconds).toBe(180); expect(request.expiresAt).toBe(1_710_000_180); expect(record.pairingStatus).toBe("pending"); expect(record.pairingCode).toBe(request.pairingCode); expect(record.pairingNotifyStatus).toBe("pending"); }); it("confirms a valid pairing and clears pairing-only fields", () => { const record = createClientRecord("client-a"); const pairing = createPairingService({ now: () => 1_710_000_100 }); const request = pairing.createPairingRequest(record, { ttlSeconds: 300 }); const result = pairing.confirmPairing(record, request.pairingCode); expect(result).toMatchObject({ success: true, pairedAt: 1_710_000_100 }); expect(typeof result.secret).toBe("string"); expect(record.pairingStatus).toBe("paired"); expect(record.secret).toBe(result.secret); expect(record.pairingCode).toBeUndefined(); expect(record.pairingExpiresAt).toBeUndefined(); expect(record.pairingNotifyStatus).toBeUndefined(); }); it("rejects expired and invalid pairing confirmations without dirtying state", () => { const record = createClientRecord("client-a"); let now = 1_710_000_000; const pairing = createPairingService({ now: () => now }); const request = pairing.createPairingRequest(record, { ttlSeconds: 60 }); const invalid = pairing.confirmPairing(record, "WRONG-CODE-000"); expect(invalid).toEqual({ success: false, reason: "invalid_code" }); expect(record.pairingStatus).toBe("pending"); expect(record.pairingCode).toBe(request.pairingCode); now = 1_710_000_100; const expired = pairing.confirmPairing(record, request.pairingCode); expect(expired).toEqual({ success: false, reason: "expired" }); expect(record.pairingStatus).toBe("unpaired"); expect(record.pairingCode).toBeUndefined(); }); it("marks notification delivery state transitions", () => { const record = createClientRecord("client-a"); const pairing = createPairingService({ now: () => 1_710_000_000 }); pairing.createPairingRequest(record); pairing.markNotificationSent(record); expect(record.pairingNotifyStatus).toBe("sent"); expect(record.pairingNotifiedAt).toBe(1_710_000_000); pairing.markNotificationFailed(record); expect(record.pairingNotifyStatus).toBe("failed"); }); }); describe("Yonexus.Server RuleRegistry", () => { it("dispatches exact-match rewritten messages to the registered processor", () => { const registry = createServerRuleRegistry(); const processor = vi.fn(); registry.registerRule("chat_sync", processor); const handled = registry.dispatch("chat_sync::client-a::{\"body\":\"hello\"}"); expect(handled).toBe(true); expect(processor).toHaveBeenCalledWith("chat_sync::client-a::{\"body\":\"hello\"}"); expect(registry.hasRule("chat_sync")).toBe(true); expect(registry.getRules()).toEqual(["chat_sync"]); }); it("rejects reserved and duplicate rule registrations", () => { const registry = createServerRuleRegistry(); registry.registerRule("chat_sync", () => undefined); expect(() => registry.registerRule("builtin", () => undefined)).toThrow(ServerRuleRegistryError); expect(() => registry.registerRule("chat_sync", () => undefined)).toThrow( "Rule 'chat_sync' is already registered" ); }); it("returns false when no processor matches a rewritten message", () => { const registry = createServerRuleRegistry(); expect(registry.dispatch("chat_sync::client-a::{\"body\":\"hello\"}")).toBe(false); }); }); describe("Yonexus.Server Auth Service", () => { it("verifies valid auth request payload", () => { const record = createClientRecord("client-a"); record.pairingStatus = "paired"; record.publicKey = "test-pk"; record.secret = "test-secret"; const nonce = "RANDOM24CHARACTERSTRINGX"; const timestamp = 1_710_000_000; const signingInput = createAuthRequestSigningInput({ secret: "test-secret", nonce, proofTimestamp: timestamp }); // Mock signature verification (in real impl would use crypto) const mockSignature = `signed:${signingInput}`; const result = verifyAuthRequest( record, { identifier: "client-a", nonce, proofTimestamp: timestamp, signature: mockSignature, publicKey: "test-pk" }, { now: () => timestamp, verifySignature: (sig, input) => sig === `signed:${input}` } ); expect(result.success).toBe(true); expect(result).toHaveProperty("authenticatedAt"); }); it("rejects auth for unpaired client", () => { const record = createClientRecord("client-a"); const result = verifyAuthRequest( record, { identifier: "client-a", nonce: "RANDOM24CHARACTERSTRINGX", proofTimestamp: 1_710_000_000, signature: "sig", publicKey: "pk" }, { now: () => 1_710_000_000 } ); expect(result.success).toBe(false); expect(result.reason).toBe("not_paired"); }); it("rejects auth with mismatched public key", () => { const record = createClientRecord("client-a"); record.pairingStatus = "paired"; record.publicKey = "expected-pk"; record.secret = "secret"; const result = verifyAuthRequest( record, { identifier: "client-a", nonce: "RANDOM24CHARACTERSTRINGX", proofTimestamp: 1_710_000_000, signature: "sig", publicKey: "different-pk" }, { now: () => 1_710_000_000 } ); expect(result.success).toBe(false); expect(result.reason).toBe("public_key_mismatch"); }); it("rejects auth with stale timestamp", () => { const record = createClientRecord("client-a"); record.pairingStatus = "paired"; record.publicKey = "pk"; record.secret = "secret"; const result = verifyAuthRequest( record, { identifier: "client-a", nonce: "RANDOM24CHARACTERSTRINGX", proofTimestamp: 1_710_000_000, signature: "sig", publicKey: "pk" }, { now: () => 1_710_000_100 } ); expect(result.success).toBe(false); expect(result.reason).toBe("stale_timestamp"); }); it("rejects auth with future timestamp", () => { const record = createClientRecord("client-a"); record.pairingStatus = "paired"; record.publicKey = "pk"; record.secret = "secret"; const result = verifyAuthRequest( record, { identifier: "client-a", nonce: "RANDOM24CHARACTERSTRINGX", proofTimestamp: 1_710_000_100, signature: "sig", publicKey: "pk" }, { now: () => 1_710_000_000 } ); expect(result.success).toBe(false); expect(result.reason).toBe("future_timestamp"); }); it("rejects auth with nonce collision", () => { const record = createClientRecord("client-a"); record.pairingStatus = "paired"; record.publicKey = "pk"; record.secret = "secret"; record.recentNonces = [{ nonce: "COLLIDING24CHARSTRINGX", timestamp: 1_710_000_000 }]; const result = verifyAuthRequest( record, { identifier: "client-a", nonce: "COLLIDING24CHARSTRINGX", proofTimestamp: 1_710_000_010, signature: "sig", publicKey: "pk" }, { now: () => 1_710_000_010 } ); expect(result.success).toBe(false); expect(result.reason).toBe("nonce_collision"); }); it("rejects auth with rate limit exceeded", () => { const record = createClientRecord("client-a"); record.pairingStatus = "paired"; record.publicKey = "pk"; record.secret = "secret"; const now = 1_710_000_000; record.recentHandshakeAttempts = Array(11).fill(now - 5); const result = verifyAuthRequest( record, { identifier: "client-a", nonce: "RANDOM24CHARSTRINGX01", proofTimestamp: now, signature: "sig", publicKey: "pk" }, { now: () => now } ); expect(result.success).toBe(false); expect(result.reason).toBe("rate_limited"); }); it("invalid signature triggers re_pair_required", () => { const record = createClientRecord("client-a"); record.pairingStatus = "paired"; record.publicKey = "pk"; record.secret = "secret"; const result = verifyAuthRequest( record, { identifier: "client-a", nonce: "RANDOM24CHARSTRINGX01", proofTimestamp: 1_710_000_000, signature: "invalid-sig", publicKey: "pk" }, { now: () => 1_710_000_000, verifySignature: () => false } ); expect(result.success).toBe(false); expect(result.reason).toBe("re_pair_required"); }); it("tracks successful auth attempt in record", () => { const record = createClientRecord("client-a"); record.pairingStatus = "paired"; record.publicKey = "pk"; record.secret = "secret"; const now = 1_710_000_000; const nonce = "RANDOM24CHARSTRINGX01"; const signingInput = createAuthRequestSigningInput({ secret: "secret", nonce, proofTimestamp: now }); const result = verifyAuthRequest( record, { identifier: "client-a", nonce, proofTimestamp: now, signature: `signed:${signingInput}`, publicKey: "pk" }, { now: () => now, verifySignature: (sig, input) => sig === `signed:${input}` } ); expect(result.success).toBe(true); expect(record.recentNonces).toContainEqual({ nonce, timestamp: now }); expect(record.recentHandshakeAttempts).toContain(now); expect(record.lastAuthenticatedAt).toBe(now); }); }); describe("Yonexus.Server Heartbeat / Liveness", () => { it("evaluates client online when recent heartbeat exists", () => { const record = createClientRecord("client-a"); record.lastHeartbeatAt = 1_710_000_000; record.status = "online"; const status = evaluateLiveness(record, { now: () => 1_710_000_300 }); expect(status).toBe("online"); }); it("evaluates client unstable after 7 minutes without heartbeat", () => { const record = createClientRecord("client-a"); record.lastHeartbeatAt = 1_710_000_000; record.status = "online"; const status = evaluateLiveness(record, { now: () => 1_710_000_420 }); expect(status).toBe("unstable"); }); it("evaluates client offline after 11 minutes without heartbeat", () => { const record = createClientRecord("client-a"); record.lastHeartbeatAt = 1_710_000_000; record.status = "online"; const status = evaluateLiveness(record, { now: () => 1_710_000_660 }); expect(status).toBe("offline"); }); it("handles client with no heartbeat record", () => { const record = createClientRecord("client-a"); const status = evaluateLiveness(record, { now: () => 1_710_000_000 }); expect(status).toBe("offline"); }); }); function evaluateLiveness( record: ReturnType, options: { now: () => number } ): "online" | "unstable" | "offline" { const now = options.now(); const lastHeartbeat = record.lastHeartbeatAt; if (!lastHeartbeat) { return "offline"; } const elapsed = now - lastHeartbeat; if (elapsed >= 11 * 60) { return "offline"; } if (elapsed >= 7 * 60) { return "unstable"; } return "online"; } interface AuthRequestPayload { identifier: string; nonce: string; proofTimestamp: number; signature: string; publicKey?: string; } interface AuthVerifyResult { success: boolean; reason?: string; authenticatedAt?: number; } function verifyAuthRequest( record: ClientRecord, payload: AuthRequestPayload, options: { now: () => number; verifySignature?: (signature: string, input: string) => boolean; } ): AuthVerifyResult { if (record.pairingStatus !== "paired") { return { success: false, reason: "not_paired" }; } if (payload.publicKey && record.publicKey !== payload.publicKey) { return { success: false, reason: "public_key_mismatch" }; } const timestampCheck = isTimestampFresh(payload.proofTimestamp, options.now()); if (!timestampCheck.ok) { return { success: false, reason: timestampCheck.reason }; } const nonceCollision = record.recentNonces.some((n) => n.nonce === payload.nonce); if (nonceCollision) { return { success: false, reason: "nonce_collision" }; } const now = options.now(); const recentAttempts = record.recentHandshakeAttempts.filter((t) => now - t < 10_000); if (recentAttempts.length >= 10) { return { success: false, reason: "rate_limited" }; } const signingInput = createAuthRequestSigningInput({ secret: record.secret!, nonce: payload.nonce, proofTimestamp: payload.proofTimestamp }); const isValidSignature = options.verifySignature?.(payload.signature, signingInput) ?? true; if (!isValidSignature) { return { success: false, reason: "re_pair_required" }; } record.recentNonces.push({ nonce: payload.nonce, timestamp: now }); if (record.recentNonces.length > 10) { record.recentNonces.shift(); } record.recentHandshakeAttempts.push(now); record.lastAuthenticatedAt = now; record.lastHeartbeatAt = now; return { success: true, authenticatedAt: now }; }