diff --git a/src/app/actions/maintainer.test.ts b/src/app/actions/maintainer.test.ts index dd7c5ba6..f5fa88dd 100644 --- a/src/app/actions/maintainer.test.ts +++ b/src/app/actions/maintainer.test.ts @@ -16,6 +16,7 @@ import { getRepoPicker, setRepoManaged, resolveFlaggedAccount, + getPrCiStatus, } from './maintainer'; import * as detect from '@/lib/maintainer/detect'; import * as rateLimitLib from '@/lib/rate-limit'; @@ -764,4 +765,28 @@ describe('maintainer actions', () => { expect(c2.update).toHaveBeenCalledWith(expect.objectContaining({ status: 'dismissed' })); }); }); + + // getPrCiStatus + + describe('getPrCiStatus', () => { + it('returns not_authorised when install does not belong to user', async () => { + // mock assertMaintainerInstall failure (no junction row) + mockFrom.mockReturnValueOnce(chain(null)); + + const res = await getPrCiStatus(999, 'org/repo', 1); + + expect(res.ok).toBe(false); + if (!res.ok) expect(res.error.code).toBe('not_authorised'); + }); + + it('returns status when install belongs to user', async () => { + // mock assertMaintainerInstall success + mockFrom.mockReturnValueOnce(chain({ installation_id: 1 })); + + // Using demo/repo hits the fallback path without mocking Octokit + const res = await getPrCiStatus(1, 'demo/repo', 1); + + expect(res.ok).toBe(true); + }); + }); }); diff --git a/src/app/actions/maintainer.ts b/src/app/actions/maintainer.ts index db5c783d..eec53b36 100644 --- a/src/app/actions/maintainer.ts +++ b/src/app/actions/maintainer.ts @@ -650,9 +650,13 @@ export async function getPrCiStatus( repoFullName: string, prNumber: number, ): Promise> { - const authRes = await requireMaintainer(); + const authRes = await requireMaintainer({ requireService: true }); if (!authRes.ok) return authRes; - const { user } = authRes.data; + const { user, service } = authRes.data; + + if (!(await assertMaintainerInstall(service, user.id, installationId))) { + return err('not_authorised', 'not your install'); + } const cacheKey = `ci:status:${repoFullName}:${prNumber}`; const cached = await cacheGet<'passing' | 'failing' | 'pending' | null>(cacheKey);