Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/lib/maintainer/detect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof getServiceSupabase>,
);

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(
Expand Down Expand Up @@ -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<typeof getServiceSupabase>,
);

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({
Expand Down
25 changes: 17 additions & 8 deletions src/lib/maintainer/detect.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -30,8 +31,8 @@ export async function isUserMaintainer(userId: string): Promise<boolean> {
.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;
});

Expand Down Expand Up @@ -64,15 +65,23 @@ export async function listMaintainerInstalls(userId: string): Promise<Maintainer
type Row = {
installation_id: number;
permission_level: MaintainerInstall['permissionLevel'];
github_installations: {
account_login: string;
account_type: 'User' | 'Organization';
uninstalled_at: string | null;
} | null;
github_installations: unknown;
};

type JoinedInstall = {
account_login: string;
account_type: 'User' | 'Organization';
uninstalled_at: string | null;
};

return (data ?? [])
.map((row) => row as unknown as Row)
.map((row) => {
const r = row as unknown as Row;
return {
...r,
github_installations: unwrapJoin<JoinedInstall>(r.github_installations),
};
})
.filter((r) => r.github_installations && r.github_installations.uninstalled_at === null)
.map((r) => ({
installationId: r.installation_id,
Expand Down
Loading