( name: string, options: ScenarioOptions, body: Effect.Effect<void, unknown, AllServices | HttpClient.HttpClient>, )
| 114 | }; |
| 115 | |
| 116 | export const scenario = ( |
| 117 | name: string, |
| 118 | options: ScenarioOptions, |
| 119 | body: Effect.Effect<void, unknown, AllServices | HttpClient.HttpClient>, |
| 120 | ): void => { |
| 121 | if (options.skip) { |
| 122 | // Blocked on a tracked, out-of-scope issue (see the scenario's `skip` |
| 123 | // reason). Registered as skipped so the suite stays green and the gap stays |
| 124 | // visible in the test report rather than silently deleted. |
| 125 | it.skip(name, () => Effect.void); |
| 126 | return; |
| 127 | } |
| 128 | const target = resolveTarget(); |
| 129 | const dir = join(RUNS_DIR, target.name, slugify(name)); |
| 130 | const context = contextFor(target, dir); |
| 131 | const testFile = captureTestFile(); |
| 132 | |
| 133 | it.live( |
| 134 | name, |
| 135 | (testCtx) => |
| 136 | Effect.gen(function* () { |
| 137 | // A run's directory is the run — never mix artifacts across attempts. |
| 138 | rmSync(dir, { recursive: true, force: true }); |
| 139 | mkdirSync(dir, { recursive: true }); |
| 140 | const startedAt = Date.now(); |
| 141 | const exit = yield* Effect.exit( |
| 142 | body.pipe(Effect.provideContext(context)) as Effect.Effect< |
| 143 | void, |
| 144 | unknown, |
| 145 | HttpClient.HttpClient |
| 146 | >, |
| 147 | ); |
| 148 | const endedAt = Date.now(); |
| 149 | |
| 150 | // Yielding a service this target can't provide is the skip signal. |
| 151 | const missing = exit._tag === "Failure" ? missingServices(exit.cause) : []; |
| 152 | if (missing.length > 0) { |
| 153 | rmSync(dir, { recursive: true, force: true }); |
| 154 | mkdirSync(dir, { recursive: true }); |
| 155 | writeFileSync( |
| 156 | join(dir, "skipped.json"), |
| 157 | JSON.stringify({ scenario: name, target: target.name, missing }, null, 1), |
| 158 | ); |
| 159 | buildManifest(RUNS_DIR); |
| 160 | return yield* Effect.sync(() => |
| 161 | testCtx.skip(`needs ${missing.join(", ")} — not on ${target.name}`), |
| 162 | ); |
| 163 | } |
| 164 | |
| 165 | const error = exit._tag === "Failure" ? failureMessage(exit.cause) : undefined; |
| 166 | // The test source is the review artifact — ship this scenario's code |
| 167 | // (imports + sibling scenarios stripped) alongside the run. |
| 168 | const source = testFile ? extractScenarioSource(testFile, name) : undefined; |
| 169 | if (source) writeFileSync(join(dir, "test.ts"), source); |
| 170 | writeFileSync( |
| 171 | join(dir, "result.json"), |
| 172 | JSON.stringify( |
| 173 | { |
no test coverage detected