Problem
Three of the highest-traffic, most data-changing server actions have no direct test coverage:
assignLicense (and the related update/revoke paths) in src/actions/assignments.ts
bulkImportUsers in src/actions/users.ts
- The bulk-license-assignment flow used by feature 004
These actions touch payments-adjacent data (license counts vs ai_tools.maxLicenses, encrypted API keys, audit history) and are exposed through admin UI flows that are easy to regress. Adding fast Vitest coverage prevents future PRs from silently breaking constraints.
Evidence
tests/unit/ and tests/integration/ contain no assignments.test.ts, users.test.ts, or bulk-import tests.
src/actions/assignments.ts:assignLicense — validates input with assignmentSchema, checks max-licenses, encrypts API key, writes assignment + history rows.
src/actions/users.ts:bulkImportUsers — accepts a CSV/JSON payload (see project spec 011), validates with Zod, dedupes against existing users, writes users + invite tokens.
Proposed approach
Use the existing integration-test harness (Vitest + real Neon test branch). Place new files at:
tests/integration/actions/assignments.test.ts
tests/integration/actions/users-bulk-import.test.ts
tests/integration/actions/license-bulk-assign.test.ts
For each, cover at minimum:
assignments.test.ts
- Happy path:
assignLicense creates a row, increments usage, writes change_history entry.
- Validation: bad email / bad tier id →
{ success: false, fieldErrors }.
- Capacity: tool with
maxLicenses = 0 → rejected without creating a row.
- Auth: non-admin caller → rejected by
requireAdmin.
- Encryption: with
apiKey provided, the stored ciphertext decrypts back to the input.
users-bulk-import.test.ts
- 10-row valid CSV → 10 users created, 10 invite tokens issued, no duplicates.
- Mixed-case email duplicates within payload → deduped per the spec's rule.
- One invalid row + nine valid → either entire-batch reject or partial success per the action's contract (mirror current behaviour).
- Existing user collision → action returns the configured per-row error.
license-bulk-assign.test.ts
- N rows assigning to N users on a tool with capacity → all succeed.
- N+1 rows on a tool with capacity N → the (N+1)th is rejected, others succeed (or whole batch fails — match current behaviour).
- Per-row API-key encryption.
For all three: stub Resend (@/lib/email) and any other external side-effects. Reuse the test-DB setup pattern from tests/integration/sync/.
Acceptance criteria
Verification
- Run
pnpm test:integration tests/integration/actions/ against a clean Neon test branch.
- Mutate
assignLicense to skip the max-licenses check → confirm the capacity test fails. Revert.
- Mutate
bulkImportUsers to skip dedupe → confirm the dedupe test fails. Revert.
Problem
Three of the highest-traffic, most data-changing server actions have no direct test coverage:
assignLicense(and the related update/revoke paths) insrc/actions/assignments.tsbulkImportUsersinsrc/actions/users.tsThese actions touch payments-adjacent data (license counts vs
ai_tools.maxLicenses, encrypted API keys, audit history) and are exposed through admin UI flows that are easy to regress. Adding fast Vitest coverage prevents future PRs from silently breaking constraints.Evidence
tests/unit/andtests/integration/contain noassignments.test.ts,users.test.ts, or bulk-import tests.src/actions/assignments.ts:assignLicense— validates input withassignmentSchema, checks max-licenses, encrypts API key, writes assignment + history rows.src/actions/users.ts:bulkImportUsers— accepts a CSV/JSON payload (see project spec 011), validates with Zod, dedupes against existing users, writes users + invite tokens.Proposed approach
Use the existing integration-test harness (Vitest + real Neon test branch). Place new files at:
tests/integration/actions/assignments.test.tstests/integration/actions/users-bulk-import.test.tstests/integration/actions/license-bulk-assign.test.tsFor each, cover at minimum:
assignments.test.ts
assignLicensecreates a row, increments usage, writeschange_historyentry.{ success: false, fieldErrors }.maxLicenses = 0→ rejected without creating a row.requireAdmin.apiKeyprovided, the stored ciphertext decrypts back to the input.users-bulk-import.test.ts
license-bulk-assign.test.ts
For all three: stub Resend (
@/lib/email) and any other external side-effects. Reuse the test-DB setup pattern fromtests/integration/sync/.Acceptance criteria
tests/integration/actions/.pnpm test:integration.pnpm lint && pnpm typecheckpass.Verification
pnpm test:integration tests/integration/actions/against a clean Neon test branch.assignLicenseto skip the max-licenses check → confirm the capacity test fails. Revert.bulkImportUsersto skip dedupe → confirm the dedupe test fails. Revert.