-
Notifications
You must be signed in to change notification settings - Fork 466
Strengthen actionpins spec tests for cache keys, fallback auditing, and container digest validation
#48497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Strengthen actionpins spec tests for cache keys, fallback auditing, and container digest validation
#48497
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -116,6 +116,24 @@ func TestSpec_PublicAPI_FormatCacheKey(t *testing.T) { | |
| version: "v3.0.0", | ||
| expected: "actions/setup-node@v3.0.0", | ||
| }, | ||
| { | ||
| name: "formats cache key with empty repo", | ||
| repo: "", | ||
| version: "v4", | ||
| expected: "@v4", | ||
| }, | ||
| { | ||
| name: "formats cache key with empty version", | ||
| repo: "actions/checkout", | ||
| version: "", | ||
| expected: "actions/checkout@", | ||
| }, | ||
| { | ||
| name: "formats cache key with empty repo and version", | ||
| repo: "", | ||
| version: "", | ||
| expected: "@", | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
|
|
@@ -361,7 +379,8 @@ func TestSpec_PublicAPI_ResolveActionPin_EnforcePinned(t *testing.T) { | |
|
|
||
| require.Len(t, failures, tt.wantFailureCount, "resolution failures should be audited consistently") | ||
| if tt.wantFailureCount > 0 { | ||
| assert.Equal(t, tt.wantFailureType, failures[0].ErrorType) | ||
| assert.Equal(t, tt.wantFailureType, failures[0].ErrorType, | ||
| "resolution failure type should match the expected classification") | ||
| } | ||
| if tt.wantWarningKey { | ||
| assert.True(t, ctx.Warnings[actionpins.FormatCacheKey("does-not-exist/x", "v1")], | ||
|
|
@@ -468,10 +487,18 @@ func TestSpec_PublicAPI_ResolveLatestActionPin_FallbackOnEnforceError(t *testing | |
| }) | ||
|
|
||
| t.Run("unknown repo returns empty when no embedded fallback exists", func(t *testing.T) { | ||
| ctx := &actionpins.PinContext{EnforcePinned: true, Warnings: make(map[string]bool)} | ||
| var failures []actionpins.ResolutionFailure | ||
| ctx := &actionpins.PinContext{ | ||
| EnforcePinned: true, | ||
| Warnings: make(map[string]bool), | ||
| RecordResolutionFailure: func(f actionpins.ResolutionFailure) { | ||
| failures = append(failures, f) | ||
| }, | ||
| } | ||
|
|
||
| result := actionpins.ResolveLatestActionPin("does-not-exist/x", ctx) | ||
| assert.Empty(t, result, "unknown repo should return empty when no embedded fallback exists") | ||
| assert.Empty(t, failures, "unknown repo without embedded pins should not invoke resolution failure callback") | ||
| }) | ||
| } | ||
|
|
||
|
|
@@ -985,4 +1012,16 @@ func TestSpec_PublicAPI_ApplyContainerPinMapping(t *testing.T) { | |
| assert.Equal(t, "ghcr.io/owner/image:latest", result, | ||
| "mapping without valid @sha256: digest should be rejected and image returned unchanged") | ||
| }) | ||
|
|
||
| t.Run("invalid mapping - mapped value with malformed digest returns image unchanged", func(t *testing.T) { | ||
| ctx := &actionpins.PinContext{ | ||
| Warnings: make(map[string]bool), | ||
| ContainerMappings: map[string]string{ | ||
| "ghcr.io/owner/image:latest": "registry.acme.com/image:latest@sha256:not-a-valid-digest", | ||
| }, | ||
| } | ||
| result := actionpins.ApplyContainerPinMapping("ghcr.io/owner/image:latest", ctx) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This subtest largely duplicates the coverage of the adjacent "without digest" case rather than adding a distinct regression check. 💡 Details
A more valuable malformed-digest case would target a distinct failure mode within the digest-present branch, e.g.: // 64 hex chars but uppercase - fails [a-f0-9] charset only, digest length correct
"registry.acme.com/image:latest@sha256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
// correct length/charset but wrong algorithm name
"registry.acme.com/image:latest@sha512:"Either would validate a different branch of the regex than the already-covered no-digest case. |
||
| assert.Equal(t, "ghcr.io/owner/image:latest", result, | ||
| "mapping with malformed @sha256 digest should be rejected and image returned unchanged") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The new malformed-digest subtest doesn't assert the warning side-effect. The production code on line 577–580 of 💡 Suggested additionAdd a warning-key assertion after the assert.False(t, ctx.Warnings["container-map:ghcr.io/owner/image:latest"],
"invalid (malformed digest) mapping should not mark the mapping as applied")This mirrors a similar assertion that would be valuable in the existing without-digest subtest too. @copilot please address this. |
||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/tdd] The new
failurescapture in the unknown-repo subtest assertsassert.Empty(t, failures), which is valuable. However, it also hides a subtle contract question: should an unknown repo withEnforcePinned: truenot fireRecordResolutionFailure? If the intent is "empty result, no audit" that's fine, but the test name doesn't communicate this expectation. Consider renaming to make it explicit.💡 Suggested rename
This makes the callback-silence guarantee part of the spec, not just an incidental assertion.
@copilot please address this.