diff --git a/src/lib/maintainer/detect.test.ts b/src/lib/maintainer/detect.test.ts index 31c64a58..977f1e3c 100644 --- a/src/lib/maintainer/detect.test.ts +++ b/src/lib/maintainer/detect.test.ts @@ -50,6 +50,19 @@ describe('isUserMaintainer', () => { expect(cacheSet).toHaveBeenCalledWith('maint:status:user1', true, 3600); }); + it('returns true when github_installations is returned as an array (inner join ambiguity)', async () => { + vi.mocked(cacheGet).mockResolvedValue(null); + vi.mocked(getServiceSupabase).mockReturnValue( + mockSupabase({ + github_installation_users: [{ github_installations: [{ uninstalled_at: null }] }], + }) as unknown as ReturnType, + ); + + const result = await isUserMaintainer('user1'); + expect(result).toBe(true); + expect(cacheSet).toHaveBeenCalledWith('maint:status:user1', true, 3600); + }); + it('returns false when all installation rows have uninstalled_at set', async () => { vi.mocked(cacheGet).mockResolvedValue(null); vi.mocked(getServiceSupabase).mockReturnValue( @@ -143,6 +156,36 @@ describe('listMaintainerInstalls', () => { ]); }); + it('handles github_installations returned as an array (inner join ambiguity)', async () => { + vi.mocked(getServiceSupabase).mockReturnValue( + mockSupabase({ + github_installation_users: [ + { + installation_id: 1, + permission_level: 'org_admin', + github_installations: [ + { + account_login: 'org1', + account_type: 'Organization', + uninstalled_at: null, + }, + ], + }, + ], + }) as unknown as ReturnType, + ); + + const result = await listMaintainerInstalls('user1'); + expect(result).toEqual([ + { + installationId: 1, + accountLogin: 'org1', + accountType: 'Organization', + permissionLevel: 'org_admin', + }, + ]); + }); + it('filters out installs where uninstalled_at is not null', async () => { vi.mocked(getServiceSupabase).mockReturnValue( mockSupabase({ diff --git a/src/lib/maintainer/detect.ts b/src/lib/maintainer/detect.ts index 0f2c9240..eaa55884 100644 --- a/src/lib/maintainer/detect.ts +++ b/src/lib/maintainer/detect.ts @@ -1,5 +1,6 @@ import { getServiceSupabase } from '@/lib/supabase/service'; import { cacheGet, cacheSet } from '@/lib/cache'; +import { unwrapJoin } from '@/lib/supabase/inner-join'; /** * "Is this user a maintainer?" — true if they have at least one active @@ -30,8 +31,8 @@ export async function isUserMaintainer(userId: string): Promise { .limit(20); const has = (data ?? []).some((row) => { - const i = (row as unknown as { github_installations: { uninstalled_at: string | null } | null }) - .github_installations; + const r = row as unknown as { github_installations: unknown }; + const i = unwrapJoin<{ uninstalled_at: string | null }>(r.github_installations); return i && i.uninstalled_at === null; }); @@ -64,15 +65,23 @@ export async function listMaintainerInstalls(userId: string): Promise row as unknown as Row) + .map((row) => { + const r = row as unknown as Row; + return { + ...r, + github_installations: unwrapJoin(r.github_installations), + }; + }) .filter((r) => r.github_installations && r.github_installations.uninstalled_at === null) .map((r) => ({ installationId: r.installation_id,