Apply editorconfig
This commit is contained in:
parent
ad3191957a
commit
b05e415acf
131 changed files with 16404 additions and 13617 deletions
|
|
@ -8,291 +8,291 @@ import type { Settings } from "../persistence/settings";
|
|||
const WebSocket = require("ws") as typeof globalThis.WebSocket;
|
||||
|
||||
class MockCloseEvent extends Event {
|
||||
public code: number;
|
||||
public reason: string;
|
||||
public code: number;
|
||||
public reason: string;
|
||||
|
||||
public constructor(
|
||||
type: string,
|
||||
options: { code: number; reason: string }
|
||||
) {
|
||||
super(type);
|
||||
this.code = options.code;
|
||||
this.reason = options.reason;
|
||||
}
|
||||
public constructor(
|
||||
type: string,
|
||||
options: { code: number; reason: string }
|
||||
) {
|
||||
super(type);
|
||||
this.code = options.code;
|
||||
this.reason = options.reason;
|
||||
}
|
||||
}
|
||||
|
||||
class MockMessageEvent extends Event {
|
||||
public data: string;
|
||||
public data: string;
|
||||
|
||||
public constructor(type: string, options: { data: string }) {
|
||||
super(type);
|
||||
this.data = options.data;
|
||||
}
|
||||
public constructor(type: string, options: { data: string }) {
|
||||
super(type);
|
||||
this.data = options.data;
|
||||
}
|
||||
}
|
||||
|
||||
class MockWebSocket {
|
||||
public readyState: number = WebSocket.CONNECTING;
|
||||
public onopen: ((event: Event) => void) | null = null;
|
||||
public onclose: ((event: MockCloseEvent) => void) | null = null;
|
||||
public onmessage: ((event: MockMessageEvent) => void) | null = null;
|
||||
public onerror: ((event: Event) => void) | null = null;
|
||||
public readyState: number = WebSocket.CONNECTING;
|
||||
public onopen: ((event: Event) => void) | null = null;
|
||||
public onclose: ((event: MockCloseEvent) => void) | null = null;
|
||||
public onmessage: ((event: MockMessageEvent) => void) | null = null;
|
||||
public onerror: ((event: Event) => void) | null = null;
|
||||
|
||||
public sentMessages: string[] = [];
|
||||
public sentMessages: string[] = [];
|
||||
|
||||
public constructor(public url: string) {
|
||||
setTimeout(() => {
|
||||
if (this.readyState === WebSocket.CONNECTING) {
|
||||
this.readyState = WebSocket.OPEN;
|
||||
this.onopen?.(new Event("open"));
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
public constructor(public url: string) {
|
||||
setTimeout(() => {
|
||||
if (this.readyState === WebSocket.CONNECTING) {
|
||||
this.readyState = WebSocket.OPEN;
|
||||
this.onopen?.(new Event("open"));
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
public send(data: string): void {
|
||||
if (this.readyState !== WebSocket.OPEN) {
|
||||
throw new Error("WebSocket is not open");
|
||||
}
|
||||
this.sentMessages.push(data);
|
||||
}
|
||||
public send(data: string): void {
|
||||
if (this.readyState !== WebSocket.OPEN) {
|
||||
throw new Error("WebSocket is not open");
|
||||
}
|
||||
this.sentMessages.push(data);
|
||||
}
|
||||
|
||||
public close(code?: number, reason?: string): void {
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
this.onclose?.(
|
||||
new MockCloseEvent("close", {
|
||||
code: code ?? 1000,
|
||||
reason: reason ?? ""
|
||||
})
|
||||
);
|
||||
}
|
||||
public close(code?: number, reason?: string): void {
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
this.onclose?.(
|
||||
new MockCloseEvent("close", {
|
||||
code: code ?? 1000,
|
||||
reason: reason ?? ""
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public simulateMessage(data: unknown): void {
|
||||
this.onmessage?.(
|
||||
new MockMessageEvent("message", { data: JSON.stringify(data) })
|
||||
);
|
||||
}
|
||||
public simulateMessage(data: unknown): void {
|
||||
this.onmessage?.(
|
||||
new MockMessageEvent("message", { data: JSON.stringify(data) })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
type MockFn<T extends (...args: unknown[]) => unknown> = T & {
|
||||
calls: Parameters<T>[];
|
||||
calls: Parameters<T>[];
|
||||
};
|
||||
|
||||
function createMockFn<T extends (...args: unknown[]) => unknown>(
|
||||
implementation?: T
|
||||
implementation?: T
|
||||
): MockFn<T> {
|
||||
const calls: Parameters<T>[] = [];
|
||||
const mockFn = ((...args: Parameters<T>) => {
|
||||
calls.push(args);
|
||||
return implementation?.(...args);
|
||||
}) as unknown as MockFn<T>;
|
||||
mockFn.calls = calls;
|
||||
return mockFn;
|
||||
const calls: Parameters<T>[] = [];
|
||||
const mockFn = ((...args: Parameters<T>) => {
|
||||
calls.push(args);
|
||||
return implementation?.(...args);
|
||||
}) as unknown as MockFn<T>;
|
||||
mockFn.calls = calls;
|
||||
return mockFn;
|
||||
}
|
||||
|
||||
describe("WebSocketManager", () => {
|
||||
let mockLogger: Logger = undefined as unknown as Logger;
|
||||
let mockSettings: Settings = undefined as unknown as Settings;
|
||||
let deviceId = "test-device-123";
|
||||
let mockLogger: Logger = undefined as unknown as Logger;
|
||||
let mockSettings: Settings = undefined as unknown as Settings;
|
||||
let deviceId = "test-device-123";
|
||||
|
||||
beforeEach(() => {
|
||||
deviceId = "test-device-123";
|
||||
const noop = (): void => {
|
||||
// Intentionally empty for mock
|
||||
};
|
||||
mockLogger = {
|
||||
info: createMockFn(noop),
|
||||
warn: createMockFn(noop),
|
||||
error: createMockFn(noop),
|
||||
debug: createMockFn(noop)
|
||||
} as unknown as Logger;
|
||||
beforeEach(() => {
|
||||
deviceId = "test-device-123";
|
||||
const noop = (): void => {
|
||||
// Intentionally empty for mock
|
||||
};
|
||||
mockLogger = {
|
||||
info: createMockFn(noop),
|
||||
warn: createMockFn(noop),
|
||||
error: createMockFn(noop),
|
||||
debug: createMockFn(noop)
|
||||
} as unknown as Logger;
|
||||
|
||||
mockSettings = {
|
||||
getSettings: () => ({
|
||||
remoteUri: "https://example.com",
|
||||
vaultName: "test-vault",
|
||||
webSocketRetryIntervalMs: 1000
|
||||
})
|
||||
} as unknown as Settings;
|
||||
});
|
||||
mockSettings = {
|
||||
getSettings: () => ({
|
||||
remoteUri: "https://example.com",
|
||||
vaultName: "test-vault",
|
||||
webSocketRetryIntervalMs: 1000
|
||||
})
|
||||
} as unknown as Settings;
|
||||
});
|
||||
|
||||
it("cleans up promises after message handling", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
it("cleans up promises after message handling", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
|
||||
manager.onRemoteVaultUpdateReceived.add(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
});
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
manager.onRemoteVaultUpdateReceived.add(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
});
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const { outstandingPromises } = manager as unknown as {
|
||||
outstandingPromises: Promise<unknown>[];
|
||||
};
|
||||
const mockWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
const { outstandingPromises } = manager as unknown as {
|
||||
outstandingPromises: Promise<unknown>[];
|
||||
};
|
||||
const mockWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
|
||||
mockWs.simulateMessage({ type: "vaultUpdate", updates: [] });
|
||||
mockWs.simulateMessage({ type: "vaultUpdate", updates: [] });
|
||||
mockWs.simulateMessage({ type: "vaultUpdate", updates: [] });
|
||||
mockWs.simulateMessage({ type: "vaultUpdate", updates: [] });
|
||||
mockWs.simulateMessage({ type: "vaultUpdate", updates: [] });
|
||||
mockWs.simulateMessage({ type: "vaultUpdate", updates: [] });
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
assert.strictEqual(outstandingPromises.length, 0);
|
||||
await manager.stop();
|
||||
});
|
||||
assert.strictEqual(outstandingPromises.length, 0);
|
||||
await manager.stop();
|
||||
});
|
||||
|
||||
it("cleans up cursor position promises", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
it("cleans up cursor position promises", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
|
||||
manager.onRemoteCursorsUpdateReceived.add(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
});
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
manager.onRemoteCursorsUpdateReceived.add(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
});
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const { outstandingPromises } = manager as unknown as {
|
||||
outstandingPromises: Promise<unknown>[];
|
||||
};
|
||||
const mockWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
const { outstandingPromises } = manager as unknown as {
|
||||
outstandingPromises: Promise<unknown>[];
|
||||
};
|
||||
const mockWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
|
||||
mockWs.simulateMessage({
|
||||
type: "cursorPositions",
|
||||
clients: [{ deviceId: "other-device", cursors: [] }]
|
||||
});
|
||||
mockWs.simulateMessage({
|
||||
type: "cursorPositions",
|
||||
clients: [{ deviceId: "other-device", cursors: [] }]
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
assert.strictEqual(outstandingPromises.length, 0);
|
||||
await manager.stop();
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
assert.strictEqual(outstandingPromises.length, 0);
|
||||
await manager.stop();
|
||||
});
|
||||
|
||||
it("logs handshake send errors", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
it("logs handshake send errors", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const mockWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
mockWs.send = (): void => {
|
||||
throw new Error("Buffer full");
|
||||
};
|
||||
const mockWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
mockWs.send = (): void => {
|
||||
throw new Error("Buffer full");
|
||||
};
|
||||
|
||||
assert.throws(() => {
|
||||
manager.sendHandshakeMessage({
|
||||
type: "handshake",
|
||||
token: "test",
|
||||
deviceId: "test",
|
||||
lastSeenVaultUpdateId: null
|
||||
});
|
||||
});
|
||||
assert.throws(() => {
|
||||
manager.sendHandshakeMessage({
|
||||
type: "handshake",
|
||||
token: "test",
|
||||
deviceId: "test",
|
||||
lastSeenVaultUpdateId: null
|
||||
});
|
||||
});
|
||||
|
||||
await manager.stop();
|
||||
});
|
||||
await manager.stop();
|
||||
});
|
||||
|
||||
it("completes stop with timeout protection", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
it("completes stop with timeout protection", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
await manager.stop();
|
||||
assert.ok(true);
|
||||
});
|
||||
await manager.stop();
|
||||
assert.ok(true);
|
||||
});
|
||||
|
||||
it("clears old handlers on reconnection", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
it("clears old handlers on reconnection", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
|
||||
let statusChangeCount = 0;
|
||||
manager.onWebSocketStatusChanged.add(() => {
|
||||
statusChangeCount++;
|
||||
});
|
||||
let statusChangeCount = 0;
|
||||
manager.onWebSocketStatusChanged.add(() => {
|
||||
statusChangeCount++;
|
||||
});
|
||||
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const firstWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
const firstWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
|
||||
statusChangeCount = 0;
|
||||
statusChangeCount = 0;
|
||||
|
||||
(
|
||||
manager as unknown as { initializeWebSocket: () => void }
|
||||
).initializeWebSocket();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
(
|
||||
manager as unknown as { initializeWebSocket: () => void }
|
||||
).initializeWebSocket();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
statusChangeCount = 0;
|
||||
statusChangeCount = 0;
|
||||
|
||||
// Old handler should be cleared
|
||||
firstWs.onclose?.(
|
||||
new MockCloseEvent("close", { code: 1000, reason: "test" })
|
||||
);
|
||||
// Old handler should be cleared
|
||||
firstWs.onclose?.(
|
||||
new MockCloseEvent("close", { code: 1000, reason: "test" })
|
||||
);
|
||||
|
||||
assert.strictEqual(statusChangeCount, 0);
|
||||
await manager.stop();
|
||||
});
|
||||
assert.strictEqual(statusChangeCount, 0);
|
||||
await manager.stop();
|
||||
});
|
||||
|
||||
it("tracks message handling promises", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
it("tracks message handling promises", async () => {
|
||||
const manager = new WebSocketManager(
|
||||
deviceId,
|
||||
mockLogger,
|
||||
mockSettings,
|
||||
MockWebSocket as unknown as typeof WebSocket
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/init-declarations
|
||||
let resolveListener: () => void;
|
||||
const listenerPromise = new Promise<void>((resolve) => {
|
||||
resolveListener = resolve;
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/init-declarations
|
||||
let resolveListener: () => void;
|
||||
const listenerPromise = new Promise<void>((resolve) => {
|
||||
resolveListener = resolve;
|
||||
});
|
||||
|
||||
manager.onRemoteVaultUpdateReceived.add(async () => {
|
||||
await listenerPromise;
|
||||
});
|
||||
manager.onRemoteVaultUpdateReceived.add(async () => {
|
||||
await listenerPromise;
|
||||
});
|
||||
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
manager.start();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
const mockWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
mockWs.simulateMessage({ type: "vaultUpdate", updates: [] });
|
||||
const mockWs = (manager as unknown as { webSocket: MockWebSocket })
|
||||
.webSocket;
|
||||
mockWs.simulateMessage({ type: "vaultUpdate", updates: [] });
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
|
||||
const { outstandingPromises } = manager as unknown as {
|
||||
outstandingPromises: Promise<unknown>[];
|
||||
};
|
||||
const { outstandingPromises } = manager as unknown as {
|
||||
outstandingPromises: Promise<unknown>[];
|
||||
};
|
||||
|
||||
assert.ok(outstandingPromises.length > 0);
|
||||
assert.ok(outstandingPromises.length > 0);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
resolveListener!();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
resolveListener!();
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
|
||||
assert.strictEqual(outstandingPromises.length, 0);
|
||||
await manager.stop();
|
||||
});
|
||||
assert.strictEqual(outstandingPromises.length, 0);
|
||||
await manager.stop();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue