Files
remoteconn-gitea/apps/miniprogram/utils/gateway.test.ts
2026-03-21 18:57:10 +08:00

215 lines
6.2 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
const { createGatewayClient } = require("./gateway.js");
function createMockSocketTask() {
const handlers = {
open: null,
message: null,
close: null,
error: null
} as {
open: null | (() => void);
message: null | ((event: { data: string }) => void);
close: null | ((event?: { code?: number; reason?: string }) => void);
error: null | ((error?: { errMsg?: string }) => void);
};
const task = {
send: vi.fn(),
close: vi.fn(),
onOpen(callback: () => void) {
handlers.open = callback;
},
onMessage(callback: (event: { data: string }) => void) {
handlers.message = callback;
},
onClose(callback: (event?: { code?: number; reason?: string }) => void) {
handlers.close = callback;
},
onError(callback: (error?: { errMsg?: string }) => void) {
handlers.error = callback;
}
};
return { handlers, task };
}
describe("gateway", () => {
afterEach(() => {
const runtime = globalThis as typeof globalThis & { wx?: unknown };
delete runtime.wx;
vi.useRealTimers();
vi.restoreAllMocks();
});
it("收到 connected 后可立刻补发一拍时延采样", async () => {
vi.useFakeTimers();
const { handlers, task } = createMockSocketTask();
const runtime = globalThis as typeof globalThis & { wx?: { connectSocket: ReturnType<typeof vi.fn> } };
runtime.wx = {
connectSocket: vi.fn(() => task)
};
const onLatency = vi.fn();
const client = createGatewayClient({
gatewayUrl: "wss://conn.biboer.cn",
gatewayToken: "demo-token",
onLatency
});
const connectPromise = client.connect({
host: "example.com",
port: 22,
username: "root",
credential: { type: "password", password: "secret" }
});
handlers.open?.();
await connectPromise;
expect(task.send).toHaveBeenCalledTimes(1);
expect(JSON.parse(task.send.mock.calls[0][0].data)).toMatchObject({
type: "init"
});
client.sampleLatency();
expect(task.send).toHaveBeenCalledTimes(2);
expect(JSON.parse(task.send.mock.calls[1][0].data)).toEqual({
type: "control",
payload: { action: "ping" }
});
handlers.message?.({
data: JSON.stringify({
type: "control",
payload: { action: "pong" }
})
});
expect(onLatency).toHaveBeenCalledTimes(1);
expect(onLatency.mock.calls[0][0]).toBeGreaterThanOrEqual(0);
client.disconnect("test");
});
it("支持按需切换时延采样心跳间隔", async () => {
vi.useFakeTimers();
const { handlers, task } = createMockSocketTask();
const runtime = globalThis as typeof globalThis & { wx?: { connectSocket: ReturnType<typeof vi.fn> } };
runtime.wx = {
connectSocket: vi.fn(() => task)
};
const client = createGatewayClient({
gatewayUrl: "wss://conn.biboer.cn",
gatewayToken: "demo-token"
});
const connectPromise = client.connect({
host: "example.com",
port: 22,
username: "root",
credential: { type: "password", password: "secret" }
});
handlers.open?.();
await connectPromise;
expect(task.send).toHaveBeenCalledTimes(1);
vi.advanceTimersByTime(9999);
expect(task.send).toHaveBeenCalledTimes(1);
vi.advanceTimersByTime(1);
expect(JSON.parse(task.send.mock.calls[1][0].data)).toEqual({
type: "control",
payload: { action: "ping" }
});
client.setLatencySampleInterval(3000);
vi.advanceTimersByTime(2999);
expect(task.send).toHaveBeenCalledTimes(2);
vi.advanceTimersByTime(1);
expect(JSON.parse(task.send.mock.calls[2][0].data)).toEqual({
type: "control",
payload: { action: "ping" }
});
client.setLatencySampleInterval(10000);
vi.advanceTimersByTime(3000);
expect(task.send).toHaveBeenCalledTimes(3);
vi.advanceTimersByTime(7000);
expect(JSON.parse(task.send.mock.calls[3][0].data)).toEqual({
type: "control",
payload: { action: "ping" }
});
client.disconnect("test");
});
it("首连遇到 connection refused 时会自动重试一次", async () => {
vi.useFakeTimers();
const first = createMockSocketTask();
const second = createMockSocketTask();
const runtime = globalThis as typeof globalThis & { wx?: { connectSocket: ReturnType<typeof vi.fn> } };
runtime.wx = {
connectSocket: vi.fn().mockReturnValueOnce(first.task).mockReturnValueOnce(second.task)
};
const onError = vi.fn();
const client = createGatewayClient({
gatewayUrl: "wss://conn.biboer.cn",
gatewayToken: "demo-token",
onError
});
const connectPromise = client.connect({
host: "example.com",
port: 22,
username: "root",
credential: { type: "password", password: "secret" }
});
first.handlers.error?.({
errMsg: "connectSocket:fail createWebSocketTask:fail connection refused"
});
await vi.advanceTimersByTimeAsync(400);
second.handlers.open?.();
await expect(connectPromise).resolves.toBeUndefined();
expect(runtime.wx.connectSocket).toHaveBeenCalledTimes(2);
expect(first.task.close).toHaveBeenCalledTimes(1);
expect(second.task.send).toHaveBeenCalledTimes(1);
expect(JSON.parse(second.task.send.mock.calls[0][0].data)).toMatchObject({
type: "init"
});
expect(onError).not.toHaveBeenCalled();
client.disconnect("test");
});
it("域名白名单错误不会进入自动重试", async () => {
vi.useFakeTimers();
const first = createMockSocketTask();
const runtime = globalThis as typeof globalThis & { wx?: { connectSocket: ReturnType<typeof vi.fn> } };
runtime.wx = {
connectSocket: vi.fn(() => first.task)
};
const client = createGatewayClient({
gatewayUrl: "wss://conn.biboer.cn",
gatewayToken: "demo-token"
});
const connectPromise = client.connect({
host: "example.com",
port: 22,
username: "root",
credential: { type: "password", password: "secret" }
});
first.handlers.error?.({
errMsg: "connectSocket:fail Error: url not in domain list"
});
await expect(connectPromise).rejects.toThrow("url not in domain list");
expect(runtime.wx.connectSocket).toHaveBeenCalledTimes(1);
});
});