Gateway: watch API, pairing, notifications, APNs sender, CLI, tests
This commit is contained in:
1 parent
972f6e4be5
commit
6ca098b701
12 files changed
+1297
-1
No files matched your search
@@ -0,0 +1,109 @@
|
||||
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",
|
||||
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."],
|
||||
]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user