Skip to content

fix(config): treat empty env vars as unset in loadConfig#8

Merged
zeshi-du merged 2 commits into
TestSprite:mainfrom
SahilRakhaiya05:fix/config-empty-env-vars
Jul 6, 2026
Merged

fix(config): treat empty env vars as unset in loadConfig#8
zeshi-du merged 2 commits into
TestSprite:mainfrom
SahilRakhaiya05:fix/config-empty-env-vars

Conversation

@SahilRakhaiya05

@SahilRakhaiya05 SahilRakhaiya05 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Summary

When TESTSPRITE_API_URL or TESTSPRITE_API_KEY is set but empty/whitespace (e.g. export TESTSPRITE_API_URL= in a shell profile), runtime commands no longer resolve to a broken empty endpoint or ignore stored credentials. Matches the normalization already used in auth configure / init.

Split from #8 per review - bundle re-commit work moved to a separate PR.

Changes

  • normalizeEnvVar exported from loadConfig and used for TESTSPRITE_API_URL / TESTSPRITE_API_KEY
  • auth configure / runWhoami dry-run use the shared helper
  • init endpoint reporting uses the shared helper

Testing

  • npx vitest run src/lib/config.test.ts - 11 passed
  • npx vitest run src/commands/auth.test.ts -t "whitespace|empty / whitespace" - 4 passed
  • npm run typecheck and npm run lint - pass

Summary by CodeRabbit

  • Bug Fixes

    • Empty or whitespace-only environment values are now treated as unset, so API URL/API key fallback chains no longer stop at blank strings.
    • Dry-run auth/init flows now resolve to the correct default endpoint when the env URL is blank.
    • HTTP client behavior is corrected so blank API keys trigger the expected “auth required” outcome.
  • New Features

    • Centralized environment-variable normalization is now used across configuration and command flows.
  • Tests

    • Expanded coverage for blank API URL/API key handling in config, auth, and client creation.

@SahilRakhaiya05 SahilRakhaiya05 changed the title fix(config): treat empty env vars as unset in loadConfig fix(config,bundle): empty env vars + atomic bundle re-commit Jun 23, 2026
@zeshi-du

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e469e174-e2ba-4d47-b51a-dec2060874bb

📥 Commits

Reviewing files that changed from the base of the PR and between 0a1ef36 and 9feeacb.

📒 Files selected for processing (2)
  • src/commands/auth.test.ts
  • src/lib/client-factory.test.ts
💤 Files with no reviewable changes (1)
  • src/commands/auth.test.ts

Walkthrough

Empty or whitespace-only TESTSPRITE_API_URL and TESTSPRITE_API_KEY values are now normalized to unset before fallback resolution. The shared helper is used in config loading and command endpoint resolution, and tests cover the updated fallback behavior.

Changes

Whitespace-only env var normalization

Layer / File(s) Summary
Config env normalization and tests
src/lib/config.ts, src/lib/config.test.ts
Adds normalizeEnvVar and uses it in loadConfig for TESTSPRITE_API_URL and TESTSPRITE_API_KEY; tests verify fallback to credentials-file values when those env vars are blank.
Command endpoint normalization
src/commands/auth.ts, src/commands/init.ts, src/commands/auth.test.ts
Replaces inline URL trimming with normalizeEnvVar in runConfigure, runWhoami, and resolveReportedEndpoint; a dry-run test checks fallback to the default endpoint for whitespace-only TESTSPRITE_API_URL.
Client API key handling tests
src/lib/client-factory.test.ts
Adjusts malformed-key validation coverage and adds a real-path test showing whitespace-only TESTSPRITE_API_KEY is treated as unset.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: ruili-testsprite, zeshi-du

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: treating empty environment variables as unset in loadConfig, which is the core focus of all file modifications.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/config.ts (1)

44-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting shared normalization helper.

The same env.X?.trim() || undefined pattern is duplicated in src/commands/auth.ts (Line 194). Extracting a small normalizeEnvVar(value) helper into a shared module would prevent future drift between the two call sites.

♻️ Example helper
+export function normalizeEnvVar(value: string | undefined): string | undefined {
+  return value?.trim() || undefined;
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/config.ts` around lines 44 - 45, The env var normalization logic is
duplicated between config parsing and auth code, so extract the repeated
`env.X?.trim() || undefined` behavior into a shared helper like
`normalizeEnvVar(value)` in a common module and reuse it from `config.ts` and
`auth.ts`. Update the existing `envApiUrl` and `envApiKey` assignments in
`config.ts`, as well as the corresponding call site in `src/commands/auth.ts`,
to use the shared helper so both paths stay consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/bundle.ts`:
- Around line 516-534: The post-swap cleanup in the bundle commit flow can
mistakenly roll back a successfully installed bundle if cleanup fails after
rename(stagingDir, dir). Update the commit/rollback logic in bundle.ts so the
cleanup of priorDir and the .partial marker happens only after the new dir is
safely committed and outside the try/catch that performs rollback, or otherwise
ensure cleanup failures do not enter the rollback path. Keep the rollback
behavior in the same commit/restore flow around dirExists, rename, and rm, but
make cleanup best-effort and non-fatal so a committed bundle is never replaced
by stale contents because rm(priorDir, ...) or unlink(join(dir, '.partial'))
throws.

---

Nitpick comments:
In `@src/lib/config.ts`:
- Around line 44-45: The env var normalization logic is duplicated between
config parsing and auth code, so extract the repeated `env.X?.trim() ||
undefined` behavior into a shared helper like `normalizeEnvVar(value)` in a
common module and reuse it from `config.ts` and `auth.ts`. Update the existing
`envApiUrl` and `envApiKey` assignments in `config.ts`, as well as the
corresponding call site in `src/commands/auth.ts`, to use the shared helper so
both paths stay consistent.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 72db9340-77c5-446b-bce7-d8579aa54c3b

📥 Commits

Reviewing files that changed from the base of the PR and between 18f6e6e and 94cddc3.

📒 Files selected for processing (5)
  • src/commands/auth.ts
  • src/lib/bundle.commit.test.ts
  • src/lib/bundle.ts
  • src/lib/config.test.ts
  • src/lib/config.ts

Comment thread src/lib/bundle.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/lib/bundle.commit.test.ts (2)

21-96: 🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoff

No coverage for rollback-itself-failing.

The upstream commitBundle catch block also performs an rm(dir, ...) + rename(priorDir, dir) when both priorDir and dir exist post-failure, and an unguarded rename(priorDir, dir) when only priorDir exists — if either of these rollback renames itself throws, that error isn't caught, potentially leaving dir absent. Consider adding a test that forces the rollback rename to fail to confirm behavior (or document it as a known gap) since it's a plausible double-failure scenario for this atomic-swap logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/bundle.commit.test.ts` around lines 21 - 96, Add coverage for the
rollback path in commitBundle by forcing the recovery rename to fail after the
staging swap error. Update the commitBundle test helpers in this spec to
simulate a failure in the catch-block rollback branch that uses rm and
rename(priorDir, dir), and verify the resulting behavior (or explicitly document
the known gap) using the existing commitBundle, renameMock, and rmMock setup.

39-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Temp directories are never cleaned up.

mkdtempSync creates real directories under the OS tmpdir in both tests, but neither has an afterEach/finally removing parent. Over repeated CI runs this leaks directories in the temp filesystem.

♻️ Suggested cleanup
   it('rolls back to the prior complete bundle when the staging swap fails', async () => {
     const parent = mkdtempSync(join(tmpdir(), 'bundle-commit-parent-'));
+    try {
       ...
+    } finally {
+      await realRm(parent, { recursive: true, force: true });
+    }
   });

Also applies to: 72-73

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/bundle.commit.test.ts` around lines 39 - 40, The temp directories
created by mkdtempSync in bundle.commit.test.ts are not removed after the tests
finish, causing leftover OS tmpdir entries. Add cleanup in the test
setup/teardown for the parent directory created in the bundle commit tests,
using the existing test helpers around mkdtempSync/join so both cases are
deleted after each run. Keep the fix localized to the affected test cases so the
temporary parent directory is always removed even if assertions fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/auth.ts`:
- Around line 194-195: Add a regression test in auth.test.ts for runWhoami
covering the dry-run fallback chain when TESTSPRITE_API_URL is whitespace-only.
The issue is that normalizeEnvVar in auth.ts may return an empty value for
env.TESTSPRITE_API_URL, so the endpoint should still fall through to the
default. Update the runWhoami test to use TESTSPRITE_API_URL: '   ' and assert
that resolvedEndpoint ends up using the default https://api.testsprite.com
rather than an empty string.

---

Nitpick comments:
In `@src/lib/bundle.commit.test.ts`:
- Around line 21-96: Add coverage for the rollback path in commitBundle by
forcing the recovery rename to fail after the staging swap error. Update the
commitBundle test helpers in this spec to simulate a failure in the catch-block
rollback branch that uses rm and rename(priorDir, dir), and verify the resulting
behavior (or explicitly document the known gap) using the existing commitBundle,
renameMock, and rmMock setup.
- Around line 39-40: The temp directories created by mkdtempSync in
bundle.commit.test.ts are not removed after the tests finish, causing leftover
OS tmpdir entries. Add cleanup in the test setup/teardown for the parent
directory created in the bundle commit tests, using the existing test helpers
around mkdtempSync/join so both cases are deleted after each run. Keep the fix
localized to the affected test cases so the temporary parent directory is always
removed even if assertions fail.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dc22d066-b0b3-48ce-8653-ef6a4df1a3ae

📥 Commits

Reviewing files that changed from the base of the PR and between 94cddc3 and 91e2988.

📒 Files selected for processing (6)
  • src/commands/auth.ts
  • src/commands/init.ts
  • src/lib/bundle.commit.test.ts
  • src/lib/bundle.ts
  • src/lib/config.test.ts
  • src/lib/config.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/lib/config.ts
  • src/lib/config.test.ts
  • src/lib/bundle.ts

Comment thread src/commands/auth.ts Outdated
SahilRakhaiya05 added a commit to SahilRakhaiya05/testsprite-cli that referenced this pull request Jul 1, 2026
- Add runWhoami dry-run regression for whitespace-only TESTSPRITE_API_URL
- Add commitBundle rollback-rename failure coverage (documents known gap)
- Clean up bundle.commit temp dirs after each test
@SahilRakhaiya05

Copy link
Copy Markdown
Contributor Author

This PR is solving Issue #97

@zeshi-du

zeshi-du commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Two updates after a deeper review:

  1. The empty-env-var normalization half is good to go. Please split it into its own PR and it'll merge fast.

  2. The bundle re-commit half needs a rethink after fix(bundle): preserve unrelated pre-existing files in --out dir (stop data-loss sweep) #162 (merging today): --out may point at a directory that also holds the user's unrelated files, and fix(bundle): preserve unrelated pre-existing files in --out dir (stop data-loss sweep) #162 establishes the contract that the commit sweep must never touch entries the bundle format doesn't own (isBundleOwnedEntry). A whole-directory swap replaces the entire --out dir, so the user's foreign files would be evicted along with the prior bundle — silent data loss.

The atomicity goal itself is right (today a failed re-commit can delete meta.json without landing a replacement — worth fixing). A compatible shape would be: stage in .tmp/ as you do now, then swap only bundle-owned entries with per-entry aside + rollback — or stage the whole bundle under one owned subdirectory so a single rename swaps it. Happy to review either design.

@SahilRakhaiya05 SahilRakhaiya05 force-pushed the fix/config-empty-env-vars branch from 5af5770 to 0a1ef36 Compare July 6, 2026 18:05
@SahilRakhaiya05 SahilRakhaiya05 changed the title fix(config,bundle): empty env vars + atomic bundle re-commit fix(config): treat empty env vars as unset in loadConfig Jul 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/commands/auth.test.ts`:
- Around line 853-867: The dry-run whoami test is still injecting a custom fetch
stub, which bypasses the real offline dry-run path. Update the test in
auth.test.ts so the runWhoami call with dryRun: true relies on the canned
client/sample behavior from whoami and src/lib/dry-run/samples.ts instead of
passing fetchImpl: makeFetch(meResponse()), while keeping the TESTSPRITE_API_URL
whitespace assertion intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1929d5a9-7585-47eb-81c0-3e600c677aa3

📥 Commits

Reviewing files that changed from the base of the PR and between 91e2988 and 0a1ef36.

📒 Files selected for processing (5)
  • src/commands/auth.test.ts
  • src/commands/auth.ts
  • src/commands/init.ts
  • src/lib/config.test.ts
  • src/lib/config.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/commands/init.ts
  • src/lib/config.test.ts
  • src/lib/config.ts
  • src/commands/auth.ts

Comment thread src/commands/auth.test.ts
Comment on lines +853 to +867
it('dry-run: whitespace-only TESTSPRITE_API_URL falls through to prod default endpoint', async () => {
const { capture, deps } = makeCapture();
await runWhoami(
{ profile: 'default', output: 'text', debug: false, dryRun: true },
{
...deps,
env: { TESTSPRITE_API_URL: ' ' },
credentialsPath,
fetchImpl: makeFetch(meResponse()),
},
);
const out = capture.stdout.join('\n');
expect(out).toContain('endpoint: https://api.testsprite.com');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect runWhoami's dry-run branch for an early return before client.get
ast-grep run --pattern 'export async function runWhoami($_, $_) { $$$ }' --lang typescript src/commands/auth.ts

Repository: TestSprite/testsprite-cli

Length of output: 163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and inspect the dry-run branch in src/commands/auth.ts.
ast-grep outline src/commands/auth.ts --view expanded || true

printf '\n--- auth.ts dry-run references ---\n'
rg -n "dryRun|client\.get\('/me'|resolvedEndpoint|runWhoami" src/commands/auth.ts

printf '\n--- auth.ts relevant slice ---\n'
sed -n '180,280p' src/commands/auth.ts

printf '\n--- auth.test.ts relevant slice ---\n'
sed -n '830,890p' src/commands/auth.test.ts

Repository: TestSprite/testsprite-cli

Length of output: 8300


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect docs / nearby implementation for dry-run and endpoint resolution behavior.
rg -n "dry-run|whitespace|TESTSPRITE_API_URL|normalize|whoami" DOCUMENTATION.md src/commands src/lib src/config* src -g '!**/dist/**' || true

printf '\n--- possible config normalization helpers ---\n'
rg -n "function normalize|const normalize|normalizeEnv|trim\\(" src -g '*.ts' || true

Repository: TestSprite/testsprite-cli

Length of output: 50382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- client-factory dry-run slice ---'
sed -n '1,220p' src/lib/client-factory.ts

printf '\n%s\n' '--- client-factory request creation slice ---'
sed -n '220,360p' src/lib/client-factory.ts

printf '\n%s\n' '--- auth.ts surrounding makeHttpClient call ---'
sed -n '201,255p' src/commands/auth.ts

Repository: TestSprite/testsprite-cli

Length of output: 14032


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- dry-run sample registry for whoami ---'
sed -n '360,395p' src/lib/dry-run/samples.ts

printf '\n%s\n' '--- config normalization helper ---'
sed -n '1,120p' src/lib/config.ts

printf '\n%s\n' '--- auth test nearby whitespace/env tests ---'
sed -n '700,875p' src/commands/auth.test.ts

Repository: TestSprite/testsprite-cli

Length of output: 10713


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- client-factory dry-run behavior ---'
sed -n '1,220p' src/lib/client-factory.ts

printf '\n%s\n' '--- client-factory request path around dry-run handling ---'
sed -n '220,320p' src/lib/client-factory.ts

printf '\n%s\n' '--- whoami dry-run sample registry entry ---'
sed -n '372,388p' src/lib/dry-run/samples.ts

Repository: TestSprite/testsprite-cli

Length of output: 12401


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
text = Path('src/commands/auth.ts').read_text()
# Print the runWhoami function body only.
start = text.index('export async function runWhoami')
end = text.index('export async function runLogout')
print(text[start:end])
PY

Repository: TestSprite/testsprite-cli

Length of output: 2500


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline src/lib/client-factory.ts --view expanded || true
printf '\n--- makeHttpClient slice ---\n'
sed -n '120,320p' src/lib/client-factory.ts

Repository: TestSprite/testsprite-cli

Length of output: 8408


Drop the custom fetch stub in this dry-run test
runWhoami(..., dryRun: true) already uses the canned offline client, and whoami has a sample in src/lib/dry-run/samples.ts. Removing fetchImpl here keeps the test on the real --dry-run path instead of a local mock.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/auth.test.ts` around lines 853 - 867, The dry-run whoami test is
still injecting a custom fetch stub, which bypasses the real offline dry-run
path. Update the test in auth.test.ts so the runWhoami call with dryRun: true
relies on the canned client/sample behavior from whoami and
src/lib/dry-run/samples.ts instead of passing fetchImpl:
makeFetch(meResponse()), while keeping the TESTSPRITE_API_URL whitespace
assertion intact.

Source: Path instructions

@zeshi-du zeshi-du left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed: scoped, tests included, CI green across the matrix.

@zeshi-du zeshi-du merged commit f7b10b7 into TestSprite:main Jul 6, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants