* fix: reduce log noise for node disconnect/late invoke errors - Handle both 'node not connected' and 'node disconnected' errors at info level - Return success with late:true for unknown invoke IDs instead of error - Add 30-second throttle to skills change listener to prevent rapid-fire probes - Add tests for isNodeUnavailableError and late invoke handling * fix: clean up skills refresh timer and listener on shutdown Store the return value from registerSkillsChangeListener() and call it on gateway shutdown. Also clear any pending refresh timer. This follows the same pattern used for agentUnsub and heartbeatUnsub. * refactor: simplify KISS/YAGNI - inline checks, remove unit tests for internal utilities * fix: reduce gateway log noise (#1607) (thanks @petter-b) * test: align agent id casing expectations (#1607) --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
126 lines
3.5 KiB
TypeScript
126 lines
3.5 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
|
|
import { WebSocket } from "ws";
|
|
|
|
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
|
import { loadOrCreateDeviceIdentity } from "../infra/device-identity.js";
|
|
|
|
vi.mock("../infra/update-runner.js", () => ({
|
|
runGatewayUpdate: vi.fn(async () => ({
|
|
status: "ok",
|
|
mode: "git",
|
|
root: "/repo",
|
|
steps: [],
|
|
durationMs: 12,
|
|
})),
|
|
}));
|
|
|
|
import {
|
|
connectOk,
|
|
installGatewayTestHooks,
|
|
rpcReq,
|
|
startServerWithClient,
|
|
} from "./test-helpers.js";
|
|
|
|
installGatewayTestHooks({ scope: "suite" });
|
|
|
|
let server: Awaited<ReturnType<typeof startServerWithClient>>["server"];
|
|
let ws: WebSocket;
|
|
let port: number;
|
|
|
|
beforeAll(async () => {
|
|
const started = await startServerWithClient();
|
|
server = started.server;
|
|
ws = started.ws;
|
|
port = started.port;
|
|
await connectOk(ws);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
ws.close();
|
|
await server.close();
|
|
});
|
|
|
|
describe("late-arriving invoke results", () => {
|
|
test("returns success for unknown invoke id (late arrival after timeout)", async () => {
|
|
// Create a node client WebSocket
|
|
const nodeWs = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
await new Promise<void>((resolve) => nodeWs.once("open", resolve));
|
|
|
|
try {
|
|
// Connect as a node with device identity
|
|
const identity = loadOrCreateDeviceIdentity();
|
|
const nodeId = identity.deviceId;
|
|
|
|
await connectOk(nodeWs, {
|
|
role: "node",
|
|
client: {
|
|
id: GATEWAY_CLIENT_NAMES.NODE_HOST,
|
|
version: "1.0.0",
|
|
platform: "ios",
|
|
mode: GATEWAY_CLIENT_MODES.NODE,
|
|
},
|
|
commands: ["canvas.snapshot"],
|
|
});
|
|
|
|
// Send an invoke result with an unknown ID (simulating late arrival after timeout)
|
|
const result = await rpcReq<{ ok?: boolean; ignored?: boolean }>(
|
|
nodeWs,
|
|
"node.invoke.result",
|
|
{
|
|
id: "unknown-invoke-id-12345",
|
|
nodeId,
|
|
ok: true,
|
|
payloadJSON: JSON.stringify({ result: "late" }),
|
|
},
|
|
);
|
|
|
|
// Late-arriving results return success instead of error to reduce log noise
|
|
expect(result.ok).toBe(true);
|
|
expect(result.payload?.ok).toBe(true);
|
|
expect(result.payload?.ignored).toBe(true);
|
|
} finally {
|
|
nodeWs.close();
|
|
}
|
|
});
|
|
|
|
test("returns success for unknown invoke id with error payload", async () => {
|
|
// Verifies late results are accepted regardless of their ok/error status
|
|
const nodeWs = new WebSocket(`ws://127.0.0.1:${port}`);
|
|
await new Promise<void>((resolve) => nodeWs.once("open", resolve));
|
|
|
|
try {
|
|
await connectOk(nodeWs, {
|
|
role: "node",
|
|
client: {
|
|
id: GATEWAY_CLIENT_NAMES.NODE_HOST,
|
|
version: "1.0.0",
|
|
platform: "darwin",
|
|
mode: GATEWAY_CLIENT_MODES.NODE,
|
|
},
|
|
commands: [],
|
|
});
|
|
|
|
const identity = loadOrCreateDeviceIdentity();
|
|
const nodeId = identity.deviceId;
|
|
|
|
// Late invoke result with error payload - should still return success
|
|
const result = await rpcReq<{ ok?: boolean; ignored?: boolean }>(
|
|
nodeWs,
|
|
"node.invoke.result",
|
|
{
|
|
id: "another-unknown-invoke-id",
|
|
nodeId,
|
|
ok: false,
|
|
error: { code: "FAILED", message: "test error" },
|
|
},
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
expect(result.payload?.ok).toBe(true);
|
|
expect(result.payload?.ignored).toBe(true);
|
|
} finally {
|
|
nodeWs.close();
|
|
}
|
|
});
|
|
});
|