@jterrazz/test 16.1.1 → 17.0.0

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.
Files changed (304) hide show
  1. package/README.md +45 -171
  2. package/dist/browser/index.js +2 -2980
  3. package/dist/browser/services.js +2 -0
  4. package/dist/browser/stubs.js +2 -0
  5. package/dist/checker.d.ts +6 -1
  6. package/dist/checker.js +4534 -2628
  7. package/dist/config.d.ts +40 -0
  8. package/dist/config.js +309 -0
  9. package/dist/in-place.global-setup.d.ts +11 -0
  10. package/dist/in-place.global-setup.js +21 -0
  11. package/dist/index.d.ts +1653 -3004
  12. package/dist/index.js +5226 -3845
  13. package/dist/mobile-session.global-setup.d.ts +14 -0
  14. package/dist/mobile-session.global-setup.js +31 -0
  15. package/dist/oxlint.cjs +6069 -5634
  16. package/dist/oxlint.d.cts +43 -7212
  17. package/dist/oxlint.d.ts +43 -7212
  18. package/dist/oxlint.js +6127 -5693
  19. package/dist/route-coverage.global-setup.d.ts +42 -0
  20. package/dist/route-coverage.global-setup.js +2 -0
  21. package/dist/services.d.ts +3 -0
  22. package/dist/services.global-setup.d.ts +43 -0
  23. package/dist/services.global-setup.js +2 -0
  24. package/dist/services.js +597 -0
  25. package/dist/shared-appium.adapter.js +428 -0
  26. package/dist/shared-artifacts.js +32 -0
  27. package/dist/shared-chapter.js +7 -0
  28. package/dist/shared-container.port.d.ts +31 -0
  29. package/dist/shared-derived-projects.js +58 -0
  30. package/dist/shared-device.port.d.ts +189 -0
  31. package/dist/shared-exchange.js +447 -0
  32. package/dist/shared-in-place.js +172 -0
  33. package/dist/shared-index.browser.js +3191 -0
  34. package/dist/shared-index.services.d.ts +346 -0
  35. package/dist/shared-index.stubs.d.ts +287 -0
  36. package/dist/shared-json-shape.js +17 -0
  37. package/dist/shared-landmarks.js +71 -0
  38. package/dist/shared-ledger.js +68 -0
  39. package/dist/shared-literate-plugin.js +183 -0
  40. package/dist/shared-manifest.js +1321 -0
  41. package/dist/shared-pause.js +11 -0
  42. package/dist/shared-peer.js +88 -0
  43. package/dist/shared-playwright.adapter.js +330 -0
  44. package/dist/shared-process.adapter.js +74 -0
  45. package/dist/shared-refusal.js +35 -0
  46. package/dist/shared-resolve.js +24 -0
  47. package/dist/shared-route-coverage.global-setup.js +187 -0
  48. package/dist/shared-scenario-failure.js +198 -0
  49. package/dist/shared-serve.js +365 -0
  50. package/dist/shared-service-images.js +10 -0
  51. package/dist/shared-services.global-setup.js +179 -0
  52. package/dist/shared-session-ledger.js +301 -0
  53. package/dist/shared-setup-entry.js +12 -0
  54. package/dist/shared-source-scan.js +133 -0
  55. package/dist/shared-spec-document.js +365 -0
  56. package/dist/shared-specifications.js +26 -0
  57. package/dist/shared-stream-body.js +130 -0
  58. package/dist/shared-stub-server.js +190 -0
  59. package/dist/shared-temp-root.global-setup.js +80 -0
  60. package/dist/shared-test-baseline.js +26 -0
  61. package/dist/shared-test-section.js +58 -0
  62. package/dist/shared-testcontainers.adapter.js +203 -0
  63. package/dist/shared-tokens.js +55 -0
  64. package/dist/shared-yaml-document.js +119 -0
  65. package/dist/stubs.d.ts +2 -0
  66. package/dist/stubs.js +657 -0
  67. package/dist/temp-root.global-setup.d.ts +7 -0
  68. package/dist/temp-root.global-setup.js +2 -0
  69. package/dist/vitest.d.ts +97 -163
  70. package/dist/vitest.js +373 -525
  71. package/docs/README.md +82 -36
  72. package/docs/concepts/architecture.md +118 -0
  73. package/docs/concepts/conventions.md +333 -0
  74. package/docs/guides/api-specs.md +374 -0
  75. package/docs/guides/cli-specs.md +564 -0
  76. package/docs/guides/component-specs.md +252 -0
  77. package/docs/guides/develop.md +94 -0
  78. package/docs/guides/integration-specs.md +181 -0
  79. package/docs/guides/job-specs.md +180 -0
  80. package/docs/guides/lint-specs.md +174 -0
  81. package/docs/guides/mobile-specs.md +319 -0
  82. package/docs/guides/module-tests.md +149 -0
  83. package/docs/guides/release.md +43 -0
  84. package/docs/guides/spec-documents.md +189 -0
  85. package/docs/guides/website-specs.md +413 -0
  86. package/docs/reference/assertions.md +345 -0
  87. package/docs/reference/glossary.md +20 -0
  88. package/docs/reference/linting.md +483 -0
  89. package/docs/reference/services.md +236 -0
  90. package/docs/reference/stubs.md +498 -0
  91. package/docs/reference/time-and-doubles.md +137 -0
  92. package/docs/reference/tokens.md +258 -0
  93. package/docs/reference/visitor.md +221 -0
  94. package/docs/start/getting-started.md +187 -0
  95. package/docs/start/set-up-a-project.md +214 -0
  96. package/package.json +50 -30
  97. package/schema/spec.schema.json +43 -17
  98. package/dist/appium.adapter.js +0 -489
  99. package/dist/artifacts.js +0 -32
  100. package/dist/fixtures.js +0 -110
  101. package/dist/literate.js +0 -1747
  102. package/dist/match.js +0 -804
  103. package/dist/playwright.adapter.js +0 -438
  104. package/dist/server.js +0 -160
  105. package/dist/stream.js +0 -321
  106. package/dist/substring-warning.js +0 -282
  107. package/docs/01-architecture.md +0 -95
  108. package/docs/02-developing.md +0 -351
  109. package/docs/03-testing.md +0 -495
  110. package/docs/04-operating.md +0 -101
  111. package/docs/05-module-tests.md +0 -152
  112. package/docs/06-integration.md +0 -180
  113. package/docs/07-component.md +0 -246
  114. package/docs/08-website.md +0 -404
  115. package/docs/09-mobile.md +0 -270
  116. package/docs/10-api.md +0 -264
  117. package/docs/11-jobs.md +0 -157
  118. package/docs/12-cli.md +0 -677
  119. package/docs/13-elements.md +0 -248
  120. package/docs/14-assertions.md +0 -314
  121. package/docs/15-tokens.md +0 -253
  122. package/docs/16-contracts.md +0 -438
  123. package/docs/17-services.md +0 -188
  124. package/docs/18-conventions.md +0 -332
  125. package/docs/19-linting.md +0 -512
  126. package/docs/decisions/001-v9-conventions-on-three-channels.md +0 -50
  127. package/docs/decisions/002-the-statique-channel-holds-only-what-upstream-does-not.md +0 -49
  128. package/docs/decisions/003-a-rendered-component-is-a-facet-on-vitest-browser-mode.md +0 -84
  129. package/docs/decisions/004-a-module-against-real-services-or-a-golden-is-the-integration-facet.md +0 -81
  130. package/docs/decisions/005-the-doubles-ladder-is-closed.md +0 -86
  131. package/docs/decisions/006-the-catalogue-reaches-a-file-by-its-role.md +0 -245
  132. package/docs/decisions/007-compose-mode-leaves-the-package.md +0 -53
  133. package/docs/decisions/008-vitest-5-node-24-and-the-native-seams-as-optional-peers.md +0 -75
  134. package/docs/decisions/_template.md +0 -22
  135. package/docs/reference/classes/Matcher.md +0 -120
  136. package/docs/reference/classes/ProcessHandle.md +0 -368
  137. package/docs/reference/functions/defineContract.md +0 -39
  138. package/docs/reference/functions/defineContracts.md +0 -36
  139. package/docs/reference/functions/disabled.md +0 -28
  140. package/docs/reference/functions/enabled.md +0 -20
  141. package/docs/reference/functions/focused.md +0 -26
  142. package/docs/reference/functions/mockOf.md +0 -79
  143. package/docs/reference/functions/postgres.md +0 -31
  144. package/docs/reference/functions/process.md +0 -33
  145. package/docs/reference/functions/redis.md +0 -28
  146. package/docs/reference/functions/required.md +0 -41
  147. package/docs/reference/functions/selected.md +0 -26
  148. package/docs/reference/functions/sqlite.md +0 -33
  149. package/docs/reference/functions/testId.md +0 -20
  150. package/docs/reference/functions/text.md +0 -40
  151. package/docs/reference/functions/valued.md +0 -28
  152. package/docs/reference/functions/waitUntil.md +0 -46
  153. package/docs/reference/functions/within.md +0 -29
  154. package/docs/reference/index.md +0 -196
  155. package/docs/reference/interfaces/BaseResult.md +0 -87
  156. package/docs/reference/interfaces/CallResult.md +0 -160
  157. package/docs/reference/interfaces/CaptureScope.md +0 -82
  158. package/docs/reference/interfaces/CliResult.md +0 -253
  159. package/docs/reference/interfaces/ContainerAccessor.md +0 -166
  160. package/docs/reference/interfaces/DirectoryAccessor.md +0 -33
  161. package/docs/reference/interfaces/FetchResult.md +0 -180
  162. package/docs/reference/interfaces/FilesystemAccessor.md +0 -46
  163. package/docs/reference/interfaces/HttpResult.md +0 -126
  164. package/docs/reference/interfaces/JsonAccessor.md +0 -46
  165. package/docs/reference/interfaces/PageResult.md +0 -356
  166. package/docs/reference/interfaces/RenderResult.md +0 -72
  167. package/docs/reference/interfaces/RenderedText.md +0 -110
  168. package/docs/reference/interfaces/ResponseAccessor.md +0 -47
  169. package/docs/reference/interfaces/ScreenResult.md +0 -128
  170. package/docs/reference/interfaces/TableAccessor.md +0 -26
  171. package/docs/reference/interfaces/TextAccessor.md +0 -96
  172. package/docs/reference/type-aliases/ApiHandle.md +0 -68
  173. package/docs/reference/type-aliases/ApiSpecification.md +0 -213
  174. package/docs/reference/type-aliases/ApiSpecificationOptions.md +0 -68
  175. package/docs/reference/type-aliases/BrowserConsoleMessage.md +0 -30
  176. package/docs/reference/type-aliases/BrowserLinkElement.md +0 -50
  177. package/docs/reference/type-aliases/BrowserMetaElement.md +0 -40
  178. package/docs/reference/type-aliases/BrowserOpenOptions.md +0 -87
  179. package/docs/reference/type-aliases/BrowserPage.md +0 -132
  180. package/docs/reference/type-aliases/BrowserPort.md +0 -51
  181. package/docs/reference/type-aliases/CliEnv.md +0 -11
  182. package/docs/reference/type-aliases/CliHandle.md +0 -80
  183. package/docs/reference/type-aliases/CliInput.md +0 -36
  184. package/docs/reference/type-aliases/CliOutput.md +0 -46
  185. package/docs/reference/type-aliases/CliPort.md +0 -58
  186. package/docs/reference/type-aliases/CliSpecification.md +0 -144
  187. package/docs/reference/type-aliases/CliSpecificationOptions.md +0 -134
  188. package/docs/reference/type-aliases/ComponentChain.md +0 -156
  189. package/docs/reference/type-aliases/ComponentScenario.md +0 -20
  190. package/docs/reference/type-aliases/ComponentUi.md +0 -47
  191. package/docs/reference/type-aliases/ComponentVisitor.md +0 -10
  192. package/docs/reference/type-aliases/ContainerPort.md +0 -113
  193. package/docs/reference/type-aliases/Contract.md +0 -68
  194. package/docs/reference/type-aliases/ContractInput.md +0 -13
  195. package/docs/reference/type-aliases/ContractRequest.md +0 -95
  196. package/docs/reference/type-aliases/ContractResponder.md +0 -23
  197. package/docs/reference/type-aliases/ContractResponse.md +0 -78
  198. package/docs/reference/type-aliases/ContractResponseValue.md +0 -13
  199. package/docs/reference/type-aliases/Contracts.md +0 -48
  200. package/docs/reference/type-aliases/DatabaseKeys.md +0 -16
  201. package/docs/reference/type-aliases/DatabasePort.md +0 -72
  202. package/docs/reference/type-aliases/DeviceOpenOptions.md +0 -46
  203. package/docs/reference/type-aliases/DevicePort.md +0 -51
  204. package/docs/reference/type-aliases/DeviceScreen.md +0 -32
  205. package/docs/reference/type-aliases/DeviceTimeouts.md +0 -39
  206. package/docs/reference/type-aliases/DockerSpecConfig.md +0 -43
  207. package/docs/reference/type-aliases/DomMount.md +0 -21
  208. package/docs/reference/type-aliases/ElementKind.md +0 -27
  209. package/docs/reference/type-aliases/ElementMatch.md +0 -74
  210. package/docs/reference/type-aliases/ElementOptions.md +0 -29
  211. package/docs/reference/type-aliases/ElementRef.md +0 -128
  212. package/docs/reference/type-aliases/ExecOptions.md +0 -37
  213. package/docs/reference/type-aliases/FileAccessor.md +0 -54
  214. package/docs/reference/type-aliases/HonoApp.md +0 -31
  215. package/docs/reference/type-aliases/HttpContractFilter.md +0 -49
  216. package/docs/reference/type-aliases/HttpResponseInit.md +0 -46
  217. package/docs/reference/type-aliases/HttpStreamInit.md +0 -58
  218. package/docs/reference/type-aliases/IntegrationHandle.md +0 -46
  219. package/docs/reference/type-aliases/IntegrationSpecification.md +0 -111
  220. package/docs/reference/type-aliases/IntegrationSpecificationOptions.md +0 -42
  221. package/docs/reference/type-aliases/Intercept.md +0 -19
  222. package/docs/reference/type-aliases/InterceptOptions.md +0 -28
  223. package/docs/reference/type-aliases/InterceptScope.md +0 -11
  224. package/docs/reference/type-aliases/IsolationStrategy.md +0 -75
  225. package/docs/reference/type-aliases/JobHandle.md +0 -34
  226. package/docs/reference/type-aliases/JobsHandle.md +0 -57
  227. package/docs/reference/type-aliases/JobsResult.md +0 -16
  228. package/docs/reference/type-aliases/JobsSpecification.md +0 -94
  229. package/docs/reference/type-aliases/JobsSpecificationOptions.md +0 -56
  230. package/docs/reference/type-aliases/LandmarkKind.md +0 -20
  231. package/docs/reference/type-aliases/LiterateRunFlags.md +0 -27
  232. package/docs/reference/type-aliases/LiterateServeRegistration.md +0 -70
  233. package/docs/reference/type-aliases/MatchFixtureOptions.md +0 -27
  234. package/docs/reference/type-aliases/MatchableRequest.md +0 -61
  235. package/docs/reference/type-aliases/MatcherKind.md +0 -34
  236. package/docs/reference/type-aliases/MobileBackendOptions.md +0 -28
  237. package/docs/reference/type-aliases/MobileElementKind.md +0 -14
  238. package/docs/reference/type-aliases/MobileElementMatch.md +0 -60
  239. package/docs/reference/type-aliases/MobileElementRef.md +0 -20
  240. package/docs/reference/type-aliases/MobileHandle.md +0 -65
  241. package/docs/reference/type-aliases/MobileScenario.md +0 -20
  242. package/docs/reference/type-aliases/MobileSpecification.md +0 -51
  243. package/docs/reference/type-aliases/MobileSpecificationOptions.md +0 -126
  244. package/docs/reference/type-aliases/MobileVisitor.md +0 -81
  245. package/docs/reference/type-aliases/MockOfOptions.md +0 -26
  246. package/docs/reference/type-aliases/MockPort.md +0 -10
  247. package/docs/reference/type-aliases/PinnedClock.md +0 -10
  248. package/docs/reference/type-aliases/PostgresOptions.md +0 -32
  249. package/docs/reference/type-aliases/ProcessOptions.md +0 -112
  250. package/docs/reference/type-aliases/RedisOptions.md +0 -20
  251. package/docs/reference/type-aliases/RenderSubject.md +0 -12
  252. package/docs/reference/type-aliases/ScreenNode.md +0 -67
  253. package/docs/reference/type-aliases/ServerPort.md +0 -36
  254. package/docs/reference/type-aliases/ServerResponse.md +0 -46
  255. package/docs/reference/type-aliases/ServerSpec.md +0 -21
  256. package/docs/reference/type-aliases/ServiceHandle.md +0 -216
  257. package/docs/reference/type-aliases/ServiceRecord.md +0 -12
  258. package/docs/reference/type-aliases/SpecDocument.md +0 -84
  259. package/docs/reference/type-aliases/SpecEnvToken.md +0 -21
  260. package/docs/reference/type-aliases/SpecFileAssertion.md +0 -27
  261. package/docs/reference/type-aliases/SpecFixture.md +0 -30
  262. package/docs/reference/type-aliases/SpecKind.md +0 -8
  263. package/docs/reference/type-aliases/SpecRun.md +0 -108
  264. package/docs/reference/type-aliases/SpecServeEntry.md +0 -40
  265. package/docs/reference/type-aliases/SpecStream.md +0 -32
  266. package/docs/reference/type-aliases/SpecificationConfig.md +0 -290
  267. package/docs/reference/type-aliases/SqliteOptions.md +0 -36
  268. package/docs/reference/type-aliases/SseEvent.md +0 -58
  269. package/docs/reference/type-aliases/StreamBody.md +0 -61
  270. package/docs/reference/type-aliases/TextFilter.md +0 -22
  271. package/docs/reference/type-aliases/VisitScenario.md +0 -20
  272. package/docs/reference/type-aliases/Visitor.md +0 -215
  273. package/docs/reference/type-aliases/WaitUntilOptions.md +0 -46
  274. package/docs/reference/type-aliases/WebsiteBackendOptions.md +0 -37
  275. package/docs/reference/type-aliases/WebsiteHandle.md +0 -51
  276. package/docs/reference/type-aliases/WebsiteSpecification.md +0 -118
  277. package/docs/reference/type-aliases/WebsiteSpecificationOptions.md +0 -40
  278. package/docs/reference/variables/anthropic.md +0 -19
  279. package/docs/reference/variables/banner.md +0 -10
  280. package/docs/reference/variables/button.md +0 -10
  281. package/docs/reference/variables/clock.md +0 -16
  282. package/docs/reference/variables/complementary.md +0 -10
  283. package/docs/reference/variables/component.md +0 -10
  284. package/docs/reference/variables/content.md +0 -10
  285. package/docs/reference/variables/contentinfo.md +0 -10
  286. package/docs/reference/variables/dialog.md +0 -10
  287. package/docs/reference/variables/field.md +0 -10
  288. package/docs/reference/variables/form.md +0 -10
  289. package/docs/reference/variables/heading.md +0 -10
  290. package/docs/reference/variables/http.md +0 -40
  291. package/docs/reference/variables/intercept.md +0 -11
  292. package/docs/reference/variables/link.md +0 -10
  293. package/docs/reference/variables/listitem.md +0 -10
  294. package/docs/reference/variables/main.md +0 -10
  295. package/docs/reference/variables/match.md +0 -8
  296. package/docs/reference/variables/navigation.md +0 -10
  297. package/docs/reference/variables/openai.md +0 -21
  298. package/docs/reference/variables/option.md +0 -17
  299. package/docs/reference/variables/region.md +0 -10
  300. package/docs/reference/variables/row.md +0 -10
  301. package/docs/reference/variables/search.md +0 -10
  302. package/docs/reference/variables/specification.md +0 -8
  303. package/docs/reference/variables/status.md +0 -10
  304. package/docs/reference/variables/table.md +0 -10
@@ -0,0 +1,190 @@
1
+ import { n as isStreamBody } from "./shared-exchange.js";
2
+ import { t as pause } from "./shared-pause.js";
3
+ import { n as StubQueue, t as toReadableStream } from "./shared-stream-body.js";
4
+ //#region src/stubs/delivery/msw-handlers.ts
5
+ const NEVER_CACHED = { "cache-control": "no-store" };
6
+ function toMswResponse(msw, response) {
7
+ const { body, headers, status = 200 } = response;
8
+ if (response.transport === "network-error") return msw.HttpResponse.error();
9
+ if (body === null || body === void 0) return new msw.HttpResponse(null, {
10
+ headers: {
11
+ ...NEVER_CACHED,
12
+ ...headers
13
+ },
14
+ status
15
+ });
16
+ if (isStreamBody(body)) return new msw.HttpResponse(toReadableStream(body), {
17
+ headers: {
18
+ ...NEVER_CACHED,
19
+ "content-type": body.contentType,
20
+ ...headers
21
+ },
22
+ status
23
+ });
24
+ if (typeof body === "string") return new msw.HttpResponse(body, {
25
+ headers: {
26
+ ...NEVER_CACHED,
27
+ "content-type": "text/plain; charset=utf-8",
28
+ ...headers
29
+ },
30
+ status
31
+ });
32
+ return msw.HttpResponse.json(body, {
33
+ headers: {
34
+ ...NEVER_CACHED,
35
+ ...headers
36
+ },
37
+ status
38
+ });
39
+ }
40
+ function handlerOf(msw, method) {
41
+ return {
42
+ "*": msw.http.all,
43
+ DELETE: msw.http.delete,
44
+ GET: msw.http.get,
45
+ HEAD: msw.http.head,
46
+ OPTIONS: msw.http.options,
47
+ PATCH: msw.http.patch,
48
+ POST: msw.http.post,
49
+ PUT: msw.http.put
50
+ }[method.toUpperCase()];
51
+ }
52
+ function buildStubHandlers(msw, stubs, bypass) {
53
+ const queue = new StubQueue(stubs);
54
+ let violation = null;
55
+ const recordViolation = (method, url) => {
56
+ violation ??= queue.unmatchedError(method, url);
57
+ return msw.HttpResponse.json({ error: `@jterrazz/test strict stubs: unmatched request ${method} ${url}` }, {
58
+ headers: NEVER_CACHED,
59
+ status: 501
60
+ });
61
+ };
62
+ const handlers = [];
63
+ for (const route of queue.routes) for (const method of route.methods) {
64
+ const handlerFn = handlerOf(msw, method);
65
+ if (handlerFn === void 0) continue;
66
+ handlers.push(handlerFn(route.url, async ({ request }) => {
67
+ const observed = await observe(request);
68
+ const stub = queue.take(observed);
69
+ if (!stub) return recordViolation(request.method, request.url);
70
+ const response = typeof stub.response === "function" ? stub.response(observed) : stub.response;
71
+ await pause(response.delay ?? 0);
72
+ return toMswResponse(msw, response);
73
+ }));
74
+ }
75
+ handlers.push(msw.http.all("*", ({ request }) => bypass?.(request.url) === true ? msw.passthrough() : recordViolation(request.method, request.url)));
76
+ return {
77
+ handlers,
78
+ violation: () => queue.verdict(violation)
79
+ };
80
+ }
81
+ async function observe(request) {
82
+ let body = null;
83
+ const rawText = await request.clone().text();
84
+ if (rawText) try {
85
+ body = JSON.parse(rawText);
86
+ } catch {
87
+ body = rawText;
88
+ }
89
+ return {
90
+ body,
91
+ headers: Object.fromEntries(request.headers),
92
+ method: request.method.toUpperCase(),
93
+ url: request.url
94
+ };
95
+ }
96
+ //#endregion
97
+ //#region src/stubs/delivery/same-process/fetch-refusals.ts
98
+ const PROBE = "http://request-init.invalid/";
99
+ const probeOf = (init) => init.body instanceof ReadableStream ? {
100
+ ...init,
101
+ body: new Blob()
102
+ } : init;
103
+ /**
104
+ * Wraps `underlying` so a RequestInit that fetch itself refuses rejects with fetch's own TypeError, before any stub can serve it.
105
+ * @see https://fetch.spec.whatwg.org/#dom-request
106
+ */
107
+ function refusingWhatFetchRefuses(underlying) {
108
+ return async (input, init) => {
109
+ if (init === void 0) return await underlying(input);
110
+ const { method } = new Request(PROBE, probeOf(init));
111
+ return await underlying(input, {
112
+ ...init,
113
+ method
114
+ });
115
+ };
116
+ }
117
+ //#endregion
118
+ //#region src/stubs/delivery/same-process/owned-body.ts
119
+ async function release(reader, reason) {
120
+ try {
121
+ await reader.cancel(reason);
122
+ } catch {}
123
+ }
124
+ function withOwnedBody(response) {
125
+ const { body } = response;
126
+ if (body === null) return response;
127
+ const reader = body.getReader();
128
+ const owned = new ReadableStream({
129
+ cancel: (reason) => {
130
+ release(reader, reason);
131
+ },
132
+ pull: async (controller) => {
133
+ const { done, value } = await reader.read();
134
+ if (done) controller.close();
135
+ else controller.enqueue(value);
136
+ }
137
+ });
138
+ const reply = new Response(owned, {
139
+ headers: response.headers,
140
+ status: response.status,
141
+ statusText: response.statusText
142
+ });
143
+ Object.defineProperties(reply, {
144
+ redirected: { value: response.redirected },
145
+ type: { value: response.type },
146
+ url: { value: response.url }
147
+ });
148
+ return reply;
149
+ }
150
+ function owningBodies(underlying) {
151
+ return async (input, init) => withOwnedBody(await underlying(input, init));
152
+ }
153
+ //#endregion
154
+ //#region src/stubs/delivery/same-process/stub-server.ts
155
+ async function loadMsw() {
156
+ const [{ setupServer }, msw] = await Promise.all([import("msw/node"), import("msw")]);
157
+ return {
158
+ msw,
159
+ setupServer: () => setupServer()
160
+ };
161
+ }
162
+ let serverInstance = null;
163
+ let installedFetch = null;
164
+ async function ensureStubServer() {
165
+ if (serverInstance !== null) return serverInstance;
166
+ const { setupServer } = await loadMsw();
167
+ const server = setupServer();
168
+ server.listen({ onUnhandledRequest: "bypass" });
169
+ serverInstance = server;
170
+ installedFetch = globalThis.fetch;
171
+ globalThis.fetch = owningBodies(refusingWhatFetchRefuses(installedFetch));
172
+ return server;
173
+ }
174
+ /** The trailing catch-all records any request no stub accepted; the chain's pipeline rethrows it. */
175
+ async function registerStubs(stubs) {
176
+ const server = await ensureStubServer();
177
+ const { msw } = await loadMsw();
178
+ const { handlers, violation } = buildStubHandlers(msw, stubs);
179
+ server.use(...handlers);
180
+ return {
181
+ cleanup: () => {
182
+ server.resetHandlers();
183
+ },
184
+ violation
185
+ };
186
+ }
187
+ //#endregion
188
+ export { registerStubs };
189
+
190
+ //# sourceMappingURL=shared-stub-server.js.map
@@ -0,0 +1,80 @@
1
+ import { a as EVIDENCE_DIR } from "./shared-artifacts.js";
2
+ import { t as setupEntry } from "./shared-setup-entry.js";
3
+ import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ //#region src/chain/paths/run-folders.ts
7
+ function isAlive(pid) {
8
+ try {
9
+ process.kill(pid, 0);
10
+ return true;
11
+ } catch (error) {
12
+ return error instanceof Error && "code" in error && error.code === "EPERM";
13
+ }
14
+ }
15
+ function runOf(entry) {
16
+ const pid = Number(entry.split("-")[0]);
17
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
18
+ }
19
+ /** Leaves alone every name no run gave: the parent of temp roots is shared with whatever else a user keeps there. */
20
+ function sweepOrphanRoots(parent) {
21
+ for (const entry of readdirSync(parent)) {
22
+ const pid = runOf(entry);
23
+ if (pid !== null && !isAlive(pid)) rmSync(join(parent, entry), {
24
+ force: true,
25
+ recursive: true
26
+ });
27
+ }
28
+ }
29
+ /** Keeps a living run's evidence alone: a finished run's stays until the next run starts, and nothing else is ever kept. */
30
+ function sweepFinishedEvidence(folder) {
31
+ if (!existsSync(folder)) return;
32
+ for (const entry of readdirSync(folder)) {
33
+ const pid = runOf(entry);
34
+ if (pid === null || !isAlive(pid)) rmSync(join(folder, entry), {
35
+ force: true,
36
+ recursive: true
37
+ });
38
+ }
39
+ }
40
+ //#endregion
41
+ //#region src/chain/paths/temp-root.global-setup.ts
42
+ /** Named by uid: a temp directory users share never holds a folder one of them cannot sweep. */
43
+ const TEMP_ROOTS_PARENT = `jterrazz-test-runs-${process.getuid?.() ?? "user"}`;
44
+ const TEMP_VARIABLES = [
45
+ "TMPDIR",
46
+ "TMP",
47
+ "TEMP"
48
+ ];
49
+ function setVariables(values) {
50
+ for (const [name, value] of Object.entries(values)) if (value === void 0) Reflect.deleteProperty(process.env, name);
51
+ else process.env[name] = value;
52
+ }
53
+ function tempRootSetup() {
54
+ return setupEntry(import.meta.dirname, "temp-root");
55
+ }
56
+ function joinedRoot() {
57
+ const current = tmpdir();
58
+ return basename(dirname(current)) === TEMP_ROOTS_PARENT && basename(current).startsWith(`${process.pid}-`);
59
+ }
60
+ function setup() {
61
+ if (joinedRoot()) return () => {};
62
+ const parent = join(tmpdir(), TEMP_ROOTS_PARENT);
63
+ mkdirSync(parent, { recursive: true });
64
+ sweepOrphanRoots(parent);
65
+ sweepFinishedEvidence(resolve(EVIDENCE_DIR));
66
+ const root = mkdtempSync(join(parent, `${process.pid}-`));
67
+ const previous = Object.fromEntries(TEMP_VARIABLES.map((name) => [name, process.env[name]]));
68
+ setVariables(Object.fromEntries(TEMP_VARIABLES.map((name) => [name, root])));
69
+ return () => {
70
+ setVariables(previous);
71
+ rmSync(root, {
72
+ force: true,
73
+ recursive: true
74
+ });
75
+ };
76
+ }
77
+ //#endregion
78
+ export { setup as n, tempRootSetup as r, TEMP_ROOTS_PARENT as t };
79
+
80
+ //# sourceMappingURL=shared-temp-root.global-setup.js.map
@@ -0,0 +1,26 @@
1
+ import { t as TEST_SECTION } from "./shared-test-section.js";
2
+ import { labelOf, sectionOf, writeSection } from "@jterrazz/typescript/rules";
3
+ //#region lint/kit/baseline/test-baseline.ts
4
+ const ROUTES_KEY = "routes/unreached";
5
+ const ROUTES_LEDGER = `${labelOf(TEST_SECTION)} › ${ROUTES_KEY}`;
6
+ const heldOf = (root) => sectionOf(root, "test") ?? {};
7
+ const withoutRoutes = (counts) => Object.fromEntries(Object.entries(counts).filter(([key]) => key !== ROUTES_KEY));
8
+ function writeBaseline(root, counts) {
9
+ const held = heldOf(root)[ROUTES_KEY];
10
+ writeSection(root, TEST_SECTION, held === void 0 ? { ...counts } : {
11
+ ...counts,
12
+ [ROUTES_KEY]: held
13
+ });
14
+ }
15
+ const heldUnreached = (root) => heldOf(root)[ROUTES_KEY] ?? 0;
16
+ function recordUnreached(root, count) {
17
+ const others = withoutRoutes(heldOf(root));
18
+ writeSection(root, TEST_SECTION, count === 0 ? others : {
19
+ ...others,
20
+ [ROUTES_KEY]: count
21
+ });
22
+ }
23
+ //#endregion
24
+ export { writeBaseline as i, heldUnreached as n, recordUnreached as r, ROUTES_LEDGER as t };
25
+
26
+ //# sourceMappingURL=shared-test-baseline.js.map
@@ -0,0 +1,58 @@
1
+ import { t as isRecord } from "./shared-json-shape.js";
2
+ import { readSettings, readSettingsSync, refusalBlock } from "@jterrazz/typescript/rules";
3
+ //#region lint/kit/project/test-section.ts
4
+ const TEST_SECTION = "test";
5
+ const UNREAD = Object.freeze({
6
+ collections: [],
7
+ methodless: [],
8
+ production: { allow: {} }
9
+ });
10
+ function wordsOf(value) {
11
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string") ? value : void 0;
12
+ }
13
+ function reasonsOf(value) {
14
+ if (!isRecord(value)) return;
15
+ const reasons = {};
16
+ for (const [name, reason] of Object.entries(value)) {
17
+ if (typeof reason !== "string" || reason.trim() === "") return;
18
+ reasons[name] = reason;
19
+ }
20
+ return reasons;
21
+ }
22
+ const BASE_PATH = /^(?:\/[a-z0-9][\w.~-]*)+$/u;
23
+ const isMountPath = (value) => BASE_PATH.test(value);
24
+ function basePathOf(value) {
25
+ if (typeof value !== "string") return;
26
+ if (!isMountPath(value)) throw new Error(refusalBlock({
27
+ fix: "write the path the routes are mounted under from the root, with no slash at its end: test: { basePath: '/api' }",
28
+ found: `jterrazz.config.ts: test.basePath ${JSON.stringify(value)} is no mount path`,
29
+ read: "docs/reference/linting.md#test-settings-section",
30
+ why: "The base path is cut from every route before its subject folder is derived, so it is read as a route prefix is written: one leading slash, lowercase segments, no trailing slash."
31
+ }));
32
+ return value;
33
+ }
34
+ /** A key of the wrong shape is read as its default, never half-used: the type check refuses it first; a base path the type lets through and no route can carry is refused here. */
35
+ function testSettingsOf(test) {
36
+ const section = isRecord(test) ? test : {};
37
+ const allow = isRecord(section.production) ? reasonsOf(section.production.allow) : void 0;
38
+ const basePath = basePathOf(section.basePath);
39
+ return {
40
+ collections: wordsOf(section.collections) ?? UNREAD.collections,
41
+ methodless: Object.keys(reasonsOf(section.methodless) ?? {}),
42
+ production: allow === void 0 ? UNREAD.production : { allow },
43
+ ...typeof section.routes === "string" ? { routes: section.routes } : {},
44
+ ...basePath === void 0 ? {} : { basePath }
45
+ };
46
+ }
47
+ /** Every pass is sync, so the file is loaded synchronously, once per process; the runtime reads the same resolution, so the run and the checker agree. A file that does not resolve is no settings: the toolchain already reports it. */
48
+ const testSettingsAtSync = (root) => testSettingsOf(readSettingsSync(root).resolved.sections[TEST_SECTION]);
49
+ /** Whether the unit's settings list this package's preset, the one whose checker judges every specs tree under it. */
50
+ const listsTestPreset = (root) => readSettingsSync(root).resolved.presets.some((preset) => preset.package === "@jterrazz/test");
51
+ async function testSettingsAt(root) {
52
+ const { resolved } = await readSettings(root);
53
+ return testSettingsOf(resolved.sections[TEST_SECTION]);
54
+ }
55
+ //#endregion
56
+ export { testSettingsAtSync as i, listsTestPreset as n, testSettingsAt as r, TEST_SECTION as t };
57
+
58
+ //# sourceMappingURL=shared-test-section.js.map
@@ -0,0 +1,203 @@
1
+ import { t as isRecord } from "./shared-json-shape.js";
2
+ import { t as invariant } from "./shared-refusal.js";
3
+ import { t as loadPeer } from "./shared-peer.js";
4
+ import { once } from "node:events";
5
+ import { createServer } from "node:http";
6
+ import { text } from "node:stream/consumers";
7
+ import { hostname } from "node:os";
8
+ //#region src/services/containers/run-containers.ts
9
+ function canonical(value) {
10
+ return isRecord(value) ? Object.fromEntries(Object.entries(value).toSorted(([a], [b]) => a.localeCompare(b)).map(([key, entry]) => [key, canonical(entry)])) : value;
11
+ }
12
+ function keyOf(request) {
13
+ return JSON.stringify(canonical(request));
14
+ }
15
+ var RunContainers = class {
16
+ started = /* @__PURE__ */ new Map();
17
+ create;
18
+ ready;
19
+ /** `ready`'s rejection is the one answer every request gets, and no container is created. */
20
+ constructor(create, ready = async () => {}) {
21
+ this.create = create;
22
+ this.ready = ready;
23
+ }
24
+ async acquire(request) {
25
+ const key = keyOf(request);
26
+ let pending = this.started.get(key);
27
+ if (pending === void 0) {
28
+ pending = this.start(key, request);
29
+ this.started.set(key, pending);
30
+ }
31
+ const container = await pending;
32
+ return {
33
+ host: container.getHost(),
34
+ key,
35
+ port: container.getMappedPort(request.port)
36
+ };
37
+ }
38
+ async logs(key, since) {
39
+ const pending = this.started.get(key);
40
+ if (pending === void 0) return "";
41
+ return await (await pending).getLogs(since);
42
+ }
43
+ async stop() {
44
+ const pending = [...this.started.values()];
45
+ this.started.clear();
46
+ await Promise.allSettled(pending.map(async (starting) => {
47
+ await (await starting).stop();
48
+ }));
49
+ }
50
+ async start(key, request) {
51
+ try {
52
+ await this.ready();
53
+ const container = this.create(request);
54
+ await container.start();
55
+ return container;
56
+ } catch (error) {
57
+ this.started.delete(key);
58
+ throw error;
59
+ }
60
+ }
61
+ };
62
+ function field(value, name) {
63
+ if (typeof value !== "object" || value === null) return;
64
+ return Reflect.get(value, name);
65
+ }
66
+ function isContainerRequest(value) {
67
+ return typeof field(value, "image") === "string" && typeof field(value, "port") === "number";
68
+ }
69
+ async function respond(containers, request, response) {
70
+ const url = new URL(request.url ?? "/", "http://run");
71
+ try {
72
+ if (request.method === "POST" && url.pathname === "/containers") {
73
+ const asked = JSON.parse(await text(request));
74
+ if (!isContainerRequest(asked)) throw invariant(`a worker asked for ${JSON.stringify(asked)}, no container request`);
75
+ const address = await containers.acquire(asked);
76
+ response.writeHead(200, { "content-type": "application/json" });
77
+ response.end(JSON.stringify(address));
78
+ return;
79
+ }
80
+ if (request.method === "GET" && url.pathname === "/logs") {
81
+ response.writeHead(200, { "content-type": "text/plain" });
82
+ const key = url.searchParams.get("key") ?? "";
83
+ const since = url.searchParams.get("since");
84
+ const moment = since === null ? void 0 : new Date(Number(since));
85
+ response.end(await containers.logs(key, moment));
86
+ return;
87
+ }
88
+ response.writeHead(404).end();
89
+ } catch (error) {
90
+ response.writeHead(500, { "content-type": "application/json" });
91
+ response.end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
92
+ }
93
+ }
94
+ async function serveContainers(containers) {
95
+ const server = createServer((request, response) => {
96
+ respond(containers, request, response);
97
+ });
98
+ server.listen(0, "127.0.0.1");
99
+ await once(server, "listening");
100
+ const address = server.address();
101
+ if (address === null || typeof address === "string") throw invariant("the run containers are served on no port");
102
+ const { port } = address;
103
+ return {
104
+ close: async () => {
105
+ server.close();
106
+ await once(server, "close");
107
+ },
108
+ url: `http://127.0.0.1:${port}`
109
+ };
110
+ }
111
+ //#endregion
112
+ //#region src/services/containers/testcontainers.adapter.ts
113
+ const CONTAINER_LABEL = "com.jterrazz.test";
114
+ const REUSED_LABEL = "com.jterrazz.test.reuse";
115
+ /** `<host>:<pid>`: a sweep removes the container once that process is dead, whatever reaper session holds it. */
116
+ const OWNER_LABEL = "com.jterrazz.test.owner";
117
+ function processOwner() {
118
+ return `${hostname()}:${process.pid}`;
119
+ }
120
+ /** Never on CI, where a run starts from nothing and leaves nothing. */
121
+ function reuseEnabled(env) {
122
+ const onCi = env.CI !== void 0 && env.CI !== "" && env.CI !== "false";
123
+ return env.TESTCONTAINERS_REUSE_ENABLE === "true" && !onCi;
124
+ }
125
+ /** A reused container names no owner: its definition hash must not change with the process asking. */
126
+ function containerLabels(reuse, owner) {
127
+ return reuse ? {
128
+ [CONTAINER_LABEL]: "true",
129
+ [REUSED_LABEL]: "true"
130
+ } : {
131
+ [CONTAINER_LABEL]: "true",
132
+ [OWNER_LABEL]: owner
133
+ };
134
+ }
135
+ async function loadTestcontainers() {
136
+ return await loadPeer("testcontainers", "a declared service", async () => await import("testcontainers"));
137
+ }
138
+ var TestcontainersAdapter = class {
139
+ image;
140
+ containerPort;
141
+ env;
142
+ command;
143
+ tmpfs;
144
+ reuse;
145
+ container = null;
146
+ peer;
147
+ constructor(options, peer = loadTestcontainers) {
148
+ this.peer = peer;
149
+ this.image = options.image;
150
+ this.containerPort = options.port;
151
+ this.env = options.env ?? {};
152
+ this.command = options.command;
153
+ this.tmpfs = options.tmpfs;
154
+ this.reuse = options.reuse ?? reuseEnabled(process.env);
155
+ }
156
+ async start() {
157
+ const { GenericContainer, Wait } = await this.peer();
158
+ let builder = new GenericContainer(this.image).withExposedPorts(this.containerPort).withLabels(containerLabels(this.reuse, processOwner()));
159
+ for (const [key, value] of Object.entries(this.env)) builder = builder.withEnvironment({ [key]: value });
160
+ if (this.command !== void 0) builder = builder.withCommand(this.command);
161
+ if (this.tmpfs !== void 0) builder = builder.withTmpFs(this.tmpfs);
162
+ if (this.image.startsWith("postgres")) builder = builder.withWaitStrategy(Wait.forLogMessage(/database system is ready to accept connections/u, 2));
163
+ if (this.reuse) builder = builder.withReuse();
164
+ this.container = await builder.start();
165
+ }
166
+ async stop() {
167
+ if (this.container && !this.reuse) {
168
+ await this.container.stop();
169
+ this.container = null;
170
+ }
171
+ }
172
+ getMappedPort(containerPort) {
173
+ if (!this.container) throw invariant("a container was read before it started");
174
+ return this.container.getMappedPort(containerPort);
175
+ }
176
+ getHost() {
177
+ if (!this.container) throw invariant("a container was read before it started");
178
+ return this.container.getHost();
179
+ }
180
+ getConnectionString() {
181
+ return `${this.getHost()}:${this.getMappedPort(this.containerPort)}`;
182
+ }
183
+ async getLogs(since) {
184
+ if (!this.container) return "";
185
+ const stream = await this.container.logs(since === void 0 ? void 0 : { since: since.getTime() / 1e3 });
186
+ return await new Promise((resolve) => {
187
+ let output = "";
188
+ stream.on("data", (chunk) => {
189
+ output += Buffer.isBuffer(chunk) ? chunk.toString() : String(chunk);
190
+ });
191
+ stream.on("end", () => {
192
+ resolve(output);
193
+ });
194
+ setTimeout(() => {
195
+ resolve(output);
196
+ }, 1e3);
197
+ });
198
+ }
199
+ };
200
+ //#endregion
201
+ export { field as a, RunContainers as i, REUSED_LABEL as n, serveContainers as o, TestcontainersAdapter as r, OWNER_LABEL as t };
202
+
203
+ //# sourceMappingURL=shared-testcontainers.adapter.js.map
@@ -0,0 +1,55 @@
1
+ //#region lint/kit/project/tokens.ts
2
+ const TOKEN_KINDS = [
3
+ "any",
4
+ "base64",
5
+ "date",
6
+ "duration",
7
+ "email",
8
+ "float",
9
+ "hex",
10
+ "int",
11
+ "ip",
12
+ "iso8601",
13
+ "number",
14
+ "path",
15
+ "port",
16
+ "semver",
17
+ "sha",
18
+ "string",
19
+ "time",
20
+ "ulid",
21
+ "url",
22
+ "uuid",
23
+ "workdir"
24
+ ];
25
+ const VALID_TOKEN = /^[A-Za-z][A-Za-z0-9]*(?:#[\w.-]+)?$/u;
26
+ const BRACE_BLOCK = /\{\{[^{}]*\}\}/gu;
27
+ const LEADING_WORD = /^[A-Za-z][A-Za-z0-9]*/u;
28
+ const KNOWN = new Set(TOKEN_KINDS);
29
+ function verdictOf(inner) {
30
+ if (VALID_TOKEN.test(inner)) {
31
+ const [kind = ""] = inner.split("#");
32
+ return KNOWN.has(kind) ? "known" : "unknown";
33
+ }
34
+ const [word = ""] = LEADING_WORD.exec(inner) ?? [];
35
+ return KNOWN.has(word) ? "malformed" : void 0;
36
+ }
37
+ function tokensOf(text, verdict) {
38
+ return text.split("\n").flatMap((lineText, index) => [...lineText.matchAll(BRACE_BLOCK)].flatMap(([token]) => verdictOf(token.slice(2, -2)) === verdict ? [{
39
+ line: index + 1,
40
+ token
41
+ }] : []));
42
+ }
43
+ function findUnknownTokens(text) {
44
+ return tokensOf(text, "unknown");
45
+ }
46
+ function findMalformedRefs(text) {
47
+ return tokensOf(text, "malformed");
48
+ }
49
+ function findKnownTokens(text) {
50
+ return tokensOf(text, "known");
51
+ }
52
+ //#endregion
53
+ export { findUnknownTokens as i, findKnownTokens as n, findMalformedRefs as r, TOKEN_KINDS as t };
54
+
55
+ //# sourceMappingURL=shared-tokens.js.map