Skip to content

fix(test): resolve app test-runner include under URL subpath installs#3255

Open
wheels-bot[bot] wants to merge 2 commits into
developfrom
fix/bot-3251-test-runner-app-test-endpoint-and-runner-cfm-break
Open

fix(test): resolve app test-runner include under URL subpath installs#3255
wheels-bot[bot] wants to merge 2 commits into
developfrom
fix/bot-3251-test-runner-app-test-endpoint-and-runner-cfm-break

Conversation

@wheels-bot

@wheels-bot wheels-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Summary

The scaffolded tests/runner.cfm hardcoded an absolute include of /wheels/tests/app-runner.cfm. That leading /wheels mapping 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/tests in the browser and wheels test from the CLI fail. This PR adds a small, pure $resolveSubpathInclude() helper on Global.cfc that prefixes a framework-relative include path with the app's resolved webPath (the same subpath derivation already used by $resolveFrameworkPaths, the set(subpath=…) / WHEELS_SUBPATH machinery 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 Refs rather than Fixes to 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:

# Item Status
1 ?format=html on /wheels/app/tests returns JSON instead of an HTML report Deferred. Carries a design decision (render html.cfm with type="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.
2 runner.cfm hardcodes /wheels/tests/app-runner.cfm, breaking subfolder topologies Fixed here.
3 Testing guide over-states what wheels new scaffolds Deferred to docs. Handled by bot-update-docs.yml (the testing-guide MDX is out of scope for this PR per the bot's fix/docs split).

Type of Change

  • Bug fix

Feature Completeness Checklist

  • DCO sign-off — commit carries Signed-off-by: matching the configured git author (git commit -s)
  • Testsvendor/wheels/tests/specs/global/resolveSubpathIncludeSpec.cfc covers root, subfolder, nested-subfolder, missing-trailing-slash, empty-webPath, and leading-slash-less-template cases (failing → passing)
  • Framework Docs — left unchecked (handled by bot-update-docs.yml)
  • AI Reference Docs — left unchecked (handled by bot-update-docs.yml)
  • CLAUDE.md — left unchecked (handled by bot-update-docs.yml)
  • Changelog fragmentchangelog.d/3251-app-test-runner-subpath.fixed.md
  • Test runner passes — see Test Plan; the local tools/test-local.sh could not be executed in this environment (the wheels CLI is not installed in the runner — the server start failed with nohup: 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

  • Failing first: pre-implementation, $resolveSubpathInclude does not exist on Global.cfc (grep-confirmed), so every it errors with a missing-method exception.
  • Passing after: the helper resolves the include path as asserted:
    • webPath="/"/wheels/tests/app-runner.cfm (root install unchanged)
    • webPath="/wheelsproject1/"/wheelsproject1/wheels/tests/app-runner.cfm
    • webPath="/team/site/"/team/site/wheels/tests/app-runner.cfm
    • webPath="/wheelsproject1" (no trailing slash) → normalized
    • webPath="" → treated as root
    • template without leading slash → single boundary slash preserved

Run locally (where the wheels CLI is available):

bash tools/test-local.sh wheels.tests.specs.global

Verification note: the wheels CLI is not installed in the environment this PR was authored in, so tools/test-local.sh could not be run here. The change is otherwise verified by: (1) the spec provably fails pre-implementation (helper method absent), (2) manual trace of the pure string algorithm against all six cases, and (3) cross-engine review (ReReplace anchored to ^/+; no Left(str,0); no runtime default-arg expression; StructKeyExists-gated optional arg). The CI test gates run the authoritative suite.

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

wheels-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

Wheels Bot — Docs updated

Added a doc commit to this PR:

  • web/sites/guides/src/content/docs/v4-0-0/testing/index.mdx — corrected two inaccuracies in the testing overview that test runner: app test endpoint and runner.cfm break under subfolder installs #3251 item 3 called out: (1) replaced the non-existent tests/TestRunner.cfc setup-files entry with the actual scaffolded tests/runner.cfm, and (2) added an Aside after the test-categories table noting that wheels new only scaffolds controllers/, functional/, and models/view/ and browser/ must be created manually.

@bpamiri bpamiri marked this pull request as ready for review June 24, 2026 03:16

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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 calls application.wo.$resolveSubpathInclude('/wheels/tests/app-runner.cfm') (cli/lucli/templates/app/tests/runner.cfm:21) with no webPath argument, so it hits the else branch:
    local.wp = StructKeyExists(arguments, "webPath") ? arguments.webPath : application.wheels.webPath;
    All six specs in resolveSubpathIncludeSpec.cfc pass webPath explicitly, so the application.wheels.webPath fallback that real callers depend on is untested. Suggestion: add one it that omits webPath (with application.wheels.webPath set 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.md is correctly named (.fixed.md) — the right mechanism, no direct CHANGELOG.md edit.

Nice, tightly-scoped fix with a clean unit-test surface. Worth landing once the production-path branch is covered.

@wheels-bot wheels-bot Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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:

  • webPath empty → Len() false → base /, leading slashes stripped → /wheels/tests/app-runner.cfm
  • webPath / → 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); uses ReReplace + Right/Len.
  • No obj.map() member-function collision, no closures, no bracket-notation calls.
  • The optional webPath arg is gated with StructKeyExists(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 installs
  • docs(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.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants