Skip to content

test(actionpins): improve test quality in actionpins_internal_test.go - #48707

Merged
pelikhan merged 4 commits into
mainfrom
copilot/testify-expert-improve-test-quality-one-more-time
Jul 28, 2026
Merged

test(actionpins): improve test quality in actionpins_internal_test.go#48707
pelikhan merged 4 commits into
mainfrom
copilot/testify-expert-improve-test-quality-one-more-time

Conversation

Copilot AI commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Addresses several test quality gaps in pkg/actionpins/actionpins_internal_test.go: missing subtests, weak panic assertions, duplicated test functions, and a misplaced helper type.

Changes

  • countingResolver relocated — moved from mid-file to immediately after imports with a clarifying comment

  • t.Run per image in TestGetContainerPin_DefaultMCPImagesArePinned — plain for loop converted to subtests, matching the pattern in TestGetContainerPin_MCPGatewayVersionsArePinned; a failure on one image no longer aborts the rest

  • Cache-correctness test — new TestGetActionPins_CacheCorrectnessOnRepeatedCalls directly asserts that repeated getActionPins() calls return equal data

  • assert.PanicsWithValue in panic tests — both TestFormatPinnedActionReference_PanicsWhenSHAIsEmpty and TestLoadActionPinsData_PanicsWhenEntrySHAIsEmpty now assert the exact panic string, catching silent message changes:

    assert.PanicsWithValue(t,
        "FormatPinnedActionReference called with empty SHA for repo=ruby/setup-ruby version=v1.319.0 — ...",
        func() { FormatPinnedActionReference("ruby/setup-ruby", "", "v1.319.0") }, "...")
  • Strict-mode exact-match subtestTestResolveActionPinFromHardcodedPins_StrictModeNoFallback gains a second subtest verifying an exact version match still resolves when StrictMode: true, covering the previously untested branch

  • Table-driven TestResolveExactHardcodedPin — four separate TestResolveExactHardcodedPin_* top-level functions (BySHA, ByVersion, NoMatch, VersionTakesPrecedenceOverSHA) consolidated into one name/pins/repo/version/isSHA/wantOK/wantContains table, consistent with the style used elsewhere in the file

  • assert.Equal replaces per-key loop in TestInitWarnings_InitializesAndPreservesMap's "preserves existing" subtest for a single clear diff on failure

  • Comment banners added for resolveExactHardcodedPin and resolveNonStrictHardcodedPin sections for visual grouping

- Move countingResolver helper type near imports for readability
- Convert TestGetContainerPin_DefaultMCPImagesArePinned to use t.Run per image
- Add TestGetActionPins_CacheCorrectnessOnRepeatedCalls for cache validation
- Use assert.PanicsWithValue in both panic tests to catch message regressions
- Replace inner loop with assert.Equal in TestInitWarnings_InitializesAndPreservesMap
- Add strict-mode exact-match subtest to StrictModeNoFallback test
- Consolidate four TestResolveExactHardcodedPin_* into one table-driven test
- Add comment banners for resolveExactHardcodedPin and resolveNonStrictHardcodedPin sections

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Improve test quality in pkg/actionpins/actionpins_internal_test.go test(actionpins): improve test quality in actionpins_internal_test.go Jul 28, 2026
Copilot AI requested a review from pelikhan July 28, 2026 18:59
@pelikhan
pelikhan marked this pull request as ready for review July 28, 2026 19:34
Copilot AI review requested due to automatic review settings July 28, 2026 19:34

Copilot AI 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.

Pull request overview

Improves actionpins unit-test organization, diagnostics, and branch coverage.

Changes:

  • Adds stronger panic and strict-mode assertions.
  • Introduces subtests and a cache-consistency test.
  • Consolidates exact-pin tests into a table-driven suite.
Show a summary per file
File Description
pkg/actionpins/actionpins_internal_test.go Refactors and expands internal action-pin tests.

Review details

Tip

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

  • Files reviewed: 1/1 changed files
  • Comments generated: 2
  • Review effort level: Medium

Comment on lines +171 to +172
existing := map[string]bool{"actions/checkout@v5": true}
ctx := &PinContext{Warnings: existing}
Comment on lines +554 to +566
{
// When isAlreadySHA=false, only the version-match path runs; the SHA-match loop is
// skipped entirely. This case uses a pin whose Version and SHA fields are identical
// to make the path selection explicit: the version loop matches and returns before
// the SHA loop would ever execute.
name: "version-match path takes precedence over SHA path when isAlreadySHA=false",
pins: []ActionPin{{Repo: "actions/checkout", Version: "sha-v5", SHA: "sha-v5"}},
repo: "actions/checkout",
version: "sha-v5",
isSHA: false,
wantOK: true,
wantContains: []string{"sha-v5"},
},
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

@github-actions github-actions Bot 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.

Review: test quality improvements in actionpins

Good overall improvements — table-driven tests, subtests for better isolation, and tighter panic-value assertions. Two existing inline comments (lines 172 and 566) flag real issues; one new comment added at line 460.

Summary of findings:

  • Line 172 (existing): existing and ctx.Warnings point to the same map — an in-place mutation would make the equality assertion pass spuriously. Use a separate expected map.
  • Line 460 (new): strict-mode subtest couples to live embedded data via GetLatestActionPinByRepo; use a synthetic fixture to keep it deterministic.
  • Line 566 (existing): the "version-match takes precedence" case is not actually distinguishable with identical Version/SHA fields and isSHA=false — the SHA loop never runs, so the comment's claim cannot be verified.

No blocking issues; all are non-blocking suggestions to strengthen test correctness.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 · 30.2 AIC · ⌖ 5.31 AIC · ⊞ 5K

Comments that could not be inline-anchored

pkg/actionpins/actionpins_internal_test.go:460

Non-blocking: This subtest calls GetLatestActionPinByRepo(&quot;actions/checkout&quot;) against live embedded data. If that action is removed from the embedded pins the require.True skip will hide the subtest entirely. Consider using a synthetic ActionPin fixture instead, keeping the test self-contained and deterministic.

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 83/100 — Excellent

Analyzed 11 test scenarios: 10 design, 1 implementation, 0 violation(s).

📊 Metrics (11 tests)
Metric Value
Analyzed 11 (Go: 11, JS: 0)
✅ Design 10 (91%)
⚠️ Implementation 1 (9%)
Edge/error coverage 6 (55%)
Duplicate clusters 0
Inflation No
🚨 Violations 0
Change File Classification Impact
Panic message verification (×2) actionpins_internal_test.go:119, 153 design_test high_value
Table-driven consolidation (4→1) actionpins_internal_test.go:518 design_test high_value
New cache correctness test actionpins_internal_test.go:429 design_test high_value
Strict mode subtest split actionpins_internal_test.go:492 design_test high_value
SubTest wrapping actionpins_internal_test.go:419 implementation_test medium_value
Warning map simplification actionpins_internal_test.go:170 implementation_test medium_value
✅ Key Strengths
  1. Panic assertions upgraded to PanicsWithValue — Now verify exact error messages, not just that panic occurred. Tests TestLoadActionPinsData_PanicsWhenEntrySHAIsEmpty and TestFormatPinnedActionReference_PanicsWhenSHAIsEmpty now enforce behavioral contracts.

  2. Table-driven consolidation — Merged 4 separate tests (TestResolveExactHardcodedPin_BySHA, ByVersion, NoMatch, VersionTakesPrecedence) into 1 parameterized test covering SHA path, version path, no-match, and path-selection logic. Eliminates duplication while expanding coverage.

  3. Cache correctness design test — New TestGetActionPins_CacheCorrectnessOnRepeatedCalls enforces that repeated calls to getActionPins() return equal data, protecting against mutation/caching bugs.

  4. Strict mode behavior clarity — Split TestResolveActionPinFromHardcodedPins_StrictModeNoFallback into two focused subtests: no fallback on mismatch, and exact version resolution. Added positive case using embedded pin data (GetLatestActionPinByRepo).

  5. SubTest isolation — Wrapped TestGetContainerPin_DefaultMCPImagesArePinned loop iterations in t.Run(image, ...) for clearer failure reporting and test parallelization.

Verdict

passed. 9% implementation tests (threshold: 30%). No violations. 91% design tests with strong edge-case coverage (SHA paths, version matching, precedence logic, caching, exact error messages).

🧪 Test quality analysis by Test Quality Sentinel · haiku45 · 21.3 AIC · ⌖ 9.66 AIC · ⊞ 8.1K ·
Comment /review to run again

@github-actions github-actions Bot 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.

✅ Test Quality Sentinel: 83/100. 9% implementation tests (threshold: 30%).

@github-actions

Copy link
Copy Markdown
Contributor

Design Decision Gate — ADR Required

This PR makes significant changes to core test logic (116 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

Draft ADR committed: docs/adr/48707-strengthen-actionpins-test-conventions.md — review and complete it before merging.

This PR cannot merge until an ADR is linked in the PR body.

What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-48707: Strengthen Test Conventions in actionpins_internal_test.go

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.

Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 0042-use-postgresql.md for PR #42).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 · 57.6 AIC · ⌖ 9.6 AIC · ⊞ 8.5K ·
Comment /review to run again

@github-actions github-actions Bot 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.

Skills-Based Review 🧠

Applied /tdd — requesting changes on three test correctness issues.

📋 Key Themes & Highlights

Issues Found

  • Map aliasing (line 177): existing and ctx.Warnings point to the same map; mutations to one silently affect the other, making the assertion vacuous.
  • Cache isolation not tested (line 434): TestGetActionPins_CacheCorrectnessOnRepeatedCalls only checks value equality, not that the cache returns independent copies; a mutable-reference regression would pass.
  • Ambiguous precedence test (line 566): The "version takes precedence" table row uses equal Version and SHA values, so both paths produce the same output and the test cannot distinguish a regression.

Positive Highlights

  • ✅ Converting the for loop to t.Run subtests in TestGetContainerPin_DefaultMCPImagesArePinned is a clear improvement — failure isolation is now correct.
  • assert.PanicsWithValue upgrades are well-targeted and will catch silent message drift.
  • ✅ Consolidating four TestResolveExactHardcodedPin_* functions into one table test improves readability and maintainability significantly.
  • ✅ Moving countingResolver to the top of the file makes the helper's scope immediately clear.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · sonnet46 · 33.5 AIC · ⌖ 4.99 AIC · ⊞ 6.7K
Comment /matt to run again

r.called++
return "", nil
require.NotEmpty(t, first, "Expected at least one action pin in embedded data")
assert.Equal(t, first, second, "Expected repeated calls to getActionPins() to return equal data (cache correctness)")

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.

[/tdd] assert.Equal(first, second) confirms the returned values are equal but does not verify cache isolation — if getActionPins() returns the same backing slice, a mutation to first silently corrupts second and the test still passes.

💡 Suggested addition

Add a mutation-isolation check:

if len(first) > 0 {
    original := first[0]
    first[0].SHA = "tampered"
    second2 := getActionPins()
    assert.Equal(t, original.SHA, second2[0].SHA,
        "cache must not expose a mutable reference")
    first[0] = original
}

Alternatively, document that callers must treat the returned slice as read-only.

@copilot please address this.

for warning := range existing {
assert.True(t, ctx.Warnings[warning], "Expected warning %q to be preserved", warning)
}
assert.Equal(t, existing, ctx.Warnings, "Expected existing warnings to be preserved unchanged")

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.

[/tdd] existing and ctx.Warnings now reference the same map, so any in-place mutation (e.g. initWarnings clearing or replacing the map) would also update existing, making the assertion trivially true and hiding regressions.

💡 Suggested fix

Pass a copy so the expected and actual values are independent:

existing := map[string]bool{"actions/checkout@v5": true}
ctx := &PinContext{Warnings: map[string]bool{"actions/checkout@v5": true}}

initWarnings(ctx)

require.NotNil(t, ctx.Warnings)
assert.Equal(t, existing, ctx.Warnings)

@copilot please address this.

isSHA: false,
wantOK: true,
wantContains: []string{"sha-v5"},
},

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.

[/tdd] The "version-match path takes precedence" case uses Version: "sha-v5", SHA: "sha-v5" with isSHA: false, but since both fields are identical, a regression that accidentally runs the SHA-match path would still produce the same result and go undetected.

💡 Suggested fix

Use distinct values for Version and SHA so the two paths are distinguishable:

{
    name:         "version-match path takes precedence over SHA path when isAlreadySHA=false",
    pins:         []ActionPin{{Repo: "actions/checkout", Version: "v5.0.0", SHA: "sha-v5"}},
    repo:         "actions/checkout",
    version:      "v5.0.0",
    isSHA:        false,
    wantOK:       true,
    wantContains: []string{"sha-v5", "v5.0.0"},
},

This ensures the test fails if the SHA-match path runs instead of the version-match path.

@copilot please address this.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

- TestInitWarnings: build expected map independently of ctx.Warnings
  to prevent aliased-map false passes on in-place mutations
- TestResolveExactHardcodedPin: replace isSHA=false precedence row with
  isSHA=true and two distinct pins so the test can distinguish which
  code path (version vs SHA loop) was actually taken

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 28, 2026

Copy link
Copy Markdown
Contributor Author

@copilot run pr-finisher skill

Addressed both reviewer issues in the latest commit:

  1. Map aliasing (TestInitWarnings)expected is now built as an independent literal instead of pointing to ctx.Warnings, so an in-place mutation to the map can no longer silently satisfy the equality assertion.

  2. Precedence test (TestResolveExactHardcodedPin) — replaced the isSHA=false row with isSHA=true and two distinct pins: pin[0] has Version=="sha-token" with SHA="real-sha-version-match", and pin[1] has SHA=="sha-token" with Version="v4.0.0". Since both loops are now eligible, only a correct version-first implementation returns "real-sha-version-match"; a SHA-first regression would return something containing "v4.0.0" instead.

@pelikhan
pelikhan merged commit e9a0012 into main Jul 28, 2026
29 checks passed
@pelikhan
pelikhan deleted the copilot/testify-expert-improve-test-quality-one-more-time branch July 28, 2026 20:19
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.5

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.

[testify-expert] Improve Test Quality: pkg/actionpins/actionpins_internal_test.go

3 participants