import { describe, it, expect, vi, beforeEach } from "vitest"; vi.mock("@/config.js", () => ({ config: { publicUrl: "https://airglow.test", pdsUrl: "" }, })); vi.mock("../config.js", () => ({ config: { publicUrl: "https://airglow.test", pdsUrl: "" }, })); vi.mock("../db/index.js", async () => { const { createTestDb } = await import("../test/db.js"); return { db: await createTestDb() }; }); vi.mock("../actions/registry.js", () => ({ ACTION_REGISTRY: { "test.writeRecord": { needsFullScope: true }, "test.webhook": { needsFullScope: false }, }, })); import { OAUTH_SCOPE_BASIC, OAUTH_SCOPE_FULL, computeScopeForDid } from "./client.js"; import { db } from "../db/index.js"; import { automations, users } from "../db/schema.js"; import { disableSet, saveEnabledSet } from "../sync/store.js"; const DID = "did:plc:scopescopescopescopescop"; const NOW = new Date(Date.UTC(2026, 8, 14, 12)); async function insertAutomation(rkey: string, fields: Record) { await db.insert(automations).values({ uri: `at://${DID}/run.airglow.automation/${rkey}`, did: DID, rkey, name: rkey, lexicon: "app.bsky.feed.post", indexedAt: NOW, ...fields, } as typeof automations.$inferInsert); } beforeEach(async () => { await db.delete(automations); await db.delete(users); await db.insert(users).values({ did: DID, handle: "scope.test", createdAt: NOW }); }); describe("computeScopeForDid", () => { it("asks for the full scope when the user syncs, even with no automations", async () => { await saveEnabledSet(DID, ["bluesky", "tangled"], NOW); expect(await computeScopeForDid(DID)).toBe(OAUTH_SCOPE_FULL); }); it("falls back to the basic scope with no automations and no enabled sync", async () => { expect(await computeScopeForDid(DID)).toBe(OAUTH_SCOPE_BASIC); await saveEnabledSet(DID, ["bluesky", "tangled"], NOW); await disableSet(DID, NOW); expect(await computeScopeForDid(DID)).toBe(OAUTH_SCOPE_BASIC); }); it("asks for the full scope when a steps-model automation writes records", async () => { // New automations write only `steps`; the legacy `actions` column stays []. await insertAutomation("a", { steps: [{ id: "s1", $type: "test.writeRecord", targetCollection: "app.bsky.graph.listitem" }], }); expect(await computeScopeForDid(DID)).toBe(OAUTH_SCOPE_FULL); }); it("finds record-writing actions nested inside condition branches", async () => { await insertAutomation("a", { steps: [ { id: "c1", $type: "condition", assertions: [], then: [{ id: "s1", $type: "test.writeRecord" }], }, ], }); expect(await computeScopeForDid(DID)).toBe(OAUTH_SCOPE_FULL); }); it("stays basic when every steps-model action is webhook-only", async () => { await insertAutomation("a", { steps: [{ id: "s1", $type: "test.webhook" }] }); expect(await computeScopeForDid(DID)).toBe(OAUTH_SCOPE_BASIC); }); it("still reads the legacy actions column for unmigrated rows", async () => { await insertAutomation("a", { actions: [{ $type: "test.writeRecord" }] }); expect(await computeScopeForDid(DID)).toBe(OAUTH_SCOPE_FULL); }); });