Skip to content

feat(api): validate plan account provider matches plan provider on assignment (closes #209)#228

Merged
cristim merged 5 commits into
feat/multicloud-web-frontendfrom
feat/issue-209-plan-account-provider
May 3, 2026
Merged

feat(api): validate plan account provider matches plan provider on assignment (closes #209)#228
cristim merged 5 commits into
feat/multicloud-web-frontendfrom
feat/issue-209-plan-account-provider

Conversation

@cristim
Copy link
Copy Markdown
Member

@cristim cristim commented May 3, 2026

Summary

Closes #209.

Adds a backend gate to PUT /api/plans/:id/accounts so account assignments must respect the plan's provider. Today the frontend enforces single-provider-per-plan but the backend accepts any account, leaving room for an out-of-band call (curl, API key, badly-behaved client) to attach a cross-cloud account and break downstream purchase execution. This PR closes that gap per acceptance criterion E-4 in specs/multi-account-execution/acceptance.md.

What changed

  • derivePlanProviders(plan) (private) — extracts the distinct set of providers a plan targets by parsing plan.Services keys ("provider:service"). Returns a sorted slice for stable error messages.
  • Handler.validatePlanAccountProviders(ctx, planID, accountIDs) — the validation block, extracted from setPlanAccounts to keep it under the gocyclo budget (10).
  • setPlanAccounts now invokes validatePlanAccountProviders immediately after the existing UUID-format check and before the store write.

Behaviour matrix

Case Response
Plan ID not found in store 404 plan not found: <plan-id>
account_id not found in store 404 account not found: <account-id>
One or more accounts have a provider not in the derived set 400 plan provider mismatch: account "<name>" has provider="<got>", expected one of [<sorted list>]; ... (every offender named in one message)
All accounts match SetPlanAccounts runs — same as before
Plan has empty Services map Validation skipped (defensive — frontend always creates plans with services; behaviour pinned by a test so a future change is conscious)

Why a single error listing every mismatch

Failing fast on the first bad account would force the client to resubmit N times to discover N mismatches. Collecting them all and naming each one in a single 400 lets the UI render every offender in one toast.

Mock changes (test-only)

  • MockConfigStore.GetPurchasePlan now resolves in this order:

    1. GetPurchasePlanFn override
    2. Registered testify .On("GetPurchasePlan", ...) expectation
    3. Default minimal stub &PurchasePlan{ID: planID} with empty Services

    The default-fallback path is what lets pre-existing tests like TestSetPlanAccounts_Success keep working without registering the new mock — the empty Services map trips the defensive skip-validation branch in the handler.

  • MockConfigStore.SetPlanAccounts now honours a new SetPlanAccountsFn so the mismatch tests can capture (or assert against) the call. The mismatch tests verify the store write is not invoked when validation fails.

Test coverage

Seven new tests in handler_accounts_test.go:

  • TestSetPlanAccounts_SingleMismatch — one Azure account vs an AWS plan → 400, store write not invoked
  • TestSetPlanAccounts_MultipleMismatches — Azure + GCP vs an AWS plan → 400 listing both
  • TestSetPlanAccounts_ValidHappyPath — AWS account vs an AWS plan → 200, store write captured
  • TestSetPlanAccounts_PlanNotFoundGetPurchasePlan → (nil, nil) → 404
  • TestSetPlanAccounts_AccountNotFoundGetCloudAccount → (nil, nil) for one of the IDs → 404 referencing that ID
  • TestSetPlanAccounts_MixedValidAndMismatch — only the offender appears in the error; store write not invoked
  • TestSetPlanAccounts_EmptyServicesSkipsValidation — pins the defensive skip

Existing TestSetPlanAccounts_Success and TestListPlanAccounts_Success continue to pass (the mock fallback covers them).

Verification

Three consecutive clean local passes before commit:

  • go test ./internal/api/... -count=1 → 1016 passed (after rebase onto base)
  • go test ./... -count=1 → 4192 passed across 37 packages
  • go vet ./... → clean
  • gofmt -l . → clean
  • gocyclo -over 10 internal/api/handler_accounts.go → no findings

Test plan

  • CI: pre-commit hooks (gofmt, gocyclo, go vet, go tests, security scanners) all green
  • CodeRabbit pass — address actionable items, justify dismissals
  • Manual smoke (post-merge to feature branch, before main): create an AWS plan, attempt to PUT an Azure account into it, expect 400 with the structured message; then PUT an AWS account, expect 200

Summary by CodeRabbit

  • New Features

    • Enforces provider validation when assigning cloud accounts to purchase plans: accounts must match providers derived from plan services. Missing plans or missing accounts map to 404; provider mismatches return a single 400 aggregating all offending accounts. Plans with empty service lists skip validation.
  • Tests

    • Added comprehensive tests for single/multiple mismatches, mixed valid/invalid accounts, missing plan/account handling, store-not-found mapping, successful writes, and empty-services behavior.

…signment (closes #209)

Backend gate for PUT /api/plans/:id/accounts. Every account assigned to a
plan must have its provider match one of the providers derived from the
plan's services map (key format "provider:service", e.g. "aws:ec2").
Mismatches return a single HTTP 400 listing every offender; the
underlying SetPlanAccounts store write is never invoked on failure.

Per spec acceptance criterion E-4 in
specs/multi-account-execution/acceptance.md, this is the backend
hardening of the existing frontend single-provider-per-plan rule.

Implementation
--------------
- New `derivePlanProviders(plan)` helper extracts the distinct provider
  set from `plan.Services` keys (sorted slice for stable error messages).
- New `Handler.validatePlanAccountProviders(ctx, planID, accountIDs)`
  helper holds the validation block — extracted from setPlanAccounts to
  stay under the gocyclo budget (limit 10).
- `setPlanAccounts` loads the plan, derives providers, then for each
  account_id in the request loads the account and checks its provider
  against the derived set. All offenders are collected and reported in a
  single error rather than failing fast — clients fix everything in one
  round-trip.
- Plan not found → 404 with the plan ID.
- Account not found → 404 with the account ID.
- Mismatch(es) → 400 "plan provider mismatch: account "<name>" has
  provider="<got>", expected one of [<sorted list>]; ..." (single line,
  parseable).
- Empty services map → defensive skip of validation; production plans
  always have ≥1 service (frontend enforces this), and the test pins the
  behaviour so a future change is conscious.

Mocks
-----
- `MockConfigStore.GetPurchasePlan` now resolves to (in order):
  `GetPurchasePlanFn` override, registered testify expectation, or a
  default minimal `{ID: planID}` plan with empty Services. The default
  fallback lets pre-existing tests like `TestSetPlanAccounts_Success`
  keep working without setting up the new mock call — empty Services
  trips the defensive skip-validation branch.
- `MockConfigStore.SetPlanAccounts` now uses `SetPlanAccountsFn` when
  set, so tests can capture and assert on the call (mismatch tests verify
  the underlying store write is NOT invoked on failure).

Tests
-----
Seven new tests in `handler_accounts_test.go`:
- TestSetPlanAccounts_SingleMismatch         — one Azure account vs aws plan → 400
- TestSetPlanAccounts_MultipleMismatches     — Azure + GCP vs aws plan → 400 listing both
- TestSetPlanAccounts_ValidHappyPath         — AWS account vs aws plan → 200, store write captured
- TestSetPlanAccounts_PlanNotFound           — GetPurchasePlan returns (nil, nil) → 404
- TestSetPlanAccounts_AccountNotFound        — GetCloudAccount returns (nil, nil) → 404 referencing the account ID
- TestSetPlanAccounts_MixedValidAndMismatch  — only the offender named in error; store write NOT called
- TestSetPlanAccounts_EmptyServicesSkipsValidation — defensive behaviour pinned
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6919adb7-6659-4057-af97-74084a7a010a

📥 Commits

Reviewing files that changed from the base of the PR and between c566d95 and 50f1af9.

📒 Files selected for processing (1)
  • internal/config/store_postgres_pgxmock_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/config/store_postgres_pgxmock_test.go

📝 Walkthrough

Walkthrough

PUT /api/plans/:id/accounts now validates that each referenced cloud account’s provider is among the plan’s derived providers (from plan.Services). Validation runs in the handler and in-store (transactional); mismatches are aggregated into a single 400, missing plan/account map to 404, and no writes occur when validation fails.

Changes

Plan Account Provider Validation

Layer / File(s) Summary
Data Shape / Helper
internal/config/purchase_plan_providers.go
Added DerivePlanProviders(plan *PurchasePlan) []string to extract, dedupe, and sort provider identifiers from plan.Services keys.
Storage Validation (TX)
internal/config/store_postgres.go
SetPlanAccounts calls validatePlanAccountProvidersTx inside the transaction; added helpers to read plan services with FOR SHARE, fetch account name, provider with FOR SHARE, collect mismatches, and return a composed error. GetPurchasePlan maps missing-row to ErrNotFound.
Handler Validation
internal/api/handler_accounts.go
Added validatePlanAccountProviders and getPlanForAccountProviderValidation; setPlanAccounts invokes validation after UUID checks and before persisting; aggregates mismatches into one 400 and maps missing entities to 404.
Mocks / Test Wiring
internal/api/mocks_test.go
MockConfigStore gains GetPurchasePlanFn and SetPlanAccountsFn; GetPurchasePlan/SetPlanAccounts support overrides or sensible defaults when no testify expectation exists.
Handler Tests
internal/api/handler_accounts_test.go
Added tests: single/multiple mismatches (aggregated 400, no write), valid happy path (write occurs), plan/account 404s, mixed valid+invalid accounts, store-level not-found after validation mapping to 404, and empty-services skip validation.
Storage Tests
internal/config/store_postgres_pgxmock_test.go
Updated tests to expect pre-write reads (plan services and account providers); added test asserting provider-mismatch causes rollback before deletes/inserts and missing plan returns ErrNotFound.
Unit Tests
internal/config/purchase_plan_providers_test.go
Added tests for DerivePlanProviders including malformed keys and nil plan.
Errors
internal/config/errors.go
Added exported sentinel ErrNotFound = errors.New("not found") used by store/handler mapping.

Sequence Diagrams

sequenceDiagram
    participant Client as Client
    participant Handler as Handler
    participant ConfigStore as ConfigStore
    participant CloudStore as CloudAccounts

    Client->>Handler: PUT /api/plans/:id/accounts [accountIDs]
    Handler->>ConfigStore: GetPurchasePlan(planID)
    ConfigStore-->>Handler: PurchasePlan {services: {...}}
    Handler->>Handler: DerivePlanProviders(services) -> [allowedProviders]
    loop for each accountID
        Handler->>CloudStore: GetCloudAccount(accountID)
        CloudStore-->>Handler: CloudAccount {id,name,provider}
        Handler->>Handler: check provider ∈ allowedProviders
        alt mismatch
            Handler-->>Handler: collect offender(info)
        end
    end
    alt any offenders
        Handler-->>Client: 400 ClientError (aggregated offenders)
    else all valid
        Handler->>ConfigStore: SetPlanAccounts(planID, accountIDs)
        ConfigStore-->>Handler: nil
        Handler-->>Client: 200 OK
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

effort/m

Poem

🐇 I hopped through service keys and found the providers,

Gathered, trimmed, and sorted them into tidy riders.
If an account strays from the plan's bright row,
I list the misfits — no writes shall go.
Safe plans now bloom beneath careful eyes.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: validating that plan account providers match the plan's derived providers on assignment, and correctly references the closed issue #209.
Linked Issues check ✅ Passed All coding requirements from issue #209 are met: derives providers from plan.Services keys, validates each account's provider against the plan's provider set, returns 400 on mismatch with details, persists on success, and includes comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes directly support the primary objective of validating plan account provider matches. No unrelated modifications detected beyond the scope of issue #209 acceptance criteria.

✏️ 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 feat/issue-209-plan-account-provider

Review rate limit: 2/5 reviews remaining, refill in 27 minutes and 17 seconds.

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

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 (1)
internal/api/mocks_test.go (1)

325-330: ⚡ Quick win

Preserve testify expectation path in SetPlanAccounts for mock consistency.

When SetPlanAccountsFn is nil, this now always returns nil and ignores any registered m.On("SetPlanAccounts", ...) expectation. Add the isExpected/m.Called fallback like other mock methods.

💡 Proposed fix
 func (m *MockConfigStore) SetPlanAccounts(ctx context.Context, planID string, accountIDs []string) error {
 	if m.SetPlanAccountsFn != nil {
 		return m.SetPlanAccountsFn(ctx, planID, accountIDs)
 	}
+	if m.isExpected("SetPlanAccounts") {
+		return m.Called(ctx, planID, accountIDs).Error(0)
+	}
 	return nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/api/mocks_test.go` around lines 325 - 330, The
MockConfigStore.SetPlanAccounts implementation currently returns nil when
SetPlanAccountsFn is nil, ignoring any testify expectations; update
SetPlanAccounts to call m.Called(ctx, planID, accountIDs) as a fallback and use
the returned isExpected/args to produce the return value (e.g., if args.Error(0)
is set return that error), matching the pattern used by other mock methods and
preserving m.On("SetPlanAccounts", ...) expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/api/handler_accounts.go`:
- Around line 1043-1057: The derivePlanProviders function is parsing
plan.Services keys with the wrong delimiter (":"), causing provider extraction
and subsequent validation to be skipped; update derivePlanProviders to split
keys using "/" instead of ":" (change strings.SplitN(k, ":", 2) to use "/" and
adjust the empty-part checks accordingly) so provider names are correctly
collected, keep the existing behavior of skipping malformed keys, and ensure the
same delimiter fix is applied where validation uses the parsed providers (the
validation block around expected/mismatch handling referenced near lines
1094-1097) so mismatched providers are rejected as intended; run/update any
tests that assume provider/service keys produced by buildServiceConfig.

---

Nitpick comments:
In `@internal/api/mocks_test.go`:
- Around line 325-330: The MockConfigStore.SetPlanAccounts implementation
currently returns nil when SetPlanAccountsFn is nil, ignoring any testify
expectations; update SetPlanAccounts to call m.Called(ctx, planID, accountIDs)
as a fallback and use the returned isExpected/args to produce the return value
(e.g., if args.Error(0) is set return that error), matching the pattern used by
other mock methods and preserving m.On("SetPlanAccounts", ...) expectations.
🪄 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: c6ce80d2-0c17-44f5-946e-321f0493e7d0

📥 Commits

Reviewing files that changed from the base of the PR and between c84fd02 and f576db2.

📒 Files selected for processing (3)
  • internal/api/handler_accounts.go
  • internal/api/handler_accounts_test.go
  • internal/api/mocks_test.go

Comment thread internal/api/handler_accounts.go Outdated
@cristim cristim added priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/s Hours type/feat New capability triaged Item has been triaged labels May 3, 2026
cristim added a commit that referenced this pull request May 3, 2026
)

A fork that pushes a PR, pings CodeRabbit, then exits leaves CR threads
unresolved — exactly what happened to PRs #228, #229, #230, etc., where
CR posted Actionable findings + Nitpicks that were never triaged.

This adds a paragraph to the CR-loop section explicitly addressing the
delegation case: subagent prompts MUST include the full loop with the
exit criteria spelled out. Stops the failure mode where the rule is
correctly stated for humans but doesn't get mirrored into fork prompts.
- derivePlanProviders: fix key delimiter ":" → "/" to match buildServiceConfig
  (CR actionable: provider extraction was silently skipping all services,
  disabling validation; keys are "provider/service" not "provider:service")
- awsPlan209() test fixture: update key to "aws/ec2" to match the corrected
  delimiter; previously the test passed only because both code and fixture used
  the wrong format
- MockConfigStore.SetPlanAccounts: add isExpected/m.Called fallback
  (CR nitpick: testify .On("SetPlanAccounts",...) expectations were ignored)
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

CR feedback addressed

Actionable fix (critical)

  • derivePlanProviders (handler_accounts.go): fixed wrong delimiter ':' to '/'. Keys are 'provider/service' per buildServiceConfig, not 'provider:service'. The old split caused provider extraction to always yield empty, silently bypassing the mismatch validation introduced for issue feat(api): validate plan account provider matches plan provider on assignment #209.
  • Updated awsPlan209() test fixture from 'aws:ec2' to 'aws/ec2' to match the canonical format.

Nitpick fix

  • MockConfigStore.SetPlanAccounts (mocks_test.go): added isExpected/m.Called fallback so testify .On("SetPlanAccounts", ...) expectations are honoured, consistent with other mock methods.

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Tip

For best results, initiate chat on the files or code changes.

@cristim: Thanks for the detailed summary! The fixes look well-targeted:

  • Critical fix — correcting the delimiter from : to / in derivePlanProviders is essential; without it, strings.SplitN(key, "/", 2) would never split correctly, causing the provider set to always be empty and silently bypassing the mismatch validation entirely.
  • Test fixture update — aligning awsPlan209() to aws/ec2 ensures the tests actually exercise the validation path.
  • Mock consistency — the isExpected/m.Called fallback in MockConfigStore.SetPlanAccounts keeps the mock behaviour consistent with the rest of the mock methods.

Triggering a fresh review now.

[review]

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

Addressed CR feedback in commit 70b8c54: fixed wrong delimiter to slash in derivePlanProviders, added mock fallback via isExpected/m.Called pattern. @coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Tip

For best results, initiate chat on the files or code changes.

@cristim: Thanks for confirming! The two addressed items look solid:

  • Delimiter fix (derivePlanProviders) — switching from : to / correctly aligns with the provider/service key format used by buildServiceConfig, ensuring the provider extraction actually works and the mismatch validation is exercised rather than silently skipped.
  • Mock fallback (MockConfigStore.SetPlanAccounts) — the isExpected/m.Called pattern keeps the mock consistent with the rest of the mock methods and properly honours testify .On(...) expectations.

Triggering a fresh incremental review now to verify the commit.

[review]

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

Caution

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

⚠️ Outside diff range comments (1)
internal/api/handler_accounts.go (1)

1086-1167: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make the validation/write path atomic.

validatePlanAccountProviders reads accounts in a separate step from SetPlanAccounts, so an account's Provider can change after it is checked but before the association write commits. That leaves a TOCTOU window where an invalid plan/account pairing can still be persisted.


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: da4308b9-26e8-4eb8-b492-1362953df007

📥 Commits

Reviewing files that changed from the base of the PR and between f576db2 and 70b8c54.

📒 Files selected for processing (3)
  • internal/api/handler_accounts.go
  • internal/api/handler_accounts_test.go
  • internal/api/mocks_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/api/mocks_test.go
  • internal/api/handler_accounts_test.go

Move plan provider derivation into config so the API and store share
the same provider/service parsing rule. Recheck account providers inside
the Postgres SetPlanAccounts transaction before deleting or inserting
plan_accounts, locking the plan and account rows that determine validity.

Add pgxmock coverage that a provider mismatch rolls back before any
plan_accounts mutation.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 2

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

Inline comments:
In `@internal/api/handler_accounts.go`:
- Around line 1058-1064: The store currently returns generic errors for missing
rows which causes handlers to return 500; add a sentinel/typed error (e.g. var
ErrNotFound = errors.New("not found")) in the store package and update
PostgresStore.GetPurchasePlan and the store method behind SetPlanAccounts to
return that sentinel when a row is not present, then in the handler code that
calls GetPurchasePlan and SetPlanAccounts use errors.Is(err, store.ErrNotFound)
to translate those cases to NewClientError(404, fmt.Sprintf("plan not found:
%s", planID)) or an appropriate 404 message; ensure any existing nil checks
remain as defensive code but rely on errors.Is for mapping store-level not-found
to 404.

In `@internal/config/store_postgres.go`:
- Around line 1955-1957: SetPlanAccounts currently returns early when accountIDs
is empty which skips the plan existence check and allows a nonexistent planID to
silently succeed; ensure the plan is validated before returning on empty
assignments by invoking the existing getPlanServicesForShareTx (or an equivalent
plan-existence check) for the supplied planID and tx context, and only then
proceed to return if there are no accounts to assign. Update SetPlanAccounts to
call getPlanServicesForShareTx (or perform the same validation logic) prior to
the len(accountIDs) == 0 early return so invalid planIDs are detected even when
clearing assignments.
🪄 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: 7ced29d2-7bb1-44fc-ac4c-3433f06fb790

📥 Commits

Reviewing files that changed from the base of the PR and between 70b8c54 and 951707b.

📒 Files selected for processing (5)
  • internal/api/handler_accounts.go
  • internal/config/purchase_plan_providers.go
  • internal/config/purchase_plan_providers_test.go
  • internal/config/store_postgres.go
  • internal/config/store_postgres_pgxmock_test.go

Comment thread internal/api/handler_accounts.go Outdated
Comment thread internal/config/store_postgres.go Outdated
Add a config-store ErrNotFound sentinel and use it for Postgres plan and
account lookups in the atomic SetPlanAccounts validation path. The
accounts handler now maps those store-level not-found races to 404s.

Also validate the plan row before accepting empty account assignments so
clearing a nonexistent plan cannot silently succeed.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

Addressed latest CR feedback in c566d95: added typed config.ErrNotFound mapping to 404 for atomic SetPlanAccounts races, and validated plan existence before empty account assignments. @coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Tip

For best results, initiate chat on the files or code changes.

@cristim: Thanks for the update! The two addressed items look well-targeted:

  • config.ErrNotFound → 404 mapping — properly propagates typed not-found errors from the store layer (e.g. in atomic SetPlanAccounts races) to correct 404 HTTP responses, rather than letting them fall through as unexpected errors.
  • Plan existence check before empty account assignments — ensures the handler returns 404 when the plan doesn't exist even if the accountIDs slice is empty, closing the gap where an empty assignment would previously skip validation and reach the store silently.

Triggering a fresh incremental review now.

[review]

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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 `@internal/config/store_postgres_pgxmock_test.go`:
- Around line 1179-1198: The test TestPGXMock_SetPlanAccounts_Empty is missing a
final assertion that the pgxmock expectations were met; add a call to
mock.ExpectationsWereMet() at the end of the test and assert no error (e.g.,
require.NoError(t, mock.ExpectationsWereMet())) so any unmet SQL expectations in
SetPlanAccounts are surfaced; locate the mock variable created by newMock(t) and
append the expectation check after the existing require.NoError(t, err) return.
🪄 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: 2a463594-f249-4dea-aa5c-7ac89300c068

📥 Commits

Reviewing files that changed from the base of the PR and between 951707b and c566d95.

📒 Files selected for processing (5)
  • internal/api/handler_accounts.go
  • internal/api/handler_accounts_test.go
  • internal/config/errors.go
  • internal/config/store_postgres.go
  • internal/config/store_postgres_pgxmock_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/config/store_postgres.go
  • internal/api/handler_accounts.go
  • internal/api/handler_accounts_test.go

Comment thread internal/config/store_postgres_pgxmock_test.go
Add the missing pgxmock expectation assertion to the empty
SetPlanAccounts test so unmet SQL expectations are surfaced.
@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

Addressed latest CR test comment in 50f1af9: added the pgxmock expectations assertion for empty SetPlanAccounts. @coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Tip

For best results, initiate chat on the files or code changes.

@cristim: Thanks for the update! Adding mock.ExpectationsWereMet() assertions to TestPGXMock_SetPlanAccounts_Empty ensures the test actually verifies that all expected SQL interactions (including the newly added SELECT services stub in the transaction) were exercised — without it, the test could pass even if some expected query was never reached. Triggering a fresh incremental review now.

[review]

@cristim
Copy link
Copy Markdown
Member Author

cristim commented May 3, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim cristim merged commit 4990734 into feat/multicloud-web-frontend May 3, 2026
5 checks passed
@cristim cristim deleted the feat/issue-209-plan-account-provider branch May 3, 2026 15:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/many Affects most users priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/feat New capability urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant