Skip to content

Improve test coverage and structure for pkg/parser/import_cache_test.go#18369

Merged
pelikhan merged 2 commits intomainfrom
copilot/improve-test-quality-import-cache
Feb 25, 2026
Merged

Improve test coverage and structure for pkg/parser/import_cache_test.go#18369
pelikhan merged 2 commits intomainfrom
copilot/improve-test-quality-import-cache

Conversation

Copy link
Contributor

Copilot AI commented Feb 25, 2026

The import cache test file had a monolithic test function, missing edge cases in table-driven tests, and no coverage for idempotent overwrites or the Get/Set validation asymmetry.

Changes

  • TestImportCache — split into four t.Run() subtests: file creation, Get-after-Set roundtrip, content verification, cross-instance hit
  • TestSanitizePath — added edge cases: empty string (→ "."), trailing slash, single dot component, leading slash (→ "_absolute_path")
  • TestValidatePathComponents — added missing error cases: empty repo, empty path, and .. embedded within a sha value ("abc..def")
  • TestImportCacheSet_Validation — added one positive (non-error) case to confirm valid inputs pass through
  • TestImportCacheSetIdempotent — new test verifying that a second Set with the same key succeeds, returns the same path, and overwrites file content
  • TestImportCacheGetDoesNotValidateComponents — new test documenting that Get skips validation (unlike Set), and that path-traversal inputs return found=false without panicking

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha iK_VzgSGT -tests 0163866/b387/_pkg_.a (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha sole.test (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha vaScript2318284702/001/test-fron-s /tmp/go-build2920677756/b296/vet-w Name,createdAt,startedAt,updated-buildmode=exe ./../.prettieriggit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha vaScript2318284702/001/test-frontmatter-with-arrays.md -buildtags /opt/hostedtoolcache/node/24.13.0/x64/bin/npx ./../.prettieriggit -ifaceassert -nilfunc npx pret�� --write **/*.cjs 0163866/b373/vet.cfg **/*.json --ignore-path ../../../.pretti--show-toplevel sh (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v4 --jq .object.sha image:v1.0.0 x_amd64/vet /usr/bin/git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha '**/*.ts' '**/*.json' --ignore-premote.origin.url 0677756/b032/vet.cfg 0/x64/bin/node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha -bool -buildtags /usr/bin/git -errorsas -ifaceassert -nilfunc git -C runs/20260225-160211-6755/test-2406991587 config /usr/bin/git remote.origin.urgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha user.email test@example.com /usr/bin/git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha -c=4 -nolocalimports -importcfg /tmp/go-build3880163866/b395/importcfg -pack /home/REDACTED/work/gh-aw/gh-aw/pkg/fileutil/fileutil.go /home/REDACTED/work/gh-aw/gh-aw/pkg/fileutil/tar.go (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha ath ../../../.pr**/*.json (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha -bool -buildtags /home/REDACTED/.dotnet/tools/sh ./../.prettieriggit -ifaceassert -nilfunc sh -c 0211-6755/test-2406991587 -tests 0163866/b362/vet.cfg l (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha vaScript2318284702/001/test-fron-test.timeout=10m0s /tmp/go-build2920677756/b233/vet-test.run=^Test /home/REDACTED/.config/composer/ve-test.short=true ./../.prettieriggit (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/1/artifacts
    • Triggering command: /usr/bin/gh gh run download 1 --dir test-logs/run-1 .cfg x_amd64/compile (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/12345/artifacts
    • Triggering command: /usr/bin/gh gh run download 12345 --dir test-logs/run-12345 .cfg 64/bin/bash (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/12346/artifacts
    • Triggering command: /usr/bin/gh gh run download 12346 --dir test-logs/run-12346 .cfg 0/x64/bin/bash (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/2/artifacts
    • Triggering command: /usr/bin/gh gh run download 2 --dir test-logs/run-2 .cfg x_amd64/compile (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/3/artifacts
    • Triggering command: /usr/bin/gh gh run download 3 --dir test-logs/run-3 .cfg ache/go/1.25.0/x64/pkg/tool/linu-lang=go1.25 (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/4/artifacts
    • Triggering command: /usr/bin/gh gh run download 4 --dir test-logs/run-4 .cfg x_amd64/link (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/5/artifacts
    • Triggering command: /usr/bin/gh gh run download 5 --dir test-logs/run-5 .cfg ache/go/1.25.0/x64/bin/go (http block)
  • https://api.github.com/repos/github/gh-aw/actions/workflows
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path (http block)
    • Triggering command: /usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 100 (http block)
    • Triggering command: /usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 6 (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha celain --ignore-.github/workflows/test.md .cfg tions/setup/node-nolocalimports (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha celain --ignore-@{u} (http block)
  • https://api.github.com/repos/nonexistent/repo/actions/runs/12345
    • Triggering command: /usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion (http block)
  • https://api.github.com/repos/owner/repo/actions/workflows
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path --repo owner/repo x_amd64/vet (http block)
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path --repo owner/repo /sh (http block)
  • https://api.github.com/repos/owner/repo/contents/file.md
    • Triggering command: /tmp/go-build3880163866/b381/cli.test /tmp/go-build3880163866/b381/cli.test -test.testlogfile=/tmp/go-build3880163866/b381/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true (http block)
  • https://api.github.com/repos/test-owner/test-repo/actions/secrets
    • Triggering command: /usr/bin/gh gh api /repos/test-owner/test-repo/actions/secrets --jq .secrets[].name (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>[testify-expert] Improve Test Quality: pkg/parser/import_cache_test.go</issue_title>
<issue_description>The test file pkg/parser/import_cache_test.go has been selected for quality improvement by the Testify Uber Super Expert. This issue provides specific, actionable recommendations to enhance test quality, coverage, and maintainability.

Current State

  • Test File: pkg/parser/import_cache_test.go (289 lines)
  • Source File: pkg/parser/import_cache.go (175 lines)
  • Test Functions: 7 test functions
  • Build Tag: ✅ //go:build !integration
  • Testify imports: ✅ Both assert and require imported

Strengths ✅

  1. Correct require / assert split — Critical setup steps (file creation, directory creation) use require.NoError, while validation assertions use assert. This is exactly the right pattern.
  2. Table-driven tests for some functionsTestSanitizePath, TestValidatePathComponents, and TestImportCacheSet_Validation all use table-driven tests with t.Run(), making them easy to extend.
  3. Descriptive assertion messages — Almost all assertions include a message explaining what is being checked, which improves debuggability.

Areas for Improvement 🎯

1. TestImportCache should use subtests via t.Run()

Current Issue: TestImportCache tests 4 distinct behaviors in sequence (Set/Get roundtrip, file creation, content verification, cross-instance cache hit) without using t.Run(). When this test fails, it's hard to know which specific behavior is broken.

Recommended Change:

// ❌ CURRENT — monolithic test, hard to diagnose failures
func TestImportCache(t *testing.T) {
    tempDir := t.TempDir()
    cache := NewImportCache(tempDir)
    testContent := []byte("# Test Workflow\n\nTest content")
    // ... all assertions in one flat function
}

// ✅ IMPROVED — each behavior is a named subtest
func TestImportCache(t *testing.T) {
    tempDir := t.TempDir()
    cache := NewImportCache(tempDir)
    const (
        owner = "testowner"
        repo  = "testrepo"
        path  = "workflows/test.md"
        sha   = "abc123"
    )
    testContent := []byte("# Test Workflow\n\nTest content")

    t.Run("Set creates file and returns path", func(t *testing.T) {
        cachedPath, err := cache.Set(owner, repo, path, sha, testContent)
        require.NoError(t, err, "Set should succeed for valid inputs")
        require.FileExists(t, cachedPath, "cache file should be created at expected path")
    })

    t.Run("Get returns cached path after Set", func(t *testing.T) {
        cachedPath, _ := cache.Set(owner, repo, path, sha, testContent)
        retrievedPath, found := cache.Get(owner, repo, path, sha)
        assert.True(t, found, "cache entry should be found after Set")
        assert.Equal(t, cachedPath, retrievedPath, "retrieved path should match path returned by Set")
    })

    t.Run("Cached file content matches original", func(t *testing.T) {
        cachedPath, err := cache.Set(owner, repo, path, sha, testContent)
        require.NoError(t, err, "Set should succeed")
        content, err := os.ReadFile(cachedPath)
        require.NoError(t, err, "reading cached file should succeed")
        assert.Equal(t, string(testContent), string(content), "cached content should match original")
    })

    t.Run("New cache instance finds existing entry", func(t *testing.T) {
        cachedPath, _ := cache.Set(owner, repo, path, sha, testContent)
        cache2 := NewImportCache(tempDir)
        retrievedPath2, found := cache2.Get(owner, repo, path, sha)
        assert.True(t, found, "cache entry should be found from new cache instance")
        assert.Equal(t, cachedPath, retrievedPath2, "path from new instance should match original")
    })
}

2. Missing test cases in TestSanitizePath

Current Issue: The table only tests 4 cases. Several important edge cases are missing.

Missing cases:

{
    name:     "empty string",
    input:    "",
    expected: ".",  // filepath.Clean("") == "."
},
{
    name:     "trailing slash",
    input:    "a/b/",
    expected: "a_b",
},
{
    name:     "single dot component",
    input:    "a/./b",
    expected: "a_b",
},
{
    name:     "leading slash becomes root-like",
    input:    "/absolute/path",
    expected: "_absolute_path",
},

3. Missing test cases in TestValidatePathComponents

Current Issue: The table covers owner and sha validation but misses:

  • Empty repo
  • Empty path
  • .. embedded in sha (e.g. "abc..def")

Missing cases:

{
    name:      "empty repo",
    owner:     "testowner",
    repo:      "",
    path:      "test.md",
    sha:       "abc123",
    shouldErr: true,
    errMsg:    "empty component",
},
{
    name:      "empty path",
    owner:     "testowner",
    repo:      "testrepo",
    path:      "",
    sha:       "abc123",
    shouldErr: true...

</details>



<!-- START COPILOT CODING AGENT SUFFIX -->

- Fixes github/gh-aw#18365

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Improve test quality for import_cache_test.go Improve test coverage and structure for pkg/parser/import_cache_test.go Feb 25, 2026
@pelikhan pelikhan marked this pull request as ready for review February 25, 2026 16:34
Copilot AI review requested due to automatic review settings February 25, 2026 16:34
@pelikhan pelikhan merged commit e4f43d1 into main Feb 25, 2026
@pelikhan pelikhan deleted the copilot/improve-test-quality-import-cache branch February 25, 2026 16:34
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves the quality and coverage of the import cache unit tests by restructuring existing tests into clearer subtests and adding additional edge-case coverage around path sanitization/validation and cache overwrite behavior.

Changes:

  • Split TestImportCache into t.Run() subtests for clearer failure localization.
  • Expanded table-driven tests for sanitizePath and validatePathComponents with additional edge cases.
  • Added new tests for Set idempotent overwrite behavior and for Get behavior with suspicious (non-validated) inputs.
Comments suppressed due to low confidence (1)

pkg/parser/import_cache_test.go:51

  • The error returned by Set is ignored here (cachedPath, _ := ...). Use require.NoError before creating the second cache instance so failures point to the real cause instead of a later "found" assertion.
	t.Run("New cache instance finds existing entry", func(t *testing.T) {
		cachedPath, _ := cache.Set(owner, repo, path, sha, testContent)
		cache2 := NewImportCache(tempDir)
		retrievedPath2, found := cache2.Get(owner, repo, path, sha)
		assert.True(t, found, "cache entry should be found from new cache instance")
		assert.Equal(t, cachedPath, retrievedPath2, "path from new instance should match original")

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +31 to +35
t.Run("Get returns cached path after Set", func(t *testing.T) {
cachedPath, _ := cache.Set(owner, repo, path, sha, testContent)
retrievedPath, found := cache.Get(owner, repo, path, sha)
assert.True(t, found, "cache entry should be found after Set")
assert.Equal(t, cachedPath, retrievedPath, "retrieved path should match path returned by Set")
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

In this subtest the error returned by Set is discarded (cachedPath, _ := ...). If Set starts failing, the test will fail later with a less-informative assertion. Prefer require.NoError (or at least check the error) before using cachedPath to keep failures actionable.

This issue also appears on line 46 of the same file.

Copilot uses AI. Check for mistakes.
Comment on lines +373 to +377
// If validation is added in the future, this test should be updated.
tempDir := t.TempDir()
cache := NewImportCache(tempDir)

// Should return not-found (not panic or error), even with suspicious inputs.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

This test name/comment claims to document that Get does not validate components, but the only assertion is found == false for a key that was never written. Even if Get did validate in the future, it would likely still return found=false, so the test wouldn’t catch the behavior change. Consider renaming the test to reflect what is actually asserted (e.g., "Get returns not-found for suspicious inputs without panicking"), or strengthen it to specifically exercise the validation asymmetry.

Suggested change
// If validation is added in the future, this test should be updated.
tempDir := t.TempDir()
cache := NewImportCache(tempDir)
// Should return not-found (not panic or error), even with suspicious inputs.
// Specifically, Set should reject suspicious inputs, while Get should
// simply return not-found (and not panic) for the same inputs.
tempDir := t.TempDir()
cache := NewImportCache(tempDir)
// First, verify that Set rejects suspicious inputs via validation.
_, err := cache.Set("../etc", "repo", "test.md", "sha", []byte("content"))
require.Error(t, err, "Set should reject suspicious owner/path input")
// Then, verify that Get with the same suspicious inputs just returns not-found,
// rather than validating and erroring or panicking.

Copilot uses AI. Check for mistakes.
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.

3 participants