diff --git a/server/src/__tests__/github-connection-work.test.ts b/server/src/__tests__/github-connection-work.test.ts index ceeb033d5ad..41ba2724079 100644 --- a/server/src/__tests__/github-connection-work.test.ts +++ b/server/src/__tests__/github-connection-work.test.ts @@ -62,7 +62,7 @@ describeDatabase("GitHub coordinator with PostgreSQL and controlled Cloud HTTP ( async function fixture(ids: string[] = []) { bindings = []; leases = 0; failBindings = false; githubRequests = 0; const companyId = randomUUID(); companyIds.push(companyId); - const [company] = await db.insert(companies).values({ id: companyId, name: "GitHub work test", issuePrefix: "GWT" }).returning(); + const [company] = await db.insert(companies).values({ id: companyId, name: "GitHub work test", issuePrefix: `GW${companyId.replaceAll("-", "").slice(0, 6).toUpperCase()}` }).returning(); const [app] = await db.insert(toolApplications).values({ companyId, applicationKey: randomUUID(), name: "GitHub", type: "mcp_server", status: "active" }).returning(); const [connection] = await db.insert(toolConnections).values({ companyId, applicationId: app!.id, uid: randomUUID(), name: "GitHub", status: "active", enabled: true, transport: "mcp_remote", authKind: "oauth", credentialPolicy: "per_user", config: { sourceTemplateKey: "github", oauth: { connectorProfile: "github.code" } }, transportConfig: {} }).returning(); diff --git a/server/src/services/github-connection-events.test.ts b/server/src/services/github-connection-events.test.ts index 2a7a15dbffc..35ebfd07d5a 100644 --- a/server/src/services/github-connection-events.test.ts +++ b/server/src/services/github-connection-events.test.ts @@ -3,7 +3,7 @@ import { chatEndpoints, type Db } from "@paperclipai/db"; import { githubConnectionEventService } from "./github-connection-events.js"; import type { PaperclipCloudConnector } from "./paperclip-cloud-connector.js"; -function fixture(push = false) { +function fixture(push = false, forcePolling = false) { let time = Date.parse("2026-09-04T12:00:00Z"); const bindings = [{ grant: { id: "grant", companyId: "company", kind: "user", subjectUserId: "owner", status: "active", @@ -19,7 +19,7 @@ function fixture(push = false) { : { innerJoin: () => ({ where: () => result(bindings) }) } })); const leaseEvents = vi.fn().mockResolvedValue(null); const connector = { leaseEvents, eventWakeVersion: () => push ? 1 : null } as unknown as PaperclipCloudConnector; - const service = githubConnectionEventService({ select } as unknown as Db, { connector, now: () => new Date(time) }); + const service = githubConnectionEventService({ select } as unknown as Db, { connector, env: forcePolling ? { PAPERCLIP_GITHUB_EVENT_POLLING: "1" } : {}, now: () => new Date(time) }); return { service, leaseEvents, select, bots, bindings, advance: (ms: number) => { time += ms; } }; } @@ -90,6 +90,15 @@ describe("GitHub external delivery activation", () => { await f.service.pollOnce({ forDelivery: true }); expect(f.leaseEvents).toHaveBeenCalledTimes(2); expect(f.service.nextPollAt()).toBeNull(); }); + it("arms forced fallback before a failed Cloud enrollment revocation", async () => { + const f = fixture(true, true); + f.leaseEvents.mockRejectedValueOnce(new Error("handoff unavailable")); + await expect(f.service.pollOnce()).rejects.toThrow("handoff unavailable"); + expect(f.service.nextPollAt()).not.toBeNull(); + expect(f.leaseEvents.mock.calls[0]![0].eventWakeVersion).toBe(0); + f.advance(5_000); await f.service.pollOnce(); + expect(f.leaseEvents).toHaveBeenCalledTimes(2); expect(f.service.nextPollAt()).not.toBeNull(); + }); it("retains old-Cloud fallback and settles once all bindings are disabled", async () => { const f = fixture(); await f.service.pollOnce(); expect(f.service.nextPollAt()).not.toBeNull(); f.bindings.length = 0; f.advance(10_000); await f.service.pollOnce();