Overview
File: pkg/actionpins/spec_test.go
Source pair: pkg/actionpins/actionpins.go
Test functions: 28 top-level Test* functions (many with subtests)
LOC: 778 (test) / 514 (source)
Testify imports: both assert and require present
Strengths
- Good use of table-driven tests for pure-function cases (
ExtractRepo, ExtractVersion, FormatPinnedActionReference, FormatCacheKey).
- Context propagation test (
TestSpec_PublicAPI_ResolveActionPin_UsesProvidedContext) is excellent — verifies exact pointer identity with assert.Same.
- Concurrent safety test uses goroutines +
sync.WaitGroup correctly.
testSHAResolver helper is clean and captures call arguments for assertion.
Prioritized Improvements
1. Missing / high-value tests
Details
These exported functions/paths in actionpins.go have no test coverage in spec_test.go:
| Function / Path |
Risk |
formatPinnedActionWithResolution (internal, but exercised via ResolveActionPin) — the two-version comment format (v4 → v4.1.2) is tested indirectly but never in a dedicated case |
Medium |
applyActionPinMapping with a mapped version that exists in embedded pins but the original does not — no negative mapping miss test |
Medium |
GetContainerPin — only 2 subtests; no test for a container whose PinnedImage does not contain its Digest (format contract) |
Low |
ResolveActionPin with ctx.Ctx == nil (zero-value PinContext) — resolver is called with context.Background() fallback path |
Low |
Highest-value addition — applyActionPinMapping negative path:
// Before (no test):
// applyActionPinMapping falls through silently when mapping target is unparseable.
// After:
func TestSpec_PublicAPI_ResolveActionPin_MappingTargetUnknown(t *testing.T) {
ctx := &actionpins.PinContext{
Warnings: make(map[string]bool),
Mappings: map[string]string{
"actions/setup-node@v4": "does-not-exist/missing@v99",
},
}
result, err := actionpins.ResolveActionPin("actions/setup-node", "v4", ctx)
require.NoError(t, err)
assert.Empty(t, result, "mapping to an unknown repo should yield an empty pin, not a panic")
}
2. Testify assertion upgrades
Details
Several assert.* calls should be require.* because a failure makes the subsequent assertions meaningless:
| Location |
Current |
Should Be |
Reason |
TestSpec_PublicAPI_ResolveActionPin_EnforcePinned L310 |
require.Len(t, failures, ...) |
already require ✓ |
— |
TestSpec_PublicAPI_RecordResolutionFailure_WarningDedup L710 |
require.Len(t, failures, 1, ...) |
already require ✓ |
— |
TestSpec_PublicAPI_ResolveActionPin_EmbeddedMatch L470 |
require.NotEmpty(t, result, ...) |
already require ✓ |
— |
TestSpec_PublicAPI_GetContainerPin L547 |
require.NotEmpty(t, pin.Digest, ...) |
already require ✓ |
— |
TestSpec_PublicAPI_ResolveActionPin_AppliesMapping L727 |
require.NotEmpty(t, checkoutPins, ...) |
already require ✓ |
— |
TestBuildByRepoIndex_GroupsByRepoAndSortsDescending (internal test, L27-28) |
assert.Equal for index 0 and 1 |
require for index assertions after require.Len |
If Len passes but the slice is nil, index access panics |
The internal test file actionpins_internal_test.go uses assert.Equal to index into slices after require.Len. This is safe only because require.Len halts the test on failure, but the index assertions should also be require to be explicit:
// Before (actionpins_internal_test.go ~L27):
assert.Equal(t, "v5.0.0", byRepo["actions/checkout"][0].Version, ...)
assert.Equal(t, "v4.0.0", byRepo["actions/checkout"][1].Version, ...)
// After:
require.Equal(t, "v5.0.0", byRepo["actions/checkout"][0].Version, ...)
require.Equal(t, "v4.0.0", byRepo["actions/checkout"][1].Version, ...)
This prevents a confusing index-out-of-bounds panic if the sort order invariant breaks.
3. Table-driven refactors
Details
TestSpec_PublicAPI_ResolveActionPin_EnforcePinned already uses a table — good. However, the four TestSpec_PublicAPI_ResolveLatestActionPin* tests share identical setup (fetching latestPin for actions/checkout) but are split into separate top-level functions. Consolidating into one table-driven test reduces boilerplate and setup duplication:
// Before: two separate top-level tests each calling GetLatestActionPinByRepo independently.
// After (sketch):
func TestSpec_PublicAPI_ResolveLatestActionPin(t *testing.T) {
latestPin, ok := actionpins.GetLatestActionPinByRepo("actions/checkout")
require.True(t, ok)
tests := []struct{ name string; repo string; ctx *actionpins.PinContext; want string }{
{ name: "known repo nil ctx", repo: "actions/checkout", ctx: nil, want: actionpins.FormatPinnedActionReference(...) },
{ name: "unknown repo nil ctx", repo: "does-not-exist/x", ctx: nil, want: "" },
{ name: "known repo non-nil ctx", ... },
}
for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ... }) }
}
4. Organization / readability
Details
- SPEC_MISMATCH comments on lines 184 and 217 document real implementation divergences from the spec. These should either be filed as separate issues or resolved — leaving them as inline comments risks them going stale and misleading future readers.
testSHAResolver placement: the helper type (L474-488) is declared in the middle of the file, between two test functions. Move it to the top of the file (after the const block) or to a helpers_test.go file so it is easy to discover.
- Test function naming:
TestSpec_PublicAPI_* is verbose. The Spec_PublicAPI_ prefix is redundant when the package is already actionpins_test — consider TestResolveActionPin_NilContext instead of TestSpec_PublicAPI_ResolveActionPin_NilContext.
Acceptance Checklist
Generated by 🧪 Daily Testify Uber Super Expert · 40 AIC · ⌖ 12.9 AIC · ⊞ 5.1K · ◷
Overview
File:
pkg/actionpins/spec_test.goSource pair:
pkg/actionpins/actionpins.goTest functions: 28 top-level
Test*functions (many with subtests)LOC: 778 (test) / 514 (source)
Testify imports: both
assertandrequirepresentStrengths
ExtractRepo,ExtractVersion,FormatPinnedActionReference,FormatCacheKey).TestSpec_PublicAPI_ResolveActionPin_UsesProvidedContext) is excellent — verifies exact pointer identity withassert.Same.sync.WaitGroupcorrectly.testSHAResolverhelper is clean and captures call arguments for assertion.Prioritized Improvements
1. Missing / high-value tests
Details
These exported functions/paths in
actionpins.gohave no test coverage inspec_test.go:formatPinnedActionWithResolution(internal, but exercised viaResolveActionPin) — the two-version comment format (v4 → v4.1.2) is tested indirectly but never in a dedicated caseapplyActionPinMappingwith a mapped version that exists in embedded pins but the original does not — no negative mapping miss testGetContainerPin— only 2 subtests; no test for a container whosePinnedImagedoes not contain itsDigest(format contract)ResolveActionPinwithctx.Ctx == nil(zero-valuePinContext) — resolver is called withcontext.Background()fallback pathHighest-value addition —
applyActionPinMappingnegative path:2. Testify assertion upgrades
Details
Several
assert.*calls should berequire.*because a failure makes the subsequent assertions meaningless:TestSpec_PublicAPI_ResolveActionPin_EnforcePinnedL310require.Len(t, failures, ...)require✓TestSpec_PublicAPI_RecordResolutionFailure_WarningDedupL710require.Len(t, failures, 1, ...)require✓TestSpec_PublicAPI_ResolveActionPin_EmbeddedMatchL470require.NotEmpty(t, result, ...)require✓TestSpec_PublicAPI_GetContainerPinL547require.NotEmpty(t, pin.Digest, ...)require✓TestSpec_PublicAPI_ResolveActionPin_AppliesMappingL727require.NotEmpty(t, checkoutPins, ...)require✓TestBuildByRepoIndex_GroupsByRepoAndSortsDescending(internal test, L27-28)assert.Equalfor index 0 and 1requirefor index assertions afterrequire.LenLenpasses but the slice is nil, index access panicsThe internal test file
actionpins_internal_test.gousesassert.Equalto index into slices afterrequire.Len. This is safe only becauserequire.Lenhalts the test on failure, but the index assertions should also berequireto be explicit:This prevents a confusing index-out-of-bounds panic if the sort order invariant breaks.
3. Table-driven refactors
Details
TestSpec_PublicAPI_ResolveActionPin_EnforcePinnedalready uses a table — good. However, the fourTestSpec_PublicAPI_ResolveLatestActionPin*tests share identical setup (fetchinglatestPinforactions/checkout) but are split into separate top-level functions. Consolidating into one table-driven test reduces boilerplate and setup duplication:4. Organization / readability
Details
testSHAResolverplacement: the helper type (L474-488) is declared in the middle of the file, between two test functions. Move it to the top of the file (after theconstblock) or to ahelpers_test.gofile so it is easy to discover.TestSpec_PublicAPI_*is verbose. TheSpec_PublicAPI_prefix is redundant when the package is alreadyactionpins_test— considerTestResolveActionPin_NilContextinstead ofTestSpec_PublicAPI_ResolveActionPin_NilContext.Acceptance Checklist
TestSpec_PublicAPI_ResolveActionPin_MappingTargetUnknown(mapping to unknown repo yields empty result, no panic).applyActionPinMappingwith a target that lacks@separator.assert.Equalcalls inactionpins_internal_test.gotorequire.Equalafterrequire.Len.testSHAResolverdeclaration to the top ofspec_test.go(afterconstblock).SPEC_MISMATCHcomments (lines 184, 217).make test-unitand confirm all tests pass with no regressions.