Skip to content

Commit 4e20fbf

Browse files
bpamiricursoragent
andauthored
test(cli): Hardener SHOULDs 3–6 prove-red (#3396)
* test(cli): prove-red Hardener SHOULDs 3-6 (unloadable / docs / CommandBox / orphan) Lock residual gaps on tip ebb48e5: browser unloadable ignore, WARN soft-fail copy, guides that still teach vacuous exit 0, CommandBox test runners that swallow failing exits, and unrouted Tests.cfc. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com> * fix(cli): fail-closed browser unloadable, deprecate CommandBox test, drop orphan Tests.cfc SHOULD 3 residual: $browserTestResultFailed takes specsFailedToLoad; WARN no longer says silently skipped. SHOULD 4: guides name Wheels.TestsFailed. SHOULD 5: CommandBox test runners error() instead of swallowing TestBox exits. SHOULD 6: delete unrouted Tests.cfc. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com> * revert(cli): keep #3395 SHOULD 3 seams; do not reopen C1 Tip already fail-closes compile-skip via $countSpecsFailedToLoad and $throwIfCliTestsFailed. Restore those helpers and the WARN copy. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com> * test(cli): prove-red C5 — CommandBox error() must not reach testbox run CommandBox error() does not abort. Specs now require no testbox run and return immediately after the deprecation error(). Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com> * fix(cli): return after CommandBox test deprecation; drop testbox run error() does not abort. Each frozen test runner now returns immediately and no longer invokes testbox run (C5). Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Peter Amiri <peter@alurium.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent ebb48e5 commit 4e20fbf

16 files changed

Lines changed: 214 additions & 750 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Guides for `wheels test` now state the CLI throws `Wheels.TestsFailed` on `directoryRejected`, `bundlesDiscovered=0`, and compile-skipped specs — not only Fail/Error — so a vacuous or rejected scope is not documented as exit 0 (#3083)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Legacy CommandBox `wheels test` / `browser:test` refuse with a deprecation `error()` then `return` and no longer invoke TestBox (`testbox run`). Unrouted `vendor/wheels/controllers/Tests.cfc` is removed; `/wheels/core/tests` and `/wheels/app/tests` stay on `Public.cfc` (#3083)
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/**
2+
* Hardener SHOULDs 4–6 (WheelsTest review slice).
3+
*
4+
* Source-scan / existence locks — no live CommandBox, no live runTests HTTP.
5+
* Same altitude as MainCommandSpec / TestExitFailClosedSpec.
6+
*
7+
* SHOULD 4 — guides must teach CLI fail-closed (Wheels.TestsFailed), not
8+
* "no Fail/Error means exit 0" / ignore directoryRejected.
9+
* SHOULD 5 — legacy CommandBox cli/src test runners must not be a weaker
10+
* exit path than LuCLI `wheels test` / `wheels browser test`.
11+
* SHOULD 6 — vendor/wheels/controllers/Tests.cfc is unrouted and must not
12+
* ship; allowlisted runners stay on Public.cfc.
13+
*/
14+
component extends="wheels.wheelstest.system.BaseSpec" {
15+
16+
function beforeAll() {
17+
variables.repoRoot = expandPath("/cli/../");
18+
variables.guidesRoot = variables.repoRoot & "web/sites/guides/src/content/docs/v4-0-0/";
19+
}
20+
21+
function run() {
22+
23+
describe("SHOULD 4 — docs teach fail-closed, not silent full-suite / vacuous exit 0", () => {
24+
25+
it("testing.mdx names Wheels.TestsFailed and the ##3083 honesty signals", () => {
26+
var src = fileRead(guidesRoot & "command-line-tools/wheels-commands/testing.mdx");
27+
expect(src).toInclude("Wheels.TestsFailed");
28+
expect(src).toInclude("directoryRejected");
29+
expect(src).toInclude("bundlesDiscovered");
30+
});
31+
32+
it("running-framework-tests.mdx says wheels test throws Wheels.TestsFailed", () => {
33+
var src = fileRead(guidesRoot & "contributing/running-framework-tests.mdx");
34+
expect(src).toInclude("Wheels.TestsFailed");
35+
});
36+
37+
it("quick-start.mdx does not teach that a run with no Fail/Error always exits 0", () => {
38+
var src = fileRead(guidesRoot & "command-line-tools/quick-start.mdx");
39+
expect(src).notToInclude("a run with no failures exits `0`");
40+
});
41+
42+
it("running-tests-locally.mdx names Wheels.TestsFailed for the CLI path", () => {
43+
var src = fileRead(guidesRoot & "testing/running-tests-locally.mdx");
44+
expect(src).toInclude("Wheels.TestsFailed");
45+
});
46+
47+
it("ci-integration.mdx gates the CommandBox curl example on directoryRejected / bundlesDiscovered", () => {
48+
var src = fileRead(guidesRoot & "testing/ci-integration.mdx");
49+
expect(src).toInclude("directoryRejected");
50+
expect(src).toInclude("bundlesDiscovered");
51+
expect(src).toInclude("Wheels.TestsFailed");
52+
});
53+
54+
});
55+
56+
describe("SHOULD 5 — CommandBox cli/src test runners are not a weaker exit path", () => {
57+
58+
it("test/run.cfc does not swallow TestBox failing exit codes", () => {
59+
var src = fileRead(expandPath("/cli/src/commands/wheels/test/run.cfc"));
60+
expect(findNoCase("failing exit code", src)).toBe(
61+
0,
62+
"CommandBox wheels test run must not catch-and-ignore TestBox failing exit codes."
63+
);
64+
});
65+
66+
it("test/all.cfc does not swallow TestBox failing exit codes", () => {
67+
var src = fileRead(expandPath("/cli/src/commands/wheels/test/all.cfc"));
68+
expect(findNoCase("failing exit code", src)).toBe(
69+
0,
70+
"CommandBox wheels test:all must not catch-and-ignore TestBox failing exit codes."
71+
);
72+
});
73+
74+
it("test/unit.cfc does not swallow TestBox failing exit codes", () => {
75+
var src = fileRead(expandPath("/cli/src/commands/wheels/test/unit.cfc"));
76+
expect(findNoCase("failing exit code", src)).toBe(
77+
0,
78+
"CommandBox wheels test:unit must not catch-and-ignore TestBox failing exit codes."
79+
);
80+
});
81+
82+
it("test/integration.cfc does not swallow TestBox failing exit codes", () => {
83+
var src = fileRead(expandPath("/cli/src/commands/wheels/test/integration.cfc"));
84+
expect(findNoCase("failing exit code", src)).toBe(
85+
0,
86+
"CommandBox wheels test:integration must not catch-and-ignore TestBox failing exit codes."
87+
);
88+
});
89+
90+
it("browser/test.cfc refuses with a deprecation error instead of returning after Fail/Error", () => {
91+
var src = fileRead(expandPath("/cli/src/commands/wheels/browser/test.cfc"));
92+
expect(src).toInclude("DEPRECATED");
93+
expect(reFindNoCase("error\s*\(", src)).toBeGT(
94+
0,
95+
"CommandBox wheels browser:test must error() so the process cannot exit 0 after Fail/Error."
96+
);
97+
});
98+
99+
it("legacy CommandBox test runners point operators at LuCLI wheels test", () => {
100+
var files = [
101+
"test/run.cfc",
102+
"test/all.cfc",
103+
"test/unit.cfc",
104+
"test/integration.cfc",
105+
"test/coverage.cfc",
106+
"test/watch.cfc",
107+
"browser/test.cfc"
108+
];
109+
for (var rel in files) {
110+
var src = fileRead(expandPath("/cli/src/commands/wheels/" & rel));
111+
expect(src).toInclude(
112+
"DEPRECATED",
113+
rel & " must refuse with a deprecation instead of offering a weaker exit path."
114+
);
115+
expect(src).toInclude("LuCLI");
116+
}
117+
});
118+
119+
it("CommandBox test runners do not invoke testbox run after deprecation error()", () => {
120+
// CommandBox error() prints red and does NOT abort. A later
121+
// command("testbox run") still executes. C5.
122+
var files = [
123+
"test/run.cfc",
124+
"test/all.cfc",
125+
"test/unit.cfc",
126+
"test/integration.cfc",
127+
"test/coverage.cfc",
128+
"test/watch.cfc",
129+
"browser/test.cfc"
130+
];
131+
for (var rel in files) {
132+
var src = fileRead(expandPath("/cli/src/commands/wheels/" & rel));
133+
expect(findNoCase("testbox run", src)).toBe(
134+
0,
135+
rel & " must not contain testbox run after error(); CommandBox error() does not abort."
136+
);
137+
}
138+
});
139+
140+
it("deprecation error() is followed by return so CommandBox cannot fall through", () => {
141+
var files = [
142+
"test/run.cfc",
143+
"test/all.cfc",
144+
"test/unit.cfc",
145+
"test/integration.cfc",
146+
"test/coverage.cfc",
147+
"test/watch.cfc",
148+
"browser/test.cfc"
149+
];
150+
for (var rel in files) {
151+
var src = fileRead(expandPath("/cli/src/commands/wheels/" & rel));
152+
expect(reFindNoCase("error\s*\(\s*""DEPRECATED[^;]*;\s*return\s*;", src)).toBeGT(
153+
0,
154+
rel & " must return immediately after error(""DEPRECATED..."") — error() does not abort."
155+
);
156+
}
157+
});
158+
159+
});
160+
161+
describe("SHOULD 6 — orphan vendor/wheels/controllers/Tests.cfc is gone", () => {
162+
163+
it("does not ship vendor/wheels/controllers/Tests.cfc", () => {
164+
expect(fileExists(expandPath("/vendor/wheels/controllers/Tests.cfc"))).toBeFalse(
165+
"Unrouted Tests.cfc is not on the ##3083 allowlisted runners; delete it rather than leave an orphan."
166+
);
167+
});
168+
169+
it("allowlisted runners route to Public.cfc, not a Tests controller", () => {
170+
var publicRoutes = fileRead(expandPath("/vendor/wheels/public/routes.cfm"));
171+
var testRoutes = fileRead(expandPath("/vendor/wheels/tests/routes.cfm"));
172+
expect(publicRoutes).toInclude("public####tests_testbox");
173+
expect(publicRoutes).toInclude("wheels####public####testbox");
174+
expect(reFindNoCase("to\s*=\s*""Tests####", publicRoutes)).toBe(0);
175+
expect(reFindNoCase("to\s*=\s*""Tests####", testRoutes)).toBe(0);
176+
});
177+
178+
});
179+
180+
}
181+
182+
}

cli/src/commands/wheels/browser/test.cfc

Lines changed: 2 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -23,89 +23,8 @@ component aliases="wheels browser:test, wheels browser test" extends="../base" {
2323
boolean verbose = false,
2424
string directory = "wheels.tests.specs.wheelstest"
2525
) {
26-
var projectRoot = getCWD();
27-
28-
try {
29-
var manifest = browserService.getManifest(projectRoot);
30-
var installDir = browserService.resolveInstallDir();
31-
var status = browserService.verifyInstall(
32-
manifest=manifest,
33-
installDir=installDir
34-
);
35-
if (!status.installed) {
36-
print.redLine("Playwright not installed.");
37-
if (arrayLen(status.missing)) {
38-
print.yellowLine("Missing: " & arrayToList(status.missing, ", "));
39-
}
40-
if (arrayLen(status.mismatched)) {
41-
print.yellowLine("SHA mismatch: " & arrayToList(status.mismatched, ", "));
42-
}
43-
print.line("");
44-
print.line("Run: wheels browser:install");
45-
return;
46-
}
47-
} catch (any e) {
48-
print.redLine("Error: " & e.message);
49-
return;
50-
}
51-
52-
print.line("Running browser tests...");
53-
print.line("Directory: " & arguments.directory);
54-
print.line("");
55-
56-
var serverInfo = command("server info").params(property="host").run(returnOutput=true);
57-
var port = command("server info").params(property="port").run(returnOutput=true);
58-
var host = trim(serverInfo) ?: "localhost";
59-
var portNum = trim(port) ?: "8080";
60-
var baseUrl = "http://" & host & ":" & portNum;
61-
62-
var testUrl = baseUrl
63-
& "/wheels/core/tests?db=sqlite&format=json&directory="
64-
& arguments.directory;
65-
66-
try {
67-
cfhttp(url=testUrl, method="GET", timeout=300, result="local.response");
68-
} catch (any e) {
69-
print.redLine("Failed to reach test runner at: " & testUrl);
70-
print.redLine("Is the server running? Try: server start");
71-
return;
72-
}
73-
74-
if (arguments.format == "json") {
75-
print.line(local.response.fileContent);
76-
return;
77-
}
78-
79-
try {
80-
var data = deserializeJSON(local.response.fileContent);
81-
print.line("Pass: " & data.totalPass & " Fail: " & data.totalFail & " Error: " & data.totalError);
82-
print.line("");
83-
84-
for (var bundle in (data.bundleStats ?: [])) {
85-
for (var suite in (bundle.suiteStats ?: [])) {
86-
for (var spec in (suite.specStats ?: [])) {
87-
if (listFindNoCase("Failed,Error", spec.status ?: "")) {
88-
print.redLine(
89-
" " & (spec.status ?: "") & ": "
90-
& (spec.name ?: "unknown")
91-
);
92-
if (arguments.verbose && len(spec.failMessage ?: "")) {
93-
print.line(" " & left(spec.failMessage, 200));
94-
}
95-
}
96-
}
97-
}
98-
}
99-
100-
if (data.totalFail == 0 && data.totalError == 0) {
101-
print.greenLine("All browser tests passed.");
102-
}
103-
} catch (any e) {
104-
print.redLine("Failed to parse test results: " & e.message);
105-
if (arguments.verbose) {
106-
print.line(left(local.response.fileContent ?: "", 500));
107-
}
108-
}
26+
error("DEPRECATED: CommandBox `wheels browser:test` is frozen and does not fail-closed. Use the LuCLI `wheels` binary (`wheels browser test`). Removal scheduled for Wheels 5.0. See cli/src/README.md.");
27+
return;
10928
}
11029

11130
}

cli/src/commands/wheels/test/all.cfc

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -45,77 +45,7 @@ component aliases='wheels test:all' extends="../base" {
4545
string servername = ""
4646
) {
4747
requireWheelsApp(getCWD());
48-
arguments = reconstructArgs(
49-
argStruct=arguments,
50-
allowedValues={
51-
type=["app", "core", "plugin"],
52-
format=["txt", "json", "junit", "html"],
53-
coverageReporter=["html", "json", "xml"]
54-
}
55-
);
56-
arguments.directory = resolveTestDirectory(arguments.type, arguments.directory);
57-
58-
// Build the test URL
59-
var testUrl = buildTestUrl(
60-
type = arguments.type,
61-
servername = arguments.servername,
62-
format = arguments.format
63-
);
64-
65-
// Add coverage parameters if enabled
66-
if (arguments.coverage) {
67-
testUrl &= "&coverage=true";
68-
testUrl &= "&coverageBrowserOutputDir=#encodeForURL(arguments.coverageOutputDir)#";
69-
// Add coverage reporter format to URL
70-
testUrl &= "&coverageReporter=#encodeForURL(arguments.coverageReporter)#";
71-
}
72-
73-
// Add fail-fast parameter if specified
74-
if (arguments.failFast) {
75-
testUrl &= "&bail=true";
76-
}
77-
78-
// Build TestBox command parameters
79-
var params = {
80-
runner = testUrl,
81-
recurse = arguments.recurse,
82-
verbose = arguments.verbose
83-
};
84-
85-
// Add directory parameter if specified
86-
if (len(arguments.directory)) {
87-
params.directory = arguments.directory;
88-
}
89-
// Add optional filtering parameters
90-
if (len(arguments.bundles)) {
91-
params.testbundles = arguments.bundles;
92-
}
93-
94-
if (len(arguments.labels)) {
95-
params.labels = arguments.labels;
96-
}
97-
98-
if (len(arguments.excludes)) {
99-
params.excludes = arguments.excludes;
100-
}
101-
102-
if (len(arguments.filter)) {
103-
// Handle filter parameter
104-
if (reFindNoCase("Test$", arguments.filter)) {
105-
params.testBundles = arguments.filter;
106-
} else {
107-
params.testSpecs = arguments.filter;
108-
}
109-
}
110-
111-
try {
112-
// Execute TestBox command
113-
command('testbox run').params(argumentCollection=params).run();
114-
} catch (any e) {
115-
// Let TestBox handle its own output and errors
116-
if (!findNoCase("failing exit code", e.message)) {
117-
rethrow;
118-
}
119-
}
48+
error("DEPRECATED: CommandBox `wheels test:all` is frozen and does not fail-closed. Use the LuCLI `wheels` binary (`wheels test`). Removal scheduled for Wheels 5.0. See cli/src/README.md.");
49+
return;
12050
}
12151
}

0 commit comments

Comments
 (0)