- Protocol spec: encrypted envelope format, device identity (Ed25519 + X25519), LWW conflict resolution with Lamport clocks - E2E encryption: XChaCha20-Poly1305 via sodium-native, X25519 key exchange - WebSocket relay server: stateless message forwarding, device auth via challenge-response, offline message queuing, ping/pong keepalive - Device pairing: time-limited pairing codes, key exchange broker via HTTP - Sync protocol: envelope builder/opener, conflict-resolving cookie store - 31 tests passing (crypto, pairing, conflict resolution, full integration) Co-Authored-By: Paperclip <noreply@paperclip.ing>
58 lines
1.8 KiB
TypeScript
58 lines
1.8 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { PairingStore, generatePairingCode } from "../src/pairing/index.js";
|
|
|
|
describe("PairingStore", () => {
|
|
it("creates and finds a pairing session", () => {
|
|
const store = new PairingStore();
|
|
const session = store.create("device-a-id", "device-a-x25519-pub");
|
|
expect(session.pairingCode).toHaveLength(6);
|
|
expect(session.deviceId).toBe("device-a-id");
|
|
|
|
const found = store.find(session.pairingCode);
|
|
expect(found).not.toBeNull();
|
|
expect(found!.deviceId).toBe("device-a-id");
|
|
});
|
|
|
|
it("consumes a session (one-time use)", () => {
|
|
const store = new PairingStore();
|
|
const session = store.create("d1", "pub1");
|
|
|
|
const consumed = store.consume(session.pairingCode);
|
|
expect(consumed).not.toBeNull();
|
|
expect(consumed!.deviceId).toBe("d1");
|
|
|
|
// Second consume returns null
|
|
const again = store.consume(session.pairingCode);
|
|
expect(again).toBeNull();
|
|
});
|
|
|
|
it("returns null for unknown code", () => {
|
|
const store = new PairingStore();
|
|
expect(store.find("999999")).toBeNull();
|
|
});
|
|
|
|
it("expires sessions after TTL", () => {
|
|
const store = new PairingStore();
|
|
const session = store.create("d1", "pub1");
|
|
|
|
// Manually expire by setting expiresAt in the past
|
|
// We access the internal session via find and mutate it
|
|
const found = store.find(session.pairingCode);
|
|
if (found) {
|
|
(found as { expiresAt: number }).expiresAt = Date.now() - 1000;
|
|
}
|
|
|
|
expect(store.find(session.pairingCode)).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("generatePairingCode", () => {
|
|
it("generates 6-digit codes", () => {
|
|
for (let i = 0; i < 20; i++) {
|
|
const code = generatePairingCode();
|
|
expect(code).toHaveLength(6);
|
|
expect(/^\d{6}$/.test(code)).toBe(true);
|
|
}
|
|
});
|
|
});
|