fix(test): resolve app test-runner include under URL subpath installs#3255
fix(test): resolve app test-runner include under URL subpath installs#3255wheels-bot[bot] wants to merge 2 commits into
Conversation
The scaffolded tests/runner.cfm hardcoded an absolute include of /wheels/tests/app-runner.cfm, which only resolves when the app is mounted at the web root. Under a URL subpath / CommandBox multi-subfolder topology the /wheels mapping does not resolve, so /wheels/app/tests and 'wheels test' failed. Adds $resolveSubpathInclude() on Global.cfc, which prefixes a framework-relative include path with the app's resolved webPath (the same subpath derivation as $resolveFrameworkPaths), and points the app test-runner template at it. Behaviour is unchanged for root installs (webPath '/'). Refs #3251 Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
…tput tests/TestRunner.cfc is not part of the scaffolded test layout; replace it with runner.cfm (the actual scaffolded HTTP entry point). Add an Aside noting that only controllers/, functional/, and models/ are created by `wheels new` — view/ and browser/ must be created manually (#3251). Signed-off-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Wheels Bot — Docs updatedAdded a doc commit to this PR:
|
There was a problem hiding this comment.
Wheels Bot — Reviewer
TL;DR — This PR fixes item 2 of #3251: the scaffolded tests/runner.cfm hardcoded <cfinclude template="/wheels/tests/app-runner.cfm">, which only resolves at the web root and breaks under a URL-subpath / CommandBox multi-subfolder install. It adds a pure $resolveSubpathInclude() helper on Global.cfc that prefixes the framework-relative include with the app's resolved webPath, and re-points the template at it. The fix is correct, cross-engine-safe, and well-tested. Verdict: comment — no blocking findings, one minor test-coverage nit below.
Tests
vendor/wheels/Global.cfc:2641— the default-arg branch is the production path but is never exercised. The shipped template callsapplication.wo.$resolveSubpathInclude('/wheels/tests/app-runner.cfm')(cli/lucli/templates/app/tests/runner.cfm:21) with nowebPathargument, so it hits theelsebranch:All six specs inlocal.wp = StructKeyExists(arguments, "webPath") ? arguments.webPath : application.wheels.webPath;
resolveSubpathIncludeSpec.cfcpasswebPathexplicitly, so theapplication.wheels.webPathfallback that real callers depend on is untested. Suggestion: add oneitthat omitswebPath(withapplication.wheels.webPathset to a known value) so the production invocation shape is covered. Non-blocking — the branch is trivial — but it is the only path the runner template actually takes.
Correctness
No issues. Traced the string algorithm against all six asserted cases (root /, subfolder, nested, missing trailing slash, empty webPath, leading-slash-less template) — each matches. application.wo (public/Application.cfc:140) and application.wheels.webPath (already read at Global.cfc:1807, populated at onapplicationstart.cfc:157/aliased :460) are both valid at test-request time. Root installs (webPath = "/") are provably unchanged, so existing apps are unaffected.
Cross-engine
No issues. ReReplace(..., "^/+", "") is start-anchored; no Left(str, 0); the helper is public with a $ prefix (Invariant 7); the optional webPath arg is StructKeyExists-gated rather than relying on a runtime default expression. The <cfinclude template="#...#"> interpolation in the .cfm template is tag-context and engine-safe.
Commits / Docs
- Single commit
fix(test): resolve app test-runner include under URL subpath installs— commitlint-clean (valid type/scope, lowercase subject, 64 chars), DCO sign-off present. - Changelog fragment
changelog.d/3251-app-test-runner-subpath.fixed.mdis correctly named (.fixed.md) — the right mechanism, no directCHANGELOG.mdedit.
Nice, tightly-scoped fix with a clean unit-test surface. Worth landing once the production-path branch is covered.
There was a problem hiding this comment.
Wheels Bot — Reviewer
TL;DR — This PR fixes item 2 of #3251: the scaffolded tests/runner.cfm hardcoded <cfinclude template="/wheels/tests/app-runner.cfm">, whose bare /wheels mapping only resolves at the web root and breaks under a URL-subpath / CommandBox multi-subfolder install. The fix adds a pure $resolveSubpathInclude() helper on Global.cfc that prefixes the framework-relative include with the app resolved webPath, and points the template at it. The change is correct, cross-engine-safe, well-tested, and the docs/changelog are accurate. Verdict: comment (clean — would approve; author is wheels-bot so a self-approval is rejected by GitHub and is not needed here, as no active CHANGES_REQUESTED blocks the PR).
Correctness
The helper logic checks out against every asserted case. I traced all six:
webPathempty →Len()false → base/, leading slashes stripped →/wheels/tests/app-runner.cfmwebPath/→ no trailing-slash append →/wheels/tests/app-runner.cfm(root unchanged)webPath/wheelsproject1→ trailing slash added →/wheelsproject1/wheels/tests/app-runner.cfm- nested and leading-slash-less template cases likewise resolve to a single boundary slash.
ReReplace(arguments.template, "^/+", "") is correctly anchored to the start, so it strips only leading slashes and never touches interior separators. The function is pure (no side effects), matching the docstring claim that it is unit-testable in isolation.
The runtime call site application.wo.$resolveSubpathInclude(...) in runner.cfm is valid: application.wo is the framework object used throughout the request lifecycle (e.g. vendor/wheels/events/onapplicationstart.cfc:99 application.wo.$cgiScope()), so it is populated by the time the test-runner endpoint is hit.
Conventions
The helper reads application.wheels.webPath, consistent with the directly-analogous core URL builder at vendor/wheels/Global.cfc:1807 (local.rv = application.wheels.webPath & ...). It is declared public string function $resolveSubpathInclude(...) with the $ prefix, satisfying Cross-Engine Invariant #7 (private mixins are not integrated). No anti-patterns present.
Cross-engine
Clean across Lucee / Adobe / BoxLang:
- No
Left(str, 0)(Invariant #8); usesReReplace+Right/Len. - No
obj.map()member-function collision, no closures, no bracket-notation calls. - The optional
webPatharg is gated withStructKeyExists(arguments, "webPath")and the docstring explicitly notes it avoids a runtime default-arg expression — good hygiene for engines that evaluate those eagerly.
Tests
vendor/wheels/tests/specs/global/resolveSubpathIncludeSpec.cfc is a BDD spec extending wheels.WheelsTest, placed in the CI-run global/ dir, covering root, subfolder, nested, missing-trailing-slash, empty-webPath, and leading-slash-less cases. The accessor g = application.wo matches the sibling resolveFrameworkPathsSpec.cfc:5. The helper provably fails pre-implementation (method absent), so the failing-then-passing TDD requirement is met.
Minor (non-blocking): the one-line runner.cfm template change itself has no integration test, but the PR discloses this and the pure helper carries the behavioral coverage — acceptable for a one-liner with no isolatable harness surface.
Docs
web/sites/guides/src/content/docs/v4-0-0/testing/index.mdx is accurate against the actual scaffold: cli/lucli/templates/app/tests/ contains runner.cfm (not TestRunner.cfc) and the spec dirs controllers/, functional/, models/ — exactly what the new Aside and the corrected table row state. Framework/AI-reference/CLAUDE.md left to bot-update-docs.yml per the fix/docs split.
Commits
Both conform to commitlint.config.js:
fix(test): resolve app test-runner include under URL subpath installsdocs(web/guides): reconcile testing index with wheels new scaffold output
Valid types, suggested scopes, subjects well under 100 chars.
Security
No exposure. The include argument is a hardcoded literal (/wheels/tests/app-runner.cfm); webPath is derived from cgi.script_name/subpath config at app start, not from per-request user input. No injection, no XSS surface.
Summary
The scaffolded
tests/runner.cfmhardcoded an absolute include of/wheels/tests/app-runner.cfm. That leading/wheelsmapping only resolves when the app is mounted at the web root — under a URL subpath / CommandBox multi-subfolder (IIS-subfolder) topology it does not resolve, so both/wheels/app/testsin the browser andwheels testfrom the CLI fail. This PR adds a small, pure$resolveSubpathInclude()helper onGlobal.cfcthat prefixes a framework-relative include path with the app's resolvedwebPath(the same subpath derivation already used by$resolveFrameworkPaths, theset(subpath=…)/WHEELS_SUBPATHmachinery from 4.0.5), and points the app test-runner template at it. Behaviour is identical for root installs (webPath=/), so existing single-rooted apps are unaffected.This addresses item 2 of #3251. The other two items are deliberately out of scope here (see Scope & follow-ups below), so this PR uses
Refsrather thanFixesto avoid auto-closing the multi-part tracking issue.Refs #3251
Scope & follow-ups
#3251 bundles three threads. This PR ships the one with a cleanly isolatable, cross-engine-safe fix and a pure unit test:
?format=htmlon/wheels/app/testsreturns JSON instead of an HTML reporthtml.cfmwithtype="App"vs. document the JSON-only limitation) and has no isolatable unit-test surface in the current harness — better resolved with a human in the loop.runner.cfmhardcodes/wheels/tests/app-runner.cfm, breaking subfolder topologieswheels newscaffoldsbot-update-docs.yml(the testing-guide MDX is out of scope for this PR per the bot's fix/docs split).Type of Change
Feature Completeness Checklist
Signed-off-by:matching the configured git author (git commit -s)vendor/wheels/tests/specs/global/resolveSubpathIncludeSpec.cfccovers root, subfolder, nested-subfolder, missing-trailing-slash, empty-webPath, and leading-slash-less-template cases (failing → passing)bot-update-docs.yml)bot-update-docs.yml)bot-update-docs.yml)changelog.d/3251-app-test-runner-subpath.fixed.mdtools/test-local.shcould not be executed in this environment (thewheelsCLI is not installed in the runner — the server start failed withnohup: failed to run command 'wheels'). CI (bot-tdd-gate,pr.yml) is authoritative for this run.Test Plan
Spec:
vendor/wheels/tests/specs/global/resolveSubpathIncludeSpec.cfc$resolveSubpathIncludedoes not exist onGlobal.cfc(grep-confirmed), so everyiterrors with a missing-method exception.webPath="/"→/wheels/tests/app-runner.cfm(root install unchanged)webPath="/wheelsproject1/"→/wheelsproject1/wheels/tests/app-runner.cfmwebPath="/team/site/"→/team/site/wheels/tests/app-runner.cfmwebPath="/wheelsproject1"(no trailing slash) → normalizedwebPath=""→ treated as rootRun locally (where the
wheelsCLI is available):