github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/goose/goose_test.go (about)

     1  /*
     2  Copyright 2019 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 goose
    18  
    19  import (
    20  	"flag"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  	"net/http/httptest"
    25  	"regexp"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/sirupsen/logrus"
    30  
    31  	"sigs.k8s.io/prow/pkg/github"
    32  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    33  )
    34  
    35  type fakeGaggle string
    36  
    37  var human = flag.Bool("human", false, "Enable to run additional manual tests")
    38  var keyPath = flag.String("key-path", "", "Path to api key if set")
    39  
    40  func (g fakeGaggle) readGoose() (string, error) {
    41  	return fmt.Sprintf("\n![fake goose image](%s)", g), nil
    42  }
    43  
    44  func TestRealGoose(t *testing.T) {
    45  	if !*human {
    46  		t.Skip("Real geese disabled for automation. Manual users can add --human")
    47  	}
    48  	if *keyPath != "" {
    49  		honk.setKey(*keyPath, logrus.WithField("plugin", pluginName))
    50  	}
    51  
    52  	if goose, err := honk.readGoose(); err != nil {
    53  		t.Errorf("Could not read geese from %#v: %v", honk, err)
    54  	} else {
    55  		fmt.Println(goose)
    56  	}
    57  }
    58  
    59  func TestUrl(t *testing.T) {
    60  	cases := []struct {
    61  		name    string
    62  		url     string
    63  		key     string
    64  		require []string
    65  		deny    []string
    66  	}{
    67  		{
    68  			name: "only url",
    69  			url:  "http://foo",
    70  		},
    71  		{
    72  			name:    "key",
    73  			url:     "http://foo",
    74  			key:     "blah",
    75  			require: []string{"client_id=blah"},
    76  		},
    77  	}
    78  
    79  	for _, tc := range cases {
    80  		rg := realGaggle{
    81  			url: tc.url,
    82  			key: tc.key,
    83  		}
    84  		url := rg.URL()
    85  		for _, r := range tc.require {
    86  			if !strings.Contains(url, r) {
    87  				t.Errorf("%s: %s does not contain %s", tc.name, url, r)
    88  			}
    89  		}
    90  		for _, d := range tc.deny {
    91  			if strings.Contains(url, d) {
    92  				t.Errorf("%s: %s contained unexpected %s", tc.name, url, d)
    93  			}
    94  		}
    95  	}
    96  }
    97  
    98  func TestFormat(t *testing.T) {
    99  	re := regexp.MustCompile(`!\[.+\]\(.+\)`)
   100  	basicURL := "http://example.com"
   101  	testcases := []struct {
   102  		name string
   103  		img  string
   104  		err  bool
   105  	}{
   106  		{
   107  			name: "basically works",
   108  			img:  basicURL,
   109  			err:  false,
   110  		},
   111  		{
   112  			name: "empty image",
   113  			img:  "",
   114  			err:  true,
   115  		},
   116  		{
   117  			name: "bad image",
   118  			img:  "http://still a bad url",
   119  			err:  true,
   120  		},
   121  	}
   122  	for _, tc := range testcases {
   123  		ret, err := gooseResult{
   124  			Images: imageSet{
   125  				Small: tc.img,
   126  			},
   127  		}.Format()
   128  
   129  		switch {
   130  		case tc.err:
   131  			if err == nil {
   132  				t.Errorf("%s: failed to raise an error", tc.name)
   133  			}
   134  		case err != nil:
   135  			t.Errorf("%s: unexpected error: %v", tc.name, err)
   136  		case !re.MatchString(ret):
   137  			t.Errorf("%s: bad return value: %s", tc.name, ret)
   138  		}
   139  	}
   140  }
   141  
   142  // Medium integration test (depends on ability to open a TCP port)
   143  func TestHttpResponse(t *testing.T) {
   144  	// create test cases for handling content length of images
   145  	contentLength := make(map[string]string)
   146  	contentLength["/goose.jpg"] = "717987"
   147  	contentLength["/biggoose.jpg"] = "12647753"
   148  
   149  	// fake server for images
   150  	ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   151  		if s, ok := contentLength[r.URL.Path]; ok {
   152  			body := "binary image"
   153  			w.Header().Set("Content-Length", s)
   154  			io.WriteString(w, body)
   155  		} else {
   156  			t.Errorf("Cannot find content length for %s", r.URL.Path)
   157  		}
   158  	}))
   159  	defer ts2.Close()
   160  
   161  	// create test cases for handling http responses
   162  	img := ts2.URL + "/goose.jpg"
   163  	bigimg := ts2.URL + "/biggoose.jpg"
   164  	validResponse := fmt.Sprintf(`{"id":"valid","urls":{"small":"%s"}}`, img)
   165  	var testcases = []struct {
   166  		name     string
   167  		path     string
   168  		response string
   169  		valid    bool
   170  		code     int
   171  	}{
   172  		{
   173  			name:     "valid",
   174  			path:     "/valid",
   175  			response: validResponse,
   176  			valid:    true,
   177  		},
   178  		{
   179  			name:     "image too big",
   180  			path:     "/too-big",
   181  			response: fmt.Sprintf(`{"id":"valid","urls":{"small":"%s"}}`, bigimg),
   182  		},
   183  		{
   184  			name: "return-406",
   185  			path: "/return-406",
   186  			code: 406,
   187  			response: `
   188  <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
   189  <html><head>
   190  <title>406 Not Acceptable</title>
   191  </head><body>
   192  <h1>Not Acceptable</h1>
   193  <p>An appropriate representation of the requested resource /api/images/get could not be found on this server.</p>
   194  Available variants:
   195  <ul>
   196  <li><a href="get.php">get.php</a> , type x-mapp-php5</li>
   197  </ul>
   198  </body></html>`,
   199  		},
   200  		{
   201  			name:     "no-geese-in-json",
   202  			path:     "/no-geese-in-json",
   203  			response: "[]",
   204  		},
   205  		{
   206  			name:     "no-image-in-json",
   207  			path:     "/no-image-in-json",
   208  			response: "[{}]",
   209  		},
   210  	}
   211  
   212  	// fake server for image urls
   213  	pathToResponse := make(map[string]string)
   214  	for _, testcase := range testcases {
   215  		pathToResponse[testcase.path] = testcase.response
   216  	}
   217  	codes := make(map[string]int)
   218  	for _, tc := range testcases {
   219  		codes[tc.path] = tc.code
   220  	}
   221  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   222  		code := codes[r.URL.Path]
   223  		if code > 0 {
   224  			w.WriteHeader(code)
   225  		}
   226  		if r, ok := pathToResponse[r.URL.Path]; ok {
   227  			io.WriteString(w, r)
   228  		} else {
   229  			io.WriteString(w, validResponse)
   230  		}
   231  	}))
   232  	defer ts.Close()
   233  
   234  	// github fake client
   235  	fc := fakegithub.NewFakeClient()
   236  	fc.IssueComments = make(map[int][]github.IssueComment)
   237  
   238  	// run test for each case
   239  	for _, testcase := range testcases {
   240  		fakehonk := &realGaggle{url: ts.URL + testcase.path}
   241  		goose, err := fakehonk.readGoose()
   242  		if testcase.valid && err != nil {
   243  			t.Errorf("For case %s, didn't expect error: %v", testcase.name, err)
   244  		} else if !testcase.valid && err == nil {
   245  			t.Errorf("For case %s, expected error, received goose: %s", testcase.name, goose)
   246  		} else if testcase.valid && goose == "" {
   247  			t.Errorf("For case %s, got an empty goose", testcase.name)
   248  		}
   249  	}
   250  
   251  	// fully test handling a comment
   252  	comment := "/honk"
   253  
   254  	e := &github.GenericCommentEvent{
   255  		Action:     github.GenericCommentActionCreated,
   256  		Body:       comment,
   257  		Number:     5,
   258  		IssueState: "open",
   259  	}
   260  	if err := handle(fc, logrus.WithField("plugin", pluginName), e, &realGaggle{url: ts.URL + "/?format=json"}, func() {}); err != nil {
   261  		t.Errorf("didn't expect error: %v", err)
   262  		return
   263  	}
   264  	if len(fc.IssueComments[5]) != 1 {
   265  		t.Error("should have commented.")
   266  		return
   267  	}
   268  	if c := fc.IssueComments[5][0]; !strings.Contains(c.Body, img) {
   269  		t.Errorf("missing image url: %s from comment: %v", img, c)
   270  	}
   271  
   272  }
   273  
   274  // Small, unit tests
   275  func TestGeese(t *testing.T) {
   276  	var testcases = []struct {
   277  		name          string
   278  		action        github.GenericCommentEventAction
   279  		body          string
   280  		state         string
   281  		pr            bool
   282  		shouldComment bool
   283  		shouldError   bool
   284  	}{
   285  		{
   286  			name:          "ignore edited comment",
   287  			state:         "open",
   288  			action:        github.GenericCommentActionEdited,
   289  			body:          "/honk",
   290  			shouldComment: false,
   291  			shouldError:   false,
   292  		},
   293  		{
   294  			name:          "leave goose on pr",
   295  			state:         "open",
   296  			action:        github.GenericCommentActionCreated,
   297  			body:          "/honk",
   298  			pr:            true,
   299  			shouldComment: true,
   300  			shouldError:   false,
   301  		},
   302  		{
   303  			name:          "leave goose on issue",
   304  			state:         "open",
   305  			action:        github.GenericCommentActionCreated,
   306  			body:          "/honk",
   307  			shouldComment: true,
   308  			shouldError:   false,
   309  		},
   310  		{
   311  			name:          "leave goose on issue, trailing space",
   312  			state:         "open",
   313  			action:        github.GenericCommentActionCreated,
   314  			body:          "/honk \r",
   315  			shouldComment: true,
   316  			shouldError:   false,
   317  		},
   318  	}
   319  	for _, tc := range testcases {
   320  		fc := fakegithub.NewFakeClient()
   321  		fc.IssueComments = make(map[int][]github.IssueComment)
   322  		e := &github.GenericCommentEvent{
   323  			Action:     tc.action,
   324  			Body:       tc.body,
   325  			Number:     5,
   326  			IssueState: tc.state,
   327  			IsPR:       tc.pr,
   328  		}
   329  		err := handle(fc, logrus.WithField("plugin", pluginName), e, fakeGaggle("thegoose"), func() {})
   330  		if !tc.shouldError && err != nil {
   331  			t.Errorf("%s: didn't expect error: %v", tc.name, err)
   332  			continue
   333  		} else if tc.shouldError && err == nil {
   334  			t.Errorf("%s: expected an error to occur", tc.name)
   335  			continue
   336  		}
   337  		if tc.shouldComment && len(fc.IssueComments[5]) != 1 {
   338  			t.Errorf("%s: should have commented.", tc.name)
   339  		} else if tc.shouldComment {
   340  			shouldImage := !tc.shouldError
   341  			body := fc.IssueComments[5][0].Body
   342  			hasImage := strings.Contains(body, "![")
   343  			if hasImage && !shouldImage {
   344  				t.Errorf("%s: unexpected image in %s", tc.name, body)
   345  			} else if !hasImage && shouldImage {
   346  				t.Errorf("%s: no image in %s", tc.name, body)
   347  			}
   348  		} else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 {
   349  			t.Errorf("%s: should not have commented.", tc.name)
   350  		}
   351  	}
   352  }