Skip to content

Commit c70e2ad

Browse files
fix: tighten project env fallback
1 parent ac98599 commit c70e2ad

3 files changed

Lines changed: 63 additions & 10 deletions

File tree

src/commands/test.run.spec.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2431,6 +2431,9 @@ describe('runTestRunAll — batch fresh run', () => {
24312431
await expect(test.parseAsync(['run', '--all'], { from: 'user' })).rejects.toMatchObject({
24322432
code: 'VALIDATION_ERROR',
24332433
exitCode: 5,
2434+
details: expect.objectContaining({
2435+
reason: expect.stringContaining('TESTSPRITE_PROJECT_ID'),
2436+
}),
24342437
});
24352438
});
24362439

@@ -2456,6 +2459,30 @@ describe('runTestRunAll — batch fresh run', () => {
24562459
const post = captured.find(c => c.method === 'POST' && c.url.includes('/tests/batch/run'))!;
24572460
expect(post.body).toMatchObject({ projectId: 'project_env', source: 'cli' });
24582461
});
2462+
2463+
it('run --all uses TESTSPRITE_PROJECT_ID when --project is blank', async () => {
2464+
const { createTestCommand } = await import('./test.js');
2465+
const { credentialsPath } = makeCreds();
2466+
type Captured = { url: string; method: string; body: unknown };
2467+
const captured: Captured[] = [];
2468+
const fetchImpl = makeFetch((url, init) => {
2469+
const method = init.method ?? 'GET';
2470+
captured.push({ url, method, body: init.body ? JSON.parse(init.body as string) : undefined });
2471+
return { body: BATCH_FRESH_RESP };
2472+
});
2473+
const test = createTestCommand({
2474+
credentialsPath,
2475+
env: { TESTSPRITE_PROJECT_ID: 'project_env' } as NodeJS.ProcessEnv,
2476+
fetchImpl,
2477+
stdout: () => undefined,
2478+
stderr: () => undefined,
2479+
sleep: instantSleep,
2480+
});
2481+
await test.parseAsync(['run', '--all', '--project', ' '], { from: 'user' });
2482+
const post = captured.find(c => c.method === 'POST' && c.url.includes('/tests/batch/run'))!;
2483+
expect(post.body).toMatchObject({ projectId: 'project_env', source: 'cli' });
2484+
});
2485+
24592486
it('--all --target-url → exit 5 (target-url has no effect on BE-only batch)', async () => {
24602487
const { createTestCommand } = await import('./test.js');
24612488
const test = createTestCommand();

src/commands/test.test.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,25 @@ describe('runList', () => {
370370
expect(seen[0]).toContain('projectId=project_env');
371371
});
372372

373+
it('uses TESTSPRITE_PROJECT_ID when --project is blank', async () => {
374+
const { credentialsPath } = makeCreds();
375+
const seen: string[] = [];
376+
const fetchImpl = makeFetch(url => {
377+
seen.push(url);
378+
return { body: { items: [FE_TEST], nextToken: null } };
379+
});
380+
await runList(
381+
{ profile: 'default', output: 'json', debug: false, projectId: ' ' },
382+
{
383+
credentialsPath,
384+
env: { TESTSPRITE_PROJECT_ID: 'project_env' } as NodeJS.ProcessEnv,
385+
fetchImpl,
386+
stdout: () => undefined,
387+
},
388+
);
389+
expect(seen[0]).toContain('projectId=project_env');
390+
});
391+
373392
it('prefers explicit --project over TESTSPRITE_PROJECT_ID', async () => {
374393
const { credentialsPath } = makeCreds();
375394
const seen: string[] = [];
@@ -704,10 +723,15 @@ describe('createTestCommand list — required flag', () => {
704723
await test.parseAsync(['list'], { from: 'user' });
705724
expect.unreachable('expected ApiError');
706725
} catch (err) {
707-
const apiErr = err as { code?: string; exitCode?: number; details?: { field?: string } };
726+
const apiErr = err as {
727+
code?: string;
728+
exitCode?: number;
729+
details?: { field?: string; reason?: string };
730+
};
708731
expect(apiErr.code).toBe('VALIDATION_ERROR');
709732
expect(apiErr.exitCode).toBe(5);
710733
expect(apiErr.details?.field).toBe('project');
734+
expect(apiErr.details?.reason).toContain('TESTSPRITE_PROJECT_ID');
711735
}
712736
});
713737

src/commands/test.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7469,12 +7469,10 @@ export function createTestCommand(deps: TestDeps = {}): Command {
74697469
if (isAll) {
74707470
// --all path: wave-ordered fresh batch run.
74717471
const projectId = resolveProjectId(cmdOpts.project, deps);
7472-
if (!projectId) {
7473-
throw localValidationError(
7474-
'project',
7475-
'--all requires a project id - pass --project <id> or set TESTSPRITE_PROJECT_ID',
7476-
);
7477-
}
7472+
requireProjectId(
7473+
projectId,
7474+
'--all requires a project id - pass --project <id> or set TESTSPRITE_PROJECT_ID',
7475+
);
74787476
// --target-url has no effect on the --all batch path: it is BE-only
74797477
// (FE tests are skipped server-side) and a BE test's base URL is baked
74807478
// into its code. Silently dropping it could run the suite against an
@@ -7777,14 +7775,18 @@ interface StepsFlagOpts {
77777775
}
77787776

77797777
function resolveProjectId(projectId: string | undefined, deps: TestDeps): string | undefined {
7780-
if (projectId !== undefined) return projectId;
7778+
const explicit = projectId?.trim();
7779+
if (explicit && explicit.length > 0) return explicit;
77817780
const envValue = (deps.env ?? process.env).TESTSPRITE_PROJECT_ID;
77827781
const trimmed = envValue?.trim();
77837782
return trimmed && trimmed.length > 0 ? trimmed : undefined;
77847783
}
7785-
function requireProjectId(projectId: string | undefined): asserts projectId is string {
7784+
function requireProjectId(
7785+
projectId: string | undefined,
7786+
message = 'is required; pass --project <id> or set TESTSPRITE_PROJECT_ID',
7787+
): asserts projectId is string {
77867788
if (typeof projectId !== 'string' || projectId.length === 0) {
7787-
throw localValidationError('project', 'is required');
7789+
throw localValidationError('project', message);
77887790
}
77897791
}
77907792

0 commit comments

Comments
 (0)