Compare commits

...

1 Commits

Author SHA1 Message Date
nav
3c760fc0f4 test: cover unauth rule + heartbeat failures 2026-04-09 01:19:13 +00:00

View File

@@ -0,0 +1,196 @@
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { buildHeartbeat, decodeBuiltin, encodeBuiltin } from "../../Yonexus.Protocol/src/index.js";
import { createYonexusServerRuntime } from "../plugin/core/runtime.js";
import { createClientRecord, type ClientRecord } from "../plugin/core/persistence.js";
import type { YonexusServerStore } from "../plugin/core/store.js";
import type { ClientConnection, ServerTransport } from "../plugin/core/transport.js";
function createMockSocket() {
return { close: vi.fn() } as unknown as ClientConnection["ws"];
}
function createConnection(identifier: string | null = null): ClientConnection {
return {
identifier,
ws: createMockSocket(),
connectedAt: 1_710_000_000,
isAuthenticated: false
};
}
function createMockStore(initialClients: ClientRecord[] = []): YonexusServerStore {
const persisted = new Map(initialClients.map((record) => [record.identifier, record]));
return {
filePath: "/tmp/yonexus-server-connection-heartbeat-failures.json",
load: vi.fn(async () => ({
version: 1,
persistedAt: 1_710_000_000,
clients: new Map(persisted)
})),
save: vi.fn(async (clients: Iterable<ClientRecord>) => {
persisted.clear();
for (const client of clients) {
persisted.set(client.identifier, client);
}
})
};
}
function createMockTransport() {
const sent: Array<{ connection: ClientConnection; message: string }> = [];
const transport: ServerTransport = {
isRunning: false,
connections: new Map(),
start: vi.fn(),
stop: vi.fn(),
send: vi.fn(),
sendToConnection: vi.fn((connection: ClientConnection, message: string) => {
sent.push({ connection, message });
return true;
}),
broadcast: vi.fn(),
closeConnection: vi.fn(),
promoteToAuthenticated: vi.fn(),
removeTempConnection: vi.fn(),
assignIdentifierToTemp: vi.fn()
};
return { transport, sent };
}
describe("YNX-1105d: Connection & Heartbeat Failure Paths", () => {
let now = 1_710_000_000;
beforeEach(() => {
now = 1_710_000_000;
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it("CF-06: unauthenticated rule message closes connection", async () => {
const record = createClientRecord("client-a");
const store = createMockStore([record]);
const { transport } = createMockTransport();
const runtime = createYonexusServerRuntime({
config: {
followerIdentifiers: ["client-a"],
notifyBotToken: "stub-token",
adminUserId: "admin-user",
listenHost: "127.0.0.1",
listenPort: 8787
},
store,
transport,
now: () => now
});
await runtime.start();
const connection = createConnection("client-a");
runtime.state.registry.sessions.set("client-a", {
identifier: "client-a",
socket: connection.ws,
isAuthenticated: false,
connectedAt: now,
lastActivityAt: now,
publicKey: undefined
});
await runtime.handleMessage(connection, "chat::hello");
expect(connection.ws.close).toHaveBeenCalledWith(1008, "Not authenticated");
});
it("HF-03: heartbeat before auth returns error", async () => {
const record = createClientRecord("client-a");
const store = createMockStore([record]);
const { transport, sent } = createMockTransport();
const runtime = createYonexusServerRuntime({
config: {
followerIdentifiers: ["client-a"],
notifyBotToken: "stub-token",
adminUserId: "admin-user",
listenHost: "127.0.0.1",
listenPort: 8787
},
store,
transport,
now: () => now
});
await runtime.start();
const connection = createConnection("client-a");
runtime.state.registry.sessions.set("client-a", {
identifier: "client-a",
socket: connection.ws,
isAuthenticated: false,
connectedAt: now,
lastActivityAt: now,
publicKey: undefined
});
await runtime.handleMessage(
connection,
encodeBuiltin(
buildHeartbeat(
{ identifier: "client-a", status: "alive" },
{ requestId: "req-hb-early", timestamp: now }
)
)
);
const lastMessage = decodeBuiltin(sent.at(-1)!.message);
expect(lastMessage.type).toBe("error");
expect(lastMessage.payload).toMatchObject({
code: "AUTH_FAILED"
});
});
it("HF-04: heartbeat without session returns error", async () => {
const record = createClientRecord("client-a");
const store = createMockStore([record]);
const { transport, sent } = createMockTransport();
const runtime = createYonexusServerRuntime({
config: {
followerIdentifiers: ["client-a"],
notifyBotToken: "stub-token",
adminUserId: "admin-user",
listenHost: "127.0.0.1",
listenPort: 8787
},
store,
transport,
now: () => now
});
await runtime.start();
const connection = createConnection("client-a");
await runtime.handleMessage(
connection,
encodeBuiltin(
buildHeartbeat(
{ identifier: "client-a", status: "alive" },
{ requestId: "req-hb-unauth", timestamp: now }
)
)
);
const lastMessage = decodeBuiltin(sent.at(-1)!.message);
expect(lastMessage.type).toBe("error");
expect(lastMessage.payload).toMatchObject({
code: "AUTH_FAILED"
});
});
});