From d7c12afab7fa8c30cecaf044e7dc5aabfc551af1 Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Wed, 25 Mar 2026 18:00:11 -0500 Subject: [PATCH 1/3] Add UpdateTypes field to Allowed struct Add update-types support to the Allowed struct, matching the existing pattern in the Condition (ignore) struct. This enables semver-level filtering (major/minor/patch) in allow blocks of dependabot.yml. Changes: - Add UpdateTypes []string field to Allowed struct in model/job.go - Add update-types entry to exampleJob YAML fixture - Add TestAllowedUpdateTypes: verifies YAML unmarshal of update-types - Add TestAllowedUpdateTypesJSON: verifies JSON round-trip serialization Relates to dependabot/dependabot-core#12668. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/model/job.go | 7 ++-- internal/model/job_test.go | 70 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/internal/model/job.go b/internal/model/job.go index e228f365..8a7f7f5a 100644 --- a/internal/model/job.go +++ b/internal/model/job.go @@ -203,9 +203,10 @@ type ExistingGroupPR struct { } type Allowed struct { - DependencyType string `json:"dependency-type,omitempty" yaml:"dependency-type,omitempty"` - DependencyName string `json:"dependency-name,omitempty" yaml:"dependency-name,omitempty"` - UpdateType string `json:"update-type,omitempty" yaml:"update-type,omitempty"` + DependencyType string `json:"dependency-type,omitempty" yaml:"dependency-type,omitempty"` + DependencyName string `json:"dependency-name,omitempty" yaml:"dependency-name,omitempty"` + UpdateType string `json:"update-type,omitempty" yaml:"update-type,omitempty"` + UpdateTypes []string `json:"update-types,omitempty" yaml:"update-types,omitempty"` } type Group struct { diff --git a/internal/model/job_test.go b/internal/model/job_test.go index 92209f95..33cb4a82 100644 --- a/internal/model/job_test.go +++ b/internal/model/job_test.go @@ -21,6 +21,72 @@ func TestInput(t *testing.T) { compareMap(t, "job", input2["job"], input.Job) } +func TestAllowedUpdateTypes(t *testing.T) { + var input Input + if err := yaml.Unmarshal([]byte(exampleJob), &input); err != nil { + t.Fatal(err) + } + + allowed := input.Job.AllowedUpdates + if len(allowed) != 2 { + t.Fatalf("expected 2 allowed updates, got %d", len(allowed)) + } + + // First entry: dependency-type + update-type (existing pattern) + if allowed[0].DependencyType != "direct" { + t.Errorf("expected dependency-type 'direct', got %q", allowed[0].DependencyType) + } + if allowed[0].UpdateType != "all" { + t.Errorf("expected update-type 'all', got %q", allowed[0].UpdateType) + } + if len(allowed[0].UpdateTypes) != 0 { + t.Errorf("expected no update-types on first entry, got %v", allowed[0].UpdateTypes) + } + + // Second entry: dependency-name + update-types (new feature) + if allowed[1].DependencyName != "rails" { + t.Errorf("expected dependency-name 'rails', got %q", allowed[1].DependencyName) + } + expectedTypes := []string{"version-update:semver-minor", "version-update:semver-patch"} + if len(allowed[1].UpdateTypes) != len(expectedTypes) { + t.Fatalf("expected %d update-types, got %d", len(expectedTypes), len(allowed[1].UpdateTypes)) + } + for i, et := range expectedTypes { + if allowed[1].UpdateTypes[i] != et { + t.Errorf("update-types[%d]: expected %q, got %q", i, et, allowed[1].UpdateTypes[i]) + } + } +} + +func TestAllowedUpdateTypesJSON(t *testing.T) { + original := Allowed{ + DependencyName: "rails", + UpdateTypes: []string{"version-update:semver-minor", "version-update:semver-patch"}, + } + + data, err := json.Marshal(original) + if err != nil { + t.Fatal(err) + } + + var decoded Allowed + if err := json.Unmarshal(data, &decoded); err != nil { + t.Fatal(err) + } + + if decoded.DependencyName != original.DependencyName { + t.Errorf("dependency-name: expected %q, got %q", original.DependencyName, decoded.DependencyName) + } + if len(decoded.UpdateTypes) != len(original.UpdateTypes) { + t.Fatalf("update-types length: expected %d, got %d", len(original.UpdateTypes), len(decoded.UpdateTypes)) + } + for i, et := range original.UpdateTypes { + if decoded.UpdateTypes[i] != et { + t.Errorf("update-types[%d]: expected %q, got %q", i, et, decoded.UpdateTypes[i]) + } + } +} + func TestExistingPullRequestsNewFormat(t *testing.T) { testYAML := `--- job: @@ -663,6 +729,10 @@ job: allowed-updates: - dependency-type: direct update-type: all + - dependency-name: "rails" + update-types: + - "version-update:semver-minor" + - "version-update:semver-patch" dependency-groups: - name: npm rules: From 657de44f057fe13bf6b06362981e8a75a65ce573 Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 27 Mar 2026 11:52:43 -0500 Subject: [PATCH 2/3] Improve TestAllowedUpdateTypesJSON to validate actual JSON keys Unmarshal into map[string]any to verify correct JSON tag names ("update-types", "dependency-name") rather than round-tripping through the same struct. Also verify omitempty behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/model/job_test.go | 42 ++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/internal/model/job_test.go b/internal/model/job_test.go index 33cb4a82..cba1f317 100644 --- a/internal/model/job_test.go +++ b/internal/model/job_test.go @@ -69,22 +69,46 @@ func TestAllowedUpdateTypesJSON(t *testing.T) { t.Fatal(err) } - var decoded Allowed - if err := json.Unmarshal(data, &decoded); err != nil { + // Verify the actual JSON keys are correct by unmarshaling into a raw map + var raw map[string]any + if err := json.Unmarshal(data, &raw); err != nil { t.Fatal(err) } - if decoded.DependencyName != original.DependencyName { - t.Errorf("dependency-name: expected %q, got %q", original.DependencyName, decoded.DependencyName) + if _, ok := raw["update-types"]; !ok { + t.Errorf("expected JSON key \"update-types\" to be present, got keys: %v", raw) } - if len(decoded.UpdateTypes) != len(original.UpdateTypes) { - t.Fatalf("update-types length: expected %d, got %d", len(original.UpdateTypes), len(decoded.UpdateTypes)) + if _, ok := raw["dependency-name"]; !ok { + t.Errorf("expected JSON key \"dependency-name\" to be present, got keys: %v", raw) } - for i, et := range original.UpdateTypes { - if decoded.UpdateTypes[i] != et { - t.Errorf("update-types[%d]: expected %q, got %q", i, et, decoded.UpdateTypes[i]) + + types, ok := raw["update-types"].([]any) + if !ok { + t.Fatalf("expected update-types to be an array, got %T", raw["update-types"]) + } + expectedTypes := []string{"version-update:semver-minor", "version-update:semver-patch"} + if len(types) != len(expectedTypes) { + t.Fatalf("expected %d update-types, got %d", len(expectedTypes), len(types)) + } + for i, et := range expectedTypes { + if types[i] != et { + t.Errorf("update-types[%d]: expected %q, got %q", i, et, types[i]) } } + + // Verify omitempty: UpdateTypes should be absent when nil + empty := Allowed{DependencyName: "rails"} + data, err = json.Marshal(empty) + if err != nil { + t.Fatal(err) + } + var rawEmpty map[string]any + if err := json.Unmarshal(data, &rawEmpty); err != nil { + t.Fatal(err) + } + if _, ok := rawEmpty["update-types"]; ok { + t.Errorf("expected \"update-types\" to be omitted when nil, but it was present") + } } func TestExistingPullRequestsNewFormat(t *testing.T) { From 10500063f09701b33abc143ca8ae8ab021942b67 Mon Sep 17 00:00:00 2001 From: Kamil Bukum Date: Fri, 27 Mar 2026 13:36:20 -0500 Subject: [PATCH 3/3] Test JSON marshal output directly without unmarshal Compare marshaled JSON string directly to verify correct key names and omitempty behavior, per reviewer feedback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/model/job_test.go | 39 ++++++++------------------------------ 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/internal/model/job_test.go b/internal/model/job_test.go index cba1f317..7026b508 100644 --- a/internal/model/job_test.go +++ b/internal/model/job_test.go @@ -69,31 +69,10 @@ func TestAllowedUpdateTypesJSON(t *testing.T) { t.Fatal(err) } - // Verify the actual JSON keys are correct by unmarshaling into a raw map - var raw map[string]any - if err := json.Unmarshal(data, &raw); err != nil { - t.Fatal(err) - } - - if _, ok := raw["update-types"]; !ok { - t.Errorf("expected JSON key \"update-types\" to be present, got keys: %v", raw) - } - if _, ok := raw["dependency-name"]; !ok { - t.Errorf("expected JSON key \"dependency-name\" to be present, got keys: %v", raw) - } - - types, ok := raw["update-types"].([]any) - if !ok { - t.Fatalf("expected update-types to be an array, got %T", raw["update-types"]) - } - expectedTypes := []string{"version-update:semver-minor", "version-update:semver-patch"} - if len(types) != len(expectedTypes) { - t.Fatalf("expected %d update-types, got %d", len(expectedTypes), len(types)) - } - for i, et := range expectedTypes { - if types[i] != et { - t.Errorf("update-types[%d]: expected %q, got %q", i, et, types[i]) - } + // Verify marshaled JSON directly without using unmarshal + expected := `{"dependency-name":"rails","update-types":["version-update:semver-minor","version-update:semver-patch"]}` + if string(data) != expected { + t.Errorf("unexpected JSON output:\n got: %s\n want: %s", string(data), expected) } // Verify omitempty: UpdateTypes should be absent when nil @@ -102,12 +81,10 @@ func TestAllowedUpdateTypesJSON(t *testing.T) { if err != nil { t.Fatal(err) } - var rawEmpty map[string]any - if err := json.Unmarshal(data, &rawEmpty); err != nil { - t.Fatal(err) - } - if _, ok := rawEmpty["update-types"]; ok { - t.Errorf("expected \"update-types\" to be omitted when nil, but it was present") + + expectedEmpty := `{"dependency-name":"rails"}` + if string(data) != expectedEmpty { + t.Errorf("unexpected JSON output for empty UpdateTypes:\n got: %s\n want: %s", string(data), expectedEmpty) } }