139 lines
4.0 KiB
TypeScript
139 lines
4.0 KiB
TypeScript
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
buildSessionTransitionNotificationData,
|
|
getDashboardNotificationStorePath,
|
|
readDashboardNotifications,
|
|
type OrchestratorEvent,
|
|
} from "@aoagents/ao-core";
|
|
import { create, manifest } from "./index.js";
|
|
|
|
let tempDir: string | null = null;
|
|
|
|
function makeConfigPath(): string {
|
|
tempDir = mkdtempSync(join(tmpdir(), "ao-dashboard-plugin-"));
|
|
return join(tempDir, "agent-orchestrator.yaml");
|
|
}
|
|
|
|
function makeEvent(overrides: Partial<OrchestratorEvent> = {}): OrchestratorEvent {
|
|
return {
|
|
id: "evt-1",
|
|
type: "session.needs_input",
|
|
priority: "action",
|
|
sessionId: "worker-1",
|
|
projectId: "demo",
|
|
timestamp: new Date("2026-05-13T12:00:00.000Z"),
|
|
message: "Agent needs input",
|
|
data: buildSessionTransitionNotificationData({
|
|
eventType: "session.needs_input",
|
|
sessionId: "worker-1",
|
|
projectId: "demo",
|
|
context: {
|
|
pr: {
|
|
number: 1,
|
|
url: "https://github.com/acme/app/pull/1",
|
|
title: "Demo PR",
|
|
branch: "demo/pr",
|
|
baseBranch: "main",
|
|
owner: "acme",
|
|
repo: "app",
|
|
isDraft: false,
|
|
},
|
|
prs: [
|
|
{
|
|
number: 1,
|
|
url: "https://github.com/acme/app/pull/1",
|
|
title: "Demo PR",
|
|
branch: "demo/pr",
|
|
baseBranch: "main",
|
|
owner: "acme",
|
|
repo: "app",
|
|
isDraft: false,
|
|
},
|
|
],
|
|
issueId: null,
|
|
issueTitle: null,
|
|
summary: "Demo session",
|
|
branch: "demo/pr",
|
|
},
|
|
oldStatus: "working",
|
|
newStatus: "needs_input",
|
|
}),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
afterEach(() => {
|
|
if (tempDir) rmSync(tempDir, { recursive: true, force: true });
|
|
tempDir = null;
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe("notifier-dashboard", () => {
|
|
it("has dashboard notifier metadata", () => {
|
|
expect(manifest.name).toBe("dashboard");
|
|
expect(manifest.slot).toBe("notifier");
|
|
});
|
|
|
|
it("persists notifications to the config-specific dashboard store", async () => {
|
|
const configPath = makeConfigPath();
|
|
const notifier = create({ configPath, limit: 50 });
|
|
|
|
await notifier.notify(makeEvent());
|
|
|
|
const records = readDashboardNotifications(configPath);
|
|
expect(records).toHaveLength(1);
|
|
expect(records[0].event.sessionId).toBe("worker-1");
|
|
expect(records[0].event.data).toMatchObject({
|
|
schemaVersion: 3,
|
|
subject: { pr: { url: "https://github.com/acme/app/pull/1" } },
|
|
});
|
|
});
|
|
|
|
it("persists actions with notifications", async () => {
|
|
const configPath = makeConfigPath();
|
|
const notifier = create({ configPath });
|
|
|
|
await notifier.notifyWithActions?.(makeEvent(), [
|
|
{ label: "Open PR", url: "https://github.com/acme/app/pull/1" },
|
|
]);
|
|
|
|
const records = readDashboardNotifications(configPath);
|
|
expect(records[0].actions).toEqual([
|
|
{ label: "Open PR", url: "https://github.com/acme/app/pull/1" },
|
|
]);
|
|
});
|
|
|
|
it("retains only the configured limit", async () => {
|
|
const configPath = makeConfigPath();
|
|
const notifier = create({ configPath, limit: 2 });
|
|
|
|
await notifier.notify(makeEvent({ id: "evt-1" }));
|
|
await notifier.notify(makeEvent({ id: "evt-2" }));
|
|
await notifier.notify(makeEvent({ id: "evt-3" }));
|
|
|
|
expect(readDashboardNotifications(configPath, 50).map((record) => record.event.id)).toEqual([
|
|
"evt-2",
|
|
"evt-3",
|
|
]);
|
|
});
|
|
|
|
it("warns and no-ops when configPath is missing", async () => {
|
|
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
const notifier = create();
|
|
|
|
await notifier.notify(makeEvent());
|
|
|
|
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("No configPath available"));
|
|
});
|
|
|
|
it("uses the expected store path shape", () => {
|
|
const configPath = makeConfigPath();
|
|
expect(getDashboardNotificationStorePath(configPath)).toContain(
|
|
"dashboard-notifications.jsonl",
|
|
);
|
|
});
|
|
});
|