@c9up/helix-plugin-ream 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.d.ts +53 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -11
- package/dist/index.js.map +1 -1
- package/dist/runTests.d.ts +4 -4
- package/dist/runTests.d.ts.map +1 -1
- package/dist/runTests.js +18 -11
- package/dist/runTests.js.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +122 -15
- package/src/runTests.ts +22 -11
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The bridge between [Ream](https://github.com/C9up/ream) and [helix](https://github.com/C9up/helix), its test runner.
|
|
4
4
|
|
|
5
|
-
Ream knows nothing about helix, helix knows nothing about Ream. The plugin that joins them lives here and declares both sides as peers — the same shape
|
|
5
|
+
Ream knows nothing about helix, helix knows nothing about Ream. The plugin that joins them lives here and declares both sides as peers — the same shape AdonisJS uses to join its own runner to the framework.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
package/dist/index.d.ts
CHANGED
|
@@ -6,10 +6,25 @@
|
|
|
6
6
|
* client itself (`TestClient`, `createTestClient`) stays in `@c9up/ream/testing`
|
|
7
7
|
* — it drives a Ream server and owes nothing to the runner.
|
|
8
8
|
*
|
|
9
|
+
* The split follows AdonisJS: the plugin puts a CLIENT on the test context, and
|
|
10
|
+
* the SERVER is started by a suite hook, so a suite that does not declare the
|
|
11
|
+
* hook never starts one.
|
|
12
|
+
*
|
|
9
13
|
* // tests/bootstrap.ts
|
|
10
14
|
* import { configure } from '@c9up/helix'
|
|
11
15
|
* import { apiClient } from '@c9up/helix-plugin-ream'
|
|
12
|
-
*
|
|
16
|
+
* import { createTestUtils } from '@c9up/ream/testing/utils'
|
|
17
|
+
*
|
|
18
|
+
* export const testUtils = createTestUtils((port) => bootApp(port))
|
|
19
|
+
*
|
|
20
|
+
* await configure({
|
|
21
|
+
* plugins: [apiClient({ testUtils })],
|
|
22
|
+
* configureSuite(suite) {
|
|
23
|
+
* if (['functional', 'e2e'].includes(suite.name)) {
|
|
24
|
+
* return suite.setup(() => testUtils.httpServer().start())
|
|
25
|
+
* }
|
|
26
|
+
* },
|
|
27
|
+
* })
|
|
13
28
|
*
|
|
14
29
|
* // a test
|
|
15
30
|
* test('health', async ({ client }) => {
|
|
@@ -19,27 +34,55 @@
|
|
|
19
34
|
import type { PluginApi } from "@c9up/helix";
|
|
20
35
|
import type { AuthStrategy, RouteManifest } from "@c9up/ream/testing";
|
|
21
36
|
import { TestClient } from "@c9up/ream/testing";
|
|
37
|
+
import type { BootServer, TestUtils } from "@c9up/ream/testing/utils";
|
|
22
38
|
/** The slice of helix's `PluginApi` this plugin actually uses. */
|
|
23
39
|
export type ClientHost = Pick<PluginApi, "context" | "cleanup">;
|
|
24
40
|
export interface ApiClientConfig {
|
|
25
|
-
/**
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The utilities whose `httpServer()` a suite hook starts. The plugin reads
|
|
43
|
+
* the client of the server that hook started, so both point at one server.
|
|
44
|
+
*/
|
|
45
|
+
testUtils?: TestUtils;
|
|
46
|
+
/**
|
|
47
|
+
* Boot the app directly, for a project that does not split its suites.
|
|
48
|
+
*
|
|
49
|
+
* A deviation from AdonisJS, where starting the server is always the suite
|
|
50
|
+
* hook's job. It exists because the server then starts on the client's FIRST
|
|
51
|
+
* REQUEST rather than at plugin time, so a file that issues none pays
|
|
52
|
+
* nothing — the same saving `testUtils` obtains by scoping, without having
|
|
53
|
+
* to split the suites first. Prefer `testUtils`; reach for this when the
|
|
54
|
+
* files are not separated.
|
|
55
|
+
*/
|
|
56
|
+
boot?: BootServer;
|
|
30
57
|
/** Warden auth strategy for `client.withAuth()`/`asUser()`. */
|
|
31
58
|
auth?: AuthStrategy;
|
|
32
59
|
/** Named-route manifest (`router.namedManifest()`) for `client.visit()`. */
|
|
33
60
|
routes?: RouteManifest;
|
|
34
61
|
}
|
|
35
62
|
/**
|
|
36
|
-
* Injects a
|
|
63
|
+
* Injects a {@link TestClient} on the test context as `ctx.client`.
|
|
64
|
+
*
|
|
65
|
+
* With `testUtils`, the client is the one belonging to the server the suite
|
|
66
|
+
* hook started — so nothing is booted here, and a suite without the hook has no
|
|
67
|
+
* server at all. With `boot`, the client owns its server and starts it on the
|
|
68
|
+
* first request.
|
|
37
69
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* reliance on process exit.
|
|
70
|
+
* Either way `api.cleanup` closes what this plugin started, which is nothing in
|
|
71
|
+
* the `testUtils` case — the hook's own teardown owns that server.
|
|
41
72
|
*/
|
|
42
73
|
export declare function apiClient(config: ApiClientConfig): (api: ClientHost) => Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* The client this run is using, reachable without a test context.
|
|
76
|
+
*
|
|
77
|
+
* `ctx.client` covers a test body. A helper module, a fixture, or a
|
|
78
|
+
* `runnerHooks` setup has no context to read it from and had to be handed one
|
|
79
|
+
* through a parameter every call site then had to thread. Same instance as
|
|
80
|
+
* `ctx.client` — the AdonisJS `services/test_utils` idiom.
|
|
81
|
+
*
|
|
82
|
+
* Throws when no client is in play, which is a wiring mistake rather than a
|
|
83
|
+
* state to handle.
|
|
84
|
+
*/
|
|
85
|
+
export declare function testClient(): TestClient;
|
|
43
86
|
declare module "@c9up/helix" {
|
|
44
87
|
interface TestContext {
|
|
45
88
|
client: TestClient;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAU,SAAS,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAEtE,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAAC,CAAC;AAEhE,MAAM,WAAW,eAAe;IAC/B;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,aAAa,CAAC;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,SACrB,UAAU,KAAG,OAAO,CAAC,IAAI,CAAC,CAkDrD;AAiBD;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAUvC;AAGD,OAAO,QAAQ,aAAa,CAAC;IAC5B,UAAU,WAAW;QACpB,MAAM,EAAE,UAAU,CAAC;KACnB;CACD"}
|
package/dist/index.js
CHANGED
|
@@ -6,10 +6,25 @@
|
|
|
6
6
|
* client itself (`TestClient`, `createTestClient`) stays in `@c9up/ream/testing`
|
|
7
7
|
* — it drives a Ream server and owes nothing to the runner.
|
|
8
8
|
*
|
|
9
|
+
* The split follows AdonisJS: the plugin puts a CLIENT on the test context, and
|
|
10
|
+
* the SERVER is started by a suite hook, so a suite that does not declare the
|
|
11
|
+
* hook never starts one.
|
|
12
|
+
*
|
|
9
13
|
* // tests/bootstrap.ts
|
|
10
14
|
* import { configure } from '@c9up/helix'
|
|
11
15
|
* import { apiClient } from '@c9up/helix-plugin-ream'
|
|
12
|
-
*
|
|
16
|
+
* import { createTestUtils } from '@c9up/ream/testing/utils'
|
|
17
|
+
*
|
|
18
|
+
* export const testUtils = createTestUtils((port) => bootApp(port))
|
|
19
|
+
*
|
|
20
|
+
* await configure({
|
|
21
|
+
* plugins: [apiClient({ testUtils })],
|
|
22
|
+
* configureSuite(suite) {
|
|
23
|
+
* if (['functional', 'e2e'].includes(suite.name)) {
|
|
24
|
+
* return suite.setup(() => testUtils.httpServer().start())
|
|
25
|
+
* }
|
|
26
|
+
* },
|
|
27
|
+
* })
|
|
13
28
|
*
|
|
14
29
|
* // a test
|
|
15
30
|
* test('health', async ({ client }) => {
|
|
@@ -18,25 +33,93 @@
|
|
|
18
33
|
*/
|
|
19
34
|
import { TestClient } from "@c9up/ream/testing";
|
|
20
35
|
/**
|
|
21
|
-
* Injects a
|
|
36
|
+
* Injects a {@link TestClient} on the test context as `ctx.client`.
|
|
22
37
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
38
|
+
* With `testUtils`, the client is the one belonging to the server the suite
|
|
39
|
+
* hook started — so nothing is booted here, and a suite without the hook has no
|
|
40
|
+
* server at all. With `boot`, the client owns its server and starts it on the
|
|
41
|
+
* first request.
|
|
42
|
+
*
|
|
43
|
+
* Either way `api.cleanup` closes what this plugin started, which is nothing in
|
|
44
|
+
* the `testUtils` case — the hook's own teardown owns that server.
|
|
26
45
|
*/
|
|
27
46
|
export function apiClient(config) {
|
|
28
47
|
const plugin = async (api) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
48
|
+
if (!config.testUtils && !config.boot) {
|
|
49
|
+
throw new Error("[E_NO_SERVER] apiClient() needs either `testUtils` (the AdonisJS shape: " +
|
|
50
|
+
"a suite hook starts the server) or `boot` (the client starts one on its " +
|
|
51
|
+
"first request). It was given neither, so `ctx.client` would have had " +
|
|
52
|
+
"nothing to talk to.");
|
|
53
|
+
}
|
|
54
|
+
// Resolved per access, not once: with `testUtils` the server does not
|
|
55
|
+
// exist yet when the plugin runs — the suite's setup hook starts it.
|
|
56
|
+
const owned = config.boot
|
|
57
|
+
? new TestClient(config.boot, {
|
|
58
|
+
auth: config.auth,
|
|
59
|
+
routes: config.routes,
|
|
60
|
+
})
|
|
61
|
+
: undefined;
|
|
62
|
+
const read = () => {
|
|
63
|
+
const client = owned ?? config.testUtils?.client();
|
|
64
|
+
if (!client) {
|
|
65
|
+
throw new Error("[E_SERVER_NOT_STARTED] No server is running for this suite. Add the " +
|
|
66
|
+
"hook that starts it:\n" +
|
|
67
|
+
" configureSuite(suite) {\n" +
|
|
68
|
+
" if (suite.name === 'functional') {\n" +
|
|
69
|
+
" return suite.setup(() => testUtils.httpServer().start())\n" +
|
|
70
|
+
" }\n" +
|
|
71
|
+
" }");
|
|
72
|
+
}
|
|
73
|
+
return client;
|
|
74
|
+
};
|
|
75
|
+
// The resolver, not a client: with `testUtils` there is nothing to record
|
|
76
|
+
// yet, and `testClient()` must not depend on someone having read
|
|
77
|
+
// `ctx.client` first — a fixture or a runner hook has no context to read
|
|
78
|
+
// it from, which is the reason the accessor exists.
|
|
79
|
+
setResolver(read);
|
|
80
|
+
api.context.getter("client", read);
|
|
81
|
+
api.cleanup(async () => {
|
|
82
|
+
if (owned)
|
|
83
|
+
await owned.close();
|
|
84
|
+
clearResolver(read);
|
|
32
85
|
});
|
|
33
|
-
await client.boot();
|
|
34
|
-
api.context.macro("client", client);
|
|
35
|
-
api.cleanup(() => client.close());
|
|
36
86
|
};
|
|
37
87
|
// A wider parameter than `PluginApi` stays assignable to `Plugin`, so the
|
|
38
88
|
// plugin declares exactly what it touches and a caller can drive it with
|
|
39
89
|
// nothing more than that.
|
|
40
90
|
return plugin;
|
|
41
91
|
}
|
|
92
|
+
let resolve;
|
|
93
|
+
/** @internal Record how to reach the client in play. */
|
|
94
|
+
function setResolver(read) {
|
|
95
|
+
resolve = read;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* @internal Forget it IF it is still the one recorded — a second `configure()`
|
|
99
|
+
* in the same process must not have the first one's teardown clear its client.
|
|
100
|
+
*/
|
|
101
|
+
function clearResolver(read) {
|
|
102
|
+
if (resolve === read)
|
|
103
|
+
resolve = undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The client this run is using, reachable without a test context.
|
|
107
|
+
*
|
|
108
|
+
* `ctx.client` covers a test body. A helper module, a fixture, or a
|
|
109
|
+
* `runnerHooks` setup has no context to read it from and had to be handed one
|
|
110
|
+
* through a parameter every call site then had to thread. Same instance as
|
|
111
|
+
* `ctx.client` — the AdonisJS `services/test_utils` idiom.
|
|
112
|
+
*
|
|
113
|
+
* Throws when no client is in play, which is a wiring mistake rather than a
|
|
114
|
+
* state to handle.
|
|
115
|
+
*/
|
|
116
|
+
export function testClient() {
|
|
117
|
+
if (!resolve) {
|
|
118
|
+
throw new Error("[E_NO_TEST_CLIENT] No client is in play. Add `apiClient()` to the plugins " +
|
|
119
|
+
"in tests/bootstrap.ts, and — with the `testUtils` shape — make sure the " +
|
|
120
|
+
"suite's setup hook has started the server. This reads the client of the " +
|
|
121
|
+
"CURRENT process.");
|
|
122
|
+
}
|
|
123
|
+
return resolve();
|
|
124
|
+
}
|
|
42
125
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAIH,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AA6BhD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,MAAuB;IAChD,MAAM,MAAM,GAAG,KAAK,EAAE,GAAe,EAAiB,EAAE;QACvD,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CACd,0EAA0E;gBACzE,0EAA0E;gBAC1E,uEAAuE;gBACvE,qBAAqB,CACtB,CAAC;QACH,CAAC;QAED,sEAAsE;QACtE,qEAAqE;QACrE,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI;YACxB,CAAC,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE;gBAC5B,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,MAAM,EAAE,MAAM,CAAC,MAAM;aACrB,CAAC;YACH,CAAC,CAAC,SAAS,CAAC;QAEb,MAAM,IAAI,GAAG,GAAe,EAAE;YAC7B,MAAM,MAAM,GAAG,KAAK,IAAI,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;YACnD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CACd,sEAAsE;oBACrE,wBAAwB;oBACxB,6BAA6B;oBAC7B,0CAA0C;oBAC1C,kEAAkE;oBAClE,SAAS;oBACT,KAAK,CACN,CAAC;YACH,CAAC;YACD,OAAO,MAAM,CAAC;QACf,CAAC,CAAC;QAEF,0EAA0E;QAC1E,iEAAiE;QACjE,yEAAyE;QACzE,oDAAoD;QACpD,WAAW,CAAC,IAAI,CAAC,CAAC;QAClB,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACnC,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YACtB,IAAI,KAAK;gBAAE,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YAC/B,aAAa,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;IACF,0EAA0E;IAC1E,yEAAyE;IACzE,0BAA0B;IAC1B,OAAO,MAAuB,CAAC;AAChC,CAAC;AAED,IAAI,OAAuC,CAAC;AAE5C,wDAAwD;AACxD,SAAS,WAAW,CAAC,IAAsB;IAC1C,OAAO,GAAG,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,IAAsB;IAC5C,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,GAAG,SAAS,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,UAAU;IACzB,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACd,4EAA4E;YAC3E,0EAA0E;YAC1E,0EAA0E;YAC1E,kBAAkB,CACnB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,EAAE,CAAC;AAClB,CAAC"}
|
package/dist/runTests.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The AdonisJS stratification, kept intact: the FRAMEWORK reads its rc file and
|
|
5
5
|
* hands the suites to the runner, exactly as `@adonisjs/core` reads
|
|
6
|
-
* `adonisrc.ts` and hands them to
|
|
6
|
+
* `adonisrc.ts` and hands them to helix. The runner itself (helix) knows nothing
|
|
7
7
|
* about ream, and ream owns no test execution — it only translates.
|
|
8
8
|
*
|
|
9
9
|
* // bin/test.ts
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* to be re-imported in each worker, so the runner needs the module's PATH, not
|
|
17
17
|
* just the object it exported. `runTests` takes it as `configModule`, and
|
|
18
18
|
* REFUSES to run when a suite declares a callback it was given no way to
|
|
19
|
-
* deliver — in
|
|
19
|
+
* deliver — in helix a declared `configure` runs, so a run that skipped it would
|
|
20
20
|
* be green in a state its own config does not describe.
|
|
21
21
|
*/
|
|
22
22
|
import type { TestsConfig } from "@c9up/ream";
|
|
@@ -57,7 +57,7 @@ export declare class UnknownSuiteError extends Error {
|
|
|
57
57
|
/**
|
|
58
58
|
* A suite declares `configure`, but this entry point cannot deliver it.
|
|
59
59
|
*
|
|
60
|
-
* Not a warning:
|
|
60
|
+
* Not a warning: helix runs a declared `configure`, so carrying on would run the
|
|
61
61
|
* suite in a state its own config does not describe — and a warning is exactly
|
|
62
62
|
* what gets scrolled past in CI.
|
|
63
63
|
*/
|
|
@@ -67,7 +67,7 @@ export declare class SuiteConfigureUnreachableError extends Error {
|
|
|
67
67
|
/**
|
|
68
68
|
* The flags the worker processes are spawned with.
|
|
69
69
|
*
|
|
70
|
-
* Split out because it is a decision, not plumbing: the
|
|
70
|
+
* Split out because it is a decision, not plumbing: the helix alias loader
|
|
71
71
|
* redirects a package specifier, so it goes in only when a project asks. It
|
|
72
72
|
* rides ALONGSIDE whatever loader is already there — the test files still need
|
|
73
73
|
* theirs to read TypeScript.
|
package/dist/runTests.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runTests.d.ts","sourceRoot":"","sources":["../src/runTests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,YAAY,CAAC;AAG/D,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC/B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,2DAA2D;AAC3D,qBAAa,iBAAkB,SAAQ,KAAK;gBAC/B,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE;CAQ5C;AAED;;;;;;GAMG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;gBAC5C,KAAK,EAAE,MAAM,EAAE;CAU3B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC7B,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,OAAO,EAAE,eAAe,GACtB,MAAM,EAAE,CAEV;AAkBD;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAC7B,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAyGjB;
|
|
1
|
+
{"version":3,"file":"runTests.d.ts","sourceRoot":"","sources":["../src/runTests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,YAAY,CAAC;AAG/D,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC/B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,iCAAiC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,2DAA2D;AAC3D,qBAAa,iBAAkB,SAAQ,KAAK;gBAC/B,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE;CAQ5C;AAED;;;;;;GAMG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;gBAC5C,KAAK,EAAE,MAAM,EAAE;CAU3B;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC7B,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,OAAO,EAAE,eAAe,GACtB,MAAM,EAAE,CAEV;AAkBD;;;;;;;;;;GAUG;AACH,wBAAsB,QAAQ,CAC7B,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAyGjB;AA2BD;;;GAGG;AACH,wBAAsB,kBAAkB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAmBjB"}
|
package/dist/runTests.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The AdonisJS stratification, kept intact: the FRAMEWORK reads its rc file and
|
|
5
5
|
* hands the suites to the runner, exactly as `@adonisjs/core` reads
|
|
6
|
-
* `adonisrc.ts` and hands them to
|
|
6
|
+
* `adonisrc.ts` and hands them to helix. The runner itself (helix) knows nothing
|
|
7
7
|
* about ream, and ream owns no test execution — it only translates.
|
|
8
8
|
*
|
|
9
9
|
* // bin/test.ts
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* to be re-imported in each worker, so the runner needs the module's PATH, not
|
|
17
17
|
* just the object it exported. `runTests` takes it as `configModule`, and
|
|
18
18
|
* REFUSES to run when a suite declares a callback it was given no way to
|
|
19
|
-
* deliver — in
|
|
19
|
+
* deliver — in helix a declared `configure` runs, so a run that skipped it would
|
|
20
20
|
* be green in a state its own config does not describe.
|
|
21
21
|
*/
|
|
22
22
|
import path from "node:path";
|
|
@@ -34,7 +34,7 @@ export class UnknownSuiteError extends Error {
|
|
|
34
34
|
/**
|
|
35
35
|
* A suite declares `configure`, but this entry point cannot deliver it.
|
|
36
36
|
*
|
|
37
|
-
* Not a warning:
|
|
37
|
+
* Not a warning: helix runs a declared `configure`, so carrying on would run the
|
|
38
38
|
* suite in a state its own config does not describe — and a warning is exactly
|
|
39
39
|
* what gets scrolled past in CI.
|
|
40
40
|
*/
|
|
@@ -51,7 +51,7 @@ export class SuiteConfigureUnreachableError extends Error {
|
|
|
51
51
|
/**
|
|
52
52
|
* The flags the worker processes are spawned with.
|
|
53
53
|
*
|
|
54
|
-
* Split out because it is a decision, not plumbing: the
|
|
54
|
+
* Split out because it is a decision, not plumbing: the helix alias loader
|
|
55
55
|
* redirects a package specifier, so it goes in only when a project asks. It
|
|
56
56
|
* rides ALONGSIDE whatever loader is already there — the test files still need
|
|
57
57
|
* theirs to read TypeScript.
|
|
@@ -106,7 +106,7 @@ export async function runTests(tests, options = {}) {
|
|
|
106
106
|
// should not pay for.
|
|
107
107
|
const configuring = selected.filter((suite) => typeof suite.configure === "function");
|
|
108
108
|
if (configuring.length > 0 && options.configModule === undefined) {
|
|
109
|
-
// In
|
|
109
|
+
// In helix a declared `configure` RUNS. It cannot here — the callback needs
|
|
110
110
|
// the module's path to be re-imported in each worker, and this entry was
|
|
111
111
|
// handed the exported object. Running anyway would produce a green suite
|
|
112
112
|
// configured differently from what the rc file says, so the run stops.
|
|
@@ -126,7 +126,7 @@ export async function runTests(tests, options = {}) {
|
|
|
126
126
|
bail: options.bail,
|
|
127
127
|
};
|
|
128
128
|
// `runnerHooks` run ONCE around the whole run, here, and the workers skip
|
|
129
|
-
// them —
|
|
129
|
+
// them — helix's semantics, and the difference between migrating once and
|
|
130
130
|
// migrating once per test file.
|
|
131
131
|
const dropGlobalHooks = await helix.runGlobalHooks(bootstrap);
|
|
132
132
|
// No suites declared: run whatever the project's discovery finds, so an app
|
|
@@ -134,7 +134,7 @@ export async function runTests(tests, options = {}) {
|
|
|
134
134
|
if (selected.length === 0) {
|
|
135
135
|
try {
|
|
136
136
|
const outcome = await helix.run(base);
|
|
137
|
-
return finish(outcome.exitCode, forceExit);
|
|
137
|
+
return finish(outcome.exitCode, forceExit, helix.armDrainGuard);
|
|
138
138
|
}
|
|
139
139
|
finally {
|
|
140
140
|
await dropGlobalHooks();
|
|
@@ -164,27 +164,34 @@ export async function runTests(tests, options = {}) {
|
|
|
164
164
|
}
|
|
165
165
|
if (steps.length === 0) {
|
|
166
166
|
await dropGlobalHooks();
|
|
167
|
-
return finish(0, forceExit);
|
|
167
|
+
return finish(0, forceExit, helix.armDrainGuard);
|
|
168
168
|
}
|
|
169
169
|
try {
|
|
170
170
|
const outcome = await helix.runSuites(steps, base);
|
|
171
|
-
return finish(outcome.exitCode, forceExit);
|
|
171
|
+
return finish(outcome.exitCode, forceExit, helix.armDrainGuard);
|
|
172
172
|
}
|
|
173
173
|
finally {
|
|
174
174
|
await dropGlobalHooks();
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
178
|
-
* Apply `tests.forceExit`.
|
|
178
|
+
* Apply `tests.forceExit`. helix does this inside its own run — sets the exit
|
|
179
179
|
* code, then `process.exit()` — rather than leaving it to the caller, because
|
|
180
180
|
* the whole point is to not wait for the event loop to drain. A run that
|
|
181
181
|
* force-exits never returns here; the value is for every other run.
|
|
182
182
|
*/
|
|
183
|
-
function finish(code, forceExit) {
|
|
183
|
+
function finish(code, forceExit, armDrainGuard) {
|
|
184
184
|
if (forceExit) {
|
|
185
185
|
process.exitCode = code;
|
|
186
186
|
process.exit();
|
|
187
187
|
}
|
|
188
|
+
// Without forceExit the loop is left to drain on its own, which is the right
|
|
189
|
+
// default — a leaked handle should be visible, not swallowed. But visible has
|
|
190
|
+
// to mean SAID: `ream test` used to print its summary and then hang with no
|
|
191
|
+
// further output, and under a CI timeout that is a non-zero exit for a run in
|
|
192
|
+
// which every test passed. helix's own CLI already said so; this entry point
|
|
193
|
+
// did not, and it is the one `ream test` goes through.
|
|
194
|
+
armDrainGuard(code);
|
|
188
195
|
return code;
|
|
189
196
|
}
|
|
190
197
|
/**
|
package/dist/runTests.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runTests.js","sourceRoot":"","sources":["../src/runTests.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"runTests.js","sourceRoot":"","sources":["../src/runTests.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAiC9C,2DAA2D;AAC3D,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC3C,YAAY,IAAY,EAAE,QAAkB;QAC3C,KAAK,CACJ,QAAQ,CAAC,MAAM,KAAK,CAAC;YACpB,CAAC,CAAC,uBAAuB,IAAI,+BAA+B;YAC5D,CAAC,CAAC,uBAAuB,IAAI,gBAAgB,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACpE,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IACjC,CAAC;CACD;AAED;;;;;;GAMG;AACH,MAAM,OAAO,8BAA+B,SAAQ,KAAK;IACxD,YAAY,KAAe;QAC1B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QAChC,KAAK,CACJ,QAAQ,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YAC1E,UAAU,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,sDAAsD;YACjF,2EAA2E;YAC3E,oDAAoD,CACrD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;IAC9C,CAAC;CACD;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC7B,KAA8B,EAC9B,OAAwB;IAExB,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,wEAAwE;AACxE,SAAS,MAAM,CACd,QAA2B,EAC3B,KAAe;IAEf,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IACxC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IACrE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACzB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,iBAAiB,CAAC,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC7B,KAA8B,EAC9B,UAA2B,EAAE;IAE7B,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC;IAE9B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3C,8EAA8E;IAC9E,4EAA4E;IAC5E,YAAY,CAAC,aAAa,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1E,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC;IACrC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAExD,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;IAEjD,4EAA4E;IAC5E,6EAA6E;IAC7E,mBAAmB;IACnB,MAAM,SAAS,GAAG,KAAK,CAAC,gBAAgB,CAAC,IAAI,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IACjE,OAAO,CAAC,GAAG,CAAC,eAAe,GAAG,SAAS,IAAI,EAAE,CAAC;IAC9C,0EAA0E;IAC1E,4EAA4E;IAC5E,MAAM,SAAS,GAAG,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAEpD,8EAA8E;IAC9E,0EAA0E;IAC1E,sBAAsB;IACtB,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAClC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,CAAC,SAAS,KAAK,UAAU,CAChD,CAAC;IACF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;QAClE,4EAA4E;QAC5E,yEAAyE;QACzE,yEAAyE;QACzE,uEAAuE;QACvE,MAAM,IAAI,8BAA8B,CACvC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CACtC,CAAC;IACH,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,kBAAkB;QAC7B,WAAW,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS;YAC3D,CAAC,CAAC,OAAO,CAAC,YAAY;YACtB,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,CAAC,GAAG,CAAC,sBAAsB,GAAG,cAAc,CAAC;IAEpD,MAAM,IAAI,GAAG;QACZ,IAAI;QACJ,QAAQ,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC;QACxC,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,SAAS,EAAE,KAAK,EAAE,OAAO;QACzB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,IAAI,EAAE,OAAO,CAAC,IAAI;KAClB,CAAC;IAEF,0EAA0E;IAC1E,0EAA0E;IAC1E,gCAAgC;IAChC,MAAM,eAAe,GAAG,MAAM,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;IAE9D,4EAA4E;IAC5E,oEAAoE;IACpE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACtC,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;QACjE,CAAC;gBAAS,CAAC;YACV,MAAM,eAAe,EAAE,CAAC;QACzB,CAAC;IACF,CAAC;IAED,MAAM,KAAK,GAAG,EAAE,CAAC;IACjB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC9B,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,iBAAiB,CAC1C;YACC,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,OAAO,EAAE,KAAK,CAAC,OAAO;SACtB,EACD,IAAI,EACJ,SAAS,CACT,CAAC;QACF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,KAAK,CAAC,IAAI,oBAAoB,CAAC,CAAC;YACrE,SAAS;QACV,CAAC;QACD,KAAK,CAAC,IAAI,CAAC;YACV,GAAG,EAAE;gBACJ,WAAW,EAAE,KAAK,CAAC,IAAI;gBACvB,kEAAkE;gBAClE,gCAAgC;gBAChC,aAAa,EAAE,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;aACvE;YACD,MAAM,EAAE,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,IAAI,KAAK,EAAE,OAAO,EAAE;SACtE,CAAC,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,eAAe,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,CAAC;QACJ,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QACnD,OAAO,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IACjE,CAAC;YAAS,CAAC;QACV,MAAM,eAAe,EAAE,CAAC;IACzB,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,SAAS,MAAM,CACd,IAAY,EACZ,SAAkB,EAClB,aAAqC;IAErC,IAAI,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;QACxB,OAAO,CAAC,IAAI,EAAE,CAAC;IAChB,CAAC;IACD,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,uDAAuD;IACvD,aAAa,CAAC,IAAI,CAAC,CAAC;IACpB,OAAO,IAAI,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACvC,MAAc,EACd,UAA2B,EAAE;IAE7B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QACvC,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9B,MAAM,QAAQ,GAAY,MAAM,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC;IACrE,MAAM,EAAE,GACP,QAAQ,KAAK,IAAI,IAAI,OAAO,QAAQ,KAAK,QAAQ;QAChD,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,QAAQ,CAAC;QAChD,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,KAAK,GACV,EAAE,KAAK,IAAI,IAAI,OAAO,EAAE,KAAK,QAAQ;QACpC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,OAAO,CAAC;QAC1B,CAAC,CAAC,SAAS,CAAC;IACd,OAAO,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,EAAE;QACzD,GAAG,OAAO;QACV,IAAI;QACJ,YAAY,EAAE,QAAQ;KACtB,CAAC,CAAC;AACJ,CAAC;AAED,6DAA6D;AAC7D,SAAS,aAAa,CAAC,KAAc;IACpC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,CAAC;AACpD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@c9up/helix-plugin-ream",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Helix plugin for Ream — boots a Ream app under test and injects a TestClient on the test context",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,13 +17,13 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@c9up/helix": "^0.2.
|
|
21
|
-
"@c9up/ream": "^0.2.
|
|
20
|
+
"@c9up/helix": "^0.2.3",
|
|
21
|
+
"@c9up/ream": "^0.2.7"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@biomejs/biome": "^2.4.10",
|
|
25
|
-
"@c9up/helix": "^0.2.
|
|
26
|
-
"@c9up/ream": "^0.2.
|
|
25
|
+
"@c9up/helix": "^0.2.3",
|
|
26
|
+
"@c9up/ream": "^0.2.7",
|
|
27
27
|
"@types/node": "^22.19.15",
|
|
28
28
|
"tsx": "^4",
|
|
29
29
|
"typescript": "^6.0.2",
|
package/src/index.ts
CHANGED
|
@@ -6,10 +6,25 @@
|
|
|
6
6
|
* client itself (`TestClient`, `createTestClient`) stays in `@c9up/ream/testing`
|
|
7
7
|
* — it drives a Ream server and owes nothing to the runner.
|
|
8
8
|
*
|
|
9
|
+
* The split follows AdonisJS: the plugin puts a CLIENT on the test context, and
|
|
10
|
+
* the SERVER is started by a suite hook, so a suite that does not declare the
|
|
11
|
+
* hook never starts one.
|
|
12
|
+
*
|
|
9
13
|
* // tests/bootstrap.ts
|
|
10
14
|
* import { configure } from '@c9up/helix'
|
|
11
15
|
* import { apiClient } from '@c9up/helix-plugin-ream'
|
|
12
|
-
*
|
|
16
|
+
* import { createTestUtils } from '@c9up/ream/testing/utils'
|
|
17
|
+
*
|
|
18
|
+
* export const testUtils = createTestUtils((port) => bootApp(port))
|
|
19
|
+
*
|
|
20
|
+
* await configure({
|
|
21
|
+
* plugins: [apiClient({ testUtils })],
|
|
22
|
+
* configureSuite(suite) {
|
|
23
|
+
* if (['functional', 'e2e'].includes(suite.name)) {
|
|
24
|
+
* return suite.setup(() => testUtils.httpServer().start())
|
|
25
|
+
* }
|
|
26
|
+
* },
|
|
27
|
+
* })
|
|
13
28
|
*
|
|
14
29
|
* // a test
|
|
15
30
|
* test('health', async ({ client }) => {
|
|
@@ -20,15 +35,28 @@
|
|
|
20
35
|
import type { Plugin, PluginApi } from "@c9up/helix";
|
|
21
36
|
import type { AuthStrategy, RouteManifest } from "@c9up/ream/testing";
|
|
22
37
|
import { TestClient } from "@c9up/ream/testing";
|
|
38
|
+
import type { BootServer, TestUtils } from "@c9up/ream/testing/utils";
|
|
23
39
|
|
|
24
40
|
/** The slice of helix's `PluginApi` this plugin actually uses. */
|
|
25
41
|
export type ClientHost = Pick<PluginApi, "context" | "cleanup">;
|
|
26
42
|
|
|
27
43
|
export interface ApiClientConfig {
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
44
|
+
/**
|
|
45
|
+
* The utilities whose `httpServer()` a suite hook starts. The plugin reads
|
|
46
|
+
* the client of the server that hook started, so both point at one server.
|
|
47
|
+
*/
|
|
48
|
+
testUtils?: TestUtils;
|
|
49
|
+
/**
|
|
50
|
+
* Boot the app directly, for a project that does not split its suites.
|
|
51
|
+
*
|
|
52
|
+
* A deviation from AdonisJS, where starting the server is always the suite
|
|
53
|
+
* hook's job. It exists because the server then starts on the client's FIRST
|
|
54
|
+
* REQUEST rather than at plugin time, so a file that issues none pays
|
|
55
|
+
* nothing — the same saving `testUtils` obtains by scoping, without having
|
|
56
|
+
* to split the suites first. Prefer `testUtils`; reach for this when the
|
|
57
|
+
* files are not separated.
|
|
58
|
+
*/
|
|
59
|
+
boot?: BootServer;
|
|
32
60
|
/** Warden auth strategy for `client.withAuth()`/`asUser()`. */
|
|
33
61
|
auth?: AuthStrategy;
|
|
34
62
|
/** Named-route manifest (`router.namedManifest()`) for `client.visit()`. */
|
|
@@ -36,21 +64,62 @@ export interface ApiClientConfig {
|
|
|
36
64
|
}
|
|
37
65
|
|
|
38
66
|
/**
|
|
39
|
-
* Injects a
|
|
67
|
+
* Injects a {@link TestClient} on the test context as `ctx.client`.
|
|
68
|
+
*
|
|
69
|
+
* With `testUtils`, the client is the one belonging to the server the suite
|
|
70
|
+
* hook started — so nothing is booted here, and a suite without the hook has no
|
|
71
|
+
* server at all. With `boot`, the client owns its server and starts it on the
|
|
72
|
+
* first request.
|
|
40
73
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* reliance on process exit.
|
|
74
|
+
* Either way `api.cleanup` closes what this plugin started, which is nothing in
|
|
75
|
+
* the `testUtils` case — the hook's own teardown owns that server.
|
|
44
76
|
*/
|
|
45
77
|
export function apiClient(config: ApiClientConfig) {
|
|
46
78
|
const plugin = async (api: ClientHost): Promise<void> => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
if (!config.testUtils && !config.boot) {
|
|
80
|
+
throw new Error(
|
|
81
|
+
"[E_NO_SERVER] apiClient() needs either `testUtils` (the AdonisJS shape: " +
|
|
82
|
+
"a suite hook starts the server) or `boot` (the client starts one on its " +
|
|
83
|
+
"first request). It was given neither, so `ctx.client` would have had " +
|
|
84
|
+
"nothing to talk to.",
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Resolved per access, not once: with `testUtils` the server does not
|
|
89
|
+
// exist yet when the plugin runs — the suite's setup hook starts it.
|
|
90
|
+
const owned = config.boot
|
|
91
|
+
? new TestClient(config.boot, {
|
|
92
|
+
auth: config.auth,
|
|
93
|
+
routes: config.routes,
|
|
94
|
+
})
|
|
95
|
+
: undefined;
|
|
96
|
+
|
|
97
|
+
const read = (): TestClient => {
|
|
98
|
+
const client = owned ?? config.testUtils?.client();
|
|
99
|
+
if (!client) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
"[E_SERVER_NOT_STARTED] No server is running for this suite. Add the " +
|
|
102
|
+
"hook that starts it:\n" +
|
|
103
|
+
" configureSuite(suite) {\n" +
|
|
104
|
+
" if (suite.name === 'functional') {\n" +
|
|
105
|
+
" return suite.setup(() => testUtils.httpServer().start())\n" +
|
|
106
|
+
" }\n" +
|
|
107
|
+
" }",
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return client;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// The resolver, not a client: with `testUtils` there is nothing to record
|
|
114
|
+
// yet, and `testClient()` must not depend on someone having read
|
|
115
|
+
// `ctx.client` first — a fixture or a runner hook has no context to read
|
|
116
|
+
// it from, which is the reason the accessor exists.
|
|
117
|
+
setResolver(read);
|
|
118
|
+
api.context.getter("client", read);
|
|
119
|
+
api.cleanup(async () => {
|
|
120
|
+
if (owned) await owned.close();
|
|
121
|
+
clearResolver(read);
|
|
50
122
|
});
|
|
51
|
-
await client.boot();
|
|
52
|
-
api.context.macro("client", client);
|
|
53
|
-
api.cleanup(() => client.close());
|
|
54
123
|
};
|
|
55
124
|
// A wider parameter than `PluginApi` stays assignable to `Plugin`, so the
|
|
56
125
|
// plugin declares exactly what it touches and a caller can drive it with
|
|
@@ -58,6 +127,44 @@ export function apiClient(config: ApiClientConfig) {
|
|
|
58
127
|
return plugin satisfies Plugin;
|
|
59
128
|
}
|
|
60
129
|
|
|
130
|
+
let resolve: (() => TestClient) | undefined;
|
|
131
|
+
|
|
132
|
+
/** @internal Record how to reach the client in play. */
|
|
133
|
+
function setResolver(read: () => TestClient): void {
|
|
134
|
+
resolve = read;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @internal Forget it IF it is still the one recorded — a second `configure()`
|
|
139
|
+
* in the same process must not have the first one's teardown clear its client.
|
|
140
|
+
*/
|
|
141
|
+
function clearResolver(read: () => TestClient): void {
|
|
142
|
+
if (resolve === read) resolve = undefined;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* The client this run is using, reachable without a test context.
|
|
147
|
+
*
|
|
148
|
+
* `ctx.client` covers a test body. A helper module, a fixture, or a
|
|
149
|
+
* `runnerHooks` setup has no context to read it from and had to be handed one
|
|
150
|
+
* through a parameter every call site then had to thread. Same instance as
|
|
151
|
+
* `ctx.client` — the AdonisJS `services/test_utils` idiom.
|
|
152
|
+
*
|
|
153
|
+
* Throws when no client is in play, which is a wiring mistake rather than a
|
|
154
|
+
* state to handle.
|
|
155
|
+
*/
|
|
156
|
+
export function testClient(): TestClient {
|
|
157
|
+
if (!resolve) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
"[E_NO_TEST_CLIENT] No client is in play. Add `apiClient()` to the plugins " +
|
|
160
|
+
"in tests/bootstrap.ts, and — with the `testUtils` shape — make sure the " +
|
|
161
|
+
"suite's setup hook has started the server. This reads the client of the " +
|
|
162
|
+
"CURRENT process.",
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return resolve();
|
|
166
|
+
}
|
|
167
|
+
|
|
61
168
|
// Typing side of the plugin — importing it augments the helix test context.
|
|
62
169
|
declare module "@c9up/helix" {
|
|
63
170
|
interface TestContext {
|
package/src/runTests.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The AdonisJS stratification, kept intact: the FRAMEWORK reads its rc file and
|
|
5
5
|
* hands the suites to the runner, exactly as `@adonisjs/core` reads
|
|
6
|
-
* `adonisrc.ts` and hands them to
|
|
6
|
+
* `adonisrc.ts` and hands them to helix. The runner itself (helix) knows nothing
|
|
7
7
|
* about ream, and ream owns no test execution — it only translates.
|
|
8
8
|
*
|
|
9
9
|
* // bin/test.ts
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* to be re-imported in each worker, so the runner needs the module's PATH, not
|
|
17
17
|
* just the object it exported. `runTests` takes it as `configModule`, and
|
|
18
18
|
* REFUSES to run when a suite declares a callback it was given no way to
|
|
19
|
-
* deliver — in
|
|
19
|
+
* deliver — in helix a declared `configure` runs, so a run that skipped it would
|
|
20
20
|
* be green in a state its own config does not describe.
|
|
21
21
|
*/
|
|
22
22
|
|
|
@@ -71,7 +71,7 @@ export class UnknownSuiteError extends Error {
|
|
|
71
71
|
/**
|
|
72
72
|
* A suite declares `configure`, but this entry point cannot deliver it.
|
|
73
73
|
*
|
|
74
|
-
* Not a warning:
|
|
74
|
+
* Not a warning: helix runs a declared `configure`, so carrying on would run the
|
|
75
75
|
* suite in a state its own config does not describe — and a warning is exactly
|
|
76
76
|
* what gets scrolled past in CI.
|
|
77
77
|
*/
|
|
@@ -91,7 +91,7 @@ export class SuiteConfigureUnreachableError extends Error {
|
|
|
91
91
|
/**
|
|
92
92
|
* The flags the worker processes are spawned with.
|
|
93
93
|
*
|
|
94
|
-
* Split out because it is a decision, not plumbing: the
|
|
94
|
+
* Split out because it is a decision, not plumbing: the helix alias loader
|
|
95
95
|
* redirects a package specifier, so it goes in only when a project asks. It
|
|
96
96
|
* rides ALONGSIDE whatever loader is already there — the test files still need
|
|
97
97
|
* theirs to read TypeScript.
|
|
@@ -162,7 +162,7 @@ export async function runTests(
|
|
|
162
162
|
(suite) => typeof suite.configure === "function",
|
|
163
163
|
);
|
|
164
164
|
if (configuring.length > 0 && options.configModule === undefined) {
|
|
165
|
-
// In
|
|
165
|
+
// In helix a declared `configure` RUNS. It cannot here — the callback needs
|
|
166
166
|
// the module's path to be re-imported in each worker, and this entry was
|
|
167
167
|
// handed the exported object. Running anyway would produce a green suite
|
|
168
168
|
// configured differently from what the rc file says, so the run stops.
|
|
@@ -186,7 +186,7 @@ export async function runTests(
|
|
|
186
186
|
};
|
|
187
187
|
|
|
188
188
|
// `runnerHooks` run ONCE around the whole run, here, and the workers skip
|
|
189
|
-
// them —
|
|
189
|
+
// them — helix's semantics, and the difference between migrating once and
|
|
190
190
|
// migrating once per test file.
|
|
191
191
|
const dropGlobalHooks = await helix.runGlobalHooks(bootstrap);
|
|
192
192
|
|
|
@@ -195,7 +195,7 @@ export async function runTests(
|
|
|
195
195
|
if (selected.length === 0) {
|
|
196
196
|
try {
|
|
197
197
|
const outcome = await helix.run(base);
|
|
198
|
-
return finish(outcome.exitCode, forceExit);
|
|
198
|
+
return finish(outcome.exitCode, forceExit, helix.armDrainGuard);
|
|
199
199
|
} finally {
|
|
200
200
|
await dropGlobalHooks();
|
|
201
201
|
}
|
|
@@ -229,28 +229,39 @@ export async function runTests(
|
|
|
229
229
|
}
|
|
230
230
|
if (steps.length === 0) {
|
|
231
231
|
await dropGlobalHooks();
|
|
232
|
-
return finish(0, forceExit);
|
|
232
|
+
return finish(0, forceExit, helix.armDrainGuard);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
try {
|
|
236
236
|
const outcome = await helix.runSuites(steps, base);
|
|
237
|
-
return finish(outcome.exitCode, forceExit);
|
|
237
|
+
return finish(outcome.exitCode, forceExit, helix.armDrainGuard);
|
|
238
238
|
} finally {
|
|
239
239
|
await dropGlobalHooks();
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
/**
|
|
244
|
-
* Apply `tests.forceExit`.
|
|
244
|
+
* Apply `tests.forceExit`. helix does this inside its own run — sets the exit
|
|
245
245
|
* code, then `process.exit()` — rather than leaving it to the caller, because
|
|
246
246
|
* the whole point is to not wait for the event loop to drain. A run that
|
|
247
247
|
* force-exits never returns here; the value is for every other run.
|
|
248
248
|
*/
|
|
249
|
-
function finish(
|
|
249
|
+
function finish(
|
|
250
|
+
code: number,
|
|
251
|
+
forceExit: boolean,
|
|
252
|
+
armDrainGuard: (code: number) => void,
|
|
253
|
+
): number {
|
|
250
254
|
if (forceExit) {
|
|
251
255
|
process.exitCode = code;
|
|
252
256
|
process.exit();
|
|
253
257
|
}
|
|
258
|
+
// Without forceExit the loop is left to drain on its own, which is the right
|
|
259
|
+
// default — a leaked handle should be visible, not swallowed. But visible has
|
|
260
|
+
// to mean SAID: `ream test` used to print its summary and then hang with no
|
|
261
|
+
// further output, and under a CI timeout that is a non-zero exit for a run in
|
|
262
|
+
// which every test passed. helix's own CLI already said so; this entry point
|
|
263
|
+
// did not, and it is the one `ream test` goes through.
|
|
264
|
+
armDrainGuard(code);
|
|
254
265
|
return code;
|
|
255
266
|
}
|
|
256
267
|
|