Expose telemetry.track through the plugin SDK and server host bridge, forward plugin-prefixed events into the shared telemetry client, and demonstrate the capability in the kitchen sink example.\n\nCo-Authored-By: Paperclip <noreply@paperclip.ing>
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { createHostClientHandlers } from "../../../packages/plugins/sdk/src/host-client-factory.js";
|
|
import { PLUGIN_RPC_ERROR_CODES } from "../../../packages/plugins/sdk/src/protocol.js";
|
|
import { buildHostServices } from "../services/plugin-host-services.js";
|
|
|
|
const mockGetTelemetryClient = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("../telemetry.js", () => ({
|
|
getTelemetryClient: mockGetTelemetryClient,
|
|
}));
|
|
|
|
function createEventBusStub() {
|
|
return {
|
|
forPlugin() {
|
|
return {
|
|
emit: vi.fn(),
|
|
subscribe: vi.fn(),
|
|
};
|
|
},
|
|
} as any;
|
|
}
|
|
|
|
describe("plugin telemetry bridge", () => {
|
|
beforeEach(() => {
|
|
mockGetTelemetryClient.mockReset();
|
|
});
|
|
|
|
it("prefixes plugin telemetry events before forwarding them to the telemetry client", async () => {
|
|
const track = vi.fn();
|
|
mockGetTelemetryClient.mockReturnValue({ track });
|
|
|
|
const services = buildHostServices(
|
|
{} as never,
|
|
"plugin-record-id",
|
|
"linear",
|
|
createEventBusStub(),
|
|
);
|
|
const handlers = createHostClientHandlers({
|
|
pluginId: "linear",
|
|
capabilities: ["telemetry.track"],
|
|
services,
|
|
});
|
|
|
|
await handlers["telemetry.track"]({
|
|
eventName: "sync_completed",
|
|
dimensions: { attempts: 2, success: true },
|
|
});
|
|
|
|
expect(track).toHaveBeenCalledWith("plugin.linear.sync_completed", {
|
|
attempts: 2,
|
|
success: true,
|
|
});
|
|
});
|
|
|
|
it("rejects invalid bare telemetry event names before prefixing", async () => {
|
|
mockGetTelemetryClient.mockReturnValue({ track: vi.fn() });
|
|
|
|
const services = buildHostServices(
|
|
{} as never,
|
|
"plugin-record-id",
|
|
"linear",
|
|
createEventBusStub(),
|
|
);
|
|
|
|
await expect(
|
|
services.telemetry.track({ eventName: "sync.completed" }),
|
|
).rejects.toThrow(
|
|
'Plugin telemetry event names must be lowercase slugs using letters, numbers, "_" or "-".',
|
|
);
|
|
});
|
|
|
|
it("rejects telemetry tracking when the plugin lacks the capability", async () => {
|
|
const services = buildHostServices(
|
|
{} as never,
|
|
"plugin-record-id",
|
|
"linear",
|
|
createEventBusStub(),
|
|
);
|
|
const handlers = createHostClientHandlers({
|
|
pluginId: "linear",
|
|
capabilities: [],
|
|
services,
|
|
});
|
|
|
|
await expect(
|
|
handlers["telemetry.track"]({ eventName: "sync_completed" }),
|
|
).rejects.toMatchObject({
|
|
code: PLUGIN_RPC_ERROR_CODES.CAPABILITY_DENIED,
|
|
});
|
|
|
|
expect(mockGetTelemetryClient).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("passes telemetry requests through when the plugin declares the capability", async () => {
|
|
const services = buildHostServices(
|
|
{} as never,
|
|
"plugin-record-id",
|
|
"linear",
|
|
createEventBusStub(),
|
|
);
|
|
const handlers = createHostClientHandlers({
|
|
pluginId: "linear",
|
|
capabilities: ["telemetry.track"],
|
|
services,
|
|
});
|
|
|
|
await handlers["telemetry.track"]({
|
|
eventName: "sync_completed",
|
|
dimensions: { source: "manual" },
|
|
});
|
|
|
|
expect(mockGetTelemetryClient).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|