brackt/app/routes/__tests__/root.onboarding-exempt.test.ts
Chris Parsons 215b0cf6b5
Add user onboarding flow with username selection (#430)
* Add username onboarding prompt for new signups

New users (especially via OAuth/Google) are redirected to /onboarding
to choose a display username before accessing the app. Adds unique
constraint on users.username and case-insensitive uniqueness validation.

https://claude.ai/code/session_01UinBMAy9d6dQzzbcUAdq8J

* Address all code review issues on username onboarding

- Replace case-sensitive unique constraint with functional unique index on
  lower(username) so DB-level uniqueness is case-insensitive (fix #1)
- Share USERNAME_RE from models/user.ts; add same format + uniqueness
  validation to the settings update-profile action (fix #2)
- Wrap updateUser calls in try/catch to handle race-condition DB errors
  gracefully in both onboarding and settings (fix #3)
- Add unit tests for suggestUsername, safeRedirectTo, USERNAME_RE, and
  isOnboardingExempt (fix #4)
- Guard suggestUsername against returning strings shorter than 3 chars (fix #5)
- Preserve the user's intended destination via redirectTo query param
  through the onboarding flow (fix #6)
- Treat empty username in settings as a validation error instead of a
  silent no-op (fix #7)
- Use exact/sub-path matching in isOnboardingExempt to prevent
  false-positive prefix matches like /loginpage (fix #8)

https://claude.ai/code/session_01UinBMAy9d6dQzzbcUAdq8J

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-05-15 11:17:15 -07:00

41 lines
1.5 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { isOnboardingExempt } from "../../root";
describe("isOnboardingExempt", () => {
it("exempts /onboarding exactly", () => {
expect(isOnboardingExempt("/onboarding")).toBe(true);
});
it("exempts /onboarding sub-paths", () => {
expect(isOnboardingExempt("/onboarding/step2")).toBe(true);
});
it("exempts /api/ prefix paths", () => {
expect(isOnboardingExempt("/api/auth/callback")).toBe(true);
expect(isOnboardingExempt("/api/draft/start")).toBe(true);
});
it("does not exempt paths that merely start with /api without trailing slash", () => {
// /api/ has a trailing slash so startsWith("/api/") is used — /apiary would not match
expect(isOnboardingExempt("/apiary")).toBe(false);
});
it("exempts auth pages exactly", () => {
expect(isOnboardingExempt("/login")).toBe(true);
expect(isOnboardingExempt("/register")).toBe(true);
expect(isOnboardingExempt("/check-email")).toBe(true);
expect(isOnboardingExempt("/forgot-password")).toBe(true);
expect(isOnboardingExempt("/reset-password")).toBe(true);
});
it("does not exempt a path that merely starts with an exempt path without slash", () => {
expect(isOnboardingExempt("/registered-user")).toBe(false);
expect(isOnboardingExempt("/loginpage")).toBe(false);
});
it("does not exempt normal app routes", () => {
expect(isOnboardingExempt("/")).toBe(false);
expect(isOnboardingExempt("/leagues/abc")).toBe(false);
expect(isOnboardingExempt("/settings")).toBe(false);
});
});