test: config — ConfigPaths.parseText JSONC parsing and substitution#392
test: config — ConfigPaths.parseText JSONC parsing and substitution#392anandgupta42 wants to merge 1 commit intomainfrom
Conversation
… error reporting
Direct unit tests for ConfigPaths.parseText covering JSONC syntax error formatting
with line/column positions, {env:} substitution, {file:} comment skipping, missing
file error handling, and relative path resolution. These paths were previously only
tested indirectly through Config.get() integration tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
https://claude.ai/code/session_0153Zp8Yg62ibyUuu3vcxz3k
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review.
Tip: disable this comment in your organization's Code Review settings.
📝 WalkthroughWalkthroughAdded a new test file with comprehensive test coverage for Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/opencode/test/config/paths.test.ts (1)
90-108: Avoid hardcoded/tmpin missing-file tests.Using a fixed absolute path is less portable and can become flaky if the file unexpectedly exists. Prefer deriving a guaranteed-missing path from
tmpdir().♻️ Suggested refactor
test("throws InvalidError when referenced file does not exist", async () => { - const text = '{ "key": "{file:/tmp/definitely-nonexistent-parse-text-file-abc123.txt}" }' + await using tmp = await tmpdir() + const missingPath = path.join(tmp.path, "definitely-missing-file.txt") + const text = `{ "key": "{file:${missingPath}}" }` await expect(ConfigPaths.parseText(text, "/fake/config.json")).rejects.toMatchObject({ name: "ConfigInvalidError", data: { path: "/fake/config.json", }, }) // Verify specific error details try { await ConfigPaths.parseText(text, "/fake/config.json") } catch (e: any) { expect(e.data.message).toContain("does not exist") } }) test("returns empty string for missing file when missing='empty'", async () => { - const text = '{ "key": "{file:/tmp/definitely-nonexistent-parse-text-file-abc123.txt}" }' + await using tmp = await tmpdir() + const missingPath = path.join(tmp.path, "definitely-missing-file.txt") + const text = `{ "key": "{file:${missingPath}}" }` const result = await ConfigPaths.parseText(text, "/fake/config.json", "empty") expect(result.key).toBe("") })As per coding guidelines, "Use the
tmpdirfunction fromfixture/fixture.tsto create temporary directories for tests with automatic cleanup in test files".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/opencode/test/config/paths.test.ts` around lines 90 - 108, The tests use a hardcoded absolute /tmp path which is non-portable and can be flaky; update both tests that call ConfigPaths.parseText to derive a guaranteed-missing file path using the tmpdir() helper from fixture/fixture.ts (create a temp directory via tmpdir(), then build a filename inside it that you never create) and use that generated path instead of "/tmp/definitely-nonexistent-parse-text-file-abc123.txt" so the assertions around ConfigPaths.parseText (and the error message check) target a reliably-missing file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/opencode/test/config/paths.test.ts`:
- Around line 70-77: The test "replaces missing env var with empty string"
mutates process.env["DEFINITELY_MISSING_PARSE_TEXT_VAR_98765"] without restoring
it; update the test around ConfigPaths.parseText to save the original value
(e.g., const _orig = process.env["DEFINITELY_MISSING_PARSE_TEXT_VAR_98765"]),
then remove or unset it for the assertion, and finally restore the original
value in a finally block or after the assertion (or use afterEach) so the global
env is returned to its prior state regardless of test outcome.
---
Nitpick comments:
In `@packages/opencode/test/config/paths.test.ts`:
- Around line 90-108: The tests use a hardcoded absolute /tmp path which is
non-portable and can be flaky; update both tests that call ConfigPaths.parseText
to derive a guaranteed-missing file path using the tmpdir() helper from
fixture/fixture.ts (create a temp directory via tmpdir(), then build a filename
inside it that you never create) and use that generated path instead of
"/tmp/definitely-nonexistent-parse-text-file-abc123.txt" so the assertions
around ConfigPaths.parseText (and the error message check) target a
reliably-missing file.
🪄 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: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: e2723ebf-3827-49cd-80f7-5f6520b7146f
📒 Files selected for processing (1)
packages/opencode/test/config/paths.test.ts
| test("replaces missing env var with empty string", async () => { | ||
| delete process.env["DEFINITELY_MISSING_PARSE_TEXT_VAR_98765"] | ||
| const result = await ConfigPaths.parseText( | ||
| '{ "key": "{env:DEFINITELY_MISSING_PARSE_TEXT_VAR_98765}" }', | ||
| "/fake/config.json", | ||
| ) | ||
| expect(result.key).toBe("") | ||
| }) |
There was a problem hiding this comment.
Restore env state after this test.
Line 71 mutates global process.env without restoring a prior value. If that variable is set in CI, this test can leak state into later tests.
🔧 Suggested fix
test("replaces missing env var with empty string", async () => {
- delete process.env["DEFINITELY_MISSING_PARSE_TEXT_VAR_98765"]
- const result = await ConfigPaths.parseText(
- '{ "key": "{env:DEFINITELY_MISSING_PARSE_TEXT_VAR_98765}" }',
- "/fake/config.json",
- )
- expect(result.key).toBe("")
+ const key = "DEFINITELY_MISSING_PARSE_TEXT_VAR_98765"
+ const prev = process.env[key]
+ delete process.env[key]
+ try {
+ const result = await ConfigPaths.parseText('{ "key": "{env:DEFINITELY_MISSING_PARSE_TEXT_VAR_98765}" }', "/fake/config.json")
+ expect(result.key).toBe("")
+ } finally {
+ if (prev === undefined) delete process.env[key]
+ else process.env[key] = prev
+ }
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| test("replaces missing env var with empty string", async () => { | |
| delete process.env["DEFINITELY_MISSING_PARSE_TEXT_VAR_98765"] | |
| const result = await ConfigPaths.parseText( | |
| '{ "key": "{env:DEFINITELY_MISSING_PARSE_TEXT_VAR_98765}" }', | |
| "/fake/config.json", | |
| ) | |
| expect(result.key).toBe("") | |
| }) | |
| test("replaces missing env var with empty string", async () => { | |
| const key = "DEFINITELY_MISSING_PARSE_TEXT_VAR_98765" | |
| const prev = process.env[key] | |
| delete process.env[key] | |
| try { | |
| const result = await ConfigPaths.parseText('{ "key": "{env:DEFINITELY_MISSING_PARSE_TEXT_VAR_98765}" }', "/fake/config.json") | |
| expect(result.key).toBe("") | |
| } finally { | |
| if (prev === undefined) delete process.env[key] | |
| else process.env[key] = prev | |
| } | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/opencode/test/config/paths.test.ts` around lines 70 - 77, The test
"replaces missing env var with empty string" mutates
process.env["DEFINITELY_MISSING_PARSE_TEXT_VAR_98765"] without restoring it;
update the test around ConfigPaths.parseText to save the original value (e.g.,
const _orig = process.env["DEFINITELY_MISSING_PARSE_TEXT_VAR_98765"]), then
remove or unset it for the assertion, and finally restore the original value in
a finally block or after the assertion (or use afterEach) so the global env is
returned to its prior state regardless of test outcome.
Merges 9 test PRs (#390, #394, #395, #396, #397, #398, #399, #401, #387) into a single PR, discarding 4 duplicates (#391, #392, #393, #400). **New test coverage:** - `id.test.ts` — 14 tests: Identifier generation, monotonic ordering, timestamp extraction, Zod schema - `shell.test.ts` — 9 tests: shell blacklist enforcement for fish/nu, cache reset, fallback - `path-traversal.test.ts` — 7 tests (new): `Protected.isSensitiveWrite` for .pem/.key, .gnupg, .env variants - `ignore.test.ts` — 7 tests (new): `FileIgnore.match` directory patterns, globs, whitelist overrides - `paths-parsetext.test.ts` — 16 tests: `ConfigPaths.parseText` env/file substitution, JSONC parsing - `finops-formatting.test.ts` — 14 tests: `formatBytes`/`truncateQuery` edge cases - `finops-role-access.test.ts` — 10 tests: RBAC `formatGrants`/`formatHierarchy`/`formatUserRoles` - `tool-lookup.test.ts` — 4 tests: Zod schema introspection for tool parameters - `summary-git-path.test.ts` — 10 tests: `unquoteGitPath` decoding for non-ASCII filenames - `tracing.test.ts` — 2 tests (added): Recap loop detection boundaries - `patch.test.ts` — 9 tests (added): `maybeParseApplyPatchVerified` entry point coverage - `instruction.test.ts` — 13 tests (new): `InstructionPrompt.loaded()` dedup guards - `message-v2.test.ts` — 23 tests (new): `MessageV2.filterCompacted()` boundary detection **Bug fixes (discovered during testing):** - `finops-formatting.ts`: fix `formatBytes` crash on negative/NaN/fractional inputs - `finops-formatting.ts`: fix `truncateQuery` returning empty for whitespace-only, exceeding `maxLen` when < 4 - `training-import.test.ts`: fix pre-existing typecheck errors (missing `context`/`rule` mock keys) - `instruction.test.ts`, `message-v2.test.ts`: fix `MessageV2.Part` cast and branded ID type errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Consolidated into #403 |
* test: consolidated test coverage across 9 modules (133 new tests) Merges 9 test PRs (#390, #394, #395, #396, #397, #398, #399, #401, #387) into a single PR, discarding 4 duplicates (#391, #392, #393, #400). **New test coverage:** - `id.test.ts` — 14 tests: Identifier generation, monotonic ordering, timestamp extraction, Zod schema - `shell.test.ts` — 9 tests: shell blacklist enforcement for fish/nu, cache reset, fallback - `path-traversal.test.ts` — 7 tests (new): `Protected.isSensitiveWrite` for .pem/.key, .gnupg, .env variants - `ignore.test.ts` — 7 tests (new): `FileIgnore.match` directory patterns, globs, whitelist overrides - `paths-parsetext.test.ts` — 16 tests: `ConfigPaths.parseText` env/file substitution, JSONC parsing - `finops-formatting.test.ts` — 14 tests: `formatBytes`/`truncateQuery` edge cases - `finops-role-access.test.ts` — 10 tests: RBAC `formatGrants`/`formatHierarchy`/`formatUserRoles` - `tool-lookup.test.ts` — 4 tests: Zod schema introspection for tool parameters - `summary-git-path.test.ts` — 10 tests: `unquoteGitPath` decoding for non-ASCII filenames - `tracing.test.ts` — 2 tests (added): Recap loop detection boundaries - `patch.test.ts` — 9 tests (added): `maybeParseApplyPatchVerified` entry point coverage - `instruction.test.ts` — 13 tests (new): `InstructionPrompt.loaded()` dedup guards - `message-v2.test.ts` — 23 tests (new): `MessageV2.filterCompacted()` boundary detection **Bug fixes (discovered during testing):** - `finops-formatting.ts`: fix `formatBytes` crash on negative/NaN/fractional inputs - `finops-formatting.ts`: fix `truncateQuery` returning empty for whitespace-only, exceeding `maxLen` when < 4 - `training-import.test.ts`: fix pre-existing typecheck errors (missing `context`/`rule` mock keys) - `instruction.test.ts`, `message-v2.test.ts`: fix `MessageV2.Part` cast and branded ID type errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address code review findings — test artifact leakage and cleanup - summary-git-path.test.ts: use tmpdir() instead of projectRoot to prevent Storage.write() from polluting the developer's .opencode/storage/ directory - finops-role-access.test.ts: consolidate duplicate afterAll hooks to ensure ALTIMATE_TELEMETRY_DISABLED env var is cleaned up after tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
What does this PR do?\n\n1.
ConfigPaths.parseText—src/config/paths.ts(12 new tests)\n\nThis function is the core JSONC config parser used by bothConfig.get()and TUI config loading. It handles{env:VAR}and{file:path}token substitution, JSONC comment handling, and error reporting with line/column positions. Previously, it was only tested indirectly through integration tests inconfig.test.ts. Zero direct unit tests existed.\n\nNew coverage includes:\n\nValid JSONC parsing (2 tests)\n- Trailing comma support (verifiesallowTrailingComma: trueflag)\n- Line comment preservation during parsing\n\nJSONC error reporting (2 tests)\n- Missing comma throwsConfigJsonErrorwith source path, line number, and column number\n- Error message includes the offending line content and a caret (^) marker pointing to the error position\n\n**{env:VAR}substitution (2 tests)\n- Present environment variable is substituted correctly\n- Missing environment variable resolves to empty string (not undefined/error)\n\n{file:}comment skipping (1 test)\n-{file:nonexistent.txt}inside a JSONC//comment is correctly skipped and does NOT trigger file I/O. This tests theprefix.startsWith(\"//\")guard in thesubstitutefunction — if broken, users with commented-out file references would get cryptic ENOENT errors.\n\n{file:}missing file error handling (2 tests)\n- Missing file throwsConfigInvalidErrorwith "does not exist" message and source config path\n-missing='empty'mode gracefully returns empty string instead of throwing\n\n{file:}real file substitution (3 tests)**\n- Absolute path file content is substituted correctly\n- File content whitespace is trimmed (verifies the.trim()call)\n- Relative{file:}paths are resolved against the config file's directory (not CWD)\n\n### Type of change\n- [x] New feature (non-breaking change which adds functionality)\n\n### Issue for this PR\nN/A — proactive test coverage from/test-discovery\n\n### How did you verify your code works?\n\n\nbun test test/config/paths.test.ts # 12 pass, 0 fail\n\n\n### Checklist\n- [x] I have added tests that prove my fix is effective or that my feature works\n- [x] New and existing unit tests pass locally with my changes\n\nhttps://claude.ai/code/session_0153Zp8Yg62ibyUuu3vcxz3kSummary by CodeRabbit