-
Notifications
You must be signed in to change notification settings - Fork 14
fix: get resolves pinned version instead of defaulting to latest tag #479
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
base: main
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -17,23 +17,30 @@ import ( | |
| // --- Mock VersionResolver --- | ||
|
|
||
| type mockVersionResolver struct { | ||
| versions map[string]string // "registry|repo" -> version | ||
| unreachable map[string]bool // registry -> true | ||
| errOnResolve map[string]error // "registry|repo" -> error | ||
| versions map[string]string // "registry|repo" -> latest version | ||
| pinnedVersions map[string]string // "registry|repo|version" -> resolved version | ||
| unreachable map[string]bool // registry -> true | ||
| errOnResolve map[string]error // "registry|repo" -> error | ||
| latestMissing map[string]bool // registry -> true (reachable but no latest tag) | ||
| } | ||
|
|
||
| func newMockVersionResolver() *mockVersionResolver { | ||
| return &mockVersionResolver{ | ||
| versions: make(map[string]string), | ||
| unreachable: make(map[string]bool), | ||
| errOnResolve: make(map[string]error), | ||
| versions: make(map[string]string), | ||
| pinnedVersions: make(map[string]string), | ||
| unreachable: make(map[string]bool), | ||
| errOnResolve: make(map[string]error), | ||
| latestMissing: make(map[string]bool), | ||
| } | ||
| } | ||
|
|
||
| func (m *mockVersionResolver) ResolveLatestVersion(registry, repository string) (string, error) { | ||
| if m.unreachable[registry] { | ||
| return "", fmt.Errorf("connection refused") | ||
| } | ||
| if m.latestMissing[registry] { | ||
| return "", fmt.Errorf("OCI version resolution failed for %s/%s:latest: not found", registry, repository) | ||
| } | ||
| key := registry + "|" + repository | ||
| if err, ok := m.errOnResolve[key]; ok { | ||
| return "", err | ||
|
|
@@ -44,6 +51,17 @@ func (m *mockVersionResolver) ResolveLatestVersion(registry, repository string) | |
| return "", fmt.Errorf("not found: %s/%s", registry, repository) | ||
| } | ||
|
|
||
| func (m *mockVersionResolver) ResolveVersion(registry, repository, version string) (string, error) { | ||
| if m.unreachable[registry] { | ||
| return "", fmt.Errorf("connection refused") | ||
| } | ||
| key := registry + "|" + repository + "|" + version | ||
| if v, ok := m.pinnedVersions[key]; ok { | ||
| return v, nil | ||
| } | ||
| return "", fmt.Errorf("not found: %s/%s:%s", registry, repository, version) | ||
| } | ||
|
|
||
| // --- Mock PolicyGraphResolver --- | ||
|
|
||
| type mockPolicyGraphResolver struct { | ||
|
|
@@ -231,6 +249,76 @@ func TestCheckPolicyVersions_RegistryUnreachable(t *testing.T) { | |
| if results[0].Status != StatusWarn { | ||
| t.Errorf("expected warn, got %s", results[0].Status) | ||
| } | ||
| if !strings.Contains(results[0].Message, "connection refused") { | ||
|
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. [INFO] Good coverage — two new test cases cover the pinned-version fallback
|
||
| t.Errorf("expected actual error in message, got %q", results[0].Message) | ||
| } | ||
| } | ||
|
|
||
| func TestCheckPolicyVersions_LatestMissing_PinnedResolves(t *testing.T) { | ||
| tmpDir := t.TempDir() | ||
|
|
||
| state := &cache.State{Policies: map[string]cache.PolicyState{ | ||
| "policies/nist": {Version: "v1.0.0"}, | ||
| }} | ||
| if err := cache.SaveState(state, tmpDir); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| cfg := &complytime.WorkspaceConfig{ | ||
| Policies: []complytime.PolicyEntry{ | ||
| {URL: "reg.io/policies/nist@v1.0.0"}, | ||
| }, | ||
| } | ||
|
|
||
| vr := newMockVersionResolver() | ||
| vr.latestMissing["reg.io"] = true | ||
| vr.pinnedVersions["reg.io|policies/nist|v1.0.0"] = "v1.0.0" | ||
|
|
||
| results := CheckPolicyVersions(cfg, tmpDir, vr) | ||
| if len(results) != 1 { | ||
| t.Fatalf("expected 1 result, got %d: %+v", len(results), results) | ||
| } | ||
| if results[0].Status != StatusPass { | ||
| t.Errorf("expected pass for reachable registry with pinned version, got %s: %s", | ||
| results[0].Status, results[0].Message) | ||
| } | ||
| if !strings.Contains(results[0].Message, "pinned") { | ||
| t.Errorf("expected 'pinned' in message, got %q", results[0].Message) | ||
| } | ||
| if !strings.Contains(results[0].Message, "latest tag unavailable") { | ||
| t.Errorf("expected 'latest tag unavailable' in message, got %q", results[0].Message) | ||
| } | ||
| } | ||
|
|
||
| func TestCheckPolicyVersions_LatestMissing_NoPinnedVersion(t *testing.T) { | ||
| tmpDir := t.TempDir() | ||
|
|
||
| state := &cache.State{Policies: map[string]cache.PolicyState{ | ||
| "policies/nist": {Version: "v1.0.0"}, | ||
| }} | ||
| if err := cache.SaveState(state, tmpDir); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
|
|
||
| cfg := &complytime.WorkspaceConfig{ | ||
| Policies: []complytime.PolicyEntry{ | ||
| {URL: "reg.io/policies/nist"}, | ||
| }, | ||
| } | ||
|
|
||
| vr := newMockVersionResolver() | ||
| vr.latestMissing["reg.io"] = true | ||
|
|
||
| results := CheckPolicyVersions(cfg, tmpDir, vr) | ||
| if len(results) != 1 { | ||
| t.Fatalf("expected 1 result, got %d: %+v", len(results), results) | ||
| } | ||
| if results[0].Status != StatusWarn { | ||
| t.Errorf("expected warn, got %s: %s", results[0].Status, results[0].Message) | ||
| } | ||
| if results[0].Name != "registry/reg.io" { | ||
| t.Errorf("expected registry warning, got %q", results[0].Name) | ||
| } | ||
| } | ||
|
|
||
| func TestCheckPolicyVersions_BadCacheState(t *testing.T) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,7 +53,10 @@ func (c *Client) DefinitionVersion(ctx context.Context, modulePath string) (stri | |
| return c.fetcher.DefinitionVersion(ctx, modulePath) | ||
| } | ||
|
|
||
| ref := fmt.Sprintf("%s/%s:latest", c.registryHost(), modulePath) | ||
| ref := fmt.Sprintf("%s/%s", c.registryHost(), modulePath) | ||
| if !strings.Contains(modulePath, ":") && !strings.Contains(modulePath, "@") { | ||
| ref += ":latest" | ||
|
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. [HIGH] CRAP regression: The new Consider adding tests that use a
Since these hit a real registry resolve, you'd likely need to test at the ref-construction level (e.g., extract a |
||
| } | ||
| digest, version, err := c.fetchVersion(ctx, ref) | ||
| if err != nil { | ||
| return "", "", fmt.Errorf("failed to fetch version for %s: %w", modulePath, err) | ||
|
|
||
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.
[MEDIUM] Missing test coverage for version-specific ref construction
The new branching (
version != "" && version != "latest"→policyID + ":" + version) is not exercised by existing sync tests — they all callSyncPolicywith"latest". CRAP score rose from 11.4 → 13.9.A test like
TestSync_CopyOnSuccess_PinnedVersioncallingSyncPolicy(ctx, "test-policy", "v1.0.0")and verifying that the source receives the"test-policy:v1.0.0"ref would cover this path.