// Copyright 2025 The go-github AUTHORS. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package github import ( "encoding/json" "math" "testing" "github.com/google/go-cmp/cmp" ) func TestCodeCoverageRuleRoundTrip(t *testing.T) { t.Parallel() tests := []struct { name string params CodeCoverageRuleParameters json string }{ { "fractional_thresholds", CodeCoverageRuleParameters{MaxCoverageDrop: new(2.5), MinimumCoverage: new(80.5)}, `{"max_coverage_drop":2.5,"minimum_coverage":80.5}`, }, { "minimum_only", CodeCoverageRuleParameters{MinimumCoverage: new(80.5)}, `{"minimum_coverage":80.5}`, }, { "no_coverage_drop", CodeCoverageRuleParameters{MaxCoverageDrop: new(0.0)}, `{"max_coverage_drop":0}`, }, { "zero_thresholds", CodeCoverageRuleParameters{MaxCoverageDrop: new(0.0), MinimumCoverage: new(0.0)}, `{"max_coverage_drop":0,"minimum_coverage":0}`, }, { "empty_parameters", CodeCoverageRuleParameters{}, `{}`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() ruleJSON := `{"type":"code_coverage","parameters":` + tt.json + `}` testJSONMarshal(t, &RepositoryRule{ Type: RulesetRuleTypeCodeCoverage, Parameters: &tt.params, }, ruleJSON) testJSONMarshal(t, &RepositoryRulesetRules{CodeCoverage: &tt.params}, `[`+ruleJSON+`]`) }) } t.Run("missing_parameters", func(t *testing.T) { t.Parallel() testJSONUnmarshalOnly(t, &RepositoryRule{ Type: RulesetRuleTypeCodeCoverage, Parameters: &CodeCoverageRuleParameters{}, }, `{"type":"code_coverage"}`) testJSONUnmarshalOnly(t, &RepositoryRulesetRules{ CodeCoverage: &CodeCoverageRuleParameters{}, }, `[{"type":"code_coverage"}]`) }) } func TestCodeCoverageRuleInvalidParameters(t *testing.T) { t.Parallel() for _, params := range []string{ `{"max_coverage_drop":"invalid"}`, `{"minimum_coverage":"invalid"}`, `"not_an_object"`, } { t.Run(params, func(t *testing.T) { t.Parallel() data := `{"type":"code_coverage","parameters":` + params + `}` if err := json.Unmarshal([]byte(data), &RepositoryRule{}); err == nil { t.Errorf("Expected error unmarshaling %q, got nil", data) } if err := json.Unmarshal([]byte(`[`+data+`]`), &RepositoryRulesetRules{}); err == nil { t.Errorf("Expected error unmarshaling [%v], got nil", data) } }) } } func TestRepositoryRulesetRules_CodeCoverageMarshalError(t *testing.T) { t.Parallel() rules := &RepositoryRulesetRules{ CodeCoverage: &CodeCoverageRuleParameters{MinimumCoverage: new(math.NaN())}, } if _, err := json.Marshal(rules); err == nil { t.Error("Expected error marshaling a NaN coverage threshold, got nil") } } func TestCodeQualityRuleRoundTrip(t *testing.T) { t.Parallel() for _, severity := range []CodeQualitySeverity{ CodeQualitySeverityErrors, CodeQualitySeverityWarnings, CodeQualitySeverityNotes, CodeQualitySeverityAll, } { t.Run(string(severity), func(t *testing.T) { t.Parallel() params := CodeQualityRuleParameters{Severity: severity} ruleJSON := `{"type":"code_quality","parameters":{"severity":"` + string(severity) + `"}}` testJSONMarshal(t, &RepositoryRule{ Type: RulesetRuleTypeCodeQuality, Parameters: ¶ms, }, ruleJSON) testJSONMarshal(t, &RepositoryRulesetRules{CodeQuality: ¶ms}, `[`+ruleJSON+`]`) }) } t.Run("missing_parameters", func(t *testing.T) { t.Parallel() testJSONUnmarshalOnly(t, &RepositoryRule{ Type: RulesetRuleTypeCodeQuality, Parameters: &CodeQualityRuleParameters{}, }, `{"type":"code_quality"}`) testJSONUnmarshalOnly(t, &RepositoryRulesetRules{ CodeQuality: &CodeQualityRuleParameters{}, }, `[{"type":"code_quality"}]`) testJSONUnmarshalOnly(t, &BranchRules{ CodeQuality: []*CodeQualityBranchRule{{ BranchRuleMetadata: BranchRuleMetadata{RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1}, }}, }, `[{"type":"code_quality","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1}]`) }) } func TestBranchRules_CodeQuality(t *testing.T) { t.Parallel() testJSONUnmarshalOnly(t, &BranchRules{ CodeQuality: []*CodeQualityBranchRule{{ BranchRuleMetadata: BranchRuleMetadata{RulesetSourceType: RulesetSourceTypeOrganization, RulesetSource: "test", RulesetID: 2}, Parameters: CodeQualityRuleParameters{Severity: CodeQualitySeverityErrors}, }}, }, `[{"type":"code_quality","ruleset_source_type":"Organization","ruleset_source":"test","ruleset_id":2,"parameters":{"severity":"errors"}}]`) } func TestCodeQualityRuleInvalidParameters(t *testing.T) { t.Parallel() for _, params := range []string{ `{"severity":1}`, `"not_an_object"`, } { t.Run(params, func(t *testing.T) { t.Parallel() data := `{"type":"code_quality","parameters":` + params + `}` if err := json.Unmarshal([]byte(data), &RepositoryRule{}); err == nil { t.Errorf("Expected error unmarshaling %q into RepositoryRule, got nil", data) } if err := json.Unmarshal([]byte(`[`+data+`]`), &RepositoryRulesetRules{}); err == nil { t.Errorf("Expected error unmarshaling [%v] into RepositoryRulesetRules, got nil", data) } if err := json.Unmarshal([]byte(`[`+data+`]`), &BranchRules{}); err == nil { t.Errorf("Expected error unmarshaling [%v] into BranchRules, got nil", data) } }) } } func TestRepositoryRulesetRules(t *testing.T) { t.Parallel() tests := []struct { name string rules *RepositoryRulesetRules json string }{ {"empty", &RepositoryRulesetRules{}, `[]`}, { "single_rule_with_empty_params", &RepositoryRulesetRules{Creation: &EmptyRuleParameters{}}, `[{"type":"creation"}]`, }, { "single_rule_with_required_params", &RepositoryRulesetRules{ RequiredDeployments: &RequiredDeploymentsRuleParameters{ RequiredDeploymentEnvironments: []string{"test"}, }, }, `[{"type":"required_deployments","parameters":{"required_deployment_environments":["test"]}}]`, }, { "all_rules_with_required_params", &RepositoryRulesetRules{ Creation: &EmptyRuleParameters{}, Update: &UpdateRuleParameters{}, Deletion: &EmptyRuleParameters{}, RequiredLinearHistory: &EmptyRuleParameters{}, MergeQueue: &MergeQueueRuleParameters{ CheckResponseTimeoutMinutes: 5, GroupingStrategy: MergeGroupingStrategyAllGreen, MaxEntriesToBuild: 10, MaxEntriesToMerge: 20, MergeMethod: MergeQueueMergeMethodSquash, MinEntriesToMerge: 1, MinEntriesToMergeWaitMinutes: 15, }, RequiredDeployments: &RequiredDeploymentsRuleParameters{ RequiredDeploymentEnvironments: []string{"test1", "test2"}, }, RequiredSignatures: &EmptyRuleParameters{}, PullRequest: &PullRequestRuleParameters{ DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireExtraApprovalForUnattributedChanges: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 2, RequiredReviewThreadResolution: true, }, RequiredStatusChecks: &RequiredStatusChecksRuleParameters{ RequiredStatusChecks: []*RuleStatusCheck{ {Context: "test1"}, {Context: "test2"}, }, StrictRequiredStatusChecksPolicy: true, }, NonFastForward: &EmptyRuleParameters{}, CommitMessagePattern: &PatternRuleParameters{ Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, CommitAuthorEmailPattern: &PatternRuleParameters{ Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, CommitterEmailPattern: &PatternRuleParameters{ Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, BranchNamePattern: &PatternRuleParameters{ Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, TagNamePattern: &PatternRuleParameters{ Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, FilePathRestriction: &FilePathRestrictionRuleParameters{ RestrictedFilePaths: []string{"test1", "test2"}, }, MaxFilePathLength: &MaxFilePathLengthRuleParameters{MaxFilePathLength: 512}, FileExtensionRestriction: &FileExtensionRestrictionRuleParameters{ RestrictedFileExtensions: []string{".exe", ".pkg"}, }, MaxFileSize: &MaxFileSizeRuleParameters{MaxFileSize: 1024}, Workflows: &WorkflowsRuleParameters{ Workflows: []*RuleWorkflow{ {Path: ".github/workflows/test1.yaml"}, {Path: ".github/workflows/test2.yaml"}, }, }, CodeScanning: &CodeScanningRuleParameters{ CodeScanningTools: []*RuleCodeScanningTool{ { AlertsThreshold: CodeScanningAlertsThresholdAll, SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdAll, Tool: "test", }, { AlertsThreshold: CodeScanningAlertsThresholdNone, SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdNone, Tool: "test", }, }, }, CopilotCodeReview: &CopilotCodeReviewRuleParameters{ ReviewOnPush: true, ReviewDraftPullRequests: false, }, RepositoryCreate: &EmptyRuleParameters{}, RepositoryDelete: &EmptyRuleParameters{}, RepositoryName: &SimplePatternRuleParameters{Pattern: "^test-.+", Negate: false}, RepositoryTransfer: &EmptyRuleParameters{}, RepositoryVisibility: &RepositoryVisibilityRuleParameters{Internal: false, Private: false, Public: 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":{"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_extra_approval_for_unattributed_changes":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_on_push":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,"public":false}}]`, }, { "all_rules_with_all_params", &RepositoryRulesetRules{ Creation: &EmptyRuleParameters{}, Update: &UpdateRuleParameters{UpdateAllowsFetchAndMerge: true}, Deletion: &EmptyRuleParameters{}, RequiredLinearHistory: &EmptyRuleParameters{}, MergeQueue: &MergeQueueRuleParameters{ CheckResponseTimeoutMinutes: 5, GroupingStrategy: MergeGroupingStrategyAllGreen, MaxEntriesToBuild: 10, MaxEntriesToMerge: 20, MergeMethod: MergeQueueMergeMethodSquash, MinEntriesToMerge: 1, MinEntriesToMergeWaitMinutes: 15, }, RequiredDeployments: &RequiredDeploymentsRuleParameters{ RequiredDeploymentEnvironments: []string{"test1", "test2"}, }, RequiredSignatures: &EmptyRuleParameters{}, PullRequest: &PullRequestRuleParameters{ AllowedMergeMethods: []PullRequestMergeMethod{ PullRequestMergeMethodSquash, PullRequestMergeMethodRebase, }, DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireExtraApprovalForUnattributedChanges: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 2, RequiredReviewThreadResolution: true, }, RequiredStatusChecks: &RequiredStatusChecksRuleParameters{ DoNotEnforceOnCreate: new(true), RequiredStatusChecks: []*RuleStatusCheck{ {Context: "test1", IntegrationID: new(int64(1))}, {Context: "test2", IntegrationID: new(int64(2))}, }, StrictRequiredStatusChecksPolicy: true, }, NonFastForward: &EmptyRuleParameters{}, CommitMessagePattern: &PatternRuleParameters{ Name: new("cmp"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, CommitAuthorEmailPattern: &PatternRuleParameters{ Name: new("caep"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, CommitterEmailPattern: &PatternRuleParameters{ Name: new("cep"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, BranchNamePattern: &PatternRuleParameters{ Name: new("bp"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, TagNamePattern: &PatternRuleParameters{ Name: new("tp"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, FilePathRestriction: &FilePathRestrictionRuleParameters{ RestrictedFilePaths: []string{"test1", "test2"}, }, MaxFilePathLength: &MaxFilePathLengthRuleParameters{MaxFilePathLength: 512}, FileExtensionRestriction: &FileExtensionRestrictionRuleParameters{ RestrictedFileExtensions: []string{".exe", ".pkg"}, }, MaxFileSize: &MaxFileSizeRuleParameters{MaxFileSize: 1024}, Workflows: &WorkflowsRuleParameters{ DoNotEnforceOnCreate: new(true), Workflows: []*RuleWorkflow{ { Path: ".github/workflows/test1.yaml", Ref: new("main"), RepositoryID: new(int64(1)), SHA: new("aaaa"), }, { Path: ".github/workflows/test2.yaml", Ref: new("main"), RepositoryID: new(int64(2)), SHA: new("bbbb"), }, }, }, CodeScanning: &CodeScanningRuleParameters{ CodeScanningTools: []*RuleCodeScanningTool{ { AlertsThreshold: CodeScanningAlertsThresholdAll, SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdAll, Tool: "test", }, { AlertsThreshold: CodeScanningAlertsThresholdNone, SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdNone, Tool: "test", }, }, }, CopilotCodeReview: &CopilotCodeReviewRuleParameters{ ReviewOnPush: true, ReviewDraftPullRequests: false, }, RepositoryCreate: &EmptyRuleParameters{}, RepositoryDelete: &EmptyRuleParameters{}, RepositoryName: &SimplePatternRuleParameters{Pattern: "^test-.+", Negate: false}, RepositoryTransfer: &EmptyRuleParameters{}, RepositoryVisibility: &RepositoryVisibilityRuleParameters{Internal: false, Private: false, Public: 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"],"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_extra_approval_for_unattributed_changes":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_on_push":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,"public":false}}]`, }, } t.Run("MarshalJSON", func(t *testing.T) { t.Parallel() for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() testJSONMarshalOnly(t, test.rules, test.json) testJSONMarshalOnly(t, *test.rules, test.json) }) } }) t.Run("UnmarshalJSON", func(t *testing.T) { t.Parallel() for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() got := &RepositoryRulesetRules{} err := json.Unmarshal([]byte(test.json), got) if err != nil { t.Errorf("Unable to unmarshal JSON %v: %v", test.json, err) } if diff := cmp.Diff(test.rules, got); diff != "" { t.Errorf( "json.Unmarshal returned:\n%#v\nwant:\n%#v\ndiff:\n%v", got, test.rules, diff, ) } }) } }) t.Run("UnmarshalJSON_Error", func(t *testing.T) { t.Parallel() tests := []struct { name string json string }{ { "invalid_copilot_code_review_bool", `[{"type":"copilot_code_review","parameters":{"review_on_push":"invalid_bool"}}]`, }, { "invalid_copilot_code_review_draft_pr", `[{"type":"copilot_code_review","parameters":{"review_on_push":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) } }) } }) } func TestBranchRules(t *testing.T) { t.Parallel() tests := []struct { name string rules *BranchRules json string }{ {"empty", &BranchRules{}, `[]`}, { "single_rule_type_single_rule_empty_params", &BranchRules{ Creation: []*BranchRuleMetadata{ { RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, }, }, `[{"type":"creation","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1}]`, }, { "single_rule_type_single_rule_with_params", &BranchRules{ Update: []*UpdateBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: UpdateRuleParameters{UpdateAllowsFetchAndMerge: true}, }, }, }, `[{"type":"update","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"update_allows_fetch_and_merge":true}}]`, }, { "all_rule_types_with_all_parameters", &BranchRules{ Creation: []*BranchRuleMetadata{ { RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, }, Update: []*UpdateBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: UpdateRuleParameters{UpdateAllowsFetchAndMerge: true}, }, }, Deletion: []*BranchRuleMetadata{ { RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, }, RequiredLinearHistory: []*BranchRuleMetadata{ { RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, }, MergeQueue: []*MergeQueueBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: MergeQueueRuleParameters{ CheckResponseTimeoutMinutes: 5, GroupingStrategy: MergeGroupingStrategyAllGreen, MaxEntriesToBuild: 10, MaxEntriesToMerge: 20, MergeMethod: MergeQueueMergeMethodSquash, MinEntriesToMerge: 1, MinEntriesToMergeWaitMinutes: 15, }, }, }, RequiredDeployments: []*RequiredDeploymentsBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: RequiredDeploymentsRuleParameters{ RequiredDeploymentEnvironments: []string{"test1", "test2"}, }, }, }, RequiredSignatures: []*BranchRuleMetadata{ { RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, }, PullRequest: []*PullRequestBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: PullRequestRuleParameters{ AllowedMergeMethods: []PullRequestMergeMethod{ PullRequestMergeMethodSquash, PullRequestMergeMethodRebase, }, DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireExtraApprovalForUnattributedChanges: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 2, RequiredReviewThreadResolution: true, }, }, }, RequiredStatusChecks: []*RequiredStatusChecksBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: RequiredStatusChecksRuleParameters{ DoNotEnforceOnCreate: new(true), RequiredStatusChecks: []*RuleStatusCheck{ {Context: "test1", IntegrationID: new(int64(1))}, {Context: "test2", IntegrationID: new(int64(2))}, }, StrictRequiredStatusChecksPolicy: true, }, }, }, NonFastForward: []*BranchRuleMetadata{ { RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, }, CommitMessagePattern: []*PatternBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: PatternRuleParameters{ Name: new("cmp"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, }, CommitAuthorEmailPattern: []*PatternBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: PatternRuleParameters{ Name: new("caep"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, }, CommitterEmailPattern: []*PatternBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: PatternRuleParameters{ Name: new("cep"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, }, BranchNamePattern: []*PatternBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: PatternRuleParameters{ Name: new("bp"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, }, TagNamePattern: []*PatternBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: PatternRuleParameters{ Name: new("tp"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, }, FilePathRestriction: []*FilePathRestrictionBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: FilePathRestrictionRuleParameters{ RestrictedFilePaths: []string{"test1", "test2"}, }, }, }, MaxFilePathLength: []*MaxFilePathLengthBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: MaxFilePathLengthRuleParameters{MaxFilePathLength: 512}, }, }, FileExtensionRestriction: []*FileExtensionRestrictionBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: FileExtensionRestrictionRuleParameters{ RestrictedFileExtensions: []string{".exe", ".pkg"}, }, }, }, MaxFileSize: []*MaxFileSizeBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: MaxFileSizeRuleParameters{MaxFileSize: 1024}, }, }, Workflows: []*WorkflowsBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: WorkflowsRuleParameters{ DoNotEnforceOnCreate: new(true), Workflows: []*RuleWorkflow{ { Path: ".github/workflows/test1.yaml", Ref: new("main"), RepositoryID: new(int64(1)), SHA: new("aaaa"), }, { Path: ".github/workflows/test2.yaml", Ref: new("main"), RepositoryID: new(int64(2)), SHA: new("bbbb"), }, }, }, }, }, CodeScanning: []*CodeScanningBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: CodeScanningRuleParameters{ CodeScanningTools: []*RuleCodeScanningTool{ { AlertsThreshold: CodeScanningAlertsThresholdAll, SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdAll, Tool: "test", }, { AlertsThreshold: CodeScanningAlertsThresholdNone, SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdNone, Tool: "test", }, }, }, }, }, CopilotCodeReview: []*CopilotCodeReviewBranchRule{ { BranchRuleMetadata: BranchRuleMetadata{ RulesetSourceType: RulesetSourceTypeRepository, RulesetSource: "test/test", RulesetID: 1, }, Parameters: CopilotCodeReviewRuleParameters{ ReviewOnPush: true, ReviewDraftPullRequests: false, }, }, }, }, `[{"type":"creation","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1},{"type":"update","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"update_allows_fetch_and_merge":true}},{"type":"deletion","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1},{"type":"required_linear_history","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1},{"type":"merge_queue","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"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","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"required_deployment_environments":["test1","test2"]}},{"type":"required_signatures","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1},{"type":"pull_request","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"allowed_merge_methods":["squash","rebase"],"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_extra_approval_for_unattributed_changes":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}},{"type":"required_status_checks","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"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","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1},{"type":"commit_message_pattern","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"name":"cmp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"commit_author_email_pattern","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"name":"caep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"committer_email_pattern","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"name":"cep","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"branch_name_pattern","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"name":"bp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"tag_name_pattern","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"name":"tp","negate":false,"operator":"starts_with","pattern":"test"}},{"type":"file_path_restriction","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"restricted_file_paths":["test1","test2"]}},{"type":"max_file_path_length","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"max_file_path_length":512}},{"type":"file_extension_restriction","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"restricted_file_extensions":[".exe",".pkg"]}},{"type":"max_file_size","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"max_file_size":1024}},{"type":"workflows","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"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","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"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","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"review_on_push":true,"review_draft_pull_requests":false}}]`, }, } t.Run("UnmarshalJSON", func(t *testing.T) { t.Parallel() for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() got := &BranchRules{} err := json.Unmarshal([]byte(test.json), got) if err != nil { t.Errorf("Unable to unmarshal JSON %v: %v", test.json, err) } if diff := cmp.Diff(test.rules, got); diff != "" { t.Errorf( "json.Unmarshal returned:\n%#v\nwant:\n%#v\ndiff:\n%v", got, test.rules, diff, ) } }) } }) t.Run("UnmarshalJSON_Error", func(t *testing.T) { t.Parallel() tests := []struct { name string json string }{ { "invalid_copilot_code_review_parameters", `[{"type":"copilot_code_review","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":"not_an_object"}]`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() got := &BranchRules{} err := json.Unmarshal([]byte(tt.json), got) if err == nil { t.Errorf("Expected error unmarshaling %q, got nil", tt.json) } }) } }) } func TestRepositoryRule(t *testing.T) { t.Parallel() tests := []struct { name string rule *RepositoryRule json string }{ { "empty", &RepositoryRule{}, `{}`, }, { "creation", &RepositoryRule{Type: RulesetRuleTypeCreation, Parameters: nil}, `{"type":"creation"}`, }, { "update", &RepositoryRule{Type: RulesetRuleTypeUpdate, Parameters: &UpdateRuleParameters{}}, `{"type":"update"}`, }, { "update_params_empty", &RepositoryRule{Type: RulesetRuleTypeUpdate, Parameters: &UpdateRuleParameters{}}, `{"type":"update","parameters":{}}`, }, { "update_params_set", &RepositoryRule{ Type: RulesetRuleTypeUpdate, Parameters: &UpdateRuleParameters{UpdateAllowsFetchAndMerge: true}, }, `{"type":"update","parameters":{"update_allows_fetch_and_merge":true}}`, }, { "deletion", &RepositoryRule{Type: RulesetRuleTypeDeletion, Parameters: nil}, `{"type":"deletion"}`, }, { "required_linear_history", &RepositoryRule{Type: RulesetRuleTypeRequiredLinearHistory, Parameters: nil}, `{"type":"required_linear_history"}`, }, { "merge_queue", &RepositoryRule{ Type: RulesetRuleTypeMergeQueue, Parameters: &MergeQueueRuleParameters{ CheckResponseTimeoutMinutes: 5, GroupingStrategy: MergeGroupingStrategyAllGreen, MaxEntriesToBuild: 10, MaxEntriesToMerge: 20, MergeMethod: MergeQueueMergeMethodSquash, MinEntriesToMerge: 1, MinEntriesToMergeWaitMinutes: 15, }, }, `{"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}}`, }, { "required_deployments", &RepositoryRule{ Type: RulesetRuleTypeRequiredDeployments, Parameters: &RequiredDeploymentsRuleParameters{ RequiredDeploymentEnvironments: []string{"test1", "test2"}, }, }, `{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}}`, }, { "required_signatures", &RepositoryRule{Type: RulesetRuleTypeRequiredSignatures, Parameters: nil}, `{"type":"required_signatures"}`, }, { "pull_request", &RepositoryRule{ Type: RulesetRuleTypePullRequest, Parameters: &PullRequestRuleParameters{ AllowedMergeMethods: []PullRequestMergeMethod{ PullRequestMergeMethodSquash, PullRequestMergeMethodRebase, }, DismissStaleReviewsOnPush: true, RequireCodeOwnerReview: true, RequireExtraApprovalForUnattributedChanges: true, RequireLastPushApproval: true, RequiredApprovingReviewCount: 2, RequiredReviewThreadResolution: true, }, }, `{"type":"pull_request","parameters":{"allowed_merge_methods":["squash","rebase"],"dismiss_stale_reviews_on_push":true,"require_code_owner_review":true,"require_extra_approval_for_unattributed_changes":true,"require_last_push_approval":true,"required_approving_review_count":2,"required_review_thread_resolution":true}}`, }, { "pull_request_with_required_reviewers", &RepositoryRule{ Type: RulesetRuleTypePullRequest, Parameters: &PullRequestRuleParameters{ AllowedMergeMethods: []PullRequestMergeMethod{ PullRequestMergeMethodMerge, PullRequestMergeMethodSquash, PullRequestMergeMethodRebase, }, DismissStaleReviewsOnPush: false, RequireCodeOwnerReview: false, RequireExtraApprovalForUnattributedChanges: false, RequireLastPushApproval: false, RequiredApprovingReviewCount: 0, RequiredReviewThreadResolution: false, RequiredReviewers: []*RulesetRequiredReviewer{ { MinimumApprovals: new(1), FilePatterns: []string{"*"}, Reviewer: &RulesetReviewer{ ID: new(int64(123456)), Type: new(RulesetReviewerTypeTeam), }, }, }, }, }, `{"type":"pull_request","parameters":{"allowed_merge_methods":["merge","squash","rebase"],"dismiss_stale_reviews_on_push":false,"require_code_owner_review":false,"require_extra_approval_for_unattributed_changes":false,"require_last_push_approval":false,"required_approving_review_count":0,"required_reviewers":[{"minimum_approvals":1,"file_patterns":["*"],"reviewer":{"id":123456,"type":"Team"}}],"required_review_thread_resolution":false}}`, }, { "pull_request_with_dismissal_restriction", &RepositoryRule{ Type: RulesetRuleTypePullRequest, Parameters: &PullRequestRuleParameters{ DismissalRestriction: &DismissalRestriction{ AllowedActors: []*DismissalRestrictionActor{ {ID: 123456, Type: DismissalRestrictionActorTypeTeam}, {ID: 5, Type: DismissalRestrictionActorTypeRepositoryRole}, }, Enabled: true, }, DismissStaleReviewsOnPush: false, RequireCodeOwnerReview: false, RequireLastPushApproval: false, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: false, }, }, `{"type":"pull_request","parameters":{"dismissal_restriction":{"allowed_actors":[{"id":123456,"type":"Team"},{"id":5,"type":"RepositoryRole"}],"enabled":true},"dismiss_stale_reviews_on_push":false,"require_code_owner_review":false,"require_last_push_approval":false,"required_approving_review_count":1,"required_review_thread_resolution":false}}`, }, { "pull_request_with_dismissal_restricted_to_nobody", &RepositoryRule{ Type: RulesetRuleTypePullRequest, Parameters: &PullRequestRuleParameters{ DismissalRestriction: &DismissalRestriction{Enabled: true}, DismissStaleReviewsOnPush: false, RequireCodeOwnerReview: false, RequireLastPushApproval: false, RequiredApprovingReviewCount: 1, RequiredReviewThreadResolution: false, }, }, `{"type":"pull_request","parameters":{"dismissal_restriction":{"enabled":true},"dismiss_stale_reviews_on_push":false,"require_code_owner_review":false,"require_last_push_approval":false,"required_approving_review_count":1,"required_review_thread_resolution":false}}`, }, { "pull_request_string_id", &RepositoryRule{ Type: RulesetRuleTypePullRequest, Parameters: &PullRequestRuleParameters{ AllowedMergeMethods: []PullRequestMergeMethod{ PullRequestMergeMethodMerge, PullRequestMergeMethodSquash, PullRequestMergeMethodRebase, }, DismissStaleReviewsOnPush: false, RequireCodeOwnerReview: false, RequireExtraApprovalForUnattributedChanges: false, RequireLastPushApproval: false, RequiredApprovingReviewCount: 0, RequiredReviewThreadResolution: false, RequiredReviewers: []*RulesetRequiredReviewer{ { MinimumApprovals: new(1), FilePatterns: []string{"*"}, Reviewer: &RulesetReviewer{ ID: new(int64(123456)), Type: new(RulesetReviewerTypeTeam), }, }, }, }, }, `{"type":"pull_request","parameters":{"allowed_merge_methods":["merge","squash","rebase"],"dismiss_stale_reviews_on_push":false,"require_code_owner_review":false,"require_extra_approval_for_unattributed_changes":false,"require_last_push_approval":false,"required_approving_review_count":0,"required_reviewers":[{"minimum_approvals":1,"file_patterns":["*"],"reviewer":{"id":"123456","type":"Team"}}],"required_review_thread_resolution":false}}`, }, { "required_status_checks", &RepositoryRule{ Type: RulesetRuleTypeRequiredStatusChecks, Parameters: &RequiredStatusChecksRuleParameters{ RequiredStatusChecks: []*RuleStatusCheck{ {Context: "test1"}, {Context: "test2"}, }, StrictRequiredStatusChecksPolicy: true, }, }, `{"type":"required_status_checks","parameters":{"required_status_checks":[{"context":"test1"},{"context":"test2"}],"strict_required_status_checks_policy":true}}`, }, { "non_fast_forward", &RepositoryRule{Type: RulesetRuleTypeNonFastForward, Parameters: nil}, `{"type":"non_fast_forward"}`, }, { "commit_message_pattern", &RepositoryRule{ Type: RulesetRuleTypeCommitMessagePattern, Parameters: &PatternRuleParameters{ Name: new("test"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, `{"type":"commit_message_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`, }, { "commit_author_email_pattern", &RepositoryRule{ Type: RulesetRuleTypeCommitAuthorEmailPattern, Parameters: &PatternRuleParameters{ Name: new("test"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, `{"type":"commit_author_email_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`, }, { "committer_email_pattern", &RepositoryRule{ Type: RulesetRuleTypeCommitterEmailPattern, Parameters: &PatternRuleParameters{ Name: new("test"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, `{"type":"committer_email_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`, }, { "branch_name_pattern", &RepositoryRule{ Type: RulesetRuleTypeBranchNamePattern, Parameters: &PatternRuleParameters{ Name: new("test"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, `{"type":"branch_name_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`, }, { "tag_name_pattern", &RepositoryRule{ Type: RulesetRuleTypeTagNamePattern, Parameters: &PatternRuleParameters{ Name: new("test"), Negate: new(false), Operator: PatternRuleOperatorStartsWith, Pattern: "test", }, }, `{"type":"tag_name_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`, }, { "file_path_restriction", &RepositoryRule{ Type: RulesetRuleTypeFilePathRestriction, Parameters: &FilePathRestrictionRuleParameters{ RestrictedFilePaths: []string{"test1", "test2"}, }, }, `{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}}`, }, { "max_file_path_length", &RepositoryRule{ Type: RulesetRuleTypeMaxFilePathLength, Parameters: &MaxFilePathLengthRuleParameters{MaxFilePathLength: 512}, }, `{"type":"max_file_path_length","parameters":{"max_file_path_length":512}}`, }, { "file_extension_restriction", &RepositoryRule{ Type: RulesetRuleTypeFileExtensionRestriction, Parameters: &FileExtensionRestrictionRuleParameters{ RestrictedFileExtensions: []string{".exe", ".pkg"}, }, }, `{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}}`, }, { "max_file_size", &RepositoryRule{ Type: RulesetRuleTypeMaxFileSize, Parameters: &MaxFileSizeRuleParameters{MaxFileSize: 1024}, }, `{"type":"max_file_size","parameters":{"max_file_size":1024}}`, }, { "workflows", &RepositoryRule{ Type: RulesetRuleTypeWorkflows, Parameters: &WorkflowsRuleParameters{ Workflows: []*RuleWorkflow{ {Path: ".github/workflows/test1.yaml"}, {Path: ".github/workflows/test2.yaml"}, }, }, }, `{"type":"workflows","parameters":{"workflows":[{"path":".github/workflows/test1.yaml"},{"path":".github/workflows/test2.yaml"}]}}`, }, { "code_scanning", &RepositoryRule{ Type: RulesetRuleTypeCodeScanning, Parameters: &CodeScanningRuleParameters{ CodeScanningTools: []*RuleCodeScanningTool{ { AlertsThreshold: CodeScanningAlertsThresholdAll, SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdAll, Tool: "test", }, { AlertsThreshold: CodeScanningAlertsThresholdNone, SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdNone, Tool: "test", }, }, }, }, `{"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{ ReviewOnPush: true, ReviewDraftPullRequests: false, }, }, `{"type":"copilot_code_review","parameters":{"review_on_push":true,"review_draft_pull_requests":false}}`, }, { "copilot_code_review_empty_params", &RepositoryRule{ Type: RulesetRuleTypeCopilotCodeReview, Parameters: &CopilotCodeReviewRuleParameters{}, }, `{"type":"copilot_code_review","parameters":{"review_on_push":false,"review_draft_pull_requests":false}}`, }, { "repository_create", &RepositoryRule{Type: RulesetRuleTypeRepositoryCreate, Parameters: nil}, `{"type":"repository_create"}`, }, { "repository_delete", &RepositoryRule{Type: RulesetRuleTypeRepositoryDelete, Parameters: nil}, `{"type":"repository_delete"}`, }, { "repository_name", &RepositoryRule{ Type: RulesetRuleTypeRepositoryName, Parameters: &SimplePatternRuleParameters{ Negate: false, Pattern: "^test-.+", }, }, `{"type":"repository_name","parameters":{"negate":false,"pattern":"^test-.+"}}`, }, { "repository_transfer", &RepositoryRule{Type: RulesetRuleTypeRepositoryTransfer, Parameters: nil}, `{"type":"repository_transfer"}`, }, { "repository_visibility", &RepositoryRule{ Type: RulesetRuleTypeRepositoryVisibility, Parameters: &RepositoryVisibilityRuleParameters{ Internal: true, Private: false, Public: true, }, }, `{"type":"repository_visibility","parameters":{"internal":true,"private":false,"public":true}}`, }, } marshalTests := []struct { name string rule *RepositoryRule json string }{ { "creation", &RepositoryRule{Type: RulesetRuleTypeCreation, Parameters: nil}, `{"type":"creation"}`, }, { "copilot_code_review", &RepositoryRule{ Type: RulesetRuleTypeCopilotCodeReview, Parameters: &CopilotCodeReviewRuleParameters{ ReviewOnPush: true, ReviewDraftPullRequests: false, }, }, `{"type":"copilot_code_review","parameters":{"review_on_push":true,"review_draft_pull_requests":false}}`, }, { "copilot_code_review_empty_params", &RepositoryRule{ Type: RulesetRuleTypeCopilotCodeReview, Parameters: &CopilotCodeReviewRuleParameters{}, }, `{"type":"copilot_code_review","parameters":{"review_on_push":false,"review_draft_pull_requests":false}}`, }, { "repository_visibility", &RepositoryRule{ Type: RulesetRuleTypeRepositoryVisibility, Parameters: &RepositoryVisibilityRuleParameters{ Internal: true, Private: false, Public: true, }, }, `{"type":"repository_visibility","parameters":{"internal":true,"private":false,"public":true}}`, }, } t.Run("MarshalJSON", func(t *testing.T) { t.Parallel() for _, test := range marshalTests { t.Run(test.name, func(t *testing.T) { t.Parallel() got, err := json.Marshal(test.rule) if err != nil { t.Errorf("Unable to marshal JSON for %#v", test.rule) } if diff := cmp.Diff(test.json, string(got)); diff != "" { t.Errorf( "json.Marshal returned:\n%v\nwant:\n%v\ndiff:\n%v", string(got), test.json, diff, ) } }) } }) t.Run("UnmarshalJSON", func(t *testing.T) { t.Parallel() for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() got := &RepositoryRule{} err := json.Unmarshal([]byte(test.json), got) if err != nil { t.Errorf("Unable to unmarshal JSON %v: %v", test.json, err) } if diff := cmp.Diff(test.rule, got); diff != "" { t.Errorf( "json.Unmarshal returned:\n%#v\nwant:\n%#v\ndiff:\n%v", got, test.rule, diff, ) } }) } }) t.Run("UnmarshalJSON_Error", func(t *testing.T) { t.Parallel() tests := []struct { name string json string }{ { "invalid_copilot_code_review_bool", `{"type":"copilot_code_review","parameters":{"review_on_push":"invalid_bool"}}`, }, { "invalid_copilot_code_review_draft_pr", `{"type":"copilot_code_review","parameters":{"review_on_push":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) } }) } }) } func TestRulesetReviewer_UnmarshalJSON(t *testing.T) { t.Parallel() tests := []struct { name string json string wantID int64 wantErr bool }{ { name: "integer_id", json: `{"id": 123456, "type": "Team"}`, wantID: 123456, wantErr: false, }, { name: "string_id", json: `{"id": "123456", "type": "Team"}`, wantID: 123456, wantErr: false, }, { name: "invalid_string_id", json: `{"id": "not-a-number", "type": "Team"}`, wantErr: true, }, { name: "invalid_type_id", json: `{"id": {}, "type": "Team"}`, wantErr: true, }, { name: "malformed_json", json: `{"id":`, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() var got RulesetReviewer err := json.Unmarshal([]byte(tt.json), &got) if (err != nil) != tt.wantErr { t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr { if got.GetID() != tt.wantID { t.Errorf("UnmarshalJSON() got ID = %v, want %v", got.GetID(), tt.wantID) } if got.GetType() == nil || *got.GetType() != RulesetReviewerTypeTeam { t.Errorf("UnmarshalJSON() got Type = %v, want %v", got.GetType(), RulesetReviewerTypeTeam) } } }) } }