test: cover connection failure edge cases
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
||||||
|
|
||||||
import { buildHeartbeat, decodeBuiltin, encodeBuiltin } from "../../Yonexus.Protocol/src/index.js";
|
import {
|
||||||
|
buildHeartbeat,
|
||||||
|
buildHello,
|
||||||
|
decodeBuiltin,
|
||||||
|
encodeBuiltin,
|
||||||
|
YONEXUS_PROTOCOL_VERSION
|
||||||
|
} from "../../Yonexus.Protocol/src/index.js";
|
||||||
import { createYonexusServerRuntime } from "../plugin/core/runtime.js";
|
import { createYonexusServerRuntime } from "../plugin/core/runtime.js";
|
||||||
import { createClientRecord, type ClientRecord } from "../plugin/core/persistence.js";
|
import { createClientRecord, type ClientRecord } from "../plugin/core/persistence.js";
|
||||||
import type { YonexusServerStore } from "../plugin/core/store.js";
|
import type { YonexusServerStore } from "../plugin/core/store.js";
|
||||||
@@ -40,10 +46,12 @@ function createMockStore(initialClients: ClientRecord[] = []): YonexusServerStor
|
|||||||
|
|
||||||
function createMockTransport() {
|
function createMockTransport() {
|
||||||
const sent: Array<{ connection: ClientConnection; message: string }> = [];
|
const sent: Array<{ connection: ClientConnection; message: string }> = [];
|
||||||
|
const tempAssignments = new Map<ClientConnection["ws"], string>();
|
||||||
|
const connections = new Map<string, ClientConnection>();
|
||||||
|
|
||||||
const transport: ServerTransport = {
|
const transport: ServerTransport = {
|
||||||
isRunning: false,
|
isRunning: false,
|
||||||
connections: new Map(),
|
connections,
|
||||||
start: vi.fn(),
|
start: vi.fn(),
|
||||||
stop: vi.fn(),
|
stop: vi.fn(),
|
||||||
send: vi.fn(),
|
send: vi.fn(),
|
||||||
@@ -53,9 +61,31 @@ function createMockTransport() {
|
|||||||
}),
|
}),
|
||||||
broadcast: vi.fn(),
|
broadcast: vi.fn(),
|
||||||
closeConnection: vi.fn(),
|
closeConnection: vi.fn(),
|
||||||
promoteToAuthenticated: vi.fn(),
|
promoteToAuthenticated: vi.fn((identifier: string, ws: ClientConnection["ws"]) => {
|
||||||
removeTempConnection: vi.fn(),
|
if (!tempAssignments.has(ws)) {
|
||||||
assignIdentifierToTemp: vi.fn()
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existing = connections.get(identifier);
|
||||||
|
if (existing) {
|
||||||
|
existing.ws.close(1008, "Connection replaced by new authenticated session");
|
||||||
|
}
|
||||||
|
|
||||||
|
connections.set(identifier, {
|
||||||
|
identifier,
|
||||||
|
ws,
|
||||||
|
connectedAt: 1_710_000_000,
|
||||||
|
isAuthenticated: true
|
||||||
|
});
|
||||||
|
tempAssignments.delete(ws);
|
||||||
|
return true;
|
||||||
|
}),
|
||||||
|
removeTempConnection: vi.fn((ws: ClientConnection["ws"]) => {
|
||||||
|
tempAssignments.delete(ws);
|
||||||
|
}),
|
||||||
|
assignIdentifierToTemp: vi.fn((ws: ClientConnection["ws"], identifier: string) => {
|
||||||
|
tempAssignments.set(ws, identifier);
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
return { transport, sent };
|
return { transport, sent };
|
||||||
@@ -193,4 +223,75 @@ describe("YNX-1105d: Connection & Heartbeat Failure Paths", () => {
|
|||||||
code: "AUTH_FAILED"
|
code: "AUTH_FAILED"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("CF-04: protocol version mismatch returns error and closes the connection", 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();
|
||||||
|
await runtime.handleMessage(
|
||||||
|
connection,
|
||||||
|
encodeBuiltin(
|
||||||
|
buildHello(
|
||||||
|
{
|
||||||
|
identifier: "client-a",
|
||||||
|
hasSecret: false,
|
||||||
|
hasKeyPair: false,
|
||||||
|
protocolVersion: `${YONEXUS_PROTOCOL_VERSION}-unsupported`
|
||||||
|
},
|
||||||
|
{ requestId: "req-hello-version", timestamp: now }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastMessage = decodeBuiltin(sent.at(-1)!.message);
|
||||||
|
expect(lastMessage.type).toBe("error");
|
||||||
|
expect(lastMessage.payload).toMatchObject({
|
||||||
|
code: "UNSUPPORTED_PROTOCOL_VERSION"
|
||||||
|
});
|
||||||
|
expect(connection.ws.close).toHaveBeenCalledWith(1002, "Unsupported protocol version");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("CF-03: promoting a new authenticated connection replaces the old one", async () => {
|
||||||
|
const previousSocket = createMockSocket();
|
||||||
|
const replacementSocket = createMockSocket();
|
||||||
|
const previousConnection = {
|
||||||
|
identifier: "client-a",
|
||||||
|
ws: previousSocket,
|
||||||
|
connectedAt: now - 5,
|
||||||
|
isAuthenticated: true
|
||||||
|
} satisfies ClientConnection;
|
||||||
|
|
||||||
|
const { transport } = createMockTransport();
|
||||||
|
transport.connections.set("client-a", previousConnection);
|
||||||
|
|
||||||
|
transport.assignIdentifierToTemp(replacementSocket, "client-a");
|
||||||
|
const promoted = transport.promoteToAuthenticated("client-a", replacementSocket);
|
||||||
|
|
||||||
|
expect(promoted).toBe(true);
|
||||||
|
expect(previousSocket.close).toHaveBeenCalledWith(
|
||||||
|
1008,
|
||||||
|
"Connection replaced by new authenticated session"
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeConnection = transport.connections.get("client-a");
|
||||||
|
expect(activeConnection?.ws).toBe(replacementSocket);
|
||||||
|
expect(activeConnection?.isAuthenticated).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user