sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/ghhook/ghhook_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package ghhook
    18  
    19  import (
    20  	"errors"
    21  	"flag"
    22  	"fmt"
    23  	"reflect"
    24  	"testing"
    25  
    26  	"sigs.k8s.io/prow/pkg/flagutil"
    27  	"sigs.k8s.io/prow/pkg/github"
    28  )
    29  
    30  func TestGetOptions(t *testing.T) {
    31  	defArgs := map[string][]string{
    32  		"--hmac-path":         {"/fake/hmac-file"},
    33  		"--hook-url":          {"https://not-a-url"},
    34  		"--repo":              {"fake-org/fake-repo"},
    35  		"--github-token-path": {"./testdata/token"},
    36  	}
    37  	cases := []struct {
    38  		name     string
    39  		args     map[string][]string
    40  		expected func(*Options)
    41  		err      bool
    42  	}{
    43  		{
    44  			name: "reject empty --hmac-path",
    45  			args: map[string][]string{
    46  				"--hmac-path": nil,
    47  			},
    48  			err: true,
    49  		},
    50  		{
    51  			name: "reject empty --hook-url",
    52  			args: map[string][]string{
    53  				"--hook-url": nil,
    54  			},
    55  			err: true,
    56  		},
    57  		{
    58  			name: "empty --repo",
    59  			args: map[string][]string{
    60  				"--repo": nil,
    61  			},
    62  			err: true,
    63  		},
    64  		{
    65  			name: "multi repo",
    66  			args: map[string][]string{
    67  				"--repo": {"org1", "org2/repo"},
    68  			},
    69  			expected: func(o *Options) {
    70  				o.Repos = flagutil.NewStrings()
    71  				o.Repos.Set("org1")
    72  				o.Repos.Set("org2/repo")
    73  			},
    74  		},
    75  		{
    76  			name: "full flags",
    77  			args: map[string][]string{
    78  				"--event":   {"this", "that"},
    79  				"--confirm": {"true"},
    80  			},
    81  			expected: func(o *Options) {
    82  				o.Events.Set("this")
    83  				o.Events.Set("that")
    84  				o.Confirm = true
    85  			},
    86  		},
    87  	}
    88  
    89  	for _, tc := range cases {
    90  		t.Run(tc.name, func(t *testing.T) {
    91  			var args []string
    92  			for k, v := range defArgs {
    93  				if _, ok := tc.args[k]; !ok {
    94  					tc.args[k] = v
    95  				}
    96  			}
    97  
    98  			for k, v := range tc.args {
    99  				for _, arg := range v {
   100  					args = append(args, k+"="+arg)
   101  				}
   102  			}
   103  
   104  			expected := Options{
   105  				HMACPath: "/fake/hmac-file",
   106  				HookURL:  "https://not-a-url",
   107  				Events:   flagutil.NewStrings(github.AllHookEvents...),
   108  			}
   109  			expected.Repos.Set("fake-org/fake-repo")
   110  
   111  			if tc.expected != nil {
   112  				tc.expected(&expected)
   113  			}
   114  
   115  			o, err := GetOptions(flag.NewFlagSet("fake-flags", flag.ExitOnError), args)
   116  			if o != nil { // TODO(fejta): github.GitHubOptions not unit testable
   117  				o.GitHubOptions = flagutil.GitHubOptions{}
   118  				expected.GitHubOptions = flagutil.GitHubOptions{}
   119  				expected.GitHubHookClient = nil
   120  				o.GitHubHookClient = nil
   121  			}
   122  			switch {
   123  			case err != nil:
   124  				if !tc.err {
   125  					t.Errorf("unexpected error %s: %v", args, err)
   126  				}
   127  			case tc.err:
   128  				t.Error("failed to receive an error")
   129  			case !reflect.DeepEqual(*o, expected):
   130  				t.Errorf("%#v != actual %#v", expected, o)
   131  			}
   132  		})
   133  	}
   134  }
   135  
   136  func TestFindHook(t *testing.T) {
   137  	const goal = "http://random-url"
   138  	number := 7
   139  	cases := []struct {
   140  		name     string
   141  		hooks    []github.Hook
   142  		expected *int
   143  	}{
   144  		{
   145  			name:  "nil on no match",
   146  			hooks: []github.Hook{{}, {}},
   147  		},
   148  		{
   149  			name: "return matched id",
   150  			hooks: []github.Hook{{
   151  				ID: number,
   152  				Config: github.HookConfig{
   153  					URL: goal,
   154  				},
   155  			}},
   156  			expected: &number,
   157  		},
   158  	}
   159  
   160  	for _, tc := range cases {
   161  		actual := findHook(tc.hooks, goal)
   162  		if !reflect.DeepEqual(actual, tc.expected) {
   163  			t.Errorf("%s: expected %v != actual %v", tc.name, tc.expected, actual)
   164  		}
   165  	}
   166  }
   167  
   168  func TestReconcileHook(t *testing.T) {
   169  	const goal = "http://goal-url"
   170  	const targetId = 1000
   171  	secret := "ingredient"
   172  	j := "json"
   173  	cases := []struct {
   174  		name         string
   175  		org          string
   176  		hooks        []github.Hook
   177  		expectCreate bool
   178  		expectEdit   bool
   179  		expectDelete bool
   180  		err          bool
   181  	}{
   182  		{
   183  			name: "fail on list error",
   184  			org:  "list-error",
   185  			err:  true,
   186  		},
   187  		{
   188  			name: "fail on create error",
   189  			org:  "create-error",
   190  			err:  true,
   191  		},
   192  		{
   193  			name: "fail on edit error",
   194  			org:  "edit-error",
   195  			hooks: []github.Hook{
   196  				{
   197  					Config: github.HookConfig{
   198  						URL: goal,
   199  					},
   200  				},
   201  			},
   202  			err: true,
   203  		},
   204  		{
   205  			name: "fail on delete error",
   206  			org:  "delete-error",
   207  			hooks: []github.Hook{
   208  				{
   209  					Config: github.HookConfig{
   210  						URL: goal,
   211  					},
   212  				},
   213  			},
   214  			err: true,
   215  		},
   216  		{
   217  			name:         "create when empty",
   218  			expectCreate: true,
   219  		},
   220  		{
   221  			name: "create when no match",
   222  			hooks: []github.Hook{
   223  				{
   224  					ID: targetId + 6666,
   225  					Config: github.HookConfig{
   226  						URL: "http://random-url",
   227  					},
   228  				},
   229  			},
   230  			expectCreate: true,
   231  		},
   232  		{
   233  			name: "edit exiting item",
   234  			hooks: []github.Hook{
   235  				{
   236  					ID: targetId,
   237  					Config: github.HookConfig{
   238  						URL: goal,
   239  					},
   240  				},
   241  			},
   242  			expectEdit: true,
   243  		},
   244  		{
   245  			name: "delete exiting item",
   246  			hooks: []github.Hook{
   247  				{
   248  					ID: targetId,
   249  					Config: github.HookConfig{
   250  						URL: goal,
   251  					},
   252  				},
   253  			},
   254  			expectDelete: true,
   255  		},
   256  	}
   257  
   258  	for _, tc := range cases {
   259  		var created, edited, deleted *github.HookRequest
   260  		ch := changer{
   261  			lister: func(org string) ([]github.Hook, error) {
   262  				if org == "list-error" {
   263  					return nil, errors.New("inject list error")
   264  				}
   265  				return tc.hooks, nil
   266  			},
   267  			editor: func(org string, id int, req github.HookRequest) error {
   268  				if org == "edit-error" {
   269  					return errors.New("inject edit error")
   270  				}
   271  				if id != targetId {
   272  					return fmt.Errorf("id %d != expected %d", id, targetId)
   273  				}
   274  				edited = &req
   275  				return nil
   276  			},
   277  			creator: func(org string, req github.HookRequest) (int, error) {
   278  				if org == "create-error" {
   279  					return 0, errors.New("inject create error")
   280  				}
   281  				if created != nil {
   282  					return 0, errors.New("already created")
   283  				}
   284  				created = &req
   285  				return targetId, nil
   286  			},
   287  			deletor: func(org string, id int, req github.HookRequest) error {
   288  				if org == "delete-error" {
   289  					return errors.New("inject delete error")
   290  				}
   291  				if deleted != nil {
   292  					return errors.New("already deleted")
   293  				}
   294  				deleted = &req
   295  				return nil
   296  			},
   297  		}
   298  		req := github.HookRequest{
   299  			Name:   "web",
   300  			Events: []string{"random"},
   301  			Config: &github.HookConfig{
   302  				URL:         goal,
   303  				ContentType: &j,
   304  				Secret:      &secret,
   305  			},
   306  		}
   307  
   308  		err := reconcileHook(ch, tc.org, req, &Options{ShouldDelete: tc.expectDelete})
   309  		switch {
   310  		case err != nil:
   311  			if !tc.err {
   312  				t.Errorf("unexpected error: %v", err)
   313  			}
   314  		case tc.err:
   315  			t.Error("failed to receive an error")
   316  		case tc.expectCreate && created == nil:
   317  			t.Error("failed to create")
   318  		case tc.expectEdit && edited == nil:
   319  			t.Error("failed to edit")
   320  		case tc.expectDelete && deleted == nil:
   321  			t.Error("failed to delete")
   322  		case created != nil && !reflect.DeepEqual(req, *created):
   323  			t.Errorf("created %#v != expected %#v", *created, req)
   324  		case edited != nil && !reflect.DeepEqual(req, *edited):
   325  			t.Errorf("edited %#v != expected %#v", *edited, req)
   326  		case deleted != nil && !reflect.DeepEqual(req, *deleted):
   327  			t.Errorf("deleted %#v != expected %#v", *deleted, req)
   328  		}
   329  	}
   330  }