Skip to content

[actionpins] Add missing internal coverage and table-drive action pin mapping tests - #47206

Merged
pelikhan merged 3 commits into
mainfrom
copilot/testify-expert-improve-test-quality-yet-again
Jul 22, 2026
Merged

[actionpins] Add missing internal coverage and table-drive action pin mapping tests#47206
pelikhan merged 3 commits into
mainfrom
copilot/testify-expert-improve-test-quality-yet-again

Conversation

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

This issue asked for targeted quality improvements in pkg/actionpins/actionpins_internal_test.go plus a small readability tweak in spec_test.go. The suite was already strong; this change closes the remaining high-value gaps and consolidates duplicated mapping tests.

  • Missing internal coverage added

    • TestGetLatestActionPinReference_ReturnsFormattedReferenceOrEmpty
    • TestRecordPinResolutionFailure_NilSafety
    • TestLogDynamicResolutionSkipped_NoResolverBranch
    • TestLoadActionPinsData_LoadsContainerPins
  • applyActionPinMapping tests consolidated

    • Replaced multiple TestApplyActionPinMapping_* functions with one table-driven TestApplyActionPinMapping.
    • Preserves prior scenarios: no mapping, exact mapping, exact-match-only behavior, deduped mapping notification, invalid mapping value, and missing @ref in mapping target.
  • Spec-test package docs

    • Added a package-level comment to pkg/actionpins/spec_test.go for clearer test-module intent.
func TestApplyActionPinMapping(t *testing.T) {
	tests := []struct {
		name                    string
		actionRepo              string
		version                 string
		mappings                map[string]string
		repeat                  int
		wantRepo                string
		wantVersion             string
		wantMappingNotification bool
		wantMapNotificationKeys int
	}{
		// no mapping, exact mapping, invalid target, dedupe, etc.
	}
	// ...
}

…ng tests

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Improve test quality for actionpins_internal_test.go [actionpins] Add missing internal coverage and table-drive action pin mapping tests Jul 22, 2026
Copilot AI requested a review from pelikhan July 22, 2026 06:25
@github-actions

Copy link
Copy Markdown
Contributor

🤖 PR Triage

Field Value
Category test
Risk 🟢 Low
Score 42/100 (impact: 20, urgency: 10, quality: 12)
Action auto_merge

Pure test improvement — adds missing internal coverage and consolidates table-driven tests in pkg/actionpins. No production code changes. Auto-merge when CI passes.

Generated by 🔧 PR Triage Agent · sonnet46 35.2 AIC · ⌖ 7.92 AIC · ⊞ 5.6K ·

@pelikhan
pelikhan marked this pull request as ready for review July 22, 2026 07:25
Copilot AI review requested due to automatic review settings July 22, 2026 07:25
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

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

Adds targeted internal test coverage and simplifies action-pin mapping tests.

Changes:

  • Covers container loading, latest-pin formatting, skipped resolution, and failure recording.
  • Consolidates mapping scenarios into a table-driven test.
  • Documents the black-box specification test package.
Show a summary per file
File Description
pkg/actionpins/actionpins_internal_test.go Adds coverage and consolidates mapping tests.
pkg/actionpins/spec_test.go Adds the package documentation comment.

Review details

Tip

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

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

@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.

The changes are well-structured: new tests cover previously untested paths (loadActionPinsData container field, getLatestActionPinReference, logDynamicResolutionSkipped, recordPinResolutionFailure nil safety), and the applyActionPinMapping consolidation improves maintainability without losing any scenario. The require.NotEmpty guard in TestGetLatestActionPinReference_ReturnsFormattedReferenceOrEmpty makes the embedded-data dependency explicit and safe. No actionable issues found.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · sonnet46 11.1 AIC · ⌖ 7.91 AIC · ⊞ 5K

@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 — commenting with minor improvement suggestions; no blocking issues.

📋 Key Themes & Highlights

Positive Highlights

  • ✅ Table-driven consolidation of TestApplyActionPinMapping_* is clean and well-structured — six cases, clear field names, all prior scenarios preserved
  • TestRecordPinResolutionFailure_NilSafety covers three distinct nil-safety scenarios with correct use of sub-tests
  • TestLoadActionPinsData_LoadsContainerPins uses a self-contained inline fixture — no external dependency
  • ✅ Package-level doc comment on spec_test.go adds useful orientation for new contributors

Minor Issues

  1. pins[0] coupling in TestGetLatestActionPinReference — assumes sort order without documenting the contract; a future re-ordering silently breaks the assertion
  2. TestLogDynamicResolutionSkipped_NoResolverBranch is incomplete — only tests the no-resolver branch; the resolver-present path is uncovered
  3. repeat: 0 zero-value ambiguity — defaults to 1 via max(tt.repeat, 1), but 0 reads as "run zero times", which could confuse future contributors

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


result := getLatestActionPinReference("actions/checkout")
assert.Equal(t, FormatPinnedActionReference("actions/checkout", pins[0].SHA, pins[0].Version), result)
})

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] pins[0] coupling may assert the wrong value — getLatestActionPinReference implies latest, but the test assumes pins[0] is the latest without verifying sort order.

💡 Suggestion

If GetActionPinsByRepo guarantees a stable order (e.g., newest-first), document that assumption here. Otherwise, assert properties of the result rather than coupling to index 0.

// assert it is a non-empty, well-formed reference string instead of pinning to pins[0]
assert.NotEmpty(t, result)
assert.Contains(t, result, "actions/checkout@")

A future re-ordering of embedded pins would silently produce a wrong assertion.

@copilot please address this.

}

func TestLogDynamicResolutionSkipped_NoResolverBranch(t *testing.T) {
assert.NotPanics(t, func() {

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] TestLogDynamicResolutionSkipped_NoResolverBranch only tests (false, false) — the function has a second branch (the resolver-present path) that is untested here.

💡 Suggestion

Add a sub-test for the resolver-present branch to ensure both code paths are exercised:

t.Run("no resolver — does not panic", func(t *testing.T) {
    assert.NotPanics(t, func() { logDynamicResolutionSkipped(false, false) })
})
t.Run("with resolver — does not panic", func(t *testing.T) {
    assert.NotPanics(t, func() { logDynamicResolutionSkipped(true, false) })
})

Without this, the resolver-present branch remains uncovered by the new test.

@copilot please address this.

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &PinContext{
Warnings: make(map[string]bool),

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 table-driven TestApplyActionPinMapping uses repeat only for the deduplication case but silently defaults to 1 via max(tt.repeat, 1) for all others — a zero repeat in a struct literal is easy to misread as "run 0 times".

💡 Suggestion

Consider making the intent explicit with a named constant or by changing the zero-value semantics:

// Option A: default clearly in the test harness comment
repeat := tt.repeat
if repeat == 0 {
    repeat = 1 // zero means "run once"
}

// Option B: use a pointer or a named field like runTimes

This is a minor readability issue, but it can confuse contributors adding future cases who might set repeat: 0 expecting no calls.

@copilot please address this.

… in actionpins

Draft ADR documenting the test consolidation and coverage gap decisions
in pkg/actionpins introduced by PR #47206.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

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

📄 Draft ADR committed: docs/adr/47206-table-driven-tests-and-internal-coverage-for-actionpins.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 could not 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-47206: Table-Driven Tests and Internal Coverage for actionpins

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

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

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., 47206-table-driven-tests-and-internal-coverage-for-actionpins.md for PR #47206).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · sonnet46 56.7 AIC · ⌖ 9.91 AIC · ⊞ 8.4K ·
Comment /review to run again

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 88/100 — Excellent

Analyzed 27 test(s): 22 design, 5 implementation, 0 violation(s).

📊 Metrics (27 tests)
Metric Value
Analyzed 27 (Go: 27, JS: 0)
✅ Design 22 (81%)
⚠️ Implementation 5 (19%)
Edge/error coverage 24 (89%)
Duplicate clusters 0
Inflation No (pure additive; no prod changes)
🚨 Violations 0
Test File Classification Issues
TestBuildByRepoIndex_GroupsByRepoAndSortsDescending actionpins_internal_test.go design_test None
TestCountPinKeyMismatches_ReturnsOnlyVersionMismatches actionpins_internal_test.go design_test None
TestCollectEntriesWithEmptySHA_ReturnsOnlyEmptySHAEntries actionpins_internal_test.go design_test None
TestLoadActionPinsData_PanicsWhenEntrySHAIsEmpty actionpins_internal_test.go design_test None
TestLoadActionPinsData_LoadsContainerPins actionpins_internal_test.go design_test None
TestFormatPinnedActionReference_PanicsWhenSHAIsEmpty actionpins_internal_test.go design_test None
TestInitWarnings_InitializesAndPreservesMap actionpins_internal_test.go design_test None
TestFormatPinnedActionWithResolution_ConsistentVersionComment actionpins_internal_test.go design_test None
TestFindCompatiblePin_SemverFallback actionpins_internal_test.go design_test None
TestFindVersionBySHA_ReturnsVersionForKnownSHA actionpins_internal_test.go design_test None
TestGetLatestActionPinReference_ReturnsFormattedReferenceOrEmpty actionpins_internal_test.go implementation_test None
TestGetContainerPin_ReturnsPinnedImage actionpins_internal_test.go design_test None
TestGetContainerPin_MCPGatewayVersionsArePinned actionpins_internal_test.go design_test None
TestGetContainerPin_DefaultMCPImagesArePinned actionpins_internal_test.go design_test None
TestResolveActionPinDynamically_SkipsForSHAInput actionpins_internal_test.go design_test None
TestLogDynamicResolutionSkipped_NoResolverBranch actionpins_internal_test.go implementation_test None
TestRecordPinResolutionFailure_NilSafety actionpins_internal_test.go design_test None
TestResolveActionPinFromHardcodedPins_StrictModeNoFallback actionpins_internal_test.go design_test None
TestResolveExactHardcodedPin_BySHA actionpins_internal_test.go implementation_test None
TestResolveExactHardcodedPin_ByVersion actionpins_internal_test.go implementation_test None
TestResolveExactHardcodedPin_NoMatch actionpins_internal_test.go implementation_test None
TestResolveExactHardcodedPin_VersionTakesPrecedenceOverSHA actionpins_internal_test.go design_test None
TestResolveNonStrictHardcodedPin_SelectsHighestCompatible actionpins_internal_test.go design_test None
TestResolveNonStrictHardcodedPin_FallsBackToHighestWhenNoCompatible actionpins_internal_test.go design_test None
TestResolveActionPinFromHardcodedPins_SkipHardcodedFallback actionpins_internal_test.go design_test None
TestApplyActionPinMapping actionpins_internal_test.go design_test None
spec_test.go (1 line added) spec_test.go design_test None

Verdict

Passed. 19% implementation tests (threshold: 30%). No violations. Tests cover panic contracts, nil safety, deduplication invariants, semver fallback semantics, SHA→version regression, and action pin mapping — all with descriptive assertion messages. Table-driven tests are well-structured with documented edge-case semantics inline.

🧪 Test quality analysis by Test Quality Sentinel · sonnet46 62.1 AIC · ⌖ 8.94 AIC · ⊞ 7K ·
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: 88/100. 19% implementation tests (threshold: 30%). No violations — build tags present, no forbidden mocks, strong behavioral coverage with descriptive assertion messages.

@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.

Two test correctness issues need fixing before merge

The consolidation to a table-driven test is a good call overall, but introduces two issues that undermine the test's effectiveness:

  1. Line 706 — false/absent ambiguity (high): ctx.Warnings[notifyKey] returns Go's zero value (false) for missing keys, so assert.Equal(t, false, ...) cannot distinguish "key never written" from "key set to false". The wantMappingNotification: false cases pass vacuously and would still pass if the production code accidentally wrote the key or if the internal key prefix silently changed.

  2. Line 699 — repeat loop hides first-iteration bugs (medium): gotRepo/gotVersion are overwritten each iteration; only the last call's output is asserted. A regression where the first mapping application returns wrong values is completely invisible.

Both are straightforward to fix (see inline comments). No other blocking issues.

🔎 Code quality review by PR Code Quality Reviewer · sonnet46 46.3 AIC · ⌖ 4.76 AIC · ⊞ 5.6K
Comment /review to run again


repo, version := applyActionPinMapping("actions/checkout", "v4", ctx)
notifyKey := "map:" + FormatCacheKey(tt.actionRepo, tt.version)
assert.Equal(t, tt.wantMappingNotification, ctx.Warnings[notifyKey], "mapping notification flag should match expected state")

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.

Absent key and explicit false are indistinguishable: assert.Equal(t, false, ctx.Warnings[notifyKey]) passes whether the key was never written or was set to false — Go returns the zero value for missing map keys. A wantMappingNotification: false test case will pass even if the production code accidentally wrote the key, hiding the bug silently.

💡 Suggested fix

Replace the single assertion with presence/absence checks:

if tt.wantMappingNotification {
    assert.True(t, ctx.Warnings[notifyKey], "expected mapping notification key to be set")
} else {
    assert.NotContains(t, ctx.Warnings, notifyKey, "expected mapping notification key to be absent")
}

There is a second latent risk here: notifyKey is constructed by mirroring the production internal prefix ("map:"). If that prefix ever changes in applyActionPinMapping, the test will still pass — the checked key is absent, zero-value is false, which matches wantMappingNotification: false — masking the regression instead of catching it.

}
var gotRepo, gotVersion string
for range repeat {
gotRepo, gotVersion = applyActionPinMapping(tt.actionRepo, tt.version, ctx)

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.

Deduplication test only validates the final iteration's return values: gotRepo and gotVersion are overwritten on every loop pass; only the last call is ever asserted. For the repeat: 2 deduplication case, a bug where the first call returns wrong values (e.g., un-mapped repo/version on the first application) is completely invisible.

💡 Suggested fix

Assert inside the loop, or capture per-iteration results separately:

for i := range repeat {
    repo, version := applyActionPinMapping(tt.actionRepo, tt.version, ctx)
    assert.Equalf(t, tt.wantRepo, repo, "iteration %d: repo mismatch", i)
    assert.Equalf(t, tt.wantVersion, version, "iteration %d: version mismatch", i)
}

This makes each iteration's output observable and will catch regressions where the idempotency of repeated calls breaks.

@pelikhan

Copy link
Copy Markdown
Collaborator

@copilot run pr-finisher skill

@pelikhan
pelikhan merged commit d4e185d into main Jul 22, 2026
23 of 24 checks passed
@pelikhan
pelikhan deleted the copilot/testify-expert-improve-test-quality-yet-again branch July 22, 2026 07:49
Copilot stopped work on behalf of pelikhan due to an error July 22, 2026 07:49
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.83.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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