github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/cat/cat_test.go (about)

     1  /*
     2  Copyright 2018 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 cat
    18  
    19  import (
    20  	"errors"
    21  	"flag"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"regexp"
    27  	"strings"
    28  	"testing"
    29  
    30  	"github.com/sirupsen/logrus"
    31  
    32  	"k8s.io/test-infra/prow/github"
    33  	"k8s.io/test-infra/prow/github/fakegithub"
    34  )
    35  
    36  type fakeClowder string
    37  
    38  var human = flag.Bool("human", false, "Enable to run additional manual tests")
    39  var category = flag.String("category", "", "Request a particular category if set")
    40  var movieCat = flag.Bool("gif", false, "Specifically request a GIF image if set")
    41  var keyPath = flag.String("key-path", "", "Path to api key if set")
    42  
    43  func (c fakeClowder) readCat(category string, movieCat bool) (string, error) {
    44  	if category == "error" {
    45  		return "", errors.New(string(c))
    46  	}
    47  	return fmt.Sprintf("![fake cat image](%s)", c), nil
    48  }
    49  
    50  func TestRealCat(t *testing.T) {
    51  	if !*human {
    52  		t.Skip("Real cats disabled for automation. Manual users can add --human [--category=foo]")
    53  	}
    54  	if *keyPath != "" {
    55  		meow.setKey(*keyPath, logrus.WithField("plugin", pluginName))
    56  	}
    57  
    58  	if cat, err := meow.readCat(*category, *movieCat); err != nil {
    59  		t.Errorf("Could not read cats from %#v: %v", meow, err)
    60  	} else {
    61  		fmt.Println(cat)
    62  	}
    63  }
    64  
    65  func TestUrl(t *testing.T) {
    66  	cases := []struct {
    67  		name     string
    68  		url      string
    69  		category string
    70  		key      string
    71  		movie    bool
    72  		require  []string
    73  		deny     []string
    74  	}{
    75  		{
    76  			name: "only url",
    77  			url:  "http://foo",
    78  		},
    79  		{
    80  			name:    "key",
    81  			url:     "http://foo",
    82  			key:     "blah",
    83  			require: []string{"api_key=blah"},
    84  			deny:    []string{"category=", "mime_types=gif"},
    85  		},
    86  		{
    87  			name:     "category",
    88  			url:      "http://foo",
    89  			category: "bar",
    90  			require:  []string{"category=bar"},
    91  			deny:     []string{"api_key=", "mime_types=gif"},
    92  		},
    93  		{
    94  			name:    "movie",
    95  			url:     "http://foo",
    96  			movie:   true,
    97  			require: []string{"mime_types=gif"},
    98  			deny:    []string{"category=this", "api_key=that"},
    99  		},
   100  		{
   101  			name:     "category and movie",
   102  			url:      "http://foo",
   103  			category: "this",
   104  			movie:    true,
   105  			require:  []string{"mime_types=gif", "category=this", "&"},
   106  			deny:     []string{"api_key="},
   107  		},
   108  		{
   109  			name:     "category and key",
   110  			url:      "http://foo",
   111  			category: "this",
   112  			key:      "that",
   113  			require:  []string{"category=this", "api_key=that", "&"},
   114  			deny:     []string{"mime_types=gif"},
   115  		},
   116  		{
   117  			name:     "category, key, and movie",
   118  			url:      "http://foo",
   119  			category: "this",
   120  			key:      "that",
   121  			movie:    true,
   122  			require:  []string{"category=this", "api_key=that", "&", "mime_types=gif"},
   123  		},
   124  	}
   125  
   126  	for _, tc := range cases {
   127  		rc := realClowder{
   128  			url: tc.url,
   129  			key: tc.key,
   130  		}
   131  		url := rc.URL(tc.category, tc.movie)
   132  		for _, r := range tc.require {
   133  			if !strings.Contains(url, r) {
   134  				t.Errorf("%s: %s does not contain %s", tc.name, url, r)
   135  			}
   136  		}
   137  		for _, d := range tc.deny {
   138  			if strings.Contains(url, d) {
   139  				t.Errorf("%s: %s contained unexpected %s", tc.name, url, d)
   140  			}
   141  		}
   142  	}
   143  }
   144  
   145  func TestFormat(t *testing.T) {
   146  	re := regexp.MustCompile(`\[!\[.+\]\(.+\)\]\(.+\)`)
   147  	basicURL := "http://example.com"
   148  	testcases := []struct {
   149  		name string
   150  		src  string
   151  		img  string
   152  		err  bool
   153  	}{
   154  		{
   155  			name: "basically works",
   156  			src:  basicURL,
   157  			img:  basicURL,
   158  			err:  false,
   159  		},
   160  		{
   161  			name: "empty source",
   162  			src:  "",
   163  			img:  basicURL,
   164  			err:  true,
   165  		},
   166  		{
   167  			name: "empty image",
   168  			src:  basicURL,
   169  			img:  "",
   170  			err:  true,
   171  		},
   172  		{
   173  			name: "bad source",
   174  			src:  "http://this is not a url",
   175  			img:  basicURL,
   176  			err:  true,
   177  		},
   178  		{
   179  			name: "bad image",
   180  			src:  basicURL,
   181  			img:  "http://still a bad url",
   182  			err:  true,
   183  		},
   184  	}
   185  	for _, tc := range testcases {
   186  		ret, err := catResult{
   187  			Source: tc.src,
   188  			Image:  tc.img,
   189  		}.Format()
   190  		switch {
   191  		case tc.err:
   192  			if err == nil {
   193  				t.Errorf("%s: failed to raise an error", tc.name)
   194  			}
   195  		case err != nil:
   196  			t.Errorf("%s: unexpected error: %v", tc.name, err)
   197  		case !re.MatchString(ret):
   198  			t.Errorf("%s: bad return value: %s", tc.name, ret)
   199  		}
   200  	}
   201  }
   202  
   203  // Medium integration test (depends on ability to open a TCP port)
   204  func TestHttpResponse(t *testing.T) {
   205  	// create test cases for handling content length of images
   206  	contentLength := make(map[string]string)
   207  	contentLength["/cat.jpg"] = "717987"
   208  	contentLength["/bigcat.jpg"] = "12647753"
   209  
   210  	// fake server for images
   211  	ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   212  		if s, ok := contentLength[r.URL.Path]; ok {
   213  			body := "binary image"
   214  			w.Header().Set("Content-Length", s)
   215  			io.WriteString(w, body)
   216  		} else {
   217  			t.Errorf("Cannot find content length for %s", r.URL.Path)
   218  		}
   219  	}))
   220  	defer ts2.Close()
   221  
   222  	// create test cases for handling http responses
   223  	img := ts2.URL + "/cat.jpg"
   224  	bigimg := ts2.URL + "/bigcat.jpg"
   225  	src := "http://localhost?kind=source_url"
   226  	validResponse := fmt.Sprintf(`[{"id":"valid","url":"%s","source_url":"%s"}]`, img, src)
   227  	var testcases = []struct {
   228  		name     string
   229  		path     string
   230  		response string
   231  		valid    bool
   232  		code     int
   233  	}{
   234  		{
   235  			name:     "valid",
   236  			path:     "/valid",
   237  			response: validResponse,
   238  			valid:    true,
   239  		},
   240  		{
   241  			name:     "image too big",
   242  			path:     "/too-big",
   243  			response: fmt.Sprintf(`[{"id":"toobig","url":"%s","source_url":"%s"}]`, bigimg, src),
   244  		},
   245  		{
   246  			name: "return-406",
   247  			path: "/return-406",
   248  			code: 406,
   249  			response: `
   250  <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
   251  <html><head>
   252  <title>406 Not Acceptable</title>
   253  </head><body>
   254  <h1>Not Acceptable</h1>
   255  <p>An appropriate representation of the requested resource /api/images/get could not be found on this server.</p>
   256  Available variants:
   257  <ul>
   258  <li><a href="get.php">get.php</a> , type x-mapp-php5</li>
   259  </ul>
   260  </body></html>`,
   261  		},
   262  		{
   263  			name:     "no-cats-in-json",
   264  			path:     "/no-cats-in-json",
   265  			response: "[]",
   266  		},
   267  		{
   268  			name:     "no-image-in-json",
   269  			path:     "/no-image-in-json",
   270  			response: "[{}]",
   271  		},
   272  	}
   273  
   274  	// fake server for image urls
   275  	pathToResponse := make(map[string]string)
   276  	for _, testcase := range testcases {
   277  		pathToResponse[testcase.path] = testcase.response
   278  	}
   279  	codes := make(map[string]int)
   280  	for _, tc := range testcases {
   281  		codes[tc.path] = tc.code
   282  	}
   283  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   284  		code := codes[r.URL.Path]
   285  		if code > 0 {
   286  			w.WriteHeader(code)
   287  		}
   288  		if r, ok := pathToResponse[r.URL.Path]; ok {
   289  			io.WriteString(w, r)
   290  		} else {
   291  			io.WriteString(w, validResponse)
   292  		}
   293  	}))
   294  	defer ts.Close()
   295  
   296  	// github fake client
   297  	fc := &fakegithub.FakeClient{
   298  		IssueComments: make(map[int][]github.IssueComment),
   299  	}
   300  
   301  	// run test for each case
   302  	for _, testcase := range testcases {
   303  		fakemeow := &realClowder{url: ts.URL + testcase.path}
   304  		cat, err := fakemeow.readCat(*category, *movieCat)
   305  		if testcase.valid && err != nil {
   306  			t.Errorf("For case %s, didn't expect error: %v", testcase.name, err)
   307  		} else if !testcase.valid && err == nil {
   308  			t.Errorf("For case %s, expected error, received cat: %s", testcase.name, cat)
   309  		} else if testcase.valid && cat == "" {
   310  			t.Errorf("For case %s, got an empty cat", testcase.name)
   311  		}
   312  	}
   313  
   314  	// fully test handling a comment
   315  	comment := "/meowvie space"
   316  
   317  	e := &github.GenericCommentEvent{
   318  		Action:     github.GenericCommentActionCreated,
   319  		Body:       comment,
   320  		Number:     5,
   321  		IssueState: "open",
   322  	}
   323  	if err := handle(fc, logrus.WithField("plugin", pluginName), e, &realClowder{url: ts.URL + "/?format=json"}, func() {}); err != nil {
   324  		t.Errorf("didn't expect error: %v", err)
   325  		return
   326  	}
   327  	if len(fc.IssueComments[5]) != 1 {
   328  		t.Error("should have commented.")
   329  		return
   330  	}
   331  	if c := fc.IssueComments[5][0]; !strings.Contains(c.Body, img) {
   332  		t.Errorf("missing image url: %s from comment: %v", img, c)
   333  	} else if !strings.Contains(c.Body, src) {
   334  		t.Errorf("missing source url: %s from comment: %v", src, c)
   335  	}
   336  
   337  }
   338  
   339  // Small, unit tests
   340  func TestCats(t *testing.T) {
   341  	var testcases = []struct {
   342  		name          string
   343  		action        github.GenericCommentEventAction
   344  		body          string
   345  		state         string
   346  		pr            bool
   347  		shouldComment bool
   348  		shouldError   bool
   349  	}{
   350  		{
   351  			name:          "ignore edited comment",
   352  			state:         "open",
   353  			action:        github.GenericCommentActionEdited,
   354  			body:          "/meow",
   355  			shouldComment: false,
   356  			shouldError:   false,
   357  		},
   358  		{
   359  			name:          "leave cat on pr",
   360  			state:         "open",
   361  			action:        github.GenericCommentActionCreated,
   362  			body:          "/meow",
   363  			pr:            true,
   364  			shouldComment: true,
   365  			shouldError:   false,
   366  		},
   367  		{
   368  			name:          "leave cat on issue",
   369  			state:         "open",
   370  			action:        github.GenericCommentActionCreated,
   371  			body:          "/meow",
   372  			shouldComment: true,
   373  			shouldError:   false,
   374  		},
   375  		{
   376  			name:          "leave cat on issue, trailing space",
   377  			state:         "open",
   378  			action:        github.GenericCommentActionCreated,
   379  			body:          "/meow \r",
   380  			shouldComment: true,
   381  			shouldError:   false,
   382  		},
   383  		{
   384  			name:          "categorical cat",
   385  			state:         "open",
   386  			action:        github.GenericCommentActionCreated,
   387  			body:          "/meow clothes",
   388  			shouldComment: true,
   389  			shouldError:   false,
   390  		},
   391  		{
   392  			name:          "bad cat",
   393  			state:         "open",
   394  			action:        github.GenericCommentActionCreated,
   395  			body:          "/meow error",
   396  			shouldComment: true,
   397  			shouldError:   true,
   398  		},
   399  		{
   400  			name:          "movie cat",
   401  			state:         "open",
   402  			action:        github.GenericCommentActionCreated,
   403  			body:          "/meowvie",
   404  			shouldComment: true,
   405  			shouldError:   false,
   406  		},
   407  		{
   408  			name:          "categorical movie cat",
   409  			state:         "open",
   410  			action:        github.GenericCommentActionCreated,
   411  			body:          "/meowvie space",
   412  			shouldComment: true,
   413  			shouldError:   false,
   414  		},
   415  	}
   416  	for _, tc := range testcases {
   417  		fc := &fakegithub.FakeClient{
   418  			IssueComments: make(map[int][]github.IssueComment),
   419  		}
   420  		e := &github.GenericCommentEvent{
   421  			Action:     tc.action,
   422  			Body:       tc.body,
   423  			Number:     5,
   424  			IssueState: tc.state,
   425  			IsPR:       tc.pr,
   426  		}
   427  		err := handle(fc, logrus.WithField("plugin", pluginName), e, fakeClowder("tubbs"), func() {})
   428  		if !tc.shouldError && err != nil {
   429  			t.Errorf("%s: didn't expect error: %v", tc.name, err)
   430  			continue
   431  		} else if tc.shouldError && err == nil {
   432  			t.Errorf("%s: expected an error to occur", tc.name)
   433  			continue
   434  		}
   435  		if tc.shouldComment && len(fc.IssueComments[5]) != 1 {
   436  			t.Errorf("%s: should have commented.", tc.name)
   437  		} else if tc.shouldComment {
   438  			shouldImage := !tc.shouldError
   439  			body := fc.IssueComments[5][0].Body
   440  			hasImage := strings.Contains(body, "![")
   441  			if hasImage && !shouldImage {
   442  				t.Errorf("%s: unexpected image in %s", tc.name, body)
   443  			} else if !hasImage && shouldImage {
   444  				t.Errorf("%s: no image in %s", tc.name, body)
   445  			}
   446  		} else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 {
   447  			t.Errorf("%s: should not have commented.", tc.name)
   448  		}
   449  	}
   450  }