From 58f549f38bf3c159145f623d14a38650225e0148 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:41:59 +0000 Subject: [PATCH 1/3] Initial plan From 0cba9f08aa752a615d648138e3b5b91b54522cef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:55:04 +0000 Subject: [PATCH 2/3] Improve actionpins spec test coverage Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/actionpins/spec_test.go | 72 +++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/pkg/actionpins/spec_test.go b/pkg/actionpins/spec_test.go index df78a7cdc8d..afc6aa6224c 100644 --- a/pkg/actionpins/spec_test.go +++ b/pkg/actionpins/spec_test.go @@ -43,11 +43,12 @@ func (r *testSHAResolver) ResolveSHA(ctx context.Context, repo, version string) // TestSpec_PublicAPI_FormatPinnedActionReference validates the documented format "repo@sha # version". func TestSpec_PublicAPI_FormatPinnedActionReference(t *testing.T) { tests := []struct { - name string - repo string - sha string - version string - expected string + name string + repo string + sha string + version string + expected string + wantPanic string }{ { name: "formats standard reference", @@ -63,10 +64,31 @@ func TestSpec_PublicAPI_FormatPinnedActionReference(t *testing.T) { version: "v5", expected: "actions/setup-go@cdabf2d4679a00bef48b5a7c69a9b8d0b4f6e3c9 # v5", }, + { + name: "formats reference with empty version comment", + repo: "actions/checkout", + sha: "abc123", + version: "", + expected: "actions/checkout@abc123 # ", + }, + { + name: "panics for empty sha", + repo: "actions/checkout", + sha: "", + version: "v4", + wantPanic: "FormatPinnedActionReference called with empty SHA for repo=actions/checkout version=v4 — this would produce invalid workflow YAML", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + if tt.wantPanic != "" { + require.PanicsWithValue(t, tt.wantPanic, func() { + actionpins.FormatPinnedActionReference(tt.repo, tt.sha, tt.version) + }) + return + } + result := actionpins.FormatPinnedActionReference(tt.repo, tt.sha, tt.version) assert.Equal(t, tt.expected, result, "FormatPinnedActionReference(%q, %q, %q) should match spec format", tt.repo, tt.sha, tt.version) }) @@ -516,15 +538,17 @@ func TestSpec_Types_ActionPinsData(t *testing.T) { // TestSpec_PublicAPI_ResolveActionPin_EmbeddedMatch validates embedded-only pin resolution returns // a formatted reference for a known repository. Spec: "Embedded-only lookup from bundled pin data" func TestSpec_PublicAPI_ResolveActionPin_EmbeddedMatch(t *testing.T) { - known := "actions/checkout" - latestPin, ok := actionpins.GetLatestActionPinByRepo(known) - require.True(t, ok, "prerequisite: known repo must be in embedded data") + t.Run("returns formatted reference for known embedded pin", func(t *testing.T) { + known := "actions/checkout" + latestPin, ok := actionpins.GetLatestActionPinByRepo(known) + require.True(t, ok, "prerequisite: known repo must be in embedded data") - ctx := &actionpins.PinContext{StrictMode: false, Warnings: make(map[string]bool)} - result, err := actionpins.ResolveActionPin(known, latestPin.Version, ctx) - require.NoError(t, err, "embedded-only ResolveActionPin should not error for known pin") - require.NotEmpty(t, result, "should return non-empty pinned reference for known embedded pin") - assert.Contains(t, result, latestPin.SHA, "resolved reference should contain the pin SHA") + ctx := &actionpins.PinContext{StrictMode: false, Warnings: make(map[string]bool)} + result, err := actionpins.ResolveActionPin(known, latestPin.Version, ctx) + require.NoError(t, err, "embedded-only ResolveActionPin should not error for known pin") + require.NotEmpty(t, result, "should return non-empty pinned reference for known embedded pin") + assert.Contains(t, result, latestPin.SHA, "resolved reference should contain the pin SHA") + }) } // TestSpec_DynamicResolution_VersionCommentConsistency validates that when dynamic resolution @@ -751,7 +775,7 @@ func TestSpec_PublicAPI_ResolveActionPin_NilCtxField(t *testing.T) { } require.NotPanics(t, func() { result, err := actionpins.ResolveActionPin("actions/checkout", "v4", ctx) - assert.NoError(t, err) + require.NoError(t, err) assert.NotEmpty(t, result) }, "nil PinContext.Ctx should fall back to context.Background() without panicking") require.NotNil(t, resolver.capturedCtx, "resolver must receive a non-nil context even when PinContext.Ctx is nil") @@ -856,6 +880,24 @@ func TestSpec_PublicAPI_ResolveActionPin_AppliesMapping(t *testing.T) { assert.True(t, ctx.Warnings["map:actions/setup-node@v4"], "mapping notification key should be set in warnings") }) + t.Run("self-mapping preserves the same resolved reference", func(t *testing.T) { + baseline, err := actionpins.ResolveActionPin("actions/checkout", "v4", &actionpins.PinContext{ + Warnings: make(map[string]bool), + }) + require.NoError(t, err) + + ctx := &actionpins.PinContext{ + Warnings: make(map[string]bool), + Mappings: map[string]string{ + "actions/checkout@v4": "actions/checkout@v4", + }, + } + result, err := actionpins.ResolveActionPin("actions/checkout", "v4", ctx) + require.NoError(t, err) + assert.Equal(t, baseline, result, "self-mapping should behave the same as the unmapped case") + assert.True(t, ctx.Warnings["map:actions/checkout@v4"], "self-mapping should still record the mapping notification key") + }) + t.Run("no mapping leaves resolution unchanged", func(t *testing.T) { ctx := &actionpins.PinContext{Warnings: make(map[string]bool)} @@ -895,7 +937,7 @@ func TestSpec_PublicAPI_ResolveActionPin_MappingTargetUnknown(t *testing.T) { require.NotPanics(t, func() { result, err := actionpins.ResolveActionPin("actions/checkout", "v4", ctx) - assert.NoError(t, err) + require.NoError(t, err) assert.Empty(t, result, "mapping to unknown repo should produce unresolved empty result") }) } From a0350d7093c125d641b2fd150329fe1ec587967b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 22 Jul 2026 19:43:12 +0000 Subject: [PATCH 3/3] test(actionpins): enforce mutually-exclusive panic/expected table fields Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/actionpins/spec_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/actionpins/spec_test.go b/pkg/actionpins/spec_test.go index afc6aa6224c..8aa34f952a8 100644 --- a/pkg/actionpins/spec_test.go +++ b/pkg/actionpins/spec_test.go @@ -83,6 +83,7 @@ func TestSpec_PublicAPI_FormatPinnedActionReference(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.wantPanic != "" { + assert.Empty(t, tt.expected, "test case %q: wantPanic and expected are mutually exclusive", tt.name) require.PanicsWithValue(t, tt.wantPanic, func() { actionpins.FormatPinnedActionReference(tt.repo, tt.sha, tt.version) })