Skip to content

test(auth): replace brittle assertions with contract/property tests - #83

Merged
2witstudios merged 3 commits into
masterfrom
claude/test-auth-routes-RDDnK
Dec 15, 2025
Merged

2witstudios merged 3 commits into
masterfrom
claude/test-auth-routes-RDDnK

Conversation

@2witstudios

@2witstudios 2witstudios commented Dec 14, 2025 •

Copy link
Copy Markdown
Owner
  • Remove cookie count assertions, keep cookie-contract assertions
  • Replace DUMMY_HASH pin with timing-safe security property test
  • Add capture-and-assert for signup user creation data
  • Enhance mobile-login timing-safe test with property assertions
  • Add auth-test-patterns.md documenting repo seam recommendations

Summary by CodeRabbit

  • Tests

    • Improved authentication test coverage with more robust cookie-contract assertions instead of exact call counts.
    • Expanded timing-safe comparison tests to assert secure behavior (compare called, password forwarded, hash validated) including non-existent user cases.
    • Strengthened signup tests to capture and validate the actual data used for user creation and ensure password hashing.
  • Documentation

    • Added a guide describing auth testing patterns, repository seams, and recommended test practices.

✏️ Tip: You can customize this high-level summary in your review settings.

- Remove cookie count assertions, keep cookie-contract assertions
- Replace DUMMY_HASH pin with timing-safe security property test
- Add capture-and-assert for signup user creation data
- Enhance mobile-login timing-safe test with property assertions
- Add auth-test-patterns.md documenting repo seam recommendations
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Dec 14, 2025 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 12 minutes and 8 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between aca6883 and f777f0c.

📒 Files selected for processing (1)
  • apps/web/src/app/api/auth/__tests__/signup.test.ts (3 hunks)

Walkthrough

Tests for auth routes were updated to assert security properties (cookie attributes, bcrypt behavior) rather than exact implementation details; signup test now captures and asserts inserted user data; new documentation describes auth testing patterns and repository/seam recommendations.

Changes

Cohort / File(s) Summary
Auth tests — login/logout/mobile/signup
apps/web/src/app/api/auth/__tests__/login.test.ts, apps/web/src/app/api/auth/__tests__/logout.test.ts, apps/web/src/app/api/auth/__tests__/mobile-login.test.ts, apps/web/src/app/api/auth/__tests__/signup.test.ts
Relaxed strict call-count assertions for cookie serialization in favor of cookie-contract checks (httpOnly, sameSite, path, expiration); replaced exact dummy-hash equality with security-focused assertions that bcrypt.compare is invoked, the provided password is passed, and the hash argument is a non-empty valid bcrypt hash; signup test added capture-and-assert for inserted user data (email, name, hashed password) with mocked db.insert returning a controlled user.
Documentation — auth test patterns
docs/testing/auth-test-patterns.md
Added new guidance covering repository/seam approach for decoupling ORM query-chain mocks, recommended test patterns (cookie contract assertions, timing-safe/security checks, capture-and-assert), example repository interface and mocks, prioritized refactor targets, and integration backstop suggestions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review the adjusted cookie-contract assertions across login/logout tests to ensure they cover required attributes and clearing semantics.
  • Validate bcrypt-related assertions (calls, argument checks, regex for bcrypt hash) in timing-safe and mobile-login tests.
  • Inspect signup test's capture-and-assert mock to confirm it accurately represents insertion shape and does not mask incorrect behavior.

Possibly related PRs

Poem

🐇 I nibbled tests in moonlit code,
Cookies checked by their true mode,
Bcrypt whispers, never lies,
Captured users, honest eyes,
Hoppy certs and passing blooms 🥕

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the primary change: replacing brittle test assertions with contract and property-based tests across the auth test suite.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/app/api/auth/__tests__/signup.test.ts (1)

205-242: Avoid implicit-any in mockImplementation param; type the captured insert payload.
With strict TS / noImplicitAny, mockImplementation((data) => ...) will fail type-checking and it also violates the “no any types” guideline. As per coding guidelines, annotate the shape you read (email, name, password) and assert password is a string before regex matching.

-      let capturedUserData: Record<string, unknown> | undefined;
-      const mockValues = vi.fn().mockImplementation((data) => {
+      type CreatedUserInsert = {
+        email?: string;
+        name?: string;
+        password?: string;
+      } & Record<string, unknown>;
+
+      let capturedUserData: CreatedUserInsert | undefined;
+      const mockValues = vi.fn().mockImplementation((data: CreatedUserInsert) => {
         // Capture the first insert (user creation)
         if (!capturedUserData && data.email) {
           capturedUserData = data;
         }
         return {
           returning: vi.fn().mockResolvedValue([
             {
               id: 'new-user-id',
               name: data.name || 'New User',
               email: data.email || 'new@example.com',
               tokenVersion: 0,
               role: 'user',
             },
           ]),
         };
       });
       (db.insert as Mock).mockReturnValue({ values: mockValues });
@@
       expect(capturedUserData!.password).not.toBe('ValidPass123!');
-      expect(capturedUserData!.password).toMatch(/^\$2[aby]?\$\d+\$/); // bcrypt hash format
+      expect(typeof capturedUserData!.password).toBe('string');
+      expect(capturedUserData!.password).toMatch(/^\$2[aby]?\$\d+\$/); // bcrypt hash format
🧹 Nitpick comments (5)
apps/web/src/app/api/auth/__tests__/logout.test.ts (1)

113-137: Cookie-clearing contract is solid; consider asserting sameSite/secure too (to guarantee overwrite).
Right now you only assert httpOnly + path + expires: new Date(0). If the real cookies are set with sameSite and/or secure (or domain), clearing should usually mirror those attributes; adding them here would better protect against “cookie didn’t actually clear” regressions.

apps/web/src/app/api/auth/__tests__/mobile-login.test.ts (1)

317-343: Nice security-property test; make the hash assertion slightly more type/shape-robust.
expect(hash).toMatch(...) assumes hash is a string; consider asserting typeof hash === 'string' first, and (optionally) using a fuller bcrypt hash regex if you want stronger guarantees than the prefix.

apps/web/src/app/api/auth/__tests__/login.test.ts (2)

136-160: Cookie contract assertions look good; consider whether secure should be part of the contract.
If production cookies are always secure, capturing that here (possibly environment-gated) would prevent accidental weakening.


327-340: Timing-safe bcrypt property test is a good replacement for the pinned dummy-hash.
Minor robustness tweak: assert typeof hash === 'string' before toMatch, and consider a stricter bcrypt hash regex if desired.

docs/testing/auth-test-patterns.md (1)

160-179: Avoid hardcoding exact test counts in docs (will go stale).
Consider rephrasing “All 218 tests passing…” to something timeless (or point to CI) so the doc doesn’t require churn with every test add/remove.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fad40b1 and 9347c07.

📒 Files selected for processing (5)
  • apps/web/src/app/api/auth/__tests__/login.test.ts (2 hunks)
  • apps/web/src/app/api/auth/__tests__/logout.test.ts (1 hunks)
  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts (2 hunks)
  • apps/web/src/app/api/auth/__tests__/signup.test.ts (2 hunks)
  • docs/testing/auth-test-patterns.md (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
apps/web/src/app/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/src/app/**/*.ts: In Next.js 15 route handlers with dynamic routes, you MUST await context.params before destructuring because params are Promise objects
Get request body using const body = await request.json();
Get search params using const { searchParams } = new URL(request.url);
Return JSON responses using return Response.json(data) or return NextResponse.json(data)

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/logout.test.ts
  • apps/web/src/app/api/auth/__tests__/login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use any types in TypeScript code - always use proper TypeScript types

**/*.{ts,tsx}: No any types - always use proper TypeScript types
Use kebab-case for filenames (e.g., image-processor.ts)
Use camelCase for variables and functions
Use UPPER_SNAKE_CASE for constants
Use PascalCase for types and enums
Use centralized permission logic: import getUserAccessLevel and canUserEditPage from @pagespace/lib/permissions
Use Drizzle client from @pagespace/db for all database access
Always structure message content using the message parts structure: { parts: [{ type: 'text', text: '...' }] }
Use ESM modules and TypeScript strict mode

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/logout.test.ts
  • apps/web/src/app/api/auth/__tests__/login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/**/*.{ts,tsx}: Use centralized permission functions from @pagespace/lib/permissions for access control, such as getUserAccessLevel() and canUserEditPage()
Always use Drizzle client from @pagespace/db for database access instead of direct database connections
Always use message parts structure with parts array containing objects with type and text fields when constructing messages for AI

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/logout.test.ts
  • apps/web/src/app/api/auth/__tests__/login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier and lint with ESLint using the configuration at apps/web/eslint.config.mjs

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/logout.test.ts
  • apps/web/src/app/api/auth/__tests__/login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/src/**/*.{ts,tsx}: Use Zustand for client-side state management
Use SWR for server state and caching

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/logout.test.ts
  • apps/web/src/app/api/auth/__tests__/login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
🧬 Code graph analysis (1)
apps/web/src/app/api/auth/__tests__/signup.test.ts (1)
packages/db/src/index.ts (1)
  • db (20-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Unit Tests

- Type CapturedUserData interface in signup.test.ts (no implicit-any)
- Add sameSite to cookie-clearing contract in logout.test.ts
- Add typeof hash === 'string' guards before regex assertions
- Use stricter full bcrypt hash regex pattern
- Remove hardcoded test count from docs (point to CI instead)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/src/app/api/auth/__tests__/signup.test.ts (1)

204-248: Capture-and-assert pattern is well-implemented.

The test effectively validates user creation data and password hashing. However, consider aligning the bcrypt regex pattern with mobile-login.test.ts for consistency and stronger validation.

The current regex at line 247 validates only the bcrypt prefix:

expect(capturedUserData!.password).toMatch(/^\$2[aby]?\$\d+\$/);

For consistency with mobile-login.test.ts (line 343) and stronger hash validation, consider using the full bcrypt format pattern:

-      expect(capturedUserData!.password).toMatch(/^\$2[aby]?\$\d+\$/); // bcrypt hash format
+      expect(capturedUserData!.password).toMatch(/^\$2[aby]?\$\d{1,2}\$[./A-Za-z0-9]{53}$/); // Full bcrypt hash format

This validates the complete hash structure (cost factor + 53-char salt+hash) rather than just the prefix, providing better security property validation without introducing brittleness.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9347c07 and aca6883.

📒 Files selected for processing (5)
  • apps/web/src/app/api/auth/__tests__/login.test.ts (2 hunks)
  • apps/web/src/app/api/auth/__tests__/logout.test.ts (2 hunks)
  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts (2 hunks)
  • apps/web/src/app/api/auth/__tests__/signup.test.ts (2 hunks)
  • docs/testing/auth-test-patterns.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/src/app/api/auth/tests/login.test.ts
  • apps/web/src/app/api/auth/tests/logout.test.ts
  • docs/testing/auth-test-patterns.md
🧰 Additional context used
📓 Path-based instructions (5)
apps/web/src/app/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/src/app/**/*.ts: In Next.js 15 route handlers with dynamic routes, you MUST await context.params before destructuring because params are Promise objects
Get request body using const body = await request.json();
Get search params using const { searchParams } = new URL(request.url);
Return JSON responses using return Response.json(data) or return NextResponse.json(data)

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use any types in TypeScript code - always use proper TypeScript types

**/*.{ts,tsx}: No any types - always use proper TypeScript types
Use kebab-case for filenames (e.g., image-processor.ts)
Use camelCase for variables and functions
Use UPPER_SNAKE_CASE for constants
Use PascalCase for types and enums
Use centralized permission logic: import getUserAccessLevel and canUserEditPage from @pagespace/lib/permissions
Use Drizzle client from @pagespace/db for all database access
Always structure message content using the message parts structure: { parts: [{ type: 'text', text: '...' }] }
Use ESM modules and TypeScript strict mode

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/**/*.{ts,tsx}: Use centralized permission functions from @pagespace/lib/permissions for access control, such as getUserAccessLevel() and canUserEditPage()
Always use Drizzle client from @pagespace/db for database access instead of direct database connections
Always use message parts structure with parts array containing objects with type and text fields when constructing messages for AI

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Format code with Prettier and lint with ESLint using the configuration at apps/web/eslint.config.mjs

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/src/**/*.{ts,tsx}: Use Zustand for client-side state management
Use SWR for server state and caching

Files:

  • apps/web/src/app/api/auth/__tests__/mobile-login.test.ts
  • apps/web/src/app/api/auth/__tests__/signup.test.ts
🧬 Code graph analysis (1)
apps/web/src/app/api/auth/__tests__/signup.test.ts (1)
packages/db/src/index.ts (1)
  • db (20-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Unit Tests
🔇 Additional comments (1)
apps/web/src/app/api/auth/__tests__/mobile-login.test.ts (1)

317-344: Excellent security property validation!

The enhanced assertions effectively validate the timing-safe comparison guarantee. The test now verifies not just that bcrypt.compare is called, but that it's called with the correct password and a valid bcrypt hash format, preventing timing attacks that could reveal user existence.

- Use full bcrypt hash regex pattern for consistency with other tests
- Update bcrypt.hash mock to return properly formatted 60-char hash
@2witstudios
2witstudios merged commit 1f4ad49 into master Dec 15, 2025
3 checks passed
@2witstudios
2witstudios deleted the claude/test-auth-routes-RDDnK branch January 29, 2026 02:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants