test: add server unit test coverage
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
1267
package-lock.json
generated
1267
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,12 +19,16 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"clean": "rm -rf dist",
|
"clean": "rm -rf dist",
|
||||||
"check": "tsc -p tsconfig.json --noEmit"
|
"check": "tsc -p tsconfig.json --noEmit",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:watch": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.6.3"
|
"@types/node": "^25.5.2",
|
||||||
|
"typescript": "^5.6.3",
|
||||||
|
"vitest": "^4.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
tests/pairing-and-rules.test.ts
Normal file
102
tests/pairing-and-rules.test.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
import { createClientRecord } from "../plugin/core/persistence.js";
|
||||||
|
import { createServerRuleRegistry, ServerRuleRegistryError } from "../plugin/core/rules.js";
|
||||||
|
import { createPairingService } from "../plugin/services/pairing.js";
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
8
vitest.config.ts
Normal file
8
vitest.config.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: "node"
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user