Skip to content

[testify-expert] Improve Test Quality: pkg/actionpins/spec_test.go #43854

Description

@github-actions

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

  • Add TestSpec_PublicAPI_ResolveActionPin_UnknownSHAPassthrough
  • Add TestSpec_PublicAPI_ResolveActionPin_InvalidMappingSkipped
  • Add TestSpec_PublicAPI_ResolveLatestActionPin_FallbackOnEnforceError
  • Add context-propagation test with a context-capturing resolver stub
  • Upgrade assert.NotEmptyrequire.NotEmpty before chained content assertions (3 tests)
  • Refactor TestSpec_PublicAPI_ResolveActionPin_EnforcePinned into a table-driven test
  • Replace done chan with sync.WaitGroup in the thread-safety test
  • Remove or replace struct-only tests for ActionPin, ActionYAMLInput, ContainerPin, ResolutionFailure

Validate with: go test ./pkg/actionpins/... -v -count=1

Generated by 🧪 Daily Testify Uber Super Expert · 75.4 AIC · ⌖ 10.6 AIC · ⊞ 5.2K ·

  • expires on Jul 8, 2026, 10:41 AM UTC-08:00

Metadata

Metadata

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions