From aab7269c3b86abda7f453507858c4de020c7224f Mon Sep 17 00:00:00 2001 From: Jiayang Zhou Date: Thu, 4 Dec 2025 19:34:42 +0100 Subject: [PATCH 1/7] feat: add copilot_code_review rule support to repository rulesets Add support for GitHub's Copilot code review feature in repository rulesets, allowing automatic code review requests for pull requests. Changes: - Add RulesetRuleTypeCopilotCodeReview constant to rule types - Add CopilotCodeReviewRuleParameters struct with review_new_pushes and review_draft_pull_requests fields - Implement marshal/unmarshal logic for copilot_code_review rules - Add comprehensive test coverage for the new rule type The copilot_code_review rule supports two configuration options: - review_new_pushes: Automatically review each new push to the PR - review_draft_pull_requests: Automatically review draft PRs --- github/rules.go | 28 ++++++++++++++++++++++++++-- github/rules_test.go | 12 ++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/github/rules.go b/github/rules.go index 2326ed0d7ac..6cf8cf6b44f 100644 --- a/github/rules.go +++ b/github/rules.go @@ -87,6 +87,7 @@ const ( RulesetRuleTypeTagNamePattern RepositoryRuleType = "tag_name_pattern" RulesetRuleTypeUpdate RepositoryRuleType = "update" RulesetRuleTypeWorkflows RepositoryRuleType = "workflows" + RulesetRuleTypeCopilotCodeReview RepositoryRuleType = "copilot_code_review" // Push target rules. RulesetRuleTypeFileExtensionRestriction RepositoryRuleType = "file_extension_restriction" @@ -306,6 +307,7 @@ type RepositoryRulesetRules struct { TagNamePattern *PatternRuleParameters Workflows *WorkflowsRuleParameters CodeScanning *CodeScanningRuleParameters + CopilotCodeReview *CopilotCodeReviewRuleParameters // Push target rules. FileExtensionRestriction *FileExtensionRestrictionRuleParameters @@ -539,6 +541,12 @@ type CodeScanningRuleParameters struct { CodeScanningTools []*RuleCodeScanningTool `json:"code_scanning_tools"` } +// CopilotCodeReviewRuleParameters represents the copilot_code_review rule parameters. +type CopilotCodeReviewRuleParameters struct { + ReviewNewPushes bool `json:"review_new_pushes"` + ReviewDraftPullRequests bool `json:"review_draft_pull_requests"` +} + // RuleCodeScanningTool represents a single code scanning tool for the code scanning parameters. type RuleCodeScanningTool struct { AlertsThreshold CodeScanningAlertsThreshold `json:"alerts_threshold"` @@ -566,9 +574,9 @@ type repositoryRulesetRuleWrapper struct { // MarshalJSON is a custom JSON marshaler for RulesetRules. func (r *RepositoryRulesetRules) MarshalJSON() ([]byte, error) { - // The RepositoryRulesetRules type marshals to between 1 and 21 rules. + // The RepositoryRulesetRules type marshals to between 1 and 22 rules. // If new rules are added to RepositoryRulesetRules the capacity below needs increasing - rawRules := make([]json.RawMessage, 0, 21) + rawRules := make([]json.RawMessage, 0, 22) if r.Creation != nil { bytes, err := marshalRepositoryRulesetRule(RulesetRuleTypeCreation, r.Creation) @@ -738,6 +746,14 @@ func (r *RepositoryRulesetRules) MarshalJSON() ([]byte, error) { rawRules = append(rawRules, json.RawMessage(bytes)) } + if r.CopilotCodeReview != nil { + bytes, err := marshalRepositoryRulesetRule(RulesetRuleTypeCopilotCodeReview, r.CopilotCodeReview) + if err != nil { + return nil, err + } + rawRules = append(rawRules, json.RawMessage(bytes)) + } + if r.RepositoryCreate != nil { bytes, err := marshalRepositoryRulesetRule(RulesetRuleTypeRepositoryCreate, r.RepositoryCreate) if err != nil { @@ -965,6 +981,14 @@ func (r *RepositoryRulesetRules) UnmarshalJSON(data []byte) error { return err } } + case RulesetRuleTypeCopilotCodeReview: + r.CopilotCodeReview = &CopilotCodeReviewRuleParameters{} + + if w.Parameters != nil { + if err := json.Unmarshal(w.Parameters, r.CopilotCodeReview); err != nil { + return err + } + } case RulesetRuleTypeRepositoryCreate: r.RepositoryCreate = &EmptyRuleParameters{} case RulesetRuleTypeRepositoryDelete: diff --git a/github/rules_test.go b/github/rules_test.go index cd0f2ae2377..e2b2ff76aee 100644 --- a/github/rules_test.go +++ b/github/rules_test.go @@ -122,13 +122,17 @@ func TestRulesetRules(t *testing.T) { }, }, }, + CopilotCodeReview: &CopilotCodeReviewRuleParameters{ + ReviewNewPushes: true, + ReviewDraftPullRequests: false, + }, RepositoryCreate: &EmptyRuleParameters{}, RepositoryDelete: &EmptyRuleParameters{}, RepositoryName: &SimplePatternRuleParameters{Pattern: "^test-.+", Negate: false}, RepositoryTransfer: &EmptyRuleParameters{}, RepositoryVisibility: &RepositoryVisibilityRuleParameters{Internal: false, Private: false}, }, - `[{"type":"creation"},{"type":"update"},{"type":"deletion"},{"type":"required_linear_history"},{"type":"merge_queue","parameters":{"check_response_timeout_minutes":5,"grouping_strategy":"ALLGREEN","max_entries_to_build":10,"max_entries_to_merge":20,"merge_method":"SQUASH","min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":15}},{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures"},{"type":"pull_request","parameters":{"allowed_merge_methods":["squash","rebase"],"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"required_status_checks":[{"context":"test1"},{"context":"test2"}],"strict_required_status_checks_policy":true}},{"type":"non_fast_forward"},{"type":"commit_message_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","parameters":{"max_file_size":1024}},{"type":"workflows","parameters":{"workflows":[{"path":".github/workflows/test1.yaml"},{"path":".github/workflows/test2.yaml"}]}},{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}},{"type":"repository_create"},{"type":"repository_delete"},{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}},{"type":"repository_transfer"},{"type":"repository_visibility","parameters":{"internal":false,"private":false}}]`, + `[{"type":"creation"},{"type":"update"},{"type":"deletion"},{"type":"required_linear_history"},{"type":"merge_queue","parameters":{"check_response_timeout_minutes":5,"grouping_strategy":"ALLGREEN","max_entries_to_build":10,"max_entries_to_merge":20,"merge_method":"SQUASH","min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":15}},{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures"},{"type":"pull_request","parameters":{"allowed_merge_methods":["squash","rebase"],"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"required_status_checks":[{"context":"test1"},{"context":"test2"}],"strict_required_status_checks_policy":true}},{"type":"non_fast_forward"},{"type":"commit_message_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","parameters":{"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","parameters":{"max_file_size":1024}},{"type":"workflows","parameters":{"workflows":[{"path":".github/workflows/test1.yaml"},{"path":".github/workflows/test2.yaml"}]}},{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}},{"type":"copilot_code_review","parameters":{"review_new_pushes":true,"review_draft_pull_requests":false}},{"type":"repository_create"},{"type":"repository_delete"},{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}},{"type":"repository_transfer"},{"type":"repository_visibility","parameters":{"internal":false,"private":false}}]`, }, { "all_rules_with_all_params", @@ -240,13 +244,17 @@ func TestRulesetRules(t *testing.T) { }, }, }, + CopilotCodeReview: &CopilotCodeReviewRuleParameters{ + ReviewNewPushes: true, + ReviewDraftPullRequests: false, + }, RepositoryCreate: &EmptyRuleParameters{}, RepositoryDelete: &EmptyRuleParameters{}, RepositoryName: &SimplePatternRuleParameters{Pattern: "^test-.+", Negate: false}, RepositoryTransfer: &EmptyRuleParameters{}, RepositoryVisibility: &RepositoryVisibilityRuleParameters{Internal: false, Private: false}, }, - `[{"type":"creation"},{"type":"update","parameters":{"update_allows_fetch_and_merge":true}},{"type":"deletion"},{"type":"required_linear_history"},{"type":"merge_queue","parameters":{"check_response_timeout_minutes":5,"grouping_strategy":"ALLGREEN","max_entries_to_build":10,"max_entries_to_merge":20,"merge_method":"SQUASH","min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":15}},{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures"},{"type":"pull_request","parameters":{"allowed_merge_methods":["squash","rebase"],"automatic_copilot_code_review_enabled":false,"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"do_not_enforce_on_create":true,"required_status_checks":[{"context":"test1","integration_id":1},{"context":"test2","integration_id":2}],"strict_required_status_checks_policy":true}},{"type":"non_fast_forward"},{"type":"commit_message_pattern","parameters":{"name":"cmp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","parameters":{"name":"caep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","parameters":{"name":"cep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","parameters":{"name":"bp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","parameters":{"name":"tp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","parameters":{"max_file_size":1024}},{"type":"workflows","parameters":{"do_not_enforce_on_create":true,"workflows":[{"path":".github/workflows/test1.yaml","ref":"main","repository_id":1,"sha":"aaaa"},{"path":".github/workflows/test2.yaml","ref":"main","repository_id":2,"sha":"bbbb"}]}},{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}},{"type":"repository_create"},{"type":"repository_delete"},{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}},{"type":"repository_transfer"},{"type":"repository_visibility","parameters":{"internal":false,"private":false}}]`, + `[{"type":"creation"},{"type":"update","parameters":{"update_allows_fetch_and_merge":true}},{"type":"deletion"},{"type":"required_linear_history"},{"type":"merge_queue","parameters":{"check_response_timeout_minutes":5,"grouping_strategy":"ALLGREEN","max_entries_to_build":10,"max_entries_to_merge":20,"merge_method":"SQUASH","min_entries_to_merge":1,"min_entries_to_merge_wait_minutes":15}},{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures"},{"type":"pull_request","parameters":{"allowed_merge_methods":["squash","rebase"],"automatic_copilot_code_review_enabled":false,"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","parameters":{"do_not_enforce_on_create":true,"required_status_checks":[{"context":"test1","integration_id":1},{"context":"test2","integration_id":2}],"strict_required_status_checks_policy":true}},{"type":"non_fast_forward"},{"type":"commit_message_pattern","parameters":{"name":"cmp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","parameters":{"name":"caep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","parameters":{"name":"cep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","parameters":{"name":"bp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","parameters":{"name":"tp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","parameters":{"max_file_size":1024}},{"type":"workflows","parameters":{"do_not_enforce_on_create":true,"workflows":[{"path":".github/workflows/test1.yaml","ref":"main","repository_id":1,"sha":"aaaa"},{"path":".github/workflows/test2.yaml","ref":"main","repository_id":2,"sha":"bbbb"}]}},{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}},{"type":"copilot_code_review","parameters":{"review_new_pushes":true,"review_draft_pull_requests":false}},{"type":"repository_create"},{"type":"repository_delete"},{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}},{"type":"repository_transfer"},{"type":"repository_visibility","parameters":{"internal":false,"private":false}}]`, }, } From 2b90cfa349382faa2b14f79051c82ddfbfe6d746 Mon Sep 17 00:00:00 2001 From: Jiayang Zhou Date: Thu, 4 Dec 2025 19:49:23 +0100 Subject: [PATCH 2/7] chore: regenerate accessor methods for CopilotCodeReview --- github/github-accessors.go | 8 ++++++++ github/github-accessors_test.go | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/github/github-accessors.go b/github/github-accessors.go index 76af1d2d588..65a2163d9b5 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -25022,6 +25022,14 @@ func (r *RepositoryRulesetRules) GetCommitterEmailPattern() *PatternRuleParamete return r.CommitterEmailPattern } +// GetCopilotCodeReview returns the CopilotCodeReview field. +func (r *RepositoryRulesetRules) GetCopilotCodeReview() *CopilotCodeReviewRuleParameters { + if r == nil { + return nil + } + return r.CopilotCodeReview +} + // GetCreation returns the Creation field. func (r *RepositoryRulesetRules) GetCreation() *EmptyRuleParameters { if r == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 937aff97dc9..03fa75fcc0a 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -32298,6 +32298,14 @@ func TestRepositoryRulesetRules_GetCommitterEmailPattern(tt *testing.T) { r.GetCommitterEmailPattern() } +func TestRepositoryRulesetRules_GetCopilotCodeReview(tt *testing.T) { + tt.Parallel() + r := &RepositoryRulesetRules{} + r.GetCopilotCodeReview() + r = nil + r.GetCopilotCodeReview() +} + func TestRepositoryRulesetRules_GetCreation(tt *testing.T) { tt.Parallel() r := &RepositoryRulesetRules{} From 72a87bab64b20628189bde9b132da4c015254aff Mon Sep 17 00:00:00 2001 From: Jiayang Zhou Date: Thu, 4 Dec 2025 20:16:09 +0100 Subject: [PATCH 3/7] added more coverage --- github/rules.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/github/rules.go b/github/rules.go index 6cf8cf6b44f..d41fee9bcde 100644 --- a/github/rules.go +++ b/github/rules.go @@ -1390,6 +1390,16 @@ func (r *RepositoryRule) UnmarshalJSON(data []byte) error { } } + r.Parameters = p + case RulesetRuleTypeCopilotCodeReview: + p := &CopilotCodeReviewRuleParameters{} + + if w.Parameters != nil { + if err := json.Unmarshal(w.Parameters, p); err != nil { + return err + } + } + r.Parameters = p case RulesetRuleTypeRepositoryCreate: r.Parameters = nil From fd5b1242b74d2c9e146f97632fb94ed814b9c2cf Mon Sep 17 00:00:00 2001 From: Jiayang Zhou Date: Sat, 6 Dec 2025 11:30:21 -0800 Subject: [PATCH 4/7] added test case --- github/rules_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/github/rules_test.go b/github/rules_test.go index e2b2ff76aee..d19c4cdc0ec 100644 --- a/github/rules_test.go +++ b/github/rules_test.go @@ -937,6 +937,17 @@ func TestRepositoryRule(t *testing.T) { }, `{"type":"code_scanning","parameters":{"code_scanning_tools":[{"alerts_threshold":"all","security_alerts_threshold":"all","tool":"test"},{"alerts_threshold":"none","security_alerts_threshold":"none","tool":"test"}]}}`, }, + { + "copilot_code_review", + &RepositoryRule{ + Type: RulesetRuleTypeCopilotCodeReview, + Parameters: &CopilotCodeReviewRuleParameters{ + ReviewNewPushes: true, + ReviewDraftPullRequests: false, + }, + }, + `{"type":"copilot_code_review","parameters":{"review_new_pushes":true,"review_draft_pull_requests":false}}`, + }, { "repository_create", &RepositoryRule{Type: RulesetRuleTypeRepositoryCreate, Parameters: nil}, From 008d84a5c7e9b9c3d44810fa974a2967e95695a7 Mon Sep 17 00:00:00 2001 From: Jiayang Zhou Date: Sat, 6 Dec 2025 12:38:04 -0800 Subject: [PATCH 5/7] added one more test --- github/rules_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/github/rules_test.go b/github/rules_test.go index d19c4cdc0ec..6bec29e88fe 100644 --- a/github/rules_test.go +++ b/github/rules_test.go @@ -306,6 +306,18 @@ func TestRulesetRules(t *testing.T) { }) } }) + + t.Run("UnmarshalJSON_Error", func(t *testing.T) { + t.Parallel() + + // Test invalid JSON for CopilotCodeReview parameters + invalidJSON := `[{"type":"copilot_code_review","parameters":{"review_new_pushes":"invalid_bool"}}]` + got := &RepositoryRulesetRules{} + err := json.Unmarshal([]byte(invalidJSON), got) + if err == nil { + t.Errorf("Expected error unmarshaling invalid JSON, got nil") + } + }) } func TestBranchRules(t *testing.T) { @@ -1011,4 +1023,16 @@ func TestRepositoryRule(t *testing.T) { }) } }) + + t.Run("UnmarshalJSON_Error", func(t *testing.T) { + t.Parallel() + + // Test invalid JSON for CopilotCodeReview parameters + invalidJSON := `{"type":"copilot_code_review","parameters":{"review_new_pushes":"invalid_bool"}}` + got := &RepositoryRule{} + err := json.Unmarshal([]byte(invalidJSON), got) + if err == nil { + t.Errorf("Expected error unmarshaling invalid JSON, got nil") + } + }) } From 3f6aeb557bb57a5958a6aff61f8451cab9d819eb Mon Sep 17 00:00:00 2001 From: Jiayang Zhou Date: Sat, 6 Dec 2025 15:59:01 -0800 Subject: [PATCH 6/7] fixed lint and refined tests --- github/rules_test.go | 74 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 12 deletions(-) diff --git a/github/rules_test.go b/github/rules_test.go index 6bec29e88fe..e0b091ecbd0 100644 --- a/github/rules_test.go +++ b/github/rules_test.go @@ -310,12 +310,33 @@ func TestRulesetRules(t *testing.T) { t.Run("UnmarshalJSON_Error", func(t *testing.T) { t.Parallel() - // Test invalid JSON for CopilotCodeReview parameters - invalidJSON := `[{"type":"copilot_code_review","parameters":{"review_new_pushes":"invalid_bool"}}]` - got := &RepositoryRulesetRules{} - err := json.Unmarshal([]byte(invalidJSON), got) - if err == nil { - t.Errorf("Expected error unmarshaling invalid JSON, got nil") + tests := []struct { + name string + json string + }{ + { + "invalid_copilot_code_review_bool", + `[{"type":"copilot_code_review","parameters":{"review_new_pushes":"invalid_bool"}}]`, + }, + { + "invalid_copilot_code_review_draft_pr", + `[{"type":"copilot_code_review","parameters":{"review_new_pushes":true,"review_draft_pull_requests":"not_a_bool"}}]`, + }, + { + "invalid_copilot_code_review_parameters", + `[{"type":"copilot_code_review","parameters":"not_an_object"}]`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := &RepositoryRulesetRules{} + err := json.Unmarshal([]byte(tt.json), got) + if err == nil { + t.Errorf("Expected error unmarshaling %q, got nil", tt.json) + } + }) } }) } @@ -960,6 +981,14 @@ func TestRepositoryRule(t *testing.T) { }, `{"type":"copilot_code_review","parameters":{"review_new_pushes":true,"review_draft_pull_requests":false}}`, }, + { + "copilot_code_review_empty_params", + &RepositoryRule{ + Type: RulesetRuleTypeCopilotCodeReview, + Parameters: &CopilotCodeReviewRuleParameters{}, + }, + `{"type":"copilot_code_review","parameters":{"review_new_pushes":false,"review_draft_pull_requests":false}}`, + }, { "repository_create", &RepositoryRule{Type: RulesetRuleTypeRepositoryCreate, Parameters: nil}, @@ -1027,12 +1056,33 @@ func TestRepositoryRule(t *testing.T) { t.Run("UnmarshalJSON_Error", func(t *testing.T) { t.Parallel() - // Test invalid JSON for CopilotCodeReview parameters - invalidJSON := `{"type":"copilot_code_review","parameters":{"review_new_pushes":"invalid_bool"}}` - got := &RepositoryRule{} - err := json.Unmarshal([]byte(invalidJSON), got) - if err == nil { - t.Errorf("Expected error unmarshaling invalid JSON, got nil") + tests := []struct { + name string + json string + }{ + { + "invalid_copilot_code_review_bool", + `{"type":"copilot_code_review","parameters":{"review_new_pushes":"invalid_bool"}}`, + }, + { + "invalid_copilot_code_review_draft_pr", + `{"type":"copilot_code_review","parameters":{"review_new_pushes":true,"review_draft_pull_requests":"not_a_bool"}}`, + }, + { + "invalid_copilot_code_review_parameters", + `{"type":"copilot_code_review","parameters":"not_an_object"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + got := &RepositoryRule{} + err := json.Unmarshal([]byte(tt.json), got) + if err == nil { + t.Errorf("Expected error unmarshaling %q, got nil", tt.json) + } + }) } }) } From 8191c33cf9d1790b0bd8385ba4be98bf9ce7e4a8 Mon Sep 17 00:00:00 2001 From: Jiayang Zhou Date: Tue, 9 Dec 2025 10:01:07 -0800 Subject: [PATCH 7/7] adjusted the order of RulesetRuleTypeCopilotCodeReview in const --- github/rules.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/github/rules.go b/github/rules.go index d41fee9bcde..76d6cbc0095 100644 --- a/github/rules.go +++ b/github/rules.go @@ -75,6 +75,7 @@ const ( RulesetRuleTypeCommitAuthorEmailPattern RepositoryRuleType = "commit_author_email_pattern" RulesetRuleTypeCommitMessagePattern RepositoryRuleType = "commit_message_pattern" RulesetRuleTypeCommitterEmailPattern RepositoryRuleType = "committer_email_pattern" + RulesetRuleTypeCopilotCodeReview RepositoryRuleType = "copilot_code_review" RulesetRuleTypeCreation RepositoryRuleType = "creation" RulesetRuleTypeDeletion RepositoryRuleType = "deletion" RulesetRuleTypeMergeQueue RepositoryRuleType = "merge_queue" @@ -87,7 +88,6 @@ const ( RulesetRuleTypeTagNamePattern RepositoryRuleType = "tag_name_pattern" RulesetRuleTypeUpdate RepositoryRuleType = "update" RulesetRuleTypeWorkflows RepositoryRuleType = "workflows" - RulesetRuleTypeCopilotCodeReview RepositoryRuleType = "copilot_code_review" // Push target rules. RulesetRuleTypeFileExtensionRestriction RepositoryRuleType = "file_extension_restriction"