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