github.com/google/go-github/v70@v70.0.0/github/rules_test.go (about)

     1  // Copyright 2025 The go-github AUTHORS. All rights reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package github
     7  
     8  import (
     9  	"encoding/json"
    10  	"testing"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  )
    14  
    15  func TestRulesetRules(t *testing.T) {
    16  	t.Parallel()
    17  	tests := []struct {
    18  		name  string
    19  		rules *RepositoryRulesetRules
    20  		json  string
    21  	}{
    22  		{"empty", &RepositoryRulesetRules{}, `[]`},
    23  		{
    24  			"single_rule_with_empty_params",
    25  			&RepositoryRulesetRules{Creation: &EmptyRuleParameters{}},
    26  			`[{"type":"creation"}]`,
    27  		},
    28  		{
    29  			"single_rule_with_required_params",
    30  			&RepositoryRulesetRules{
    31  				RequiredDeployments: &RequiredDeploymentsRuleParameters{
    32  					RequiredDeploymentEnvironments: []string{"test"},
    33  				},
    34  			},
    35  			`[{"type":"required_deployments","parameters":{"required_deployment_environments":["test"]}}]`,
    36  		},
    37  		{
    38  			"all_rules_with_required_params",
    39  			&RepositoryRulesetRules{
    40  				Creation:              &EmptyRuleParameters{},
    41  				Update:                &UpdateRuleParameters{},
    42  				Deletion:              &EmptyRuleParameters{},
    43  				RequiredLinearHistory: &EmptyRuleParameters{},
    44  				MergeQueue: &MergeQueueRuleParameters{
    45  					CheckResponseTimeoutMinutes:  5,
    46  					GroupingStrategy:             MergeGroupingStrategyAllGreen,
    47  					MaxEntriesToBuild:            10,
    48  					MaxEntriesToMerge:            20,
    49  					MergeMethod:                  MergeMethodSquash,
    50  					MinEntriesToMerge:            1,
    51  					MinEntriesToMergeWaitMinutes: 15,
    52  				},
    53  				RequiredDeployments: &RequiredDeploymentsRuleParameters{
    54  					RequiredDeploymentEnvironments: []string{"test1", "test2"},
    55  				},
    56  				RequiredSignatures: &EmptyRuleParameters{},
    57  				PullRequest: &PullRequestRuleParameters{
    58  					AllowedMergeMethods: []MergeMethod{
    59  						MergeMethodSquash,
    60  						MergeMethodRebase,
    61  					},
    62  					AutomaticCopilotCodeReviewEnabled: nil,
    63  					DismissStaleReviewsOnPush:         true,
    64  					RequireCodeOwnerReview:            true,
    65  					RequireLastPushApproval:           true,
    66  					RequiredApprovingReviewCount:      2,
    67  					RequiredReviewThreadResolution:    true,
    68  				},
    69  				RequiredStatusChecks: &RequiredStatusChecksRuleParameters{
    70  					RequiredStatusChecks: []*RuleStatusCheck{
    71  						{Context: "test1"},
    72  						{Context: "test2"},
    73  					},
    74  					StrictRequiredStatusChecksPolicy: true,
    75  				},
    76  				NonFastForward: &EmptyRuleParameters{},
    77  				CommitMessagePattern: &PatternRuleParameters{
    78  					Operator: PatternRuleOperatorStartsWith,
    79  					Pattern:  "test",
    80  				},
    81  				CommitAuthorEmailPattern: &PatternRuleParameters{
    82  					Operator: PatternRuleOperatorStartsWith,
    83  					Pattern:  "test",
    84  				},
    85  				CommitterEmailPattern: &PatternRuleParameters{
    86  					Operator: PatternRuleOperatorStartsWith,
    87  					Pattern:  "test",
    88  				},
    89  				BranchNamePattern: &PatternRuleParameters{
    90  					Operator: PatternRuleOperatorStartsWith,
    91  					Pattern:  "test",
    92  				},
    93  				TagNamePattern: &PatternRuleParameters{
    94  					Operator: PatternRuleOperatorStartsWith,
    95  					Pattern:  "test",
    96  				},
    97  				FilePathRestriction: &FilePathRestrictionRuleParameters{
    98  					RestrictedFilePaths: []string{"test1", "test2"},
    99  				},
   100  				MaxFilePathLength: &MaxFilePathLengthRuleParameters{MaxFilePathLength: 512},
   101  				FileExtensionRestriction: &FileExtensionRestrictionRuleParameters{
   102  					RestrictedFileExtensions: []string{".exe", ".pkg"},
   103  				},
   104  				MaxFileSize: &MaxFileSizeRuleParameters{MaxFileSize: 1024},
   105  				Workflows: &WorkflowsRuleParameters{
   106  					Workflows: []*RuleWorkflow{
   107  						{Path: ".github/workflows/test1.yaml"},
   108  						{Path: ".github/workflows/test2.yaml"},
   109  					},
   110  				},
   111  				CodeScanning: &CodeScanningRuleParameters{
   112  					CodeScanningTools: []*RuleCodeScanningTool{
   113  						{
   114  							AlertsThreshold:         CodeScanningAlertsThresholdAll,
   115  							SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdAll,
   116  							Tool:                    "test",
   117  						},
   118  						{
   119  							AlertsThreshold:         CodeScanningAlertsThresholdNone,
   120  							SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdNone,
   121  							Tool:                    "test",
   122  						},
   123  					},
   124  				},
   125  			},
   126  			`[{"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"}]}}]`,
   127  		},
   128  		{
   129  			"all_rules_with_all_params",
   130  			&RepositoryRulesetRules{
   131  				Creation:              &EmptyRuleParameters{},
   132  				Update:                &UpdateRuleParameters{UpdateAllowsFetchAndMerge: true},
   133  				Deletion:              &EmptyRuleParameters{},
   134  				RequiredLinearHistory: &EmptyRuleParameters{},
   135  				MergeQueue: &MergeQueueRuleParameters{
   136  					CheckResponseTimeoutMinutes:  5,
   137  					GroupingStrategy:             MergeGroupingStrategyAllGreen,
   138  					MaxEntriesToBuild:            10,
   139  					MaxEntriesToMerge:            20,
   140  					MergeMethod:                  MergeMethodSquash,
   141  					MinEntriesToMerge:            1,
   142  					MinEntriesToMergeWaitMinutes: 15,
   143  				},
   144  				RequiredDeployments: &RequiredDeploymentsRuleParameters{
   145  					RequiredDeploymentEnvironments: []string{"test1", "test2"},
   146  				},
   147  				RequiredSignatures: &EmptyRuleParameters{},
   148  				PullRequest: &PullRequestRuleParameters{
   149  					AllowedMergeMethods: []MergeMethod{
   150  						MergeMethodSquash,
   151  						MergeMethodRebase,
   152  					},
   153  					AutomaticCopilotCodeReviewEnabled: Ptr(false),
   154  					DismissStaleReviewsOnPush:         true,
   155  					RequireCodeOwnerReview:            true,
   156  					RequireLastPushApproval:           true,
   157  					RequiredApprovingReviewCount:      2,
   158  					RequiredReviewThreadResolution:    true,
   159  				},
   160  				RequiredStatusChecks: &RequiredStatusChecksRuleParameters{
   161  					DoNotEnforceOnCreate: Ptr(true),
   162  					RequiredStatusChecks: []*RuleStatusCheck{
   163  						{Context: "test1", IntegrationID: Ptr(int64(1))},
   164  						{Context: "test2", IntegrationID: Ptr(int64(2))},
   165  					},
   166  					StrictRequiredStatusChecksPolicy: true,
   167  				},
   168  				NonFastForward: &EmptyRuleParameters{},
   169  				CommitMessagePattern: &PatternRuleParameters{
   170  					Name:     Ptr("cmp"),
   171  					Negate:   Ptr(false),
   172  					Operator: PatternRuleOperatorStartsWith,
   173  					Pattern:  "test",
   174  				},
   175  				CommitAuthorEmailPattern: &PatternRuleParameters{
   176  					Name:     Ptr("caep"),
   177  					Negate:   Ptr(false),
   178  					Operator: PatternRuleOperatorStartsWith,
   179  					Pattern:  "test",
   180  				},
   181  				CommitterEmailPattern: &PatternRuleParameters{
   182  					Name:     Ptr("cep"),
   183  					Negate:   Ptr(false),
   184  					Operator: PatternRuleOperatorStartsWith,
   185  					Pattern:  "test",
   186  				},
   187  				BranchNamePattern: &PatternRuleParameters{
   188  					Name:     Ptr("bp"),
   189  					Negate:   Ptr(false),
   190  					Operator: PatternRuleOperatorStartsWith,
   191  					Pattern:  "test",
   192  				},
   193  				TagNamePattern: &PatternRuleParameters{
   194  					Name:     Ptr("tp"),
   195  					Negate:   Ptr(false),
   196  					Operator: PatternRuleOperatorStartsWith,
   197  					Pattern:  "test",
   198  				},
   199  				FilePathRestriction: &FilePathRestrictionRuleParameters{
   200  					RestrictedFilePaths: []string{"test1", "test2"},
   201  				},
   202  				MaxFilePathLength: &MaxFilePathLengthRuleParameters{MaxFilePathLength: 512},
   203  				FileExtensionRestriction: &FileExtensionRestrictionRuleParameters{
   204  					RestrictedFileExtensions: []string{".exe", ".pkg"},
   205  				},
   206  				MaxFileSize: &MaxFileSizeRuleParameters{MaxFileSize: 1024},
   207  				Workflows: &WorkflowsRuleParameters{
   208  					DoNotEnforceOnCreate: Ptr(true),
   209  					Workflows: []*RuleWorkflow{
   210  						{
   211  							Path:         ".github/workflows/test1.yaml",
   212  							Ref:          Ptr("main"),
   213  							RepositoryID: Ptr(int64(1)),
   214  							SHA:          Ptr("aaaa"),
   215  						},
   216  						{
   217  							Path:         ".github/workflows/test2.yaml",
   218  							Ref:          Ptr("main"),
   219  							RepositoryID: Ptr(int64(2)),
   220  							SHA:          Ptr("bbbb"),
   221  						},
   222  					},
   223  				},
   224  				CodeScanning: &CodeScanningRuleParameters{
   225  					CodeScanningTools: []*RuleCodeScanningTool{
   226  						{
   227  							AlertsThreshold:         CodeScanningAlertsThresholdAll,
   228  							SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdAll,
   229  							Tool:                    "test",
   230  						},
   231  						{
   232  							AlertsThreshold:         CodeScanningAlertsThresholdNone,
   233  							SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdNone,
   234  							Tool:                    "test",
   235  						},
   236  					},
   237  				},
   238  			},
   239  			`[{"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"}]}}]`,
   240  		},
   241  	}
   242  
   243  	t.Run("MarshalJSON", func(t *testing.T) {
   244  		t.Parallel()
   245  
   246  		for _, test := range tests {
   247  			t.Run(test.name, func(t *testing.T) {
   248  				t.Parallel()
   249  
   250  				got, err := json.Marshal(test.rules)
   251  				if err != nil {
   252  					t.Errorf("Unable to marshal JSON for %#v", test.rules)
   253  				}
   254  
   255  				if diff := cmp.Diff(test.json, string(got)); diff != "" {
   256  					t.Errorf(
   257  						"json.Marshal returned:\n%s\nwant:\n%s\ndiff:\n%v",
   258  						got,
   259  						test.json,
   260  						diff,
   261  					)
   262  				}
   263  			})
   264  		}
   265  	})
   266  
   267  	t.Run("UnmarshalJSON", func(t *testing.T) {
   268  		t.Parallel()
   269  
   270  		for _, test := range tests {
   271  			t.Run(test.name, func(t *testing.T) {
   272  				t.Parallel()
   273  
   274  				got := &RepositoryRulesetRules{}
   275  				err := json.Unmarshal([]byte(test.json), got)
   276  				if err != nil {
   277  					t.Errorf("Unable to unmarshal JSON %v: %v", test.json, err)
   278  				}
   279  
   280  				if diff := cmp.Diff(test.rules, got); diff != "" {
   281  					t.Errorf(
   282  						"json.Unmarshal returned:\n%#v\nwant:\n%#v\ndiff:\n%v",
   283  						got,
   284  						test.rules,
   285  						diff,
   286  					)
   287  				}
   288  			})
   289  		}
   290  	})
   291  }
   292  
   293  func TestBranchRules(t *testing.T) {
   294  	t.Parallel()
   295  	tests := []struct {
   296  		name  string
   297  		rules *BranchRules
   298  		json  string
   299  	}{
   300  		{"empty", &BranchRules{}, `[]`},
   301  		{
   302  			"single_rule_type_single_rule_empty_params",
   303  			&BranchRules{
   304  				Creation: []*BranchRuleMetadata{
   305  					{
   306  						RulesetSourceType: RulesetSourceTypeRepository,
   307  						RulesetSource:     "test/test",
   308  						RulesetID:         1,
   309  					},
   310  				},
   311  			},
   312  			`[{"type":"creation","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1}]`,
   313  		},
   314  		{
   315  			"single_rule_type_single_rule_with_params",
   316  			&BranchRules{
   317  				Update: []*UpdateBranchRule{
   318  					{
   319  						BranchRuleMetadata: BranchRuleMetadata{
   320  							RulesetSourceType: RulesetSourceTypeRepository,
   321  							RulesetSource:     "test/test",
   322  							RulesetID:         1,
   323  						},
   324  						Parameters: UpdateRuleParameters{UpdateAllowsFetchAndMerge: true},
   325  					},
   326  				},
   327  			},
   328  			`[{"type":"update","ruleset_source_type":"Repository","ruleset_source":"test/test","ruleset_id":1,"parameters":{"update_allows_fetch_and_merge":true}}]`,
   329  		},
   330  		{
   331  			"all_rule_types_with_all_parameters",
   332  			&BranchRules{
   333  				Creation: []*BranchRuleMetadata{
   334  					{
   335  						RulesetSourceType: RulesetSourceTypeRepository,
   336  						RulesetSource:     "test/test",
   337  						RulesetID:         1,
   338  					},
   339  				},
   340  				Update: []*UpdateBranchRule{
   341  					{
   342  						BranchRuleMetadata: BranchRuleMetadata{
   343  							RulesetSourceType: RulesetSourceTypeRepository,
   344  							RulesetSource:     "test/test",
   345  							RulesetID:         1,
   346  						},
   347  						Parameters: UpdateRuleParameters{UpdateAllowsFetchAndMerge: true},
   348  					},
   349  				},
   350  				Deletion: []*BranchRuleMetadata{
   351  					{
   352  						RulesetSourceType: RulesetSourceTypeRepository,
   353  						RulesetSource:     "test/test",
   354  						RulesetID:         1,
   355  					},
   356  				},
   357  				RequiredLinearHistory: []*BranchRuleMetadata{
   358  					{
   359  						RulesetSourceType: RulesetSourceTypeRepository,
   360  						RulesetSource:     "test/test",
   361  						RulesetID:         1,
   362  					},
   363  				},
   364  				MergeQueue: []*MergeQueueBranchRule{
   365  					{
   366  						BranchRuleMetadata: BranchRuleMetadata{
   367  							RulesetSourceType: RulesetSourceTypeRepository,
   368  							RulesetSource:     "test/test",
   369  							RulesetID:         1,
   370  						},
   371  						Parameters: MergeQueueRuleParameters{
   372  							CheckResponseTimeoutMinutes:  5,
   373  							GroupingStrategy:             MergeGroupingStrategyAllGreen,
   374  							MaxEntriesToBuild:            10,
   375  							MaxEntriesToMerge:            20,
   376  							MergeMethod:                  MergeMethodSquash,
   377  							MinEntriesToMerge:            1,
   378  							MinEntriesToMergeWaitMinutes: 15,
   379  						},
   380  					},
   381  				},
   382  				RequiredDeployments: []*RequiredDeploymentsBranchRule{
   383  					{
   384  						BranchRuleMetadata: BranchRuleMetadata{
   385  							RulesetSourceType: RulesetSourceTypeRepository,
   386  							RulesetSource:     "test/test",
   387  							RulesetID:         1,
   388  						},
   389  						Parameters: RequiredDeploymentsRuleParameters{
   390  							RequiredDeploymentEnvironments: []string{"test1", "test2"},
   391  						},
   392  					},
   393  				},
   394  				RequiredSignatures: []*BranchRuleMetadata{
   395  					{
   396  						RulesetSourceType: RulesetSourceTypeRepository,
   397  						RulesetSource:     "test/test",
   398  						RulesetID:         1,
   399  					},
   400  				},
   401  				PullRequest: []*PullRequestBranchRule{
   402  					{
   403  						BranchRuleMetadata: BranchRuleMetadata{
   404  							RulesetSourceType: RulesetSourceTypeRepository,
   405  							RulesetSource:     "test/test",
   406  							RulesetID:         1,
   407  						},
   408  						Parameters: PullRequestRuleParameters{
   409  							AllowedMergeMethods: []MergeMethod{
   410  								MergeMethodSquash,
   411  								MergeMethodRebase,
   412  							},
   413  							DismissStaleReviewsOnPush:      true,
   414  							RequireCodeOwnerReview:         true,
   415  							RequireLastPushApproval:        true,
   416  							RequiredApprovingReviewCount:   2,
   417  							RequiredReviewThreadResolution: true,
   418  						},
   419  					},
   420  				},
   421  				RequiredStatusChecks: []*RequiredStatusChecksBranchRule{
   422  					{
   423  						BranchRuleMetadata: BranchRuleMetadata{
   424  							RulesetSourceType: RulesetSourceTypeRepository,
   425  							RulesetSource:     "test/test",
   426  							RulesetID:         1,
   427  						},
   428  						Parameters: RequiredStatusChecksRuleParameters{
   429  							DoNotEnforceOnCreate: Ptr(true),
   430  							RequiredStatusChecks: []*RuleStatusCheck{
   431  								{Context: "test1", IntegrationID: Ptr(int64(1))},
   432  								{Context: "test2", IntegrationID: Ptr(int64(2))},
   433  							},
   434  							StrictRequiredStatusChecksPolicy: true,
   435  						},
   436  					},
   437  				},
   438  				NonFastForward: []*BranchRuleMetadata{
   439  					{
   440  						RulesetSourceType: RulesetSourceTypeRepository,
   441  						RulesetSource:     "test/test",
   442  						RulesetID:         1,
   443  					},
   444  				},
   445  				CommitMessagePattern: []*PatternBranchRule{
   446  					{
   447  						BranchRuleMetadata: BranchRuleMetadata{
   448  							RulesetSourceType: RulesetSourceTypeRepository,
   449  							RulesetSource:     "test/test",
   450  							RulesetID:         1,
   451  						},
   452  						Parameters: PatternRuleParameters{
   453  							Name:     Ptr("cmp"),
   454  							Negate:   Ptr(false),
   455  							Operator: PatternRuleOperatorStartsWith,
   456  							Pattern:  "test",
   457  						},
   458  					},
   459  				},
   460  				CommitAuthorEmailPattern: []*PatternBranchRule{
   461  					{
   462  						BranchRuleMetadata: BranchRuleMetadata{
   463  							RulesetSourceType: RulesetSourceTypeRepository,
   464  							RulesetSource:     "test/test",
   465  							RulesetID:         1,
   466  						},
   467  						Parameters: PatternRuleParameters{
   468  							Name:     Ptr("caep"),
   469  							Negate:   Ptr(false),
   470  							Operator: PatternRuleOperatorStartsWith,
   471  							Pattern:  "test",
   472  						},
   473  					},
   474  				},
   475  				CommitterEmailPattern: []*PatternBranchRule{
   476  					{
   477  						BranchRuleMetadata: BranchRuleMetadata{
   478  							RulesetSourceType: RulesetSourceTypeRepository,
   479  							RulesetSource:     "test/test",
   480  							RulesetID:         1,
   481  						},
   482  						Parameters: PatternRuleParameters{
   483  							Name:     Ptr("cep"),
   484  							Negate:   Ptr(false),
   485  							Operator: PatternRuleOperatorStartsWith,
   486  							Pattern:  "test",
   487  						},
   488  					},
   489  				},
   490  				BranchNamePattern: []*PatternBranchRule{
   491  					{
   492  						BranchRuleMetadata: BranchRuleMetadata{
   493  							RulesetSourceType: RulesetSourceTypeRepository,
   494  							RulesetSource:     "test/test",
   495  							RulesetID:         1,
   496  						},
   497  						Parameters: PatternRuleParameters{
   498  							Name:     Ptr("bp"),
   499  							Negate:   Ptr(false),
   500  							Operator: PatternRuleOperatorStartsWith,
   501  							Pattern:  "test",
   502  						},
   503  					},
   504  				},
   505  				TagNamePattern: []*PatternBranchRule{
   506  					{
   507  						BranchRuleMetadata: BranchRuleMetadata{
   508  							RulesetSourceType: RulesetSourceTypeRepository,
   509  							RulesetSource:     "test/test",
   510  							RulesetID:         1,
   511  						},
   512  						Parameters: PatternRuleParameters{
   513  							Name:     Ptr("tp"),
   514  							Negate:   Ptr(false),
   515  							Operator: PatternRuleOperatorStartsWith,
   516  							Pattern:  "test",
   517  						},
   518  					},
   519  				},
   520  				FilePathRestriction: []*FilePathRestrictionBranchRule{
   521  					{
   522  						BranchRuleMetadata: BranchRuleMetadata{
   523  							RulesetSourceType: RulesetSourceTypeRepository,
   524  							RulesetSource:     "test/test",
   525  							RulesetID:         1,
   526  						},
   527  						Parameters: FilePathRestrictionRuleParameters{
   528  							RestrictedFilePaths: []string{"test1", "test2"},
   529  						},
   530  					},
   531  				},
   532  				MaxFilePathLength: []*MaxFilePathLengthBranchRule{
   533  					{
   534  						BranchRuleMetadata: BranchRuleMetadata{
   535  							RulesetSourceType: RulesetSourceTypeRepository,
   536  							RulesetSource:     "test/test",
   537  							RulesetID:         1,
   538  						},
   539  						Parameters: MaxFilePathLengthRuleParameters{MaxFilePathLength: 512},
   540  					},
   541  				},
   542  				FileExtensionRestriction: []*FileExtensionRestrictionBranchRule{
   543  					{
   544  						BranchRuleMetadata: BranchRuleMetadata{
   545  							RulesetSourceType: RulesetSourceTypeRepository,
   546  							RulesetSource:     "test/test",
   547  							RulesetID:         1,
   548  						},
   549  						Parameters: FileExtensionRestrictionRuleParameters{
   550  							RestrictedFileExtensions: []string{".exe", ".pkg"},
   551  						},
   552  					},
   553  				},
   554  				MaxFileSize: []*MaxFileSizeBranchRule{
   555  					{
   556  						BranchRuleMetadata: BranchRuleMetadata{
   557  							RulesetSourceType: RulesetSourceTypeRepository,
   558  							RulesetSource:     "test/test",
   559  							RulesetID:         1,
   560  						},
   561  						Parameters: MaxFileSizeRuleParameters{MaxFileSize: 1024},
   562  					},
   563  				},
   564  				Workflows: []*WorkflowsBranchRule{
   565  					{
   566  						BranchRuleMetadata: BranchRuleMetadata{
   567  							RulesetSourceType: RulesetSourceTypeRepository,
   568  							RulesetSource:     "test/test",
   569  							RulesetID:         1,
   570  						},
   571  						Parameters: WorkflowsRuleParameters{
   572  							DoNotEnforceOnCreate: Ptr(true),
   573  							Workflows: []*RuleWorkflow{
   574  								{
   575  									Path:         ".github/workflows/test1.yaml",
   576  									Ref:          Ptr("main"),
   577  									RepositoryID: Ptr(int64(1)),
   578  									SHA:          Ptr("aaaa"),
   579  								},
   580  								{
   581  									Path:         ".github/workflows/test2.yaml",
   582  									Ref:          Ptr("main"),
   583  									RepositoryID: Ptr(int64(2)),
   584  									SHA:          Ptr("bbbb"),
   585  								},
   586  							},
   587  						},
   588  					},
   589  				},
   590  				CodeScanning: []*CodeScanningBranchRule{
   591  					{
   592  						BranchRuleMetadata: BranchRuleMetadata{
   593  							RulesetSourceType: RulesetSourceTypeRepository,
   594  							RulesetSource:     "test/test",
   595  							RulesetID:         1,
   596  						},
   597  						Parameters: CodeScanningRuleParameters{
   598  							CodeScanningTools: []*RuleCodeScanningTool{
   599  								{
   600  									AlertsThreshold:         CodeScanningAlertsThresholdAll,
   601  									SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdAll,
   602  									Tool:                    "test",
   603  								},
   604  								{
   605  									AlertsThreshold:         CodeScanningAlertsThresholdNone,
   606  									SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdNone,
   607  									Tool:                    "test",
   608  								},
   609  							},
   610  						},
   611  					},
   612  				},
   613  			},
   614  			`[{"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_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"}]}}]`,
   615  		},
   616  	}
   617  
   618  	t.Run("UnmarshalJSON", func(t *testing.T) {
   619  		t.Parallel()
   620  
   621  		for _, test := range tests {
   622  			t.Run(test.name, func(t *testing.T) {
   623  				t.Parallel()
   624  
   625  				got := &BranchRules{}
   626  				err := json.Unmarshal([]byte(test.json), got)
   627  				if err != nil {
   628  					t.Errorf("Unable to unmarshal JSON %v: %v", test.json, err)
   629  				}
   630  
   631  				if diff := cmp.Diff(test.rules, got); diff != "" {
   632  					t.Errorf(
   633  						"json.Unmarshal returned:\n%#v\nwant:\n%#v\ndiff:\n%v",
   634  						got,
   635  						test.rules,
   636  						diff,
   637  					)
   638  				}
   639  			})
   640  		}
   641  	})
   642  }
   643  
   644  func TestRepositoryRule(t *testing.T) {
   645  	t.Parallel()
   646  	tests := []struct {
   647  		name string
   648  		rule *RepositoryRule
   649  		json string
   650  	}{
   651  		{
   652  			"empty",
   653  			&RepositoryRule{},
   654  			`{}`,
   655  		},
   656  		{
   657  			"creation",
   658  			&RepositoryRule{Type: RulesetRuleTypeCreation, Parameters: nil},
   659  			`{"type":"creation"}`,
   660  		},
   661  		{
   662  			"update",
   663  			&RepositoryRule{Type: RulesetRuleTypeUpdate, Parameters: &UpdateRuleParameters{}},
   664  			`{"type":"update"}`,
   665  		},
   666  		{
   667  			"update_params_empty",
   668  			&RepositoryRule{Type: RulesetRuleTypeUpdate, Parameters: &UpdateRuleParameters{}},
   669  			`{"type":"update","parameters":{}}`,
   670  		},
   671  		{
   672  			"update_params_set",
   673  			&RepositoryRule{
   674  				Type:       RulesetRuleTypeUpdate,
   675  				Parameters: &UpdateRuleParameters{UpdateAllowsFetchAndMerge: true},
   676  			},
   677  			`{"type":"update","parameters":{"update_allows_fetch_and_merge":true}}`,
   678  		},
   679  		{
   680  			"deletion",
   681  			&RepositoryRule{Type: RulesetRuleTypeDeletion, Parameters: nil},
   682  			`{"type":"deletion"}`,
   683  		},
   684  		{
   685  			"required_linear_history",
   686  			&RepositoryRule{Type: RulesetRuleTypeRequiredLinearHistory, Parameters: nil},
   687  			`{"type":"required_linear_history"}`,
   688  		},
   689  		{
   690  			"merge_queue",
   691  			&RepositoryRule{
   692  				Type: RulesetRuleTypeMergeQueue,
   693  				Parameters: &MergeQueueRuleParameters{
   694  					CheckResponseTimeoutMinutes:  5,
   695  					GroupingStrategy:             MergeGroupingStrategyAllGreen,
   696  					MaxEntriesToBuild:            10,
   697  					MaxEntriesToMerge:            20,
   698  					MergeMethod:                  MergeMethodSquash,
   699  					MinEntriesToMerge:            1,
   700  					MinEntriesToMergeWaitMinutes: 15,
   701  				},
   702  			},
   703  			`{"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}}`,
   704  		},
   705  		{
   706  			"required_deployments",
   707  			&RepositoryRule{
   708  				Type: RulesetRuleTypeRequiredDeployments,
   709  				Parameters: &RequiredDeploymentsRuleParameters{
   710  					RequiredDeploymentEnvironments: []string{"test1", "test2"},
   711  				},
   712  			},
   713  			`{"type":"required_deployments","parameters":{"required_deployment_environments":["test1","test2"]}}`,
   714  		},
   715  		{
   716  			"required_signatures",
   717  			&RepositoryRule{Type: RulesetRuleTypeRequiredSignatures, Parameters: nil},
   718  			`{"type":"required_signatures"}`,
   719  		},
   720  		{
   721  			"pull_request",
   722  			&RepositoryRule{
   723  				Type: RulesetRuleTypePullRequest,
   724  				Parameters: &PullRequestRuleParameters{
   725  					AllowedMergeMethods: []MergeMethod{
   726  						MergeMethodSquash,
   727  						MergeMethodRebase,
   728  					},
   729  					AutomaticCopilotCodeReviewEnabled: Ptr(true),
   730  					DismissStaleReviewsOnPush:         true,
   731  					RequireCodeOwnerReview:            true,
   732  					RequireLastPushApproval:           true,
   733  					RequiredApprovingReviewCount:      2,
   734  					RequiredReviewThreadResolution:    true,
   735  				},
   736  			},
   737  			`{"type":"pull_request","parameters":{"allowed_merge_methods":["squash","rebase"],"automatic_copilot_code_review_enabled": true,"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}}`,
   738  		},
   739  		{
   740  			"required_status_checks",
   741  			&RepositoryRule{
   742  				Type: RulesetRuleTypeRequiredStatusChecks,
   743  				Parameters: &RequiredStatusChecksRuleParameters{
   744  					RequiredStatusChecks: []*RuleStatusCheck{
   745  						{Context: "test1"},
   746  						{Context: "test2"},
   747  					},
   748  					StrictRequiredStatusChecksPolicy: true,
   749  				},
   750  			},
   751  			`{"type":"required_status_checks","parameters":{"required_status_checks":[{"context":"test1"},{"context":"test2"}],"strict_required_status_checks_policy":true}}`,
   752  		},
   753  		{
   754  			"non_fast_forward",
   755  			&RepositoryRule{Type: RulesetRuleTypeNonFastForward, Parameters: nil},
   756  			`{"type":"non_fast_forward"}`,
   757  		},
   758  		{
   759  			"commit_message_pattern",
   760  			&RepositoryRule{
   761  				Type: RulesetRuleTypeCommitMessagePattern,
   762  				Parameters: &PatternRuleParameters{
   763  					Name:     Ptr("test"),
   764  					Negate:   Ptr(false),
   765  					Operator: PatternRuleOperatorStartsWith,
   766  					Pattern:  "test",
   767  				},
   768  			},
   769  			`{"type":"commit_message_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`,
   770  		},
   771  		{
   772  			"commit_author_email_pattern",
   773  			&RepositoryRule{
   774  				Type: RulesetRuleTypeCommitAuthorEmailPattern,
   775  				Parameters: &PatternRuleParameters{
   776  					Name:     Ptr("test"),
   777  					Negate:   Ptr(false),
   778  					Operator: PatternRuleOperatorStartsWith,
   779  					Pattern:  "test",
   780  				},
   781  			},
   782  			`{"type":"commit_author_email_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`,
   783  		},
   784  		{
   785  			"committer_email_pattern",
   786  			&RepositoryRule{
   787  				Type: RulesetRuleTypeCommitterEmailPattern,
   788  				Parameters: &PatternRuleParameters{
   789  					Name:     Ptr("test"),
   790  					Negate:   Ptr(false),
   791  					Operator: PatternRuleOperatorStartsWith,
   792  					Pattern:  "test",
   793  				},
   794  			},
   795  			`{"type":"committer_email_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`,
   796  		},
   797  		{
   798  			"branch_name_pattern",
   799  			&RepositoryRule{
   800  				Type: RulesetRuleTypeBranchNamePattern,
   801  				Parameters: &PatternRuleParameters{
   802  					Name:     Ptr("test"),
   803  					Negate:   Ptr(false),
   804  					Operator: PatternRuleOperatorStartsWith,
   805  					Pattern:  "test",
   806  				},
   807  			},
   808  			`{"type":"branch_name_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`,
   809  		},
   810  		{
   811  			"tag_name_pattern",
   812  			&RepositoryRule{
   813  				Type: RulesetRuleTypeTagNamePattern,
   814  				Parameters: &PatternRuleParameters{
   815  					Name:     Ptr("test"),
   816  					Negate:   Ptr(false),
   817  					Operator: PatternRuleOperatorStartsWith,
   818  					Pattern:  "test",
   819  				},
   820  			},
   821  			`{"type":"tag_name_pattern","parameters":{"name":"test","negate":false,"operator":"starts_with","pattern":"test"}}`,
   822  		},
   823  		{
   824  			"file_path_restriction",
   825  			&RepositoryRule{
   826  				Type: RulesetRuleTypeFilePathRestriction,
   827  				Parameters: &FilePathRestrictionRuleParameters{
   828  					RestrictedFilePaths: []string{"test1", "test2"},
   829  				},
   830  			},
   831  			`{"type":"file_path_restriction","parameters":{"restricted_file_paths":["test1","test2"]}}`,
   832  		},
   833  		{
   834  			"max_file_path_length",
   835  			&RepositoryRule{
   836  				Type:       RulesetRuleTypeMaxFilePathLength,
   837  				Parameters: &MaxFilePathLengthRuleParameters{MaxFilePathLength: 512},
   838  			},
   839  			`{"type":"max_file_path_length","parameters":{"max_file_path_length":512}}`,
   840  		},
   841  		{
   842  			"file_extension_restriction",
   843  			&RepositoryRule{
   844  				Type: RulesetRuleTypeFileExtensionRestriction,
   845  				Parameters: &FileExtensionRestrictionRuleParameters{
   846  					RestrictedFileExtensions: []string{".exe", ".pkg"},
   847  				},
   848  			},
   849  			`{"type":"file_extension_restriction","parameters":{"restricted_file_extensions":[".exe",".pkg"]}}`,
   850  		},
   851  		{
   852  			"max_file_size",
   853  			&RepositoryRule{
   854  				Type:       RulesetRuleTypeMaxFileSize,
   855  				Parameters: &MaxFileSizeRuleParameters{MaxFileSize: 1024},
   856  			},
   857  			`{"type":"max_file_size","parameters":{"max_file_size":1024}}`,
   858  		},
   859  		{
   860  			"workflows",
   861  			&RepositoryRule{
   862  				Type: RulesetRuleTypeWorkflows,
   863  				Parameters: &WorkflowsRuleParameters{
   864  					Workflows: []*RuleWorkflow{
   865  						{Path: ".github/workflows/test1.yaml"},
   866  						{Path: ".github/workflows/test2.yaml"},
   867  					},
   868  				},
   869  			},
   870  			`{"type":"workflows","parameters":{"workflows":[{"path":".github/workflows/test1.yaml"},{"path":".github/workflows/test2.yaml"}]}}`,
   871  		},
   872  		{
   873  			"code_scanning",
   874  			&RepositoryRule{
   875  				Type: RulesetRuleTypeCodeScanning,
   876  				Parameters: &CodeScanningRuleParameters{
   877  					CodeScanningTools: []*RuleCodeScanningTool{
   878  						{
   879  							AlertsThreshold:         CodeScanningAlertsThresholdAll,
   880  							SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdAll,
   881  							Tool:                    "test",
   882  						},
   883  						{
   884  							AlertsThreshold:         CodeScanningAlertsThresholdNone,
   885  							SecurityAlertsThreshold: CodeScanningSecurityAlertsThresholdNone,
   886  							Tool:                    "test",
   887  						},
   888  					},
   889  				},
   890  			},
   891  			`{"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"}]}}`,
   892  		},
   893  	}
   894  
   895  	t.Run("UnmarshalJSON", func(t *testing.T) {
   896  		t.Parallel()
   897  
   898  		for _, test := range tests {
   899  			t.Run(test.name, func(t *testing.T) {
   900  				t.Parallel()
   901  
   902  				got := &RepositoryRule{}
   903  				err := json.Unmarshal([]byte(test.json), got)
   904  				if err != nil {
   905  					t.Errorf("Unable to unmarshal JSON %v: %v", test.json, err)
   906  				}
   907  
   908  				if diff := cmp.Diff(test.rule, got); diff != "" {
   909  					t.Errorf(
   910  						"json.Unmarshal returned:\n%#v\nwant:\n%#v\ndiff:\n%v",
   911  						got,
   912  						test.rule,
   913  						diff,
   914  					)
   915  				}
   916  			})
   917  		}
   918  	})
   919  }