* 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>
41 lines
1.5 KiB
TypeScript
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);
|
|
});
|
|
});
|