* Fan-out trigger for `test create-batch --run`. * * For each test successfully created in `createResponse`, mints a fresh * idempotency key and calls `POST /tests/{testId}/runs`. Concurrency is * bounded by `opts.maxConcurrency` (defaults to `DEFAULT_BATCH_RUN_CONCURRENCY` = 50 when absent). Wi
( opts: CreateBatchOptions, createResponse: CliCreateBatchResponse, client: HttpClient, out: Output, deps: TestDeps, /** R3b: testId → projectId mapping built from create results + specs, used to enrich * run-path JSON items with dashboardUrl. Populated by the caller; absent (undefined) * means no enrichment (e.g. dry-run or caller didn't supply it). */ testIdToProjectId?: Map<string, string>, /** R3b: resolved API URL for portal link resolution. */ apiUrlForDashboard?: string, )
| 2349 | * that code only when ALL runs share the same code; otherwise 1. |
| 2350 | */ |
| 2351 | async function runBatchRun( |
| 2352 | opts: CreateBatchOptions, |
| 2353 | createResponse: CliCreateBatchResponse, |
| 2354 | client: HttpClient, |
| 2355 | out: Output, |
| 2356 | deps: TestDeps, |
| 2357 | /** R3b: testId → projectId mapping built from create results + specs, used to enrich |
| 2358 | * run-path JSON items with dashboardUrl. Populated by the caller; absent (undefined) |
| 2359 | * means no enrichment (e.g. dry-run or caller didn't supply it). */ |
| 2360 | testIdToProjectId?: Map<string, string>, |
| 2361 | /** R3b: resolved API URL for portal link resolution. */ |
| 2362 | apiUrlForDashboard?: string, |
| 2363 | ): Promise<void> { |
| 2364 | const stderrFn = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`)); |
| 2365 | const timeoutSeconds = opts.timeoutSeconds ?? DEFAULT_RUN_TIMEOUT_SECONDS; |
| 2366 | const concurrencyLimit = opts.maxConcurrency ?? DEFAULT_BATCH_RUN_CONCURRENCY; |
| 2367 | |
| 2368 | // Collect successfully-created testIds in specIndex order. |
| 2369 | const testIds = createResponse.results |
| 2370 | .filter(r => r.status === 'created' && r.testId !== undefined) |
| 2371 | .map(r => r.testId as string); |
| 2372 | |
| 2373 | if (testIds.length === 0) { |
| 2374 | // All specs failed at create time — already threw above; unreachable. |
| 2375 | return; |
| 2376 | } |
| 2377 | |
| 2378 | // Dry-run: print a descriptor envelope and return without real triggers. |
| 2379 | if (opts.dryRun) { |
| 2380 | const dryRunResults: CliBatchRunResult[] = testIds.map(testId => ({ |
| 2381 | testId, |
| 2382 | runId: `dry-run-${randomUUID()}`, |
| 2383 | status: 'queued', |
| 2384 | codeVersion: 'v1', |
| 2385 | })); |
| 2386 | const envelope = { |
| 2387 | dryRun: true, |
| 2388 | method: 'POST', |
| 2389 | pathTemplate: '/api/cli/v1/tests/{testId}/runs', |
| 2390 | maxConcurrency: opts.maxConcurrency ?? null, |
| 2391 | wait: opts.wait ?? false, |
| 2392 | timeoutSeconds, |
| 2393 | testIds, |
| 2394 | ...(opts.wait ? { thenPoll: `/api/cli/v1/runs/<run-id>?waitSeconds=25` } : {}), |
| 2395 | results: dryRunResults, |
| 2396 | }; |
| 2397 | out.print(envelope); |
| 2398 | return; |
| 2399 | } |
| 2400 | |
| 2401 | const batchRunResults: CliBatchRunResult[] = []; |
| 2402 | const sleepFn = deps.sleep ?? ((ms: number) => new Promise<void>(r => setTimeout(r, ms))); |
| 2403 | |
| 2404 | /** |
| 2405 | * Client-side sliding-window throttle: caps outgoing triggers at |
| 2406 | * BATCH_RUN_RATE_LIMIT (50) per BATCH_RUN_RATE_WINDOW_MS (60 s), sitting |
| 2407 | * just under the server's 60 triggers/min/key cap as a courtesy brake |
| 2408 | * regardless of `--max-concurrency`. |
no test coverage detected