Skip to content

refactor(data-connections): single definition of the product/connection rule - #481

Draft
alukach wants to merge 2 commits into
mainfrom
refactor/dedupe-data-connection-rule
Draft

refactor(data-connections): single definition of the product/connection rule#481
alukach wants to merge 2 commits into
mainfrom
refactor/dedupe-data-connection-rule

Conversation

@alukach

@alukach alukach commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What was duplicated

createProduct (src/lib/actions/products.ts) reimplemented inline the same two
rules that canUseDataConnectionFor (src/lib/data-connections.ts) already
encoded, as two separate if blocks:

  1. the connection must permit the caller — isAuthorized(session, connection, Actions.UseDataConnection), which is what covers read-only connections and connections gated behind an account flag;
  2. the connection must be available to the product's account — either system-level (unowned) or owned by that account.

Two copies of the same security rule that have to be kept in sync by hand.

Why the duplication existed

canUseDataConnectionFor returns a bare boolean, and createProduct needs to
report the two rules as distinct form errors — rule 1 as a form-level message
with empty fieldErrors, rule 2 as a data_connection_id field error. A boolean
can't carry which rule failed, so the call site open-coded both.

The change

  • New denyDataConnectionFor(session, connection, accountId): DataConnectionDenial | null in src/lib/data-connections.ts, returning "not-usable", "wrong-account", or null. It is now the single definition of the rule.
  • canUseDataConnectionFor is kept exported (other callers: src/lib/actions/product-mirrors.ts, src/app/(app)/edit/product/[account_id]/[product_id]/data-connections/page.tsx) and becomes the boolean wrapper: denyDataConnectionFor(...) === null. Its doc comment moved to the new function and the now-stale sentence about createProduct applying the rules inline was dropped.
  • createProduct calls denyDataConnectionFor once and switches on the reason.

Messages preserved verbatim. Both strings and the fieldErrors shape are
byte-identical to before:

  • "not-usable"message: "You are not permitted to use the selected data connection", fieldErrors: {}
  • "wrong-account"message: "Invalid data connection for this account", fieldErrors.data_connection_id: ["Selected data connection is not available for this account"]

Evaluation order is also unchanged: "not-usable" is checked and reported ahead
of "wrong-account". The allowed_visibilities check that follows is a
different, non-duplicated rule and was not touched.

Tests

  • New denyDataConnectionFor describe block in src/lib/data-connections.test.ts (same style as the file, real isAuthorized via jest.requireActual): unowned → null, owned by the account → null, owned by another account → "wrong-account", read-only → "not-usable", flag-gated the caller lacks → "not-usable", plus one asserting "not-usable" wins when both rules fail. The existing canUseDataConnectionFor tests are unchanged and still pass.
  • src/lib/actions/products.test.ts already had a test per denial path but asserted only success: false / fieldErrors.data_connection_id being defined. Both now assert the exact message and the exact fieldErrors contents, so the strings are pinned against future drift.

Verification

  • npx tsc --noEmit -p tsconfig.typecheck.json — 14 errors, identical before and after this change (all pre-existing, in src/components/features/analytics/AdminBreakdownChart.tsx and panels.tsx). No new errors.
  • npx jest --forceExit5 failed, 486 passed, 491 total; 3 failed suites, 53 passed, 56 total. That matches the origin/main baseline exactly: DropdownSection.integration.test.tsx plus 4 analytics card tests, all pre-existing and unrelated (jsdom/recharts). No new failures. The touched suites (data-connections.test.ts, actions/products.test.ts, actions/product-mirrors.test.ts) are 71/71 passing.
  • npm run lint — exit 0. Diffed against baseline: output is identical apart from four line numbers shifting by 1 in products.ts from the added import. No new warnings.

🤖 Generated with Claude Code

…on rule

`createProduct` reimplemented inline the same two rules `canUseDataConnectionFor`
already encoded: the connection must permit the caller
(`Actions.UseDataConnection`, which covers read-only and flag-gated connections)
and must be available to the product's account (system-level or owned by it).
Two copies of a security rule that have to stay in sync.

The duplication existed because `createProduct` reports each rule as a distinct
form field error, which a boolean predicate can't express. Introduce
`denyDataConnectionFor`, which returns the reason ("not-usable" |
"wrong-account") or null; `canUseDataConnectionFor` becomes the boolean wrapper
over it, and `createProduct` switches on the reason. Both messages and their
`fieldErrors` keys are unchanged, as is the order in which the two rules are
reported.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
source-cooperative Ready Ready Preview Aug 4, 2026 6:46am

Request Review

@claude

claude Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Claude finished @alukach's task in 46s —— View job


✅ No blocking issues — safe to merge.

Reviewed the diff (origin/main...HEAD) across all 4 changed files.

  • The new denyDataConnectionFor (src/lib/data-connections.ts:59-71) correctly encodes the same two rules the old createProduct inline checks did, in the same order (not-usable before wrong-account), and canUseDataConnectionFor (:74-80) is a faithful boolean wrapper — confirmed the two untouched call sites (src/lib/actions/product-mirrors.ts:111, .../data-connections/page.tsx:44) still get identical behavior.
  • The switch in createProduct (src/lib/actions/products.ts:177-202) has no default case, but that's intentional, not a fallthrough bug: both "not-usable" and "wrong-account" branches return, so the only way execution reaches the next check (allowed_visibilities) is the null case — exactly matching prior behavior.
  • Error messages and fieldErrors shapes are byte-identical to the pre-refactor code.
  • New tests in data-connections.test.ts cover all branches including the "both rules fail → not-usable wins" ordering case, and the products.test.ts assertions were tightened from "defined" to exact string/array matches — good regression coverage for the exact strings.

No correctness, security, or best-practice issues found.

# Conflicts:
#	src/lib/actions/products.ts
#	src/lib/data-connections.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant