github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/pony/pony_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 pony
    18  
    19  import (
    20  	"encoding/json"
    21  	"flag"
    22  	"fmt"
    23  	"io"
    24  	"net/http"
    25  	"net/http/httptest"
    26  	"strings"
    27  	"testing"
    28  
    29  	"github.com/sirupsen/logrus"
    30  	"k8s.io/test-infra/prow/github"
    31  	"k8s.io/test-infra/prow/github/fakegithub"
    32  )
    33  
    34  type fakeHerd string
    35  
    36  var human = flag.Bool("human", false, "Enable to run additional manual tests")
    37  var ponyFlag = flag.String("pony", "", "Request a particular pony if set")
    38  
    39  func (c fakeHerd) readPony(tags string) (string, error) {
    40  	if tags != "" {
    41  		return tags, nil
    42  	}
    43  	return string(c), nil
    44  }
    45  
    46  func TestRealPony(t *testing.T) {
    47  	if !*human {
    48  		t.Skip("Real ponies disabled for automation. Manual users can add --human [--category=foo]")
    49  	}
    50  	if pony, err := ponyURL.readPony(*ponyFlag); err != nil {
    51  		t.Errorf("Could not read pony from %s: %v", ponyURL, err)
    52  	} else {
    53  		fmt.Println(pony)
    54  	}
    55  }
    56  
    57  func TestFormat(t *testing.T) {
    58  	result := formatURLs("http://example.com/small", "http://example.com/full")
    59  	expected := "[![pony image](http://example.com/small)](http://example.com/full)"
    60  	if result != expected {
    61  		t.Errorf("Expected %q, but got %q", expected, result)
    62  	}
    63  }
    64  
    65  // Medium integration test (depends on ability to open a TCP port)
    66  func TestHttpResponse(t *testing.T) {
    67  
    68  	// create test cases for handling content length of images
    69  	contentLength := make(map[string]string)
    70  	contentLength["/pony.jpg"] = "717987"
    71  	contentLength["/horse.png"] = "12647753"
    72  
    73  	// fake server for images
    74  	ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    75  		if r.URL.Path == "/full" {
    76  			t.Errorf("Requested full-size image instead of small image.")
    77  			http.NotFound(w, r)
    78  			return
    79  		}
    80  		if s, ok := contentLength[r.URL.Path]; ok {
    81  			body := "binary image"
    82  			w.Header().Set("Content-Length", s)
    83  			io.WriteString(w, body)
    84  		} else {
    85  			t.Errorf("Cannot find content length for %s", r.URL.Path)
    86  		}
    87  	}))
    88  	defer ts2.Close()
    89  
    90  	// setup a stock valid request
    91  	url := ts2.URL + "/pony.jpg"
    92  	b, err := json.Marshal(&ponyResult{
    93  		Pony: ponyResultPony{
    94  			Representations: ponyRepresentations{
    95  				Small: ts2.URL + "/pony.jpg",
    96  				Full:  ts2.URL + "/full",
    97  			},
    98  		},
    99  	})
   100  	if err != nil {
   101  		t.Errorf("Failed to encode test data: %v", err)
   102  	}
   103  
   104  	// create test cases for handling http responses
   105  	validResponse := string(b)
   106  
   107  	type testcase struct {
   108  		name      string
   109  		comment   string
   110  		path      string
   111  		response  string
   112  		expected  string
   113  		expectTag string
   114  		isValid   bool
   115  		noPony    bool
   116  	}
   117  
   118  	var testcases = []testcase{
   119  		{
   120  			name:     "valid",
   121  			comment:  "/pony",
   122  			path:     "/valid",
   123  			response: validResponse,
   124  			expected: url,
   125  			isValid:  true,
   126  		},
   127  		{
   128  			name:    "no pony found",
   129  			comment: "/pony",
   130  			path:    "/404",
   131  			noPony:  true,
   132  			isValid: false,
   133  		},
   134  		{
   135  			name:     "invalid JSON",
   136  			comment:  "/pony",
   137  			path:     "/bad-json",
   138  			response: `{"bad-blob": "not-a-url"`,
   139  			isValid:  false,
   140  		},
   141  		{
   142  			name:     "image too big",
   143  			comment:  "/pony",
   144  			path:     "/too-big",
   145  			response: fmt.Sprintf(`{"pony":{"representations": {"small": "%s/horse.png", "full": "%s/full"}}}`, ts2.URL, ts2.URL),
   146  			isValid:  false,
   147  		},
   148  		{
   149  			name:      "has tag",
   150  			comment:   "/pony peach hack",
   151  			path:      "/peach",
   152  			isValid:   true,
   153  			expectTag: "peach hack",
   154  			response:  validResponse,
   155  		},
   156  	}
   157  
   158  	// fake server for image urls
   159  	pathToTestCase := make(map[string]*testcase)
   160  	for _, testcase := range testcases {
   161  		tc := testcase
   162  		pathToTestCase[testcase.path] = &tc
   163  	}
   164  	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   165  		if tc, ok := pathToTestCase[r.URL.Path]; ok {
   166  			if tc.noPony {
   167  				http.NotFound(w, r)
   168  				return
   169  			}
   170  			q := r.URL.Query().Get("q")
   171  			if strings.HasSuffix(q, ",") {
   172  				t.Errorf("Expected query without trailing comma: %q", q)
   173  			}
   174  			if tc.expectTag != "" && !strings.HasSuffix(q, ", "+tc.expectTag) {
   175  				t.Errorf("Expected tag %q, but didn't find it in %q", tc.expectTag, q)
   176  			}
   177  			io.WriteString(w, tc.response)
   178  		} else {
   179  			io.WriteString(w, validResponse)
   180  		}
   181  	}))
   182  	defer ts.Close()
   183  
   184  	// run test for each case
   185  	for _, testcase := range testcases {
   186  		pony, err := realHerd(ts.URL + testcase.path).readPony(testcase.expectTag)
   187  		if testcase.isValid && err != nil {
   188  			t.Errorf("For case %s, didn't expect error: %v", testcase.name, err)
   189  		} else if !testcase.isValid && err == nil {
   190  			t.Errorf("For case %s, expected error, received pony: %s", testcase.name, pony)
   191  		}
   192  
   193  		if !testcase.isValid {
   194  			continue
   195  		}
   196  
   197  		// github fake client
   198  		fc := &fakegithub.FakeClient{
   199  			IssueComments: make(map[int][]github.IssueComment),
   200  		}
   201  
   202  		// fully test handling a comment
   203  		e := &github.GenericCommentEvent{
   204  			Action:     github.GenericCommentActionCreated,
   205  			Body:       testcase.comment,
   206  			Number:     5,
   207  			IssueState: "open",
   208  		}
   209  		err = handle(fc, logrus.WithField("plugin", pluginName), e, realHerd(ts.URL+testcase.path))
   210  		if err != nil {
   211  			t.Errorf("tc %s: For comment %s, didn't expect error: %v", testcase.name, testcase.comment, err)
   212  		}
   213  
   214  		if len(fc.IssueComments[5]) != 1 {
   215  			t.Errorf("tc %s: should have commented", testcase.name)
   216  		}
   217  		if c := fc.IssueComments[5][0]; !strings.Contains(c.Body, testcase.expected) {
   218  			t.Errorf("tc %s: missing image url: %s from comment: %v", testcase.name, testcase.expected, c.Body)
   219  		}
   220  	}
   221  }
   222  
   223  // Small, unit tests
   224  func TestPonies(t *testing.T) {
   225  	var testcases = []struct {
   226  		name          string
   227  		action        github.GenericCommentEventAction
   228  		body          string
   229  		state         string
   230  		pr            bool
   231  		shouldComment bool
   232  	}{
   233  		{
   234  			name:          "ignore edited comment",
   235  			state:         "open",
   236  			action:        github.GenericCommentActionEdited,
   237  			body:          "/pony",
   238  			shouldComment: false,
   239  		},
   240  		{
   241  			name:          "leave pony on pr",
   242  			state:         "open",
   243  			action:        github.GenericCommentActionCreated,
   244  			body:          "/pony",
   245  			pr:            true,
   246  			shouldComment: true,
   247  		},
   248  		{
   249  			name:          "leave pony on issue",
   250  			state:         "open",
   251  			action:        github.GenericCommentActionCreated,
   252  			body:          "/pony",
   253  			shouldComment: true,
   254  		},
   255  		{
   256  			name:          "leave pony on issue, trailing space",
   257  			state:         "open",
   258  			action:        github.GenericCommentActionCreated,
   259  			body:          "/pony \r",
   260  			shouldComment: true,
   261  		},
   262  		{
   263  			name:          "leave pony on issue, tag specified",
   264  			state:         "open",
   265  			action:        github.GenericCommentActionCreated,
   266  			body:          "/pony Twilight Sparkle",
   267  			shouldComment: true,
   268  		},
   269  		{
   270  			name:          "leave pony on issue, tag specified, trailing space",
   271  			state:         "open",
   272  			action:        github.GenericCommentActionCreated,
   273  			body:          "/pony Twilight Sparkle \r",
   274  			shouldComment: true,
   275  		},
   276  		{
   277  			name:          "don't leave cats or dogs",
   278  			state:         "open",
   279  			action:        github.GenericCommentActionCreated,
   280  			body:          "/woof\n/meow",
   281  			shouldComment: false,
   282  		},
   283  		{
   284  			name:          "do nothing in the middle of a line",
   285  			state:         "open",
   286  			action:        github.GenericCommentActionCreated,
   287  			body:          "did you know that /pony makes ponies happen?",
   288  			shouldComment: false,
   289  		},
   290  	}
   291  	for _, tc := range testcases {
   292  		fc := &fakegithub.FakeClient{
   293  			IssueComments: make(map[int][]github.IssueComment),
   294  		}
   295  		e := &github.GenericCommentEvent{
   296  			Action:     tc.action,
   297  			Body:       tc.body,
   298  			Number:     5,
   299  			IssueState: tc.state,
   300  			IsPR:       tc.pr,
   301  		}
   302  		err := handle(fc, logrus.WithField("plugin", pluginName), e, fakeHerd("pone"))
   303  		if err != nil {
   304  			t.Errorf("For case %s, didn't expect error: %v", tc.name, err)
   305  		}
   306  		if tc.shouldComment && len(fc.IssueComments[5]) != 1 {
   307  			t.Errorf("For case %s, should have commented.", tc.name)
   308  		} else if !tc.shouldComment && len(fc.IssueComments[5]) != 0 {
   309  			t.Errorf("For case %s, should not have commented.", tc.name)
   310  		}
   311  	}
   312  }