github.com/abayer/test-infra@v0.0.5/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 path = flag.String("key-path", "", "Path to api key if set")
    41  
    42  func (c fakeClowder) readCat(category string) (string, error) {
    43  	if category == "error" {
    44  		return "", errors.New(string(c))
    45  	}
    46  	return fmt.Sprintf("![fake cat image](%s)", c), nil
    47  }
    48  
    49  func TestRealCat(t *testing.T) {
    50  	if !*human {
    51  		t.Skip("Real cats disabled for automation. Manual users can add --human [--category=foo]")
    52  	}
    53  	if *path != "" {
    54  		meow.setKey(*path, logrus.WithField("plugin", pluginName))
    55  	}
    56  
    57  	if cat, err := meow.readCat(*category); err != nil {
    58  		t.Errorf("Could not read cats from %#v: %v", meow, err)
    59  	} else {
    60  		fmt.Println(cat)
    61  	}
    62  }
    63  
    64  func TestUrl(t *testing.T) {
    65  	cases := []struct {
    66  		name     string
    67  		url      string
    68  		category string
    69  		key      string
    70  		require  []string
    71  		deny     []string
    72  	}{
    73  		{
    74  			name: "only url",
    75  			url:  "http://foo",
    76  		},
    77  		{
    78  			name:    "key",
    79  			url:     "http://foo",
    80  			key:     "blah",
    81  			require: []string{"api_key=blah"},
    82  			deny:    []string{"category="},
    83  		},
    84  		{
    85  			name:     "category",
    86  			url:      "http://foo",
    87  			category: "bar",
    88  			require:  []string{"category=bar"},
    89  			deny:     []string{"api_key="},
    90  		},
    91  		{
    92  			name:     "category and key",
    93  			url:      "http://foo",
    94  			category: "this",
    95  			key:      "that",
    96  			require:  []string{"category=this", "api_key=that", "&"},
    97  		},
    98  	}
    99  
   100  	for _, tc := range cases {
   101  		rc := realClowder{
   102  			url: tc.url,
   103  			key: tc.key,
   104  		}
   105  		url := rc.Url(tc.category)
   106  		for _, r := range tc.require {
   107  			if !strings.Contains(url, r) {
   108  				t.Errorf("%s: %s does not contain %s", tc.name, url, r)
   109  			}
   110  		}
   111  		for _, d := range tc.deny {
   112  			if strings.Contains(url, d) {
   113  				t.Errorf("%s: %s contained unexpected %s", tc.name, url, d)
   114  			}
   115  		}
   116  	}
   117  }
   118  
   119  func TestFormat(t *testing.T) {
   120  	re := regexp.MustCompile(`\[!\[.+\]\(.+\)\]\(.+\)`)
   121  	basicURL := "http://example.com"
   122  	testcases := []struct {
   123  		name string
   124  		src  string
   125  		img  string
   126  		err  bool
   127  	}{
   128  		{
   129  			name: "basically works",
   130  			src:  basicURL,
   131  			img:  basicURL,
   132  			err:  false,
   133  		},
   134  		{
   135  			name: "empty source",
   136  			src:  "",
   137  			img:  basicURL,
   138  			err:  true,
   139  		},
   140  		{
   141  			name: "empty image",
   142  			src:  basicURL,
   143  			img:  "",
   144  			err:  true,
   145  		},
   146  		{
   147  			name: "bad source",
   148  			src:  "http://this is not a url",
   149  			img:  basicURL,
   150  			err:  true,
   151  		},
   152  		{
   153  			name: "bad image",
   154  			src:  basicURL,
   155  			img:  "http://still a bad url",
   156  			err:  true,
   157  		},
   158  	}
   159  	for _, tc := range testcases {
   160  		ret, err := catResult{
   161  			Source: tc.src,
   162  			Image:  tc.img,
   163  		}.Format()
   164  		switch {
   165  		case tc.err:
   166  			if err == nil {
   167  				t.Errorf("%s: failed to raise an error", tc.name)
   168  			}
   169  		case err != nil:
   170  			t.Errorf("%s: unexpected error: %v", tc.name, err)
   171  		case !re.MatchString(ret):
   172  			t.Errorf("%s: bad return value: %s", tc.name, ret)
   173  		}
   174  	}
   175  }
   176  
   177  // Medium integration test (depends on ability to open a TCP port)
   178  func TestHttpResponse(t *testing.T) {
   179  	// create test cases for handling content length of images
   180  	contentLength := make(map[string]string)
   181  	contentLength["/cat.jpg"] = "717987"
   182  	contentLength["/bigcat.jpg"] = "12647753"
   183  
   184  	// fake server for images
   185  	ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   186  		if s, ok := contentLength[r.URL.Path]; ok {
   187  			body := "binary image"
   188  			w.Header().Set("Content-Length", s)
   189  			io.WriteString(w, body)
   190  		} else {
   191  			t.Errorf("Cannot find content length for %s", r.URL.Path)
   192  		}
   193  	}))
   194  	defer ts2.Close()
   195  
   196  	// create test cases for handling http responses
   197  	img := ts2.URL + "/cat.jpg"
   198  	bigimg := ts2.URL + "/bigcat.jpg"
   199  	src := "http://localhost?kind=source_url"
   200  	validResponse := fmt.Sprintf(`<response><data><images><image><url>%s</url><source_url>%s</source_url></image></images></data></response>`, img, src)
   201  	var testcases = []struct {
   202  		name     string
   203  		path     string
   204  		response string
   205  		isValid  bool
   206  	}{
   207  		{
   208  			name:     "valid",
   209  			path:     "/valid",
   210  			response: fmt.Sprintf(`<response><data><images><image><url>%s</url><source_url>%s</source_url></image></images></data></response>`, img, src),
   211  			isValid:  true,
   212  		},
   213  		{
   214  			name:     "image too big",
   215  			path:     "/too-big",
   216  			response: fmt.Sprintf(`<response><data><images><image><url>%s</url><source_url>%s</source_url></image></images></data></response>`, bigimg, src),
   217  			isValid:  false,
   218  		},
   219  	}
   220  
   221  	// fake server for image urls
   222  	pathToResponse := make(map[string]string)
   223  	for _, testcase := range testcases {
   224  		pathToResponse[testcase.path] = testcase.response
   225  	}
   226  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   227  		if r, ok := pathToResponse[r.URL.Path]; ok {
   228  			io.WriteString(w, r)
   229  		} else {
   230  			io.WriteString(w, validResponse)
   231  		}
   232  	}))
   233  	defer ts.Close()
   234  
   235  	// github fake client
   236  	fc := &fakegithub.FakeClient{
   237  		IssueComments: make(map[int][]github.IssueComment),
   238  	}
   239  
   240  	// run test for each case
   241  	for _, testcase := range testcases {
   242  		fakemeow := &realClowder{url: ts.URL + testcase.path}
   243  		cat, err := fakemeow.readCat(*category)
   244  		if testcase.isValid && err != nil {
   245  			t.Errorf("For case %s, didn't expect error: %v", testcase.name, err)
   246  		} else if !testcase.isValid && err == nil {
   247  			t.Errorf("For case %s, expected error, received cat: %s", testcase.name, cat)
   248  		}
   249  	}
   250  
   251  	// fully test handling a comment
   252  	comment := "/meow"
   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, &realClowder{url: ts.URL}, 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  	} else if !strings.Contains(c.Body, src) {
   271  		t.Errorf("missing source url: %s from comment: %v", src, c)
   272  	}
   273  
   274  }
   275  
   276  // Small, unit tests
   277  func TestCats(t *testing.T) {
   278  	var testcases = []struct {
   279  		name          string
   280  		action        github.GenericCommentEventAction
   281  		body          string
   282  		state         string
   283  		pr            bool
   284  		shouldComment bool
   285  		shouldError   bool
   286  	}{
   287  		{
   288  			name:          "ignore edited comment",
   289  			state:         "open",
   290  			action:        github.GenericCommentActionEdited,
   291  			body:          "/meow",
   292  			shouldComment: false,
   293  			shouldError:   false,
   294  		},
   295  		{
   296  			name:          "leave cat on pr",
   297  			state:         "open",
   298  			action:        github.GenericCommentActionCreated,
   299  			body:          "/meow",
   300  			pr:            true,
   301  			shouldComment: true,
   302  			shouldError:   false,
   303  		},
   304  		{
   305  			name:          "leave cat on issue",
   306  			state:         "open",
   307  			action:        github.GenericCommentActionCreated,
   308  			body:          "/meow",
   309  			shouldComment: true,
   310  			shouldError:   false,
   311  		},
   312  		{
   313  			name:          "leave cat on issue, trailing space",
   314  			state:         "open",
   315  			action:        github.GenericCommentActionCreated,
   316  			body:          "/meow \r",
   317  			shouldComment: true,
   318  			shouldError:   false,
   319  		},
   320  		{
   321  			name:          "categorical cat",
   322  			state:         "open",
   323  			action:        github.GenericCommentActionCreated,
   324  			body:          "/meow clothes",
   325  			shouldComment: true,
   326  			shouldError:   false,
   327  		},
   328  		{
   329  			name:          "bad cat",
   330  			state:         "open",
   331  			action:        github.GenericCommentActionCreated,
   332  			body:          "/meow error",
   333  			shouldComment: true,
   334  			shouldError:   true,
   335  		},
   336  	}
   337  	for _, tc := range testcases {
   338  		fc := &fakegithub.FakeClient{
   339  			IssueComments: make(map[int][]github.IssueComment),
   340  		}
   341  		e := &github.GenericCommentEvent{
   342  			Action:     tc.action,
   343  			Body:       tc.body,
   344  			Number:     5,
   345  			IssueState: tc.state,
   346  			IsPR:       tc.pr,
   347  		}
   348  		err := handle(fc, logrus.WithField("plugin", pluginName), e, fakeClowder("tubbs"), func() {})
   349  		if !tc.shouldError && err != nil {
   350  			t.Errorf("%s: didn't expect error: %v", tc.name, err)
   351  			continue
   352  		} else if tc.shouldError && err == nil {
   353  			t.Errorf("%s: expected an error to occur", tc.name)
   354  			continue
   355  		}
   356  		if tc.shouldComment && len(fc.IssueComments[5]) != 1 {
   357  			t.Errorf("%s: should have commented.", tc.name)
   358  		} else if tc.shouldComment {
   359  			shouldImage := !tc.shouldError
   360  			body := fc.IssueComments[5][0].Body
   361  			hasImage := strings.Contains(body, "![")
   362  			if hasImage && !shouldImage {
   363  				t.Errorf("%s: unexpected image in %s", tc.name, body)
   364  			} else if !hasImage && shouldImage {
   365  				t.Errorf("%s: no image in %s", tc.name, body)
   366  			}
   367  		} else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 {
   368  			t.Errorf("%s: should not have commented.", tc.name)
   369  		}
   370  	}
   371  }