diff --git a/src/inngest/functions/process-installation-event.test.ts b/src/inngest/functions/process-installation-event.test.ts index c0ef45e..7bbead4 100644 --- a/src/inngest/functions/process-installation-event.test.ts +++ b/src/inngest/functions/process-installation-event.test.ts @@ -1,5 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { processInstallationEvent } from './process-installation-event'; +import { + processInstallationEvent, + processInstallationReposEvent, +} from './process-installation-event'; import { sb, wire, step } from './__tests__/test-helpers'; // Mock external dependencies. @@ -13,6 +16,11 @@ vi.mock('../client', () => ({ }, })); +const reposRun = processInstallationReposEvent as unknown as (ctx: { + event: { data: { payload: Record } }; + step: typeof step; +}) => Promise; + // Handler references. const installRun = processInstallationEvent as unknown as (ctx: { event: { data: { payload: Record } }; @@ -121,4 +129,28 @@ describe('processInstallationEvent', () => { }), ); }); + it('repositories_added uses upsert to support webhook replays', async () => { + const repos = sb({ + upsert: vi.fn().mockResolvedValue({ error: null }), + }); + + wire({ + installation_repositories: repos, + }); + + await reposRun({ + event: { + data: { + payload: { + action: 'added', + installation: { id: 100 }, + repositories_added: [{ full_name: 'myorg/repo-a' }], + }, + }, + }, + step, + }); + + expect(repos.upsert).toHaveBeenCalled(); + }); }); diff --git a/src/inngest/functions/process-installation-event.ts b/src/inngest/functions/process-installation-event.ts index 7a2c715..089c99b 100644 --- a/src/inngest/functions/process-installation-event.ts +++ b/src/inngest/functions/process-installation-event.ts @@ -220,11 +220,14 @@ export const processInstallationReposEvent = inngest.createFunction( if (!sb) throw new Error('service role missing'); if (payload.repositories_added?.length) { - await sb.from('installation_repositories').insert( + await sb.from('installation_repositories').upsert( payload.repositories_added.map((r) => ({ installation_id: payload.installation.id, repo_full_name: r.full_name, })), + { + onConflict: 'installation_id,repo_full_name', + }, ); // Fan-out a per-repo backfill for each new repo so the maintainer // queue picks them up without waiting for the next cron tick.