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