Skip to content

Commit bc09332

Browse files
committed
fixes
1 parent 2d8b695 commit bc09332

24 files changed

Lines changed: 1441 additions & 225 deletions

AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This repo is a Next.js 16 + TypeScript app-router project.
1212
- `hooks/`: reusable React hooks.
1313
- `public/`: static assets.
1414
- `drizzle/`: generated Drizzle SQL migrations and metadata.
15-
- `tests/`: test suites (`tests/unit/*` for Vitest, `tests/e2e/*` for Playwright).
15+
- `tests/`: test suites (`tests/unit/*` for Vitest unit tests, `tests/integration/*` for Vitest API integration tests, `tests/e2e/*` for Playwright UI tests).
1616
- `styles/` and `app/globals.css`: global styling.
1717

1818
## Coding Style & Naming Conventions
@@ -25,7 +25,8 @@ This repo is a Next.js 16 + TypeScript app-router project.
2525
## Testing Guidelines
2626
When adding behavior:
2727
- Add/update unit tests in `tests/unit/` where possible.
28-
- Add/update integration scenarios in `tests/e2e/api-suite.spec.ts` and supporting helpers in `tests/utils/` when flow-level behavior changes.
28+
- Add/update API integration scenarios in `tests/integration/api-suite.test.ts` and supporting helpers in `tests/utils/` when flow-level behavior changes.
29+
- Add/update browser/page checks in `tests/e2e/pages-reference.spec.ts` when UI behavior changes.
2930
- Validate with `pnpm test` locally (see `README.md` command table).
3031

3132
## Commit & Pull Request Guidelines

README.md

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# CLA Bot
22

3-
CLA Bot is a Next.js app that automates Contributor License Agreement (CLA) workflows for GitHub organizations.
3+
CLA Bot is a Next.js app that automates Contributor License Agreement (CLA) workflows for GitHub organizations and personal accounts.
44

55
It gives org admins a place to manage CLA text and signing history, and gives contributors a place to review/sign/re-sign agreements. A GitHub webhook handler enforces CLA status on pull requests by creating checks/comments.
66

@@ -15,7 +15,7 @@ It gives org admins a place to manage CLA text and signing history, and gives co
1515

1616
## What The App Is For
1717

18-
- Keep legal contributor agreements tied to each GitHub organization.
18+
- Keep legal contributor agreements tied to each installed GitHub account.
1919
- Automatically block/allow PRs based on CLA status.
2020
- Reduce maintainer overhead by automating "please sign the CLA" comments/checks.
2121
- Let contributors re-sign when CLA text changes (versioned by SHA-256 hash).
@@ -47,6 +47,12 @@ Optional:
4747
- `DRIZZLE_MIGRATIONS_SCHEMA` (default: `drizzle`)
4848
- `DRIZZLE_MIGRATIONS_TABLE` (default: `__drizzle_migrations`)
4949

50+
For browser UI tests, install Playwright browsers once:
51+
52+
```bash
53+
pnpm exec playwright install chromium
54+
```
55+
5056
If you set custom migration metadata location, configure the same values in both build and runtime environments.
5157

5258
## Dev Setup
@@ -79,9 +85,11 @@ pnpm dev
7985
| `pnpm build` | Production build + TypeScript checks |
8086
| `pnpm start` | Run built app |
8187
| `pnpm lint` | Biome checks |
82-
| `pnpm test` | Run unit + e2e tests |
88+
| `pnpm test` | Run unit + integration tests (fast default) |
89+
| `pnpm test:all` | Run unit + integration + browser e2e tests |
8390
| `pnpm test:unit` | Run Vitest unit tests |
84-
| `pnpm test:e2e` | Run Playwright end-to-end tests |
91+
| `pnpm test:integration` | Run Vitest API integration suite |
92+
| `pnpm test:e2e` | Run Playwright browser/page tests |
8593
| `pnpm db:generate` | Generate Drizzle migrations |
8694
| `pnpm db:migrate` | Apply migrations |
8795
| `pnpm db:studio` | Open Drizzle Studio |
@@ -116,8 +124,8 @@ This section amends your scenario list and adds missing scenarios.
116124
### 2) User selects Admin
117125

118126
- If signed out: user sees auth-required state and can start GitHub login.
119-
- If signed in and authorized on at least one installed org: user sees the org list and install button.
120-
- If signed in but authorized on zero installed orgs: user is redirected to GitHub App install flow.
127+
- If signed in and authorized on at least one installed account: user sees the account list and install button.
128+
- If signed in but authorized on zero installed accounts: user is redirected to GitHub App install flow.
121129

122130
### 3) User selects Contributor
123131

@@ -136,6 +144,7 @@ This section amends your scenario list and adds missing scenarios.
136144
### 5) Contributor opens a PR
137145

138146
- Org member: check passes, no CLA comment.
147+
- Personal-account repository owner: check passes, no CLA comment.
139148
- Non-member + current signature: check passes, no CLA comment.
140149
- Non-member + outdated signature: check fails, re-sign comment posted.
141150
- Non-member + never signed: check fails, sign prompt comment posted.
@@ -179,13 +188,14 @@ This section amends your scenario list and adds missing scenarios.
179188

180189
### 12) Installation lifecycle scenarios
181190

182-
- Installation `created` or `unsuspend`: org is created/reactivated, installation ID refreshed.
183-
- Installation `deleted` or `suspend`: org is deactivated and installation ID cleared.
191+
- Installation `created` or `unsuspend`: account row is created/reactivated, installation ID refreshed, and installation target metadata (`organization` vs `user`) is persisted.
192+
- Installation `deleted` or `suspend`: account is deactivated and installation ID cleared.
184193
- Installation repository-change events refresh installation linkage.
185194

186195
### 13) Access-control scenarios
187196

188-
- In production, org-admin APIs require live GitHub org-admin verification.
197+
- In production, org installs require live GitHub org-admin verification.
198+
- In production, personal-account installs are authorized when the signed-in GitHub user matches the installation target account.
189199
- In local dev/test, org-admin verification is relaxed to keep tests deterministic.
190200
- `/admin/[orgSlug]` and `/sign/[orgSlug]` handle unknown orgs with explicit not-found states.
191201

@@ -217,7 +227,7 @@ Reference UI coverage:
217227

218228
Reference API/flow coverage:
219229

220-
- `tests/e2e/api-suite.spec.ts`
230+
- `tests/integration/api-suite.test.ts`
221231
- Broad integration flow coverage for auth/session APIs, org management, signing/re-signing, webhook checks/comments, install/uninstall/suspend lifecycle, and `/recheck` authorization.
222232
- Includes edge cases like stale-signature recheck on open PRs, proactive recheck sweep after CLA update, non-PR `/recheck` handling, malformed webhook payload rejection, and duplicate webhook delivery de-duplication.
223233

@@ -226,5 +236,5 @@ Reference API/flow coverage:
226236
When behavior changes:
227237

228238
1. Update this README page/spec sections.
229-
2. Update or add Playwright coverage in `tests/e2e/pages-reference.spec.ts` and/or `tests/e2e/api-suite.spec.ts`.
230-
3. Run `pnpm test` and `pnpm build` before merging.
239+
2. Update or add coverage in `tests/integration/api-suite.test.ts` and/or `tests/e2e/pages-reference.spec.ts` as appropriate.
240+
3. Run `pnpm test` and `pnpm build` before merging. Run `pnpm test:all` when UI/browser behavior changes.

app/admin/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ export default function AdminPage() {
4949
<div>
5050
<h1 className="text-2xl font-bold tracking-tight text-foreground">Admin Dashboard</h1>
5151
<p className="mt-1 text-muted-foreground">
52-
Manage CLAs for your GitHub organizations.
52+
Manage CLAs for your GitHub organizations and personal accounts.
5353
</p>
5454
</div>
5555
<Button className="gap-2" onClick={handleInstall}>
5656
<Plus className="h-4 w-4" />
57-
Install on Organization
57+
Install on GitHub Account
5858
</Button>
5959
</div>
6060

@@ -69,7 +69,7 @@ export default function AdminPage() {
6969
<CardContent className="py-12 text-center">
7070
<h3 className="text-lg font-semibold text-foreground">Sign in required</h3>
7171
<p className="mt-1 text-sm text-muted-foreground">
72-
Sign in with GitHub to manage organization CLAs.
72+
Sign in with GitHub to manage account CLAs.
7373
</p>
7474
<a href="/api/auth/github?returnTo=%2Fadmin" className="inline-block">
7575
<Button className="mt-4 gap-2">
@@ -130,8 +130,8 @@ export default function AdminPage() {
130130
</h3>
131131
<p className="mb-6 max-w-sm text-sm text-muted-foreground">
132132
{hasInstalledOrgs
133-
? "We found an installation, but your account is not recognized as an admin for any installed organization."
134-
: "Install the CLA Bot GitHub App on your organization to get started."}
133+
? "We found an installation, but your account is not recognized as an admin for any installed GitHub account."
134+
: "Install the CLA Bot GitHub App on your organization or personal account to get started."}
135135
</p>
136136
<Button className="gap-2" onClick={handleInstall}>
137137
<Github className="h-4 w-4" />

app/api/orgs/[orgSlug]/route.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
createAuditEvent,
99
} from "@/lib/db/queries"
1010
import { getSessionUser } from "@/lib/auth"
11-
import { isGitHubOrgAdmin } from "@/lib/github/admin-authorization"
11+
import { isGitHubInstallationAccountAdmin } from "@/lib/github/admin-authorization"
1212
import { recheckOpenPullRequestsAfterClaUpdate } from "@/lib/cla/recheck-open-prs"
1313

1414
export async function GET(
@@ -112,20 +112,20 @@ async function authorizeOrgAccess(orgSlug: string) {
112112
}
113113

114114
try {
115-
const isAdmin = await isGitHubOrgAdmin(user, orgSlug)
115+
const isAdmin = await isGitHubInstallationAccountAdmin(user, org)
116116
if (!isAdmin) {
117117
return {
118118
error: NextResponse.json(
119-
{ error: "Forbidden: GitHub org admin access required" },
119+
{ error: "Forbidden: GitHub installation admin access required" },
120120
{ status: 403 }
121121
),
122122
}
123123
}
124124
} catch (err) {
125-
console.error("GitHub org-admin verification failed:", err)
125+
console.error("GitHub installation-admin verification failed:", err)
126126
return {
127127
error: NextResponse.json(
128-
{ error: "Failed to verify GitHub org admin access" },
128+
{ error: "Failed to verify GitHub installation admin access" },
129129
{ status: 502 }
130130
),
131131
}

app/api/orgs/route.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,22 @@ export async function GET() {
2121
userId: user.id,
2222
totalOrgs: allOrgs.length,
2323
installedCount: installedOrgs.length,
24-
installedOrgSlugs: installedOrgs.map((org) => org.githubOrgSlug),
24+
installedOrgs: installedOrgs.map((org) => ({
25+
slug: org.githubOrgSlug,
26+
accountType: org.githubAccountType ?? "organization",
27+
accountId: org.githubAccountId ?? null,
28+
})),
2529
})
2630

2731
const orgs = await filterInstalledOrganizationsForAdmin(user, allOrgs)
2832
console.info("[api/orgs] Authorized organizations resolved", {
2933
userId: user.id,
3034
authorizedCount: orgs.length,
31-
authorizedOrgSlugs: orgs.map((org) => org.githubOrgSlug),
35+
authorizedOrgs: orgs.map((org) => ({
36+
slug: org.githubOrgSlug,
37+
accountType: org.githubAccountType ?? "organization",
38+
accountId: org.githubAccountId ?? null,
39+
})),
3240
})
3341
return NextResponse.json({
3442
orgs,
@@ -38,7 +46,7 @@ export async function GET() {
3846
} catch (err) {
3947
console.error("Failed to list authorized organizations:", err)
4048
return NextResponse.json(
41-
{ error: "Failed to verify GitHub organization admin access" },
49+
{ error: "Failed to verify GitHub installation admin access" },
4250
{ status: 502 }
4351
)
4452
}

0 commit comments

Comments
 (0)