Overview
File: pkg/actionpins/spec_test.go
Paired source: pkg/actionpins/actionpins.go
Test functions: 29 top-level | LOC: 711 test / 514 source
Strengths
- Good
require.*/assert.* separation; clean testSHAResolver stub; honest SPEC_MISMATCH annotations; solid table-driven coverage for pure functions; thread-safety test for sync.Once.
1. Missing / High-Value Tests
1a. SHA-as-version fallback (source lines 330–333) — untested
When an unknown 40-char SHA is passed as version, the source returns "repo@<sha> # <sha>". No test covers this path.
func TestSpec_PublicAPI_ResolveActionPin_UnknownSHAPassthrough(t *testing.T) {
unknownSHA := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
result, err := actionpins.ResolveActionPin("actions/checkout", unknownSHA, nil)
require.NoError(t, err)
expected := actionpins.FormatPinnedActionReference("actions/checkout", unknownSHA, unknownSHA)
assert.Equal(t, expected, result, "unknown SHA should produce repo@sha # sha")
}
1b. Invalid mapping value silently skipped (source lines 497–500) — untested
A malformed mapping value (e.g. missing @, empty repo) is logged and skipped. No test verifies resolution falls through unchanged.
func TestSpec_PublicAPI_ResolveActionPin_InvalidMappingSkipped(t *testing.T) {
ctx := &actionpins.PinContext{
Warnings: make(map[string]bool),
Mappings: map[string]string{"actions/checkout@v4": "actions/checkout"}, // no `@version`
}
result, err := actionpins.ResolveActionPin("actions/checkout", "v4", ctx)
require.NoError(t, err, "invalid mapping should not cause an error")
assert.Contains(t, result, "actions/checkout@", "invalid mapping should leave resolution unchanged")
}
1c. ResolveLatestActionPin error-path fallback (source lines 474–476) — untested
When ResolveActionPin errors (e.g. EnforcePinned=true, unknown repo), ResolveLatestActionPin falls back to getLatestActionPinReference. No test verifies this.
func TestSpec_PublicAPI_ResolveLatestActionPin_FallbackOnEnforceError(t *testing.T) {
ctx := &actionpins.PinContext{EnforcePinned: true, Warnings: make(map[string]bool)}
result := actionpins.ResolveLatestActionPin("does-not-exist/x", ctx)
assert.Empty(t, result, "unknown repo with enforce mode should return empty from fallback")
}
1d. PinContext.Ctx propagated to resolver — untested
The documented nil-fallback to context.Background() path is exercised by other tests, but no test verifies that a non-nil (e.g. already-cancelled) context is forwarded to the resolver.
2. Testify Assertion Upgrades
2a. Upgrade assert.NotEmpty → require.NotEmpty before chained content assertions
In TestSpec_PublicAPI_ResolveActionPin_DynamicHappyPath, TestSpec_PublicAPI_ResolveActionPin_EmbeddedMatch, and the TestSpec_DynamicResolution_VersionCommentConsistency sub-tests, assert.NotEmpty(result) is followed by assert.Contains(result, ...). If result is empty, the Contains assertions produce confusing noise.
// Before:
assert.NotEmpty(t, result, "should return a non-empty pinned reference when resolver succeeds")
assert.Contains(t, result, latestPin.SHA, ...)
// After:
require.NotEmpty(t, result, "should return a non-empty pinned reference when resolver succeeds")
assert.Contains(t, result, latestPin.SHA, ...)
3. Table-Driven Refactors
3a. Consolidate TestSpec_PublicAPI_ResolveActionPin_EnforcePinned sub-tests
The 3 sub-tests each build a PinContext nearly identically. Extract into a table with fields: resolver, allowActionRefs, wantErr, wantErrContains, wantFailureType, wantFailureCount.
4. Organization / Readability
4a. Thread-safety test: use sync.WaitGroup instead of manual channel
// Before: manual done channel
done := make(chan int, goroutines)
for i := range goroutines {
go func(idx int) { results[idx] = ...; done <- idx }(i)
}
for range goroutines { <-done }
// After: idiomatic
var wg sync.WaitGroup
for i := range goroutines {
wg.Add(1)
go func(idx int) { defer wg.Done(); results[idx] = ... }(i)
}
wg.Wait()
4b. Consider removing struct-field-only tests
TestSpec_Types_ActionPin, TestSpec_Types_ActionYAMLInput, TestSpec_Types_ContainerPin, and TestSpec_Types_ResolutionFailure only construct structs and read field values. They test nothing behavioral and will require updates on any field rename. Replace with compile-time assertions or remove.
Acceptance Checklist
Validate with: go test ./pkg/actionpins/... -v -count=1
Generated by 🧪 Daily Testify Uber Super Expert · 75.4 AIC · ⌖ 10.6 AIC · ⊞ 5.2K · ◷
Overview
File:
pkg/actionpins/spec_test.goPaired source:
pkg/actionpins/actionpins.goTest functions: 29 top-level | LOC: 711 test / 514 source
Strengths
require.*/assert.*separation; cleantestSHAResolverstub; honestSPEC_MISMATCHannotations; solid table-driven coverage for pure functions; thread-safety test forsync.Once.1. Missing / High-Value Tests
1a. SHA-as-version fallback (source lines 330–333) — untested
When an unknown 40-char SHA is passed as
version, the source returns"repo@<sha> # <sha>". No test covers this path.1b. Invalid mapping value silently skipped (source lines 497–500) — untested
A malformed mapping value (e.g. missing
@, empty repo) is logged and skipped. No test verifies resolution falls through unchanged.1c.
ResolveLatestActionPinerror-path fallback (source lines 474–476) — untestedWhen
ResolveActionPinerrors (e.g.EnforcePinned=true, unknown repo),ResolveLatestActionPinfalls back togetLatestActionPinReference. No test verifies this.1d.
PinContext.Ctxpropagated to resolver — untestedThe documented nil-fallback to
context.Background()path is exercised by other tests, but no test verifies that a non-nil (e.g. already-cancelled) context is forwarded to the resolver.2. Testify Assertion Upgrades
2a. Upgrade
assert.NotEmpty→require.NotEmptybefore chained content assertionsIn
TestSpec_PublicAPI_ResolveActionPin_DynamicHappyPath,TestSpec_PublicAPI_ResolveActionPin_EmbeddedMatch, and theTestSpec_DynamicResolution_VersionCommentConsistencysub-tests,assert.NotEmpty(result)is followed byassert.Contains(result, ...). Ifresultis empty, theContainsassertions produce confusing noise.3. Table-Driven Refactors
3a. Consolidate
TestSpec_PublicAPI_ResolveActionPin_EnforcePinnedsub-testsThe 3 sub-tests each build a
PinContextnearly identically. Extract into a table with fields:resolver,allowActionRefs,wantErr,wantErrContains,wantFailureType,wantFailureCount.4. Organization / Readability
4a. Thread-safety test: use
sync.WaitGroupinstead of manual channel4b. Consider removing struct-field-only tests
TestSpec_Types_ActionPin,TestSpec_Types_ActionYAMLInput,TestSpec_Types_ContainerPin, andTestSpec_Types_ResolutionFailureonly construct structs and read field values. They test nothing behavioral and will require updates on any field rename. Replace with compile-time assertions or remove.Acceptance Checklist
TestSpec_PublicAPI_ResolveActionPin_UnknownSHAPassthroughTestSpec_PublicAPI_ResolveActionPin_InvalidMappingSkippedTestSpec_PublicAPI_ResolveLatestActionPin_FallbackOnEnforceErrorassert.NotEmpty→require.NotEmptybefore chained content assertions (3 tests)TestSpec_PublicAPI_ResolveActionPin_EnforcePinnedinto a table-driven testdone chanwithsync.WaitGroupin the thread-safety testActionPin,ActionYAMLInput,ContainerPin,ResolutionFailureValidate with:
go test ./pkg/actionpins/... -v -count=1