Skip to content

Add unit tests for auto-switch guard logic in create-org.vue#133

Merged
JoachimLK merged 3 commits into
mainfrom
fix/issues-&-problems
Apr 8, 2026
Merged

Add unit tests for auto-switch guard logic in create-org.vue#133
JoachimLK merged 3 commits into
mainfrom
fix/issues-&-problems

Conversation

@JoachimLK
Copy link
Copy Markdown
Contributor

@JoachimLK JoachimLK commented Apr 8, 2026

Summary

  • What does this PR change?
  • Why is this needed?

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Chore

Validation

  • I tested locally
  • I added/updated relevant documentation
  • I verified multi-tenant scoping and auth behavior for affected API paths

DCO

  • All commits in this PR are signed off (Signed-off-by) via git commit -s

Summary by CodeRabbit

  • New Features

    • Added a “Test connection” AI setting with UI feedback and a backend test endpoint to verify provider connectivity.
  • Bug Fixes

    • Prevented unnecessary org auto-switch when an active organization already exists.
    • Updated live-demo sign-in prefill to use the renamed passcode config.
    • Ensure AI config checks include browser cookie headers.
  • Chores / Tests

    • Expanded unit tests for org auto-switch, AI config schema/encryption, and test fixtures.

- Implement tests to verify the behavior of the auto-switch feature.
- Ensure redirection occurs only when the user has exactly 1 organization and no active organization.
- Include tests for various scenarios including loading state, existing active organization, and different view modes.
- Regression test for issue #131.
@railway-app
Copy link
Copy Markdown

railway-app Bot commented Apr 8, 2026

🚅 Deployed to the reqcore-pr-133 environment in applirank

Service Status Web Updated (UTC)
applirank ✅ Success (View Logs) Apr 8, 2026 at 9:15 pm

@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-133 April 8, 2026 18:48 Destroyed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Renamed live-demo runtime config key and updated sign-in autofill; onboarding auto-switch now respects an existing active org; added AI connection test endpoint and UI, plus related encryption/schema tests and parse-retry UI for document analysis; E2E fixture password is now env-configurable.

Changes

Cohort / File(s) Summary
Sign-in & Runtime Config
app/pages/auth/sign-in.vue, nuxt.config.ts
Renamed public runtime config from liveDemoSecretliveDemoPasscode; sign-in autofill uses the new key when route.query.live === '1'.
Onboarding Auto-switch & Tests
app/pages/onboarding/create-org.vue, tests/unit/create-org-auto-switch.test.ts
useCurrentOrg() now exposes activeOrg; auto-switch to single org skipped if activeOrg exists. Added unit tests covering guard logic and edge cases.
AI Config: API, Settings UI, Jobs
server/api/ai-config/test-connection.post.ts, app/pages/dashboard/settings/ai.vue, app/pages/dashboard/jobs/new.vue
Added POST /api/ai-config/test-connection with rate limiting, permission check, DB lookup, and provider connectivity probe; settings UI gets “Test connection” flow with banners; job creation fetch now includes cookie headers.
AI Encryption & Schema Tests
tests/unit/ai-config-encryption.test.ts, tests/unit/ai-config-schema.test.ts
New unit tests validating encrypt/decrypt behaviors and AI config schema (providers, baseUrl SSRF checks, optional apiKey).
Document Parse Retry UI
app/components/CandidateDetailSidebar.vue, app/components/ScoreBreakdown.vue
Added parse-failure detection, per-document retry flows (POST /api/documents/{id}/parse), UI retry buttons/spinners, and parse-specific error handling; updated lucide imports.
Application Analyze & Candidate GET
server/api/applications/[id]/analyze.post.ts, server/api/candidates/[id].get.ts
Analyze endpoint now includes document id and returns PARSE_FAILED with documentId when extraction fails; candidate GET now computes parsed boolean from parsedContent and omits parsedContent in response.
E2E Fixtures
e2e/fixtures.ts
generateTestAccount reads E2E_TEST_PASSWORD env var with fallback to previous default password.
Misc (tests + small edits)
tests/unit/*
New unit test files added for AI features and auto-switch logic; small related test helper additions.

Sequence Diagram(s)

sequenceDiagram
  participant UI as "Settings UI"
  participant Browser as "Browser (with cookies)"
  participant Server as "Server /api/ai-config/test-connection"
  participant DB as "Database (aiConfig)"
  participant AI as "AI Provider"

  UI->>Browser: Click "Test connection"
  Browser->>Server: POST /api/ai-config/test-connection (cookie headers)
  Server->>Server: Rate limiter & permission checks
  Server->>DB: query.aiConfig.findFirst(activeOrganizationId)
  DB-->>Server: aiConfig (provider, model, apiKeyEncrypted, baseUrl)
  Server->>AI: generateStructuredOutput(provider, model, apiKeyDecrypted, prompt)
  AI-->>Server: { ok: boolean } or error
  Server-->>Browser: { success: true } or HTTP 422 with message
  Browser-->>UI: Render success/failure banner
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I swapped a secret for a pass with a cheerful little hop,
I paused before switching orgs — no needless flip-flop.
I poked the AI with a tiny test, cookies in tow,
Re-tried the parses when the PDFs said "no".
Hop, test, and ship — the demo's ready to go!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: adding unit tests for the auto-switch guard logic in create-org.vue, which is the primary focus of this PR.
Description check ✅ Passed The PR description provides context on what is being tested (auto-switch feature behavior) and why (regression test for issue #131), though the template checkboxes remain unchecked.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issues-&-problems

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
e2e/fixtures.ts (1)

23-23: Unify E2E password source to avoid cross-spec auth failures

Line 23 introduces E2E_TEST_PASSWORD, but e2e/critical-flows/resume-upload.spec.ts still hardcodes 'TestPassword123!' (see Lines 54/64 and usage at Lines 69/72 in the provided snippet). If E2E_TEST_PASSWORD is set, those flows can fail login while fixture-based tests pass.

Consider extracting a shared password resolver (or reusing generateTestAccount) and updating that spec to consume it.

Suggested direction
+// e2e/fixtures.ts
+export const getE2eTestPassword = (): string =>
+  process.env.E2E_TEST_PASSWORD || 'TestPassword123!'

 function generateTestAccount(workerId: number): TestAccount {
   const id = `${Date.now()}-${workerId}`
   return {
     name: `E2E Tester ${id}`,
     email: `e2e-${id}@test.local`,
-    password: process.env.E2E_TEST_PASSWORD || 'TestPassword123!',
+    password: getE2eTestPassword(),
     orgName: `E2E Org ${id}`,
     orgSlug: `e2e-org-${id}`,
   }
 }
-// e2e/critical-flows/resume-upload.spec.ts
-password: 'TestPassword123!',
+password: getE2eTestPassword(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@e2e/fixtures.ts` at line 23, Create and export a single password resolver
(e.g., getE2EPassword or reuse generateTestAccount's password logic) that
returns process.env.E2E_TEST_PASSWORD || 'TestPassword123!', then replace the
hardcoded 'TestPassword123!' occurrences in resume-upload.spec.ts with an import
call to that resolver (or use generateTestAccount's returned password) so both
fixture-based tests and the critical-flows spec use the same source of truth
(update imports and references to call the new function instead of using the
literal string).
tests/unit/create-org-auto-switch.test.ts (1)

17-33: Avoid logic duplication between test helper and production guard

Lines 17–33 duplicate the guard logic instead of exercising a shared source function, so future drift can make tests pass while behavior regresses.

Refactor direction (shared guard)
+// app/pages/onboarding/create-org-auto-switch.guard.ts
+export interface AutoSwitchContext {
+  loading: boolean
+  autoSwitched: boolean
+  viewMode: 'picker' | 'create' | 'join'
+  orgCount: number
+  hasActiveOrg: boolean
+}
+
+export function shouldAutoSwitch(ctx: AutoSwitchContext): boolean {
+  if (ctx.loading || ctx.autoSwitched || ctx.viewMode !== 'picker') return false
+  return ctx.orgCount === 1 && !ctx.hasActiveOrg
+}
- function shouldAutoSwitch(ctx: AutoSwitchContext): boolean {
-   if (ctx.loading || ctx.autoSwitched || ctx.viewMode !== 'picker') return false
-   if (ctx.orgCount === 1 && !ctx.activeOrg) return true
-   return false
- }
+ import { shouldAutoSwitch } from '~/app/pages/onboarding/create-org-auto-switch.guard'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/create-org-auto-switch.test.ts` around lines 17 - 33, The test
duplicates the production guard logic in the helper shouldAutoSwitch, risking
drift; refactor so the test imports and uses the single shared guard from the
production code (the watcher guard in create-org.vue) instead of reimplementing
it: export the guard function from the production module (preserve the same
signature/shape as AutoSwitchContext), remove the duplicate shouldAutoSwitch
implementation from tests, update the test to import that exported guard and
call it with the existing test context, and ensure the test's AutoSwitchContext
type aligns with the exported function's expected type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/pages/onboarding/create-org.vue`:
- Around line 39-41: The watcher on orgs/isOrgsLoading can fire before activeOrg
is settled causing premature auto-switch; update the guard in the watch([orgs,
isOrgsLoading], ...) callback to also check the active-org resolution flag (or
the composable's "activeOrg loading/resolved" state) before deciding to run
switchOrg — i.e., include the activeOrg-loading/resolved symbol (or a provided
isActiveOrgResolved/isHydrated flag) alongside orgs, isOrgsLoading,
autoSwitched, and viewMode in the conditional so switchOrg only runs when
activeOrg is fully settled.

---

Nitpick comments:
In `@e2e/fixtures.ts`:
- Line 23: Create and export a single password resolver (e.g., getE2EPassword or
reuse generateTestAccount's password logic) that returns
process.env.E2E_TEST_PASSWORD || 'TestPassword123!', then replace the hardcoded
'TestPassword123!' occurrences in resume-upload.spec.ts with an import call to
that resolver (or use generateTestAccount's returned password) so both
fixture-based tests and the critical-flows spec use the same source of truth
(update imports and references to call the new function instead of using the
literal string).

In `@tests/unit/create-org-auto-switch.test.ts`:
- Around line 17-33: The test duplicates the production guard logic in the
helper shouldAutoSwitch, risking drift; refactor so the test imports and uses
the single shared guard from the production code (the watcher guard in
create-org.vue) instead of reimplementing it: export the guard function from the
production module (preserve the same signature/shape as AutoSwitchContext),
remove the duplicate shouldAutoSwitch implementation from tests, update the test
to import that exported guard and call it with the existing test context, and
ensure the test's AutoSwitchContext type aligns with the exported function's
expected type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 74ce201b-9f54-4e94-bb19-18e00ab447d2

📥 Commits

Reviewing files that changed from the base of the PR and between ccfa7a2 and 43c26c4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • app/pages/auth/sign-in.vue
  • app/pages/onboarding/create-org.vue
  • e2e/fixtures.ts
  • nuxt.config.ts
  • tests/unit/create-org-auto-switch.test.ts

Comment on lines 39 to +41
watch([orgs, isOrgsLoading], async ([orgList, loading]) => {
if (loading || autoSwitched.value || viewMode.value !== 'picker') return
if (orgList.length === 1) {
if (orgList.length === 1 && !activeOrg.value) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Auto-switch can run before active-org state is fully settled

On Line 39, the watcher only reacts to orgs and isOrgsLoading. If activeOrg is still unset during the immediate run, Line 41 can still pass and trigger switchOrg, even for users who already have an active org once auth state finishes hydrating.

Suggested hardening
- watch([orgs, isOrgsLoading], async ([orgList, loading]) => {
-   if (loading || autoSwitched.value || viewMode.value !== 'picker') return
-   if (orgList.length === 1 && !activeOrg.value) {
+ watch([orgs, isOrgsLoading, activeOrg], async ([orgList, loading, currentActiveOrg]) => {
+   if (loading || autoSwitched.value || viewMode.value !== 'picker') return
+   if (orgList.length === 1 && !currentActiveOrg) {
      const firstOrg = orgList[0]
      if (!firstOrg) return
      autoSwitched.value = true
      isLoading.value = true
      try {
        await switchOrg(firstOrg.id)
      }
      catch {
        isLoading.value = false
        autoSwitched.value = false
      }
    }
  }, { immediate: true })

If your composable exposes an explicit “active org loading/resolved” flag, include it in this guard as well.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
watch([orgs, isOrgsLoading], async ([orgList, loading]) => {
if (loading || autoSwitched.value || viewMode.value !== 'picker') return
if (orgList.length === 1) {
if (orgList.length === 1 && !activeOrg.value) {
watch([orgs, isOrgsLoading, activeOrg], async ([orgList, loading, currentActiveOrg]) => {
if (loading || autoSwitched.value || viewMode.value !== 'picker') return
if (orgList.length === 1 && !currentActiveOrg) {
const firstOrg = orgList[0]
if (!firstOrg) return
autoSwitched.value = true
isLoading.value = true
try {
await switchOrg(firstOrg.id)
}
catch {
isLoading.value = false
autoSwitched.value = false
}
}
}, { immediate: true })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/pages/onboarding/create-org.vue` around lines 39 - 41, The watcher on
orgs/isOrgsLoading can fire before activeOrg is settled causing premature
auto-switch; update the guard in the watch([orgs, isOrgsLoading], ...) callback
to also check the active-org resolution flag (or the composable's "activeOrg
loading/resolved" state) before deciding to run switchOrg — i.e., include the
activeOrg-loading/resolved symbol (or a provided isActiveOrgResolved/isHydrated
flag) alongside orgs, isOrgsLoading, autoSwitched, and viewMode in the
conditional so switchOrg only runs when activeOrg is fully settled.

@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-133 April 8, 2026 19:54 Destroyed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/api/ai-config/test-connection.post.ts`:
- Line 21: The permission scope for the cost-incurring test endpoint is too
permissive: change the requirePermission call in
server/api/ai-config/test-connection.post.ts to require the narrower
scoring:create scope instead of scoring: ['read']; specifically update the call
to requirePermission(event, { scoring: ['create'] }) so only users allowed to
create/run provider tests can invoke the billed provider call (adjust any
related checks or error messages in the same function if present).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b813fc7-4fdd-44d6-9792-abbe553f3f2f

📥 Commits

Reviewing files that changed from the base of the PR and between 43c26c4 and c9f4afd.

📒 Files selected for processing (5)
  • app/pages/dashboard/jobs/new.vue
  • app/pages/dashboard/settings/ai.vue
  • server/api/ai-config/test-connection.post.ts
  • tests/unit/ai-config-encryption.test.ts
  • tests/unit/ai-config-schema.test.ts

*/
export default defineEventHandler(async (event) => {
await limiter(event)
const session = await requirePermission(event, { scoring: ['read'] })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tighten permission scope for this cost-incurring endpoint.

Line 21 currently allows scoring: ['read'], but this route triggers a real provider call. That is broader than the settings UI guard (scoring: ['create'] in app/pages/dashboard/settings/ai.vue Line 14) and allows unauthorized users to invoke billable tests.

🔧 Proposed fix
-  const session = await requirePermission(event, { scoring: ['read'] })
+  const session = await requirePermission(event, { scoring: ['create'] })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const session = await requirePermission(event, { scoring: ['read'] })
const session = await requirePermission(event, { scoring: ['create'] })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/api/ai-config/test-connection.post.ts` at line 21, The permission
scope for the cost-incurring test endpoint is too permissive: change the
requirePermission call in server/api/ai-config/test-connection.post.ts to
require the narrower scoring:create scope instead of scoring: ['read'];
specifically update the call to requirePermission(event, { scoring: ['create']
}) so only users allowed to create/run provider tests can invoke the billed
provider call (adjust any related checks or error messages in the same function
if present).

@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-133 April 8, 2026 21:12 Destroyed
@JoachimLK JoachimLK merged commit 596e6c8 into main Apr 8, 2026
4 of 5 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

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.

1 participant