Something went wrong. Try again.
Automations and webhooks for the AT Protocol airglow.run
automation webhook atproto atprotocol
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091import { 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<string, unknown>) { 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); });});