import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Builder } from "./builder.js"; import { notion } from "./notion-as-code/index.js"; import { bindSyncCapabilityKey, sync, type SyncChangeFor } from "./sync.js"; import { createWorkflowSyncClient } from "./workflow-sync.js"; const issues = notion.database("issues-db", { dataSourceResourceId: "issues-source", name: "Issues", schema: { Name: { type: "title", resourceId: "issues-name" }, "External ID": { type: "text", resourceId: "issues-id" }, }, }); const issuesSync = sync({ dataSource: issues.dataSource, primaryKey: "External ID", handler: async (state: { cursor: string } | undefined) => ({ changes: [], nextState: state ?? { cursor: "start" }, }), }); describe("workflow sync client", () => { beforeEach(() => { process.env.NOTION_API_BASE_URL = "https://notion.example/"; process.env.NOTION_API_TOKEN = "workflow-token"; bindSyncCapabilityKey(issuesSync, "issues"); }); afterEach(() => { delete process.env.NOTION_API_BASE_URL; delete process.env.NOTION_API_TOKEN; vi.unstubAllGlobals(); }); it("triggers a declared sync capability", async () => { const fetchMock = vi.fn(async () => Response.json({ status: "queued" })); vi.stubGlobal("fetch", fetchMock); await expect(createWorkflowSyncClient().trigger(issuesSync)).resolves.toEqual({ status: "queued", }); expect(fetchMock).toHaveBeenCalledWith( "https://notion.example/api/v3/workersWorkflowSyncTrigger", expect.objectContaining({ method: "POST", body: JSON.stringify({ capabilityKey: "issues" }), }), ); }); it("applies typed changes through a declared sync capability", async () => { const fetchMock = vi.fn(async () => Response.json({ upserted: 1, deleted: 0, skipped: 0 }), ); vi.stubGlobal("fetch", fetchMock); const changes: SyncChangeFor[] = [ { type: "upsert", key: "issue-1", properties: { Name: Builder.title("Fix the bug") }, }, ]; await expect(createWorkflowSyncClient().apply(issuesSync, { changes })).resolves.toEqual({ upserted: 1, deleted: 0, skipped: 0, }); expect(fetchMock).toHaveBeenCalledWith( "https://notion.example/api/v3/workersWorkflowSyncApply", expect.objectContaining({ method: "POST", body: JSON.stringify({ capabilityKey: "issues", changes }), }), ); }); it("replaces sync state only when the workflow provides it", async () => { const fetchMock = vi.fn(async () => Response.json({ upserted: 0, deleted: 1, skipped: 0 }), ); vi.stubGlobal("fetch", fetchMock); const changes: SyncChangeFor[] = [{ type: "delete", key: "issue-1" }]; await createWorkflowSyncClient().apply(issuesSync, { changes, state: { cursor: "event-42" }, }); expect(fetchMock).toHaveBeenCalledWith( "https://notion.example/api/v3/workersWorkflowSyncApply", expect.objectContaining({ body: JSON.stringify({ capabilityKey: "issues", changes, state: { cursor: "event-42" }, }), }), ); // @ts-expect-error state must match the sync's declared state type. void createWorkflowSyncClient().apply(issuesSync, { changes, state: { page: 1 } }); }); });