Files
software-mansion__argent/packages/tool-server/test/network/network-inspector-blueprint.test.ts
Paweł Fornagiel 73b619bfed Require device_id for debugger/profiler; React profiler session metadata; skill updates (#117)
* Reapply "feat: enhance React profiler session handling with device ID support"

This reverts commit 2bf25cb950.

* Reapply "refactor: remove device-related fields from React profiler session"

This reverts commit badf127411.

* Reapply "refactor: unify device ID handling across debugger and profiler tools, make device_id required, not optional"

This reverts commit c6f1fd7578.

* format: run format

* refactor: enforce device ID requirement in JsRuntimeDebugger and ReactProfilerSession blueprints

* feat: add appName and deviceName to React profiler session and related tools

* fix: update React profiler session output to reflect accurate field names for runtime and project details

* skills: enhance skills for Metro debugger and React profiler tools to clarify device_id usage and connection diagnostics

* format: run format

* test: update NetworkInspector integration tests to include mock-device in service resolutions
2026-04-15 10:32:58 +02:00

237 lines
8.3 KiB
TypeScript

import { describe, it, expect, vi } from "vitest";
import { Registry, ServiceState } from "@argent/registry";
import {
networkInspectorBlueprint,
NETWORK_INSPECTOR_NAMESPACE,
} from "../../src/blueprints/network-inspector";
describe("NetworkInspector blueprint", () => {
it("has namespace 'NetworkInspector'", () => {
expect(networkInspectorBlueprint.namespace).toBe("NetworkInspector");
expect(NETWORK_INSPECTOR_NAMESPACE).toBe("NetworkInspector");
});
it("getURN returns correct format", () => {
expect(networkInspectorBlueprint.getURN("8081:SIM-UDID-123")).toBe(
"NetworkInspector:8081:SIM-UDID-123"
);
expect(networkInspectorBlueprint.getURN("3000:DEV-B")).toBe("NetworkInspector:3000:DEV-B");
});
it("declares JsRuntimeDebugger as a dependency via getDependencies", () => {
expect(networkInspectorBlueprint.getDependencies).toBeDefined();
const deps = networkInspectorBlueprint.getDependencies!("8081:SIM-UDID-123");
expect(deps).toEqual({ debugger: "JsRuntimeDebugger:8081:SIM-UDID-123" });
});
it("getDependencies uses the composite port:deviceId payload for the JsRuntimeDebugger URN", () => {
const deps3000 = networkInspectorBlueprint.getDependencies!("3000:DEV-A");
expect(deps3000).toEqual({ debugger: "JsRuntimeDebugger:3000:DEV-A" });
const deps9090 = networkInspectorBlueprint.getDependencies!("9090:DEV-B");
expect(deps9090).toEqual({ debugger: "JsRuntimeDebugger:9090:DEV-B" });
});
it("factory reuses the CDP client from the debugger dependency (does NOT create its own)", async () => {
const mockEvaluate = vi.fn().mockResolvedValue(JSON.stringify({ installed: true }));
const mockCdp = {
evaluate: mockEvaluate,
events: { on: vi.fn() },
};
const mockDebuggerApi = {
port: 8081,
cdp: mockCdp,
};
const instance = await networkInspectorBlueprint.factory({ debugger: mockDebuggerApi }, "8081");
// The API's cdp should be the SAME object as the debugger's cdp
expect(instance.api.cdp).toBe(mockCdp);
expect(instance.api.port).toBe(8081);
});
it("factory injects the network interceptor script via cdp.evaluate", async () => {
const mockEvaluate = vi.fn().mockResolvedValue(JSON.stringify({ installed: true }));
const mockCdp = {
evaluate: mockEvaluate,
events: { on: vi.fn() },
};
const mockDebuggerApi = {
port: 8081,
cdp: mockCdp,
};
await networkInspectorBlueprint.factory({ debugger: mockDebuggerApi }, "8081");
expect(mockEvaluate).toHaveBeenCalledTimes(1);
// Verify it called evaluate with a string containing the interceptor guard
const script = mockEvaluate.mock.calls[0][0] as string;
expect(script).toContain("__argent_network_installed");
expect(script).toContain("globalThis.fetch");
});
it("factory does not fail if interceptor injection throws (catches error)", async () => {
const mockEvaluate = vi.fn().mockRejectedValue(new Error("eval failed"));
const mockCdp = {
evaluate: mockEvaluate,
events: { on: vi.fn() },
};
const mockDebuggerApi = {
port: 8081,
cdp: mockCdp,
};
// Should not throw
const instance = await networkInspectorBlueprint.factory({ debugger: mockDebuggerApi }, "8081");
expect(instance.api).toBeDefined();
expect(instance.api.port).toBe(8081);
});
it("dispose does NOT disconnect the CDP client (owned by JsRuntimeDebugger)", async () => {
const mockDisconnect = vi.fn();
const mockCdp = {
evaluate: vi.fn().mockResolvedValue("{}"),
disconnect: mockDisconnect,
events: { on: vi.fn() },
};
const mockDebuggerApi = {
port: 8081,
cdp: mockCdp,
};
const instance = await networkInspectorBlueprint.factory({ debugger: mockDebuggerApi }, "8081");
await instance.dispose();
expect(mockDisconnect).not.toHaveBeenCalled();
});
it("emits 'terminated' when the CDP connection disconnects", async () => {
let disconnectHandler: ((error?: Error) => void) | null = null;
const mockCdp = {
evaluate: vi.fn().mockResolvedValue("{}"),
events: {
on: vi.fn((event: string, handler: (error?: Error) => void) => {
if (event === "disconnected") {
disconnectHandler = handler;
}
}),
},
};
const mockDebuggerApi = {
port: 8081,
cdp: mockCdp,
};
const instance = await networkInspectorBlueprint.factory({ debugger: mockDebuggerApi }, "8081");
const terminatedHandler = vi.fn();
instance.events.on("terminated", terminatedHandler);
// Simulate CDP disconnect
expect(disconnectHandler).not.toBeNull();
disconnectHandler!(new Error("connection lost"));
expect(terminatedHandler).toHaveBeenCalledOnce();
expect(terminatedHandler.mock.calls[0][0]).toBeInstanceOf(Error);
expect(terminatedHandler.mock.calls[0][0].message).toBe("connection lost");
});
it("registers in a Registry and resolves correctly with JsRuntimeDebugger dependency", async () => {
// Use the real Registry to verify wiring is correct at the registry level.
// We create a mock JsRuntimeDebugger blueprint that returns a fake API.
const registry = new Registry();
const mockCdp = {
evaluate: vi.fn().mockResolvedValue(JSON.stringify({ installed: true })),
events: { on: vi.fn() },
};
// Register a fake JsRuntimeDebugger blueprint
registry.registerBlueprint({
namespace: "JsRuntimeDebugger",
getURN(payload: string) {
return `JsRuntimeDebugger:${payload}`;
},
async factory() {
const { TypedEventEmitter } = await import("@argent/registry");
const events = new TypedEventEmitter();
return {
api: { port: 8081, cdp: mockCdp, projectRoot: "/test" },
dispose: async () => {},
events,
};
},
});
registry.registerBlueprint(networkInspectorBlueprint);
const api = (await registry.resolveService("NetworkInspector:8081:SIM-UDID-123")) as {
port: number;
cdp: unknown;
};
expect(api.port).toBe(8081);
expect(api.cdp).toBe(mockCdp);
// Both services should be RUNNING
expect(registry.getServiceState("JsRuntimeDebugger:8081:SIM-UDID-123")).toBe(
ServiceState.RUNNING
);
expect(registry.getServiceState("NetworkInspector:8081:SIM-UDID-123")).toBe(
ServiceState.RUNNING
);
await registry.dispose();
});
it("emits 'terminated' with fallback Error when disconnectHandler called with undefined", async () => {
let disconnectHandler: ((error?: Error) => void) | null = null;
const mockCdp = {
evaluate: vi.fn().mockResolvedValue("{}"),
events: {
on: vi.fn((event: string, handler: (error?: Error) => void) => {
if (event === "disconnected") {
disconnectHandler = handler;
}
}),
},
};
const mockDebuggerApi = {
port: 8081,
cdp: mockCdp,
};
const instance = await networkInspectorBlueprint.factory({ debugger: mockDebuggerApi }, "8081");
const terminatedHandler = vi.fn();
instance.events.on("terminated", terminatedHandler);
// Simulate CDP disconnect with no error (undefined)
disconnectHandler!(undefined as unknown as Error);
expect(terminatedHandler).toHaveBeenCalledOnce();
const emittedError = terminatedHandler.mock.calls[0][0];
expect(emittedError).toBeInstanceOf(Error);
expect(emittedError.message).toBe("CDP disconnected");
});
it("does not import discovery, target-selection, or construct CDPClient", async () => {
// Read the source file to verify no dangerous imports exist.
// This is a static analysis check to catch regressions.
const fs = await import("node:fs/promises");
const path = await import("node:path");
const source = await fs.readFile(
path.resolve(__dirname, "../../src/blueprints/network-inspector.ts"),
"utf-8"
);
// Must NOT import from discovery or target-selection (that would mean it creates its own connection)
expect(source).not.toContain('from "../utils/debugger/discovery"');
expect(source).not.toContain('from "../utils/debugger/target-selection"');
// Must NOT construct new CDPClient (only imports the type)
expect(source).not.toContain("new CDPClient");
// Should use type-only import for CDPClient
expect(source).toContain("import type");
});
});