Skip to content

fix(cli): wheels test --db is --core only; surface real datasource for app suite#2517

Merged
bpamiri merged 1 commit intodevelopfrom
claude/issue-2489
May 8, 2026
Merged

fix(cli): wheels test --db is --core only; surface real datasource for app suite#2517
bpamiri merged 1 commit intodevelopfrom
claude/issue-2489

Conversation

@bpamiri
Copy link
Copy Markdown
Collaborator

@bpamiri bpamiri commented May 8, 2026

Summary

Resolves #2489. The --filter half was already fixed by #2394; this PR addresses the --db half: app tests were silently ignoring --db while the CLI's preamble echoed the user's flag, falsely implying the suite had run against the requested engine.

Researched Rails, Laravel, Symfony, Django, Phoenix, PHPUnit, and pytest. None of them expose a per-invocation CLI flag for test-DB selection. The convention is environment variables (DATABASE_URL, DB_CONNECTION, APP_ENV, DJANGO_SETTINGS_MODULE, MIX_ENV) because DB selection is structural — the framework's transactional isolation needs to know the connection at boot.

Wheels' --core matrix is the legitimate place for --db (the framework's self-test wires up wheelstestdb_<engine> per supported engine). App tests don't have that need. So this PR doesn't add per-invocation switching for app tests; it makes the CLI honest about what it does and doesn't do, and documents the env-var pattern.

Changes

cli/lucli/Module.cfc

  1. Track dbExplicit in test() — distinguishes the default sqlite from a user-supplied --db=.... Threaded through to runTests().

  2. Real datasource in app-test preamble. New $resolveAppTestDataSource(useTestDB) helper:

    • Reads .env's DATASOURCE_NAME, then config/settings.cfm's set(dataSourceName="...").
    • Applies _test suffix when useTestDB is on (matching app-runner.cfm's swap logic).
    • Returns (unknown) as a label fallback — never fatal.

    So users now see Running app tests (myapp_test)... instead of the misleading (sqlite).

  3. Warn on --db for app tests. Yellow block tells the user the flag is --core only and points at the testing guide for the env-var pattern.

Docs

  • command-line-tools/wheels-commands/testing.mdx — flag table now annotates --db as --core only; new "Testing against different engines" section walks through the env-var pattern (WHEELS_DATASOURCE=myapp_mysql wheels test) and names the Rails / Laravel / Symfony / Django / Phoenix parity. Engine table now has a "Where used" column.
  • testing/running-tests-locally.mdx — Aside near the example block calling out the --db-is---core-only boundary, replacing the misleading wheels test --db=mysql example with the corrected wheels test --core --db=mysql.

What this doesn't change

  • Behaviour for --core is identical. wheels test --core --db=mysql still wires up wheelstestdb_mysql exactly like before.
  • App-test behaviour is identical. The framework's runner already used <datasource>_test correctly; the CLI was just printing a different value than the runner used.

Test plan

  • wheels test (no flags) — preamble shows Running app tests (<datasource>_test)... from your config/settings.cfm.
  • wheels test --no-test-db — preamble shows Running app tests (<datasource>)... without the suffix.
  • wheels test --db=mysql — preamble still shows the real app datasource; warning block fires explaining --db is core-only.
  • wheels test --core --db=mysql — preamble shows Running core tests (mysql)...; no warning.
  • wheels test --filter=models — confirms the existing positional/filter path still works (regression check on fix(cli): normalize wheels test --filter so short names actually scope #2394).

Generated by Claude Code

…asource for app suite

Issue #2489 reported `wheels test --filter` and `wheels test --db`
silently ignored. The filter side was already addressed by #2394's
$normalizeTestFilter (commit 4c5233b on develop). The db side was a
genuine UX bug: the CLI accepted --db, attached it to the test URL
as &db=<engine>, but the framework's app-runner.cfm ignores
url.db (it only honours url.useTestDB). Meanwhile the CLI's
preamble printed `Running app tests (mysql)...` echoing the
user's --db, falsely implying the suite had run against MySQL.

Researched Rails, Laravel, Symfony, Django, and Phoenix: every one
selects test database via process-level env var (DATABASE_URL,
DB_CONNECTION, APP_ENV, DJANGO_SETTINGS_MODULE, MIX_ENV). None
expose a per-invocation CLI flag. The reasoning: DB selection is
structural — the framework's transactional test isolation depends
on knowing the connection at boot. A mid-run switch breaks that.

Wheels' --core matrix is the legitimate place for --db: the
framework's self-test wires up wheelstestdb_<engine> per supported
engine. App tests don't have that need.

Three changes shipped:

1. Track whether --db was explicitly passed (dbExplicit boolean
   threaded through test() → runTests()). Default sqlite vs user-
   supplied are now distinguishable.

2. Print the real datasource in the app-test preamble. New
   $resolveAppTestDataSource helper reads .env DATASOURCE_NAME
   then config/settings.cfm's set(dataSourceName=...) and applies
   the _test suffix when useTestDB is on. So users see
   `Running app tests (myapp_test)...` instead of the misleading
   `(sqlite)`.

3. Warn when --db is passed to an app run. Yellow warning block
   tells the user the flag is --core-only and points at the
   testing guide's "Testing against different engines" section.

Docs: testing.mdx grew a "Testing against different engines"
subsection with the env-var pattern + the Rails/Laravel/etc.
parity rationale, the flag table now annotates --db's scope, and
running-tests-locally.mdx gained an Aside calling out the same.

Resolves #2489.
@bpamiri bpamiri merged commit ab1b099 into develop May 8, 2026
10 of 11 checks passed
@bpamiri bpamiri deleted the claude/issue-2489 branch May 8, 2026 23:42
Comment thread cli/lucli/Module.cfc
Comment on lines +5031 to +5057
var envFile = variables.projectRoot & "/.env";
if (fileExists(envFile)) {
var envContent = fileRead(envFile);
var match = reFindNoCase("DATASOURCE_NAME\s*=\s*(.+)", envContent, 1, true);
if (arrayLen(match.match) > 1 && len(trim(match.match[2]))) {
base = trim(match.match[2]);
}
}

if (!len(base)) {
var settingsFile = variables.projectRoot & "/config/settings.cfm";
if (fileExists(settingsFile)) {
var settingsContent = fileRead(settingsFile);
var settingsMatch = reFindNoCase('dataSourceName\s*=\s*"([^"]*)"', settingsContent, 1, true);
if (arrayLen(settingsMatch.match) > 1 && len(trim(settingsMatch.match[2]))) {
base = trim(settingsMatch.match[2]);
}
}
}

if (!len(base)) {
return "(unknown)";
}

return arguments.useTestDB ? base & "_test" : base;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 The new $resolveAppTestDataSource helper produces misleading or (unknown) labels in three common configs, undercutting the PR's stated goal of replacing the misleading (sqlite) echo with the truth: (1) the settings.cfm regex dataSourceName\s*=\s*"([^"]*)" requires a literal double-quoted value and won't match the set(dataSourceName=application.wo.env("WHEELS_DATASOURCE", "myapp")) pattern this PR's own new docs recommend; (2) the .env regex DATASOURCE_NAME\s*=\s*(.+) is not line-anchored, so it substring-matches keys like PRIMARY_DATASOURCE_NAME=... or commented # DATASOURCE_NAME=...; (3) the .env regex captures quoted values and trailing comments verbatim (DATASOURCE_NAME="myapp" → label "myapp"_test; DATASOURCE_NAME=myapp # prod → label myapp # prod_test). Suggested fix: anchor the .env regex with (?m)^, also probe WHEELS_DATASOURCE, extract the second-arg default from env("KEY", "default") in settings.cfm, and strip surrounding quotes / inline comments from captured values.

Extended reasoning...

Why this is worth fixing in this PR

The PR's stated goal (issue #2489) is to stop the CLI from lying about which datasource ran. The previous Running app tests (sqlite)… echoed --db even though the runner ignored it. The new helper is supposed to surface the truthful label — but in three configurations that are either (a) explicitly recommended by this PR's own new docs, or (b) very common in real .env files, the helper instead emits a wrong or (unknown) label. That re-introduces exactly the failure mode the PR is fixing, just with different cosmetics.

None of these break test execution — the framework's app-runner.cfm does its own datasource resolution and ignores this label. So it's a display bug, not a correctness bug. But it's a display bug in code whose entire reason for existing is to make the display correct.

Failure mode 1 — the settings.cfm regex misses the env-wrapped pattern this PR's docs recommend

The regex at cli/lucli/Module.cfc:5045:

dataSourceName\s*=\s*"([^"]*)"

requires a literal double-quoted string immediately after = (modulo whitespace). But web/sites/guides/.../testing.mdx (the new "Testing against different engines" section added in this same PR) tells users to write:

set(dataSourceName=application.wo.env("WHEELS_DATASOURCE", "myapp"));

After dataSourceName= the next non-space character is a (start of application), not a ", so the regex doesn't match. The .env fallback won't save them either, because the docs use WHEELS_DATASOURCE as the env-var key but the helper's .env probe only looks for DATASOURCE_NAME. So a user who follows the PR's recommended setup verbatim ends up at (unknown) and sees Running app tests ((unknown)_test)….

Step-by-step proof:

  1. User reads the new Testing against different engines section.
  2. User's .env contains WHEELS_DATASOURCE=myapp_mysql.
  3. User's config/settings.cfm contains set(dataSourceName=application.wo.env("WHEELS_DATASOURCE", "myapp"));.
  4. $resolveAppTestDataSource runs reFindNoCase("DATASOURCE_NAME\s*=\s*(.+)", envContent, 1, true) → no match (key is WHEELS_DATASOURCE, not DATASOURCE_NAME).
  5. Falls back to reFindNoCase('dataSourceName\s*=\s*"([^"]*)"', settingsContent, 1, true) → no match (next char after = is a, not ").
  6. Returns "(unknown)". Preamble shows Running app tests ((unknown)_test)…. The actual app suite runs against myapp_mysql_test.

Failure mode 2 — .env regex is not line-anchored

At cli/lucli/Module.cfc:5034, reFindNoCase("DATASOURCE_NAME\s*=\s*(.+)", envContent, 1, true) has no ^ anchor or word boundary. It will substring-match the key portion. Two real-world cases:

  • PRIMARY_DATASOURCE_NAME=primary_db (or BACKUP_, SECONDARY_) appearing before the canonical DATASOURCE_NAME=… line → the regex finds DATASOURCE_NAME=primary_db inside the longer key and captures primary_db.
  • # DATASOURCE_NAME=oldvalue followed by the real DATASOURCE_NAME=newvalue → matches the commented-out line first, captures oldvalue.

Failure mode 3 — .env regex captures quotes/comments verbatim

The (.+) capture matches greedily to end-of-line and only trim() is applied. Common dotenv conventions break the label:

  • DATASOURCE_NAME="myapp" → captured as "myapp" (literal quotes) → label becomes "myapp"_test. This is a real divergence from runtime: the project's actual env loader at cli/lucli/templates/app/public/Application.cfc:374-377 strips surrounding quotes, so the runtime sees myapp_test while the CLI shows "myapp"_test. That's exactly the label-vs-reality mismatch this PR set out to eliminate.
  • DATASOURCE_NAME=myapp # prod → captured as myapp # prod → label becomes myapp # prod_test.

(CFML's trim() does strip \r, so CRLF line endings aren't an issue here — that part of the original report is mitigated.)

Why the existing code doesn't prevent it

The helper has only two branches (.env then settings.cfm) and each uses a single regex with no normalization. There's no per-line scan, no quote-stripping, no inline-comment stripping, and no awareness of the WHEELS_DATASOURCE key or the env("KEY", "default") wrapper pattern that the new docs introduce. The (unknown) fallback is the safety net for "no match" but it's the wrong outcome for the doc-recommended setup.

Suggested fix

  1. Anchor the .env regex with (?m)^ (or scan line-by-line, splitting on \r?\n).
  2. Also try WHEELS_DATASOURCE as a key in .env (the var name the new docs use).
  3. Strip surrounding "/' and trailing # … from captured values before assignment.
  4. In settings.cfm, if the literal-quoted regex misses, also try to extract the second argument of env("KEY", "default") — that's the recommended pattern and the second arg is the literal default that'll be used when the env var is unset.

Severity is low (display-only — runtime datasource selection is unaffected) but worth fixing in this PR since the primary motivation is label truthfulness.

Comment thread cli/lucli/Module.cfc
Comment on lines +3717 to +3725
if (dbExplicit) {
out("", "yellow");
out("Warning: --db only applies to --core tests; ignoring for the app suite.", "yellow");
out("App tests run against the configured app datasource (or", "yellow");
out("<datasource>_test when --useTestDB is set). To test against a different", "yellow");
out("engine, point your app's datasource env var at it (or use --core).", "yellow");
out("See: command-line-tools/wheels-commands/testing#testing-against-different-engines", "yellow");
out("", "yellow");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 The new yellow warning at Module.cfc:3721 references a non-existent CLI flag: it says <datasource>_test when --useTestDB is set, but there's no --useTestDB flag in the parser (only --no-test-db exists at line 448, and useTestDB=true is the default). The phrasing also inverts the polarity — the suffix is the default, not opt-in. A user who tries wheels test --useTestDB will have it silently dropped (line 450's positional fallback excludes args starting with --). Suggest: <datasource>_test by default; pass --no-test-db to opt out — matching the language already used in the running-tests-locally.mdx aside in this same PR.

Extended reasoning...

What's wrong

The new warning block printed in runTests when --db is passed to an app run says:

App tests run against the configured app datasource (or
<datasource>_test when --useTestDB is set). To test against a different…

There is no --useTestDB CLI flag. The arg parser at cli/lucli/Module.cfc:415-454 only recognizes --no-test-db (line 448), which sets useTestDB=false. useTestDB defaults to true (line 413) and is opt-OUT, not opt-in. The token useTestDB exists only as an internal variable name and a URL query parameter on the runner — it is not a CLI surface.

Why the wording is doubly misleading

  1. Non-existent flag name. A user who reads the warning and tries wheels test --useTestDB gets a silent no-op. Walking through the parser: the explicit --foo branches at lines 425-454 are equality checks (--no-test-db, --core, --ci, etc.) — none match --useTestDB. The positional fallback at line 450 is gated on !arg.startsWith("--"), so the token is excluded there too. It falls through every branch and is dropped without any error message.
  2. Polarity inversion. "<datasource>_test when --useTestDB is set" implies the test-DB swap is opt-in. The actual semantics are the inverse: the swap is the default, and --no-test-db opts out.

Step-by-step proof

Given wheels test --db=mysql --useTestDB:

  1. Args parser at line 415 iterates: --db=mysql matches the reFindNoCase("^--db=", arg) branch, sets db="mysql", dbExplicit=true.
  2. Next arg --useTestDB: does not equal --no-test-db, --core, --ci, --verbose, -v, or any other recognized flag. Does not match ^--db=, ^--filter=, ^--reporter=, etc.
  3. Falls to the positional branch at line 450: !arg.startsWith("--") is false, so this branch is also skipped.
  4. Loop continues, arg is silently dropped. useTestDB retains its default true.
  5. runTests runs, prints the very warning that pointed the user at this non-existent flag.

Why this matters

The same PR's running-tests-locally.mdx aside correctly says --no-test-db (don't auto-swap to <datasource>_test), and the testing.mdx flag table also uses --no-test-db. Only the inline CLI warning is inconsistent — and it's the surface a user is most likely to read when troubleshooting a --db invocation.

Fix

A one-line wording change to match the docs:

out("App tests run against the configured app datasource", "yellow");
out("(<datasource>_test by default; pass --no-test-db to opt out).", "yellow");

The inline comment at Module.cfc:3704 referencing --useTestDB should be updated for consistency too.

bpamiri added a commit that referenced this pull request May 9, 2026
…_bots) (#2520)

* fix(cli): escape # in test help URL fragment

Module.cfc:3723 prints a help URL containing the unescaped fragment
"testing#testing-against-different-engines". Lucee's parser interprets
unescaped # in CFScript string literals as expression delimiters and
crashes the file's compilation with "Invalid Syntax Closing [#] not
found" when no closing # is found.

This crashed Module.cfc compilation, which broke `wheels new` and
the Wheels Snapshots smoke test pipeline (build / Smoke Test Installed
Distribution). Every push to develop has been failing this gate since
PR #2517 introduced the line.

CFML rule: ## inside a string literal outputs a literal #. The fix is
a one-character change.

See CLAUDE.md "# escape gotcha" for the same bug class historical
context.

* ci: remove unreachable vars.WHEELS_BOT_ENABLED check from skip-check action

Composite actions cannot read the `vars` context — that lookup is only
available in workflow files. The skip-check action's internal kill-switch
check failed every time the action was invoked, with:

  Unrecognized named-value: 'vars'.
  Located at position 1 within expression: vars.WHEELS_BOT_ENABLED

This silently broke from PR #2518 onwards but only surfaced after the
kill switch was activated and Reviewer A actually fired on a real PR.

The kill switch is already enforced at the job level via
`if: vars.WHEELS_BOT_ENABLED == 'true'` in every wheels-bot workflow,
so the action's internal check was redundant as well as broken.

Removed:
- `WHEELS_BOT_ENABLED` env binding (couldn't resolve)
- The `if [[ ... != "true" ]]; then skip=true ...` block (redundant)

Description updated to note that the kill switch lives at the job
level, not in this action.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: add allowed_bots to all wheels-bot claude-code-action invocations

The anthropics/claude-code-action defaults to blocking workflow runs
initiated by non-human actors (any GitHub Bot identity). Without an
explicit allowlist, every bot-triggered workflow fails fast with:

  Action failed with error: Workflow initiated by non-human actor:
  wheels-bot (type: Bot). Add bot to allowed_bots list or use '*' to
  allow all bots.

This surfaced first on bot-review-b.yml — Reviewer B is triggered by
wheels-bot[bot] submitting a Reviewer A review, and the action blocked
on the bot initiator. Same shape would hit bot-research, bot-propose-fix,
and bot-auto-close (cron actor is github-actions[bot]) as the rollout
progresses through Phases 2-5.

Adding `allowed_bots: 'wheels-bot[bot],github-actions[bot]'` to all six
wheels-bot workflows. Specific allowlist (not '*') because the repo is
public — '*' would let any external GitHub App invoke the action with
prompts they could influence (per the action's docs/security.md).

The two identities allowed:
- wheels-bot[bot]: our App's bot identity
- github-actions[bot]: GitHub's own actor for scheduled and
  workflow-internal triggers

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

wheels test ignores --filter and --db arguments

2 participants