LMS mutation test sweep
Problem¶
The LMS mutation surface is under-tested relative to its security rules. Audit findings:
- Server actions: ~8
actions.tsfiles inapps/lms/src/app/**, but only 2 indirect test files (apps/lms/tests/unit/app/admin/courses/featureFlagActions.test.tsand a similar tenants one). No dedicated tests for enroll, quiz submit, sign-up, account, invite, or roster actions. - Route handlers: only 1 of 9 is tested —
apps/lms/src/app/api/cms/revalidate/route.test.ts. Untested:api/admin/impersonate/start,api/admin/impersonate/stop, CMS asset proxies, NextAuth handler.
This is the largest test gap in the monorepo and shadows several .cursor/rules/120-security.mdc requirements (rate limits on enroll/quiz/sign-up are documented but not unit-asserted at the action boundary).
Proposal¶
- Server-action unit tests under
apps/lms/tests/unit/app/**(mirroring source paths):learn/enroll/actions.test.ts— happy path, unauthenticated denial, rate-limit reached, cross-tenant denial.learn/quiz/actions.test.ts— submission persists; idempotency; rate-limit; auth required.(auth)/sign-up/actions.test.ts— happy path, duplicate email, rate-limit, invalid input (Zod).account/actions.test.ts— profile/password updates require auth and bumptoken_version.admin/users/actions.test.ts— invite, role change, deactivation; all requiretenant_adminorsuper_admin.- Use the existing
apps/lms/tests/unit/db/setup.ts#createTestDb+tests/unit/db/fixtures.tspatterns; mock@/auth/guardsand rate-limit driver where appropriate.
- Route handler tests under
apps/lms/src/app/api/**:admin/impersonate/start/route.test.ts— super_admin only; audit event written; one-time proof generated; non-super_admin → 403.admin/impersonate/stop/route.test.ts— restores original session; audit event written.- Asset proxy routes — happy path + auth/tenant denial.
- NextAuth handler smoke test (importable, exports
GET/POST).
- Re-run after T-003 lands so assertions can target the standardized error shape.
Acceptance criteria¶
- Every
actions.tsunderapps/lms/src/app/**has at least one happy-path and one auth-denied test. - Every route handler under
apps/lms/src/app/api/**has at least one happy-path and one auth-denied test (or documented exception in this ticket). - Rate-limited actions assert the
RATE_LIMITEDcode path via the in-memory driver. -
npm run checkpasses; coverage report (T-004) shows measurable bump onsrc/app/**.
Out of scope¶
- E2e additions (Playwright). Those follow once unit coverage is solid.
- Changing rate-limit driver implementation (covered by T-014, deferred).
- Refactoring action signatures (only assertion-level changes here).
Notes / decisions log¶
- 2026-05-24: Ticket created from monorepo audit.
- 2026-05-25: Implemented full mutation sweep (574 LMS unit tests). Harness:
tests/unit/mutations/_harness.ts,assertions.ts; extendedtests/unit/db/fixtures.ts. Auth actions covered undertests/unit/auth/actions/*(not onlysrc/app/**/actions.ts). Route exceptions: publiccms/site/assets/*(invalid id / upstream only);test/oauth/google(non-production); NextAuth handler export smoke via mocked@/auth.handlers. Course asset routes delegate auth tocourseAssetAccessErrorResponse(unit-tested separately). Rate-limit tests useuseMemoryRateLimitDriver()+exhaustRateLimitKey()without mocking@/lib/rate-limit.