Skip to content

fix(fixes): replace N+1 getSuggestions loop with batch query#2512

Open
mimchome wants to merge 9 commits into
mainfrom
fix/n-plus-1-suggestions-SITES-45307-v2
Open

fix(fixes): replace N+1 getSuggestions loop with batch query#2512
mimchome wants to merge 9 commits into
mainfrom
fix/n-plus-1-suggestions-SITES-45307-v2

Conversation

@mimchome
Copy link
Copy Markdown
Contributor

SITES-45307

Summary

  • Replace per-fix getSuggestions() calls with single getAllFixesWithSuggestionsByOpportunityId batch query (2-3 DB calls instead of N+1)
  • Fixes without mapping table entries now return with empty suggestions instead of being silently dropped (58% of fixes on dev were affected)

Test plan

  • Unit tests pass (new empty-array test + updated stubs)
  • Integration tests pass (added FIX_2 junction seed record)
  • codecov/patch green
  • After deploy: sunstar.com opp 78c9837b returns 3 fixes (was 2)

Use getAllFixesWithSuggestionsByOpportunityId to fetch all fixes and
suggestions in 2-3 queries instead of one per fix. Fixes without
mapping table entries are now returned with empty suggestions instead
of being silently dropped.

SITES-45307
@github-actions
Copy link
Copy Markdown

This PR will trigger a patch release when merged.

@mimchome mimchome requested a review from MysticatBot-Dev May 28, 2026 14:54
Copy link
Copy Markdown

@MysticatBot-Dev MysticatBot-Dev left a comment

Choose a reason for hiding this comment

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

Hey @mimchome,

Strengths

  • N+1 elimination follows existing pattern (src/controllers/fixes.js:150-151): Aligns with getAllFixesWithSuggestionByCreatedAt already used on the fixCreatedDate path, reducing the number of distinct query shapes the team needs to reason about.
  • Corrects silent data loss: Fixes without junction table entries (58% on dev per PR description) now return with empty suggestions instead of being silently dropped. The right contract for a list endpoint.
  • Integration tests assert on actual IDs (test/it/shared/tests/fixes.js:64-71): Verifying specific suggestion IDs per fix, not just array lengths. This is the kind of test that would have caught the original silent-drop bug.
  • Tightly scoped change: One controller method, its tests, and seed data. No unrelated cleanup mixed in.
  • New unit test for early-return path (test/controllers/fixes.test.js:194-203): Clean AAA structure, asserts on both status and body.
  • Conventions compliance: PR title follows conventional commit format, unit and integration tests are present, DTOs are used correctly.

Issues

Important (Should Fix)

  1. src/controllers/fixes.js:153-155 - Early return on empty results bypasses ownership check

    if (fixEntitiesWithSuggestions.length === 0) {
      return ok([]);
    }

    The old code called checkOwnership(fixEntities[0], opportunityId, siteId, this.#Opportunity) regardless of whether fixes existed. With the new early return, if an opportunity has zero fixes but belongs to a different site, the caller gets 200 [] instead of the 404 that checkOwnership would have produced.

    Why it matters: A caller with a valid siteId they own and a guessed opportunityId from a different site now gets 200 instead of 404 when that opportunity has no fixes. Returning 200 for a resource the caller does not own is incorrect regardless of what data it carries. The unit test at test/controllers/fixes.test.js:176 previously tested this scenario (empty result + wrong site = 404) but was changed to return a non-empty array, masking the behavioral change.

    How to fix: Validate the opportunity-to-site relationship before the early return. One approach - move the ownership check above the empty guard:

    const fixEntitiesWithSuggestions = await this.#FixEntity
      .getAllFixesWithSuggestionsByOpportunityId(opportunityId);
    
    res = await checkOwnership(
      fixEntitiesWithSuggestions[0]?.fixEntity,
      opportunityId, siteId, this.#Opportunity
    );
    if (res) {
      return res;
    }
    
    if (fixEntitiesWithSuggestions.length === 0) {
      return ok([]);
    }

    If checkOwnership requires a non-null first argument to trace the relationship, validate the opportunity's siteId directly before the early return instead.

Recommendations

  • The integration test verifies the happy path (2 fixes, each with 1 suggestion). Consider adding a seed fixture where a fix has no junction entry to validate the stated behavior change at the integration level ("fixes without mapping table entries now return with empty suggestions").

Assessment

Ready to merge? With fixes
Reasoning: The N+1 elimination and the silent-drop bug fix are both correct and well-tested. The ownership bypass on the empty-results path is a behavioral regression in access control that should be addressed before merge - it is a reordering fix, not a redesign.

Next Steps

  1. Address the ownership check ordering so the empty-results path still validates opportunity-site ownership.

Skill: pr-review | Model: us.anthropic.claude-opus-4-6-v1[1m] | Duration: 1m 42s | Cost: $5.81 | Commit: 3efd6010aa2796dce436c3e614475380e70be5ca
If this code review was useful, please react with 👍. Otherwise, react with 👎.

Comment thread src/controllers/fixes.js
@MysticatBot-Dev MysticatBot-Dev added the ai-reviewed Reviewed by AI label May 28, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

mimchome added 2 commits May 29, 2026 14:26
…return

Move checkOwnership above the length===0 guard in both the main and
fixCreatedDate paths. checkOwnership falls back to Opportunity.findById
when no fix is passed, so an opportunity belonging to a different site
now returns 404 instead of 200 [] when it has no fixes.

Add unit test for empty-result + wrong-site = 404, and an IT fixture
(FIX_3, no junction entry) asserting fixes without mapping table entries
are returned with empty suggestions.
…5307-v2' into fix/n-plus-1-suggestions-SITES-45307-v2
@mimchome
Copy link
Copy Markdown
Contributor Author

mimchome commented Jun 1, 2026

@MysticatBot-Dev issues are addressed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed Reviewed by AI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants