diff --git a/src/app/actions/maintainer.test.ts b/src/app/actions/maintainer.test.ts index 477cbb9..39b2109 100644 --- a/src/app/actions/maintainer.test.ts +++ b/src/app/actions/maintainer.test.ts @@ -6,6 +6,10 @@ import { getCommunityLinks, upsertCommunityLink, deleteCommunityLink, + getRepoHealthOverview, + getStaleIssues, + getTopContributors, + getFlaggedAccounts, } from './maintainer'; import * as detect from '@/lib/maintainer/detect'; import * as rateLimitLib from '@/lib/rate-limit'; @@ -286,4 +290,39 @@ describe('maintainer actions', () => { if (!res.ok) expect(res.error.code).toBe('not_found'); }); }); + it('getRepoHealthOverview returns rate_limited when rate limit exceeded', async () => { + vi.mocked(rateLimitLib.rateLimit).mockResolvedValue({ ok: false } as never); + + const res = await getRepoHealthOverview(); + + expect(res.ok).toBe(false); + if (!res.ok) expect(res.error.code).toBe('rate_limited'); + }); + + it('getStaleIssues returns rate_limited when rate limit exceeded', async () => { + vi.mocked(rateLimitLib.rateLimit).mockResolvedValue({ ok: false } as never); + + const res = await getStaleIssues(); + + expect(res.ok).toBe(false); + if (!res.ok) expect(res.error.code).toBe('rate_limited'); + }); + + it('getTopContributors returns rate_limited when rate limit exceeded', async () => { + vi.mocked(rateLimitLib.rateLimit).mockResolvedValue({ ok: false } as never); + + const res = await getTopContributors(); + + expect(res.ok).toBe(false); + if (!res.ok) expect(res.error.code).toBe('rate_limited'); + }); + + it('getFlaggedAccounts returns rate_limited when rate limit exceeded', async () => { + vi.mocked(rateLimitLib.rateLimit).mockResolvedValue({ ok: false } as never); + + const res = await getFlaggedAccounts(); + + expect(res.ok).toBe(false); + if (!res.ok) expect(res.error.code).toBe('rate_limited'); + }); }); diff --git a/src/app/actions/maintainer.ts b/src/app/actions/maintainer.ts index 93e4455..f07bedf 100644 --- a/src/app/actions/maintainer.ts +++ b/src/app/actions/maintainer.ts @@ -633,13 +633,17 @@ export async function getRepoHealthOverview(): Promise> return err('not_authenticated', 'sign in first'); } - await rateLimit({ + const limited = await rateLimit({ namespace: 'maintainer', key: user.id, limit: 30, windowSec: 60, }); + if (!limited.ok) { + return err('rate_limited', 'slow down', true); + } + if (!(await isUserMaintainer(user.id))) { return err('not_authorised', 'not a maintainer'); } @@ -722,13 +726,17 @@ export async function getStaleIssues(): Promise> { return err('not_authenticated', 'sign in first'); } - await rateLimit({ + const limited = await rateLimit({ namespace: 'maintainer', key: user.id, limit: 30, windowSec: 60, }); + if (!limited.ok) { + return err('rate_limited', 'slow down', true); + } + if (!(await isUserMaintainer(user.id))) { return err('not_authorised', 'not a maintainer'); } @@ -804,13 +812,17 @@ export async function getTopContributors(): Promise> { return err('not_authenticated', 'sign in first'); } - await rateLimit({ + const limited = await rateLimit({ namespace: 'maintainer', key: user.id, limit: 30, windowSec: 60, }); + if (!limited.ok) { + return err('rate_limited', 'slow down', true); + } + if (!(await isUserMaintainer(user.id))) { return err('not_authorised', 'not a maintainer'); } @@ -1096,13 +1108,17 @@ export async function getFlaggedAccounts(): Promise> return err('not_authenticated', 'sign in first'); } - await rateLimit({ + const limited = await rateLimit({ namespace: 'maintainer', key: user.id, limit: 30, windowSec: 60, }); + if (!limited.ok) { + return err('rate_limited', 'slow down', true); + } + if (!(await isUserMaintainer(user.id))) { return err('not_authorised', 'not a maintainer'); }