Skip to content

Commit b16d53e

Browse files
feat: support project env default
1 parent 2708a40 commit b16d53e

5 files changed

Lines changed: 164 additions & 54 deletions

File tree

DOCUMENTATION.md

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -600,24 +600,25 @@ These apply to every command:
600600

601601
### Environment variables
602602

603-
| Variable | Purpose |
604-
| ----------------------------------------- | ---------------------------------------------------------------------------------------- |
605-
| `TESTSPRITE_API_KEY` | API key — overrides the credentials file |
606-
| `TESTSPRITE_API_URL` | API endpoint — overrides the credentials file |
607-
| `TESTSPRITE_PROFILE` | Active profile (below `--profile`, above `default`) |
608-
| `TESTSPRITE_REQUEST_TIMEOUT_MS` | Per-request timeout in **milliseconds** (default `120000`, range `1000``600000`) |
609-
| `TESTSPRITE_NO_UPDATE_NOTIFIER` | Any non-empty value disables the once-per-24h "new version available" notice |
610-
| `NO_COLOR` | Suppress ANSI escape sequences in ticker output ([no-color.org](https://no-color.org/)) |
611-
| `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` | Standard proxy support — API traffic is routed through the configured proxy |
612-
| `TESTSPRITE_NO_SKILL_WARNING` | Any non-empty value silences the "verify skill not installed" reminder (CI / manual use) |
613-
| `TESTSPRITE_PORTAL_URL` | Override the Portal origin used for `dashboardUrl` links (non-prod environments) |
603+
| Variable | Purpose |
604+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------ |
605+
| `TESTSPRITE_API_KEY` | API key - overrides the credentials file |
606+
| `TESTSPRITE_API_URL` | API endpoint - overrides the credentials file |
607+
| `TESTSPRITE_PROFILE` | Active profile (below `--profile`, above `default`) |
608+
| `TESTSPRITE_PROJECT_ID` | Default project for `test list`, `test create`, and `test run --all` when `--project` is omitted |
609+
| `TESTSPRITE_REQUEST_TIMEOUT_MS` | Per-request timeout in **milliseconds** (default `120000`, range `1000`-`600000`) |
610+
| `TESTSPRITE_NO_UPDATE_NOTIFIER` | Any non-empty value disables the once-per-24h "new version available" notice |
611+
| `NO_COLOR` | Suppress ANSI escape sequences in ticker output ([no-color.org](https://no-color.org/)) |
612+
| `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` | Standard proxy support - API traffic is routed through the configured proxy |
613+
| `TESTSPRITE_NO_SKILL_WARNING` | Any non-empty value silences the "verify skill not installed" reminder (CI / manual use) |
614+
| `TESTSPRITE_PORTAL_URL` | Override the Portal origin used for `dashboardUrl` links (non-prod environments) |
614615

615616
### Update notice
616617

617618
Interactive runs print a one-line "new version available" notice on stderr when
618619
a newer release exists. To learn this, the CLI contacts the public npm registry
619620
(`registry.npmjs.org`) at most once per 24 hours; the request carries the
620-
package name only never your API key, project data, or command line. The
621+
package name only - never your API key, project data, or command line. The
621622
check is skipped in CI, when stderr is not a TTY, under `--output json` /
622623
`--dry-run`, and entirely when `TESTSPRITE_NO_UPDATE_NOTIFIER` is set. Any
623624
failure is silent: the notice can never break or delay a command. This is the
@@ -627,7 +628,7 @@ Separately, the backend advertises its **minimum supported CLI version** on
627628
every `/api/cli/v1` response. When the running CLI is below that floor, a
628629
one-line upgrade advisory is printed to stderr (same opt-outs as the update
629630
notice; it never changes the exit status). A backend may also reject a
630-
too-old client outright with HTTP 426 surfaced as `CLIENT_TOO_OLD`,
631+
too-old client outright with HTTP 426 - surfaced as `CLIENT_TOO_OLD`,
631632
exit `14`, non-retriable, with upgrade guidance.
632633

633634
### Scopes

src/commands/test.run.spec.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,6 +2585,28 @@ describe('runTestRunAll — batch fresh run', () => {
25852585
});
25862586
});
25872587

2588+
it('run --all uses TESTSPRITE_PROJECT_ID when --project is omitted', async () => {
2589+
const { createTestCommand } = await import('./test.js');
2590+
const { credentialsPath } = makeCreds();
2591+
type Captured = { url: string; method: string; body: unknown };
2592+
const captured: Captured[] = [];
2593+
const fetchImpl = makeFetch((url, init) => {
2594+
const method = init.method ?? 'GET';
2595+
captured.push({ url, method, body: init.body ? JSON.parse(init.body as string) : undefined });
2596+
return { body: BATCH_FRESH_RESP };
2597+
});
2598+
const test = createTestCommand({
2599+
credentialsPath,
2600+
env: { TESTSPRITE_PROJECT_ID: 'project_env' } as NodeJS.ProcessEnv,
2601+
fetchImpl,
2602+
stdout: () => undefined,
2603+
stderr: () => undefined,
2604+
sleep: instantSleep,
2605+
});
2606+
await test.parseAsync(['run', '--all'], { from: 'user' });
2607+
const post = captured.find(c => c.method === 'POST' && c.url.includes('/tests/batch/run'))!;
2608+
expect(post.body).toMatchObject({ projectId: 'project_env', source: 'cli' });
2609+
});
25882610
it('--all --target-url → exit 5 (target-url has no effect on BE-only batch)', async () => {
25892611
const { createTestCommand } = await import('./test.js');
25902612
const test = createTestCommand();

src/commands/test.test.ts

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,45 @@ describe('runList', () => {
365365
expect(seen[0]).toContain('createdFrom=portal');
366366
});
367367

368+
it('uses TESTSPRITE_PROJECT_ID when --project is omitted', async () => {
369+
const { credentialsPath } = makeCreds();
370+
const seen: string[] = [];
371+
const fetchImpl = makeFetch(url => {
372+
seen.push(url);
373+
return { body: { items: [FE_TEST], nextToken: null } };
374+
});
375+
await runList(
376+
{ profile: 'default', output: 'json', debug: false },
377+
{
378+
credentialsPath,
379+
env: { TESTSPRITE_PROJECT_ID: 'project_env' } as NodeJS.ProcessEnv,
380+
fetchImpl,
381+
stdout: () => undefined,
382+
},
383+
);
384+
expect(seen[0]).toContain('projectId=project_env');
385+
});
386+
387+
it('prefers explicit --project over TESTSPRITE_PROJECT_ID', async () => {
388+
const { credentialsPath } = makeCreds();
389+
const seen: string[] = [];
390+
const fetchImpl = makeFetch(url => {
391+
seen.push(url);
392+
return { body: { items: [FE_TEST], nextToken: null } };
393+
});
394+
await runList(
395+
{ profile: 'default', output: 'json', debug: false, projectId: 'project_flag' },
396+
{
397+
credentialsPath,
398+
env: { TESTSPRITE_PROJECT_ID: 'project_env' } as NodeJS.ProcessEnv,
399+
fetchImpl,
400+
stdout: () => undefined,
401+
},
402+
);
403+
expect(seen[0]).toContain('projectId=project_flag');
404+
expect(seen[0]).not.toContain('projectId=project_env');
405+
});
406+
368407
it('accepts --created-from cli and passes createdFrom=cli to the wire (dogfood 2026-06-04)', async () => {
369408
// End-to-end through parseEnumFlag: backend now stamps createFrom='cli'
370409
// on `testsprite test create` rows, so the filter must accept 'cli'.
@@ -4847,7 +4886,7 @@ describe('runCreate', () => {
48474886
...SAMPLE_RESPONSE,
48484887
type: 'backend',
48494888
warnings: [
4850-
'This test appears to hardcode an auth credential read auth from __AUTH_HEADERS__.',
4889+
'This test appears to hardcode an auth credential - read auth from __AUTH_HEADERS__.',
48514890
],
48524891
},
48534892
};
@@ -4873,10 +4912,43 @@ describe('runCreate', () => {
48734912
);
48744913
// Warning lands on stderr, prefixed `[warn]`.
48754914
expect(err.some(l => l.includes('[warn]') && l.includes('__AUTH_HEADERS__'))).toBe(true);
4876-
// stdout stays the parseable wire object no warning noise.
4915+
// stdout stays the parseable wire object - no warning noise.
48774916
expect(out.join('\n')).not.toContain('[warn]');
48784917
});
48794918

4919+
it('uses TESTSPRITE_PROJECT_ID for create when --project is omitted', async () => {
4920+
const { credentialsPath } = makeCreds();
4921+
const codeFile = writeCodeFile('code body');
4922+
type Captured = { method: string; body: unknown; url: string };
4923+
const captured: Captured[] = [];
4924+
const fetchImpl = makeFetch((url, init) => {
4925+
const method = init.method ?? 'GET';
4926+
captured.push({ url, method, body: init.body ? JSON.parse(init.body as string) : undefined });
4927+
if (method === 'GET') return { status: 200, body: { items: [] } };
4928+
return { status: 200, body: SAMPLE_RESPONSE };
4929+
});
4930+
await runCreate(
4931+
{
4932+
profile: 'default',
4933+
output: 'json',
4934+
debug: false,
4935+
type: 'frontend',
4936+
name: 'n',
4937+
codeFile,
4938+
},
4939+
{
4940+
credentialsPath,
4941+
env: { TESTSPRITE_PROJECT_ID: 'project_env' } as NodeJS.ProcessEnv,
4942+
fetchImpl,
4943+
stdout: () => undefined,
4944+
},
4945+
);
4946+
expect(captured.some(c => c.method === 'GET' && c.url.includes('projectId=project_env'))).toBe(
4947+
true,
4948+
);
4949+
const post = captured.find(c => c.method === 'POST')!;
4950+
expect(post.body).toMatchObject({ projectId: 'project_env' });
4951+
});
48804952
it('respects a caller-supplied --idempotency-key (for safe retries)', async () => {
48814953
const { credentialsPath } = makeCreds();
48824954
const codeFile = writeCodeFile('code body');
@@ -5030,7 +5102,6 @@ describe('runCreate', () => {
50305102
profile: 'default',
50315103
output: 'json',
50325104
debug: false,
5033-
// @ts-expect-error — exercising the runtime gate
50345105
projectId: undefined,
50355106
type: 'frontend',
50365107
name: 'n',

0 commit comments

Comments
 (0)