111 lines
3.9 KiB
TypeScript
111 lines
3.9 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
|
|
import { Notifier, type Push } from "../src/notify.ts";
|
|
import type { T3Host } from "../src/t3/host.ts";
|
|
import type { ThreadShell } from "../src/t3/types.ts";
|
|
import { SummaryCache } from "../src/views.ts";
|
|
|
|
const host = {
|
|
id: "host-1",
|
|
label: "Mac mini",
|
|
projects: new Map([["project-1", { id: "project-1", title: "wristcode" }]]),
|
|
bounded: () => Promise.reject(new Error("offline")),
|
|
} as unknown as T3Host;
|
|
|
|
const shell = (overrides: Partial<ThreadShell> = {}): ThreadShell => ({
|
|
id: "thread-1",
|
|
projectId: "project-1",
|
|
title: "Watch app scaffold",
|
|
createdAt: "2026-11-01T09:00:00.000Z",
|
|
updatedAt: "2026-11-01T09:00:00.000Z",
|
|
status: "running",
|
|
activeRunId: "run-1",
|
|
pendingRuntimeRequest: null,
|
|
modelSelection: { instanceId: "claudeAgent", model: "claude-opus-5-5" },
|
|
lineage: { relationshipToParent: null },
|
|
...overrides,
|
|
});
|
|
|
|
const request = (id: string) => ({ id, kind: "command", createdAt: "2026-11-01T09:00:00.000Z" });
|
|
|
|
function setup() {
|
|
const pushes: Push[] = [];
|
|
const notifier = new Notifier(async (push) => void pushes.push(push), new SummaryCache());
|
|
let previous: ThreadShell | undefined;
|
|
const change = (next: ThreadShell | undefined, initial = false) => {
|
|
notifier.onThreadChange({ host, previous, next, initial });
|
|
previous = next;
|
|
};
|
|
// Pushes are sent after a summary lookup, which fails fast here.
|
|
const settle = () => Bun.sleep(5);
|
|
return { pushes, change, settle, notifier };
|
|
}
|
|
|
|
describe("Notifier", () => {
|
|
it("stays quiet while loading the first snapshot", async () => {
|
|
const { pushes, change, settle } = setup();
|
|
change(shell({ status: "completed" }), true);
|
|
change(shell({ status: "completed" }));
|
|
await settle();
|
|
expect(pushes).toEqual([]);
|
|
});
|
|
|
|
it("notifies once when a running thread finishes", async () => {
|
|
const { pushes, change, settle } = setup();
|
|
change(shell(), true);
|
|
change(shell({ status: "completed" }));
|
|
change(shell({ status: "completed", updatedAt: "2026-11-01T09:01:00.000Z" }));
|
|
await settle();
|
|
expect(pushes.map((push) => [push.category, push.subtitle, push.data.threadId])).toEqual([
|
|
["finished", "Mac mini · wristcode", "thread-1"],
|
|
]);
|
|
});
|
|
|
|
it("notifies for each new approval request, with its request id", async () => {
|
|
const { pushes, change, settle } = setup();
|
|
change(shell(), true);
|
|
change(shell({ pendingRuntimeRequest: request("r1") }));
|
|
change(shell({ pendingRuntimeRequest: request("r2") }));
|
|
await settle();
|
|
expect(pushes.map((push) => [push.category, push.data.requestId])).toEqual([
|
|
["approval", "r1"],
|
|
["approval", "r2"],
|
|
]);
|
|
});
|
|
|
|
it("does not announce a turn the user stopped", async () => {
|
|
const { pushes, change, settle } = setup();
|
|
change(shell(), true);
|
|
change(shell({ status: "completed", latestRunStopRequested: true }));
|
|
await settle();
|
|
expect(pushes).toEqual([]);
|
|
});
|
|
|
|
it("reports failures with the server's error", async () => {
|
|
const { pushes, change, settle } = setup();
|
|
change(shell(), true);
|
|
change(shell({ status: "failed", lastError: "Provider quota exceeded" }));
|
|
await settle();
|
|
expect(pushes.map((push) => [push.category, push.body])).toEqual([["failed", "Provider quota exceeded"]]);
|
|
});
|
|
|
|
it("warns about a token that expires within a week, once a day", async () => {
|
|
const { pushes, notifier } = setup();
|
|
const row = {
|
|
id: "host-1",
|
|
label: "Mac mini",
|
|
url: "https://t3mac.gbrown.org",
|
|
alt_urls: null,
|
|
environment_id: "host-1",
|
|
token: "t",
|
|
token_expires_at: new Date(Date.now() + 3 * 24 * 60 * 60_000).toISOString(),
|
|
created_at: "",
|
|
};
|
|
notifier.checkTokenExpiry([row]);
|
|
notifier.checkTokenExpiry([row]);
|
|
expect(pushes.map((push) => [push.category, push.body])).toEqual([
|
|
["host", "The gateway's T3 token expires in 3 days. Pair Mac mini again."],
|
|
]);
|
|
});
|
|
});
|