github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/cmd/deck/main_test.go (about)

     1  /*
     2  Copyright 2016 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 main
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"encoding/json"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"net/http"
    28  	"net/http/httptest"
    29  	"net/url"
    30  	"reflect"
    31  	"strconv"
    32  	"testing"
    33  	"time"
    34  
    35  	"sigs.k8s.io/yaml"
    36  
    37  	"k8s.io/test-infra/prow/config"
    38  	"k8s.io/test-infra/prow/kube"
    39  	"k8s.io/test-infra/prow/pluginhelp"
    40  	"k8s.io/test-infra/prow/tide"
    41  	"k8s.io/test-infra/prow/tide/history"
    42  )
    43  
    44  func TestOptions_Validate(t *testing.T) {
    45  	var testCases = []struct {
    46  		name        string
    47  		input       options
    48  		expectedErr bool
    49  	}{
    50  		{
    51  			name: "minimal set ok",
    52  			input: options{
    53  				configPath: "test",
    54  			},
    55  			expectedErr: false,
    56  		},
    57  		{
    58  			name:        "missing configpath",
    59  			input:       options{},
    60  			expectedErr: true,
    61  		},
    62  		{
    63  			name: "ok with oauth",
    64  			input: options{
    65  				configPath:            "test",
    66  				oauthURL:              "website",
    67  				githubOAuthConfigFile: "something",
    68  				cookieSecretFile:      "yum",
    69  			},
    70  			expectedErr: false,
    71  		},
    72  		{
    73  			name: "missing github config with oauth",
    74  			input: options{
    75  				configPath:       "test",
    76  				oauthURL:         "website",
    77  				cookieSecretFile: "yum",
    78  			},
    79  			expectedErr: true,
    80  		},
    81  		{
    82  			name: "missing cookie with oauth",
    83  			input: options{
    84  				configPath:            "test",
    85  				oauthURL:              "website",
    86  				githubOAuthConfigFile: "something",
    87  			},
    88  			expectedErr: true,
    89  		},
    90  	}
    91  
    92  	for _, testCase := range testCases {
    93  		err := testCase.input.Validate()
    94  		if testCase.expectedErr && err == nil {
    95  			t.Errorf("%s: expected an error but got none", testCase.name)
    96  		}
    97  		if !testCase.expectedErr && err != nil {
    98  			t.Errorf("%s: expected no error but got one: %v", testCase.name, err)
    99  		}
   100  	}
   101  }
   102  
   103  type flc int
   104  
   105  func (f flc) GetJobLog(job, id string) ([]byte, error) {
   106  	if job == "job" && id == "123" {
   107  		return []byte("hello"), nil
   108  	}
   109  	return nil, errors.New("muahaha")
   110  }
   111  
   112  func TestHandleLog(t *testing.T) {
   113  	var testcases = []struct {
   114  		name string
   115  		path string
   116  		code int
   117  	}{
   118  		{
   119  			name: "no job name",
   120  			path: "",
   121  			code: http.StatusBadRequest,
   122  		},
   123  		{
   124  			name: "job but no id",
   125  			path: "?job=job",
   126  			code: http.StatusBadRequest,
   127  		},
   128  		{
   129  			name: "id but no job",
   130  			path: "?id=123",
   131  			code: http.StatusBadRequest,
   132  		},
   133  		{
   134  			name: "id and job, found",
   135  			path: "?job=job&id=123",
   136  			code: http.StatusOK,
   137  		},
   138  		{
   139  			name: "id and job, not found",
   140  			path: "?job=ohno&id=123",
   141  			code: http.StatusNotFound,
   142  		},
   143  	}
   144  	handler := handleLog(flc(0))
   145  	for _, tc := range testcases {
   146  		req, err := http.NewRequest(http.MethodGet, "", nil)
   147  		if err != nil {
   148  			t.Fatalf("Error making request: %v", err)
   149  		}
   150  		u, err := url.Parse(tc.path)
   151  		if err != nil {
   152  			t.Fatalf("Error parsing URL: %v", err)
   153  		}
   154  		var follow = false
   155  		if ok, _ := strconv.ParseBool(u.Query().Get("follow")); ok {
   156  			follow = true
   157  		}
   158  		req.URL = u
   159  		rr := httptest.NewRecorder()
   160  		handler.ServeHTTP(rr, req)
   161  		if rr.Code != tc.code {
   162  			t.Errorf("Wrong error code. Got %v, want %v", rr.Code, tc.code)
   163  		} else if rr.Code == http.StatusOK {
   164  			if follow {
   165  				//wait a little to get the chunks
   166  				time.Sleep(2 * time.Millisecond)
   167  				reader := bufio.NewReader(rr.Body)
   168  				var buf bytes.Buffer
   169  				for {
   170  					line, err := reader.ReadBytes('\n')
   171  					if err == io.EOF {
   172  						break
   173  					}
   174  					if err != nil {
   175  						t.Fatalf("Expecting reply with content but got error: %v", err)
   176  					}
   177  					buf.Write(line)
   178  				}
   179  				if !bytes.Contains(buf.Bytes(), []byte("hello")) {
   180  					t.Errorf("Unexpected body: got %s.", buf.String())
   181  				}
   182  			} else {
   183  				resp := rr.Result()
   184  				defer resp.Body.Close()
   185  				if body, err := ioutil.ReadAll(resp.Body); err != nil {
   186  					t.Errorf("Error reading response body: %v", err)
   187  				} else if string(body) != "hello" {
   188  					t.Errorf("Unexpected body: got %s.", string(body))
   189  				}
   190  			}
   191  		}
   192  	}
   193  }
   194  
   195  type fpjc kube.ProwJob
   196  
   197  func (fc *fpjc) GetProwJob(name string) (kube.ProwJob, error) {
   198  	return kube.ProwJob(*fc), nil
   199  }
   200  
   201  // TestRerun just checks that the result can be unmarshaled properly, has an
   202  // updated status, and has equal spec.
   203  func TestRerun(t *testing.T) {
   204  	fc := fpjc(kube.ProwJob{
   205  		Spec: kube.ProwJobSpec{
   206  			Job:  "whoa",
   207  			Type: kube.PresubmitJob,
   208  			Refs: &kube.Refs{
   209  				Org:  "org",
   210  				Repo: "repo",
   211  				Pulls: []kube.Pull{
   212  					{Number: 1},
   213  				},
   214  			},
   215  		},
   216  		Status: kube.ProwJobStatus{
   217  			State: kube.PendingState,
   218  		},
   219  	})
   220  	handler := handleRerun(&fc)
   221  	req, err := http.NewRequest(http.MethodGet, "/rerun?prowjob=wowsuch", nil)
   222  	if err != nil {
   223  		t.Fatalf("Error making request: %v", err)
   224  	}
   225  	rr := httptest.NewRecorder()
   226  	handler.ServeHTTP(rr, req)
   227  	if rr.Code != http.StatusOK {
   228  		t.Fatalf("Bad error code: %d", rr.Code)
   229  	}
   230  	resp := rr.Result()
   231  	defer resp.Body.Close()
   232  	body, err := ioutil.ReadAll(resp.Body)
   233  	if err != nil {
   234  		t.Fatalf("Error reading response body: %v", err)
   235  	}
   236  	var res kube.ProwJob
   237  	if err := yaml.Unmarshal(body, &res); err != nil {
   238  		t.Fatalf("Error unmarshaling: %v", err)
   239  	}
   240  	if res.Spec.Job != "whoa" {
   241  		t.Errorf("Wrong job, expected \"whoa\", got \"%s\"", res.Spec.Job)
   242  	}
   243  	if res.Status.State != kube.TriggeredState {
   244  		t.Errorf("Wrong state, expected \"%v\", got \"%v\"", kube.TriggeredState, res.Status.State)
   245  	}
   246  }
   247  
   248  func TestTide(t *testing.T) {
   249  	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   250  		pools := []tide.Pool{
   251  			{
   252  				Org: "o",
   253  			},
   254  		}
   255  		b, err := json.Marshal(pools)
   256  		if err != nil {
   257  			t.Fatalf("Marshaling: %v", err)
   258  		}
   259  		fmt.Fprintf(w, string(b))
   260  	}))
   261  	ca := &config.Agent{}
   262  	ca.Set(&config.Config{
   263  		ProwConfig: config.ProwConfig{
   264  			Tide: config.Tide{
   265  				Queries: []config.TideQuery{
   266  					{Repos: []string{"kubernetes/test-infra"}},
   267  				},
   268  			},
   269  		},
   270  	})
   271  	ta := tideAgent{
   272  		path:         s.URL,
   273  		updatePeriod: func() time.Duration { return time.Minute },
   274  	}
   275  	if err := ta.updatePools(); err != nil {
   276  		t.Fatalf("Updating: %v", err)
   277  	}
   278  	if len(ta.pools) != 1 {
   279  		t.Fatalf("Wrong number of pools. Got %d, expected 1 in %v", len(ta.pools), ta.pools)
   280  	}
   281  	if ta.pools[0].Org != "o" {
   282  		t.Errorf("Wrong org in pool. Got %s, expected o in %v", ta.pools[0].Org, ta.pools)
   283  	}
   284  	handler := handleTidePools(ca, &ta)
   285  	req, err := http.NewRequest(http.MethodGet, "/tide.js", nil)
   286  	if err != nil {
   287  		t.Fatalf("Error making request: %v", err)
   288  	}
   289  	rr := httptest.NewRecorder()
   290  	handler.ServeHTTP(rr, req)
   291  	if rr.Code != http.StatusOK {
   292  		t.Fatalf("Bad error code: %d", rr.Code)
   293  	}
   294  	resp := rr.Result()
   295  	defer resp.Body.Close()
   296  	body, err := ioutil.ReadAll(resp.Body)
   297  	if err != nil {
   298  		t.Fatalf("Error reading response body: %v", err)
   299  	}
   300  	res := tidePools{}
   301  	if err := json.Unmarshal(body, &res); err != nil {
   302  		t.Fatalf("Error unmarshaling: %v", err)
   303  	}
   304  	if len(res.Pools) != 1 {
   305  		t.Fatalf("Wrong number of pools. Got %d, expected 1 in %v", len(res.Pools), res.Pools)
   306  	}
   307  	if res.Pools[0].Org != "o" {
   308  		t.Errorf("Wrong org in pool. Got %s, expected o in %v", res.Pools[0].Org, res.Pools)
   309  	}
   310  	if len(res.Queries) != 1 {
   311  		t.Fatalf("Wrong number of pools. Got %d, expected 1 in %v", len(res.Queries), res.Queries)
   312  	}
   313  	if expected := "is:pr state:open repo:\"kubernetes/test-infra\""; res.Queries[0] != expected {
   314  		t.Errorf("Wrong query. Got %s, expected %s", res.Queries[0], expected)
   315  	}
   316  }
   317  
   318  func TestTideHistory(t *testing.T) {
   319  	testHist := map[string][]history.Record{
   320  		"o/r:b": {
   321  			{Action: "MERGE"}, {Action: "TRIGGER"},
   322  		},
   323  	}
   324  	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   325  		b, err := json.Marshal(testHist)
   326  		if err != nil {
   327  			t.Fatalf("Marshaling: %v", err)
   328  		}
   329  		fmt.Fprintf(w, string(b))
   330  	}))
   331  
   332  	ta := tideAgent{
   333  		path:         s.URL,
   334  		updatePeriod: func() time.Duration { return time.Minute },
   335  	}
   336  	if err := ta.updateHistory(); err != nil {
   337  		t.Fatalf("Updating: %v", err)
   338  	}
   339  	if !reflect.DeepEqual(ta.history, testHist) {
   340  		t.Fatalf("Expected tideAgent history:\n%#v\n,but got:\n%#v\n", testHist, ta.history)
   341  	}
   342  
   343  	handler := handleTideHistory(&ta)
   344  	req, err := http.NewRequest(http.MethodGet, "/tide-history.js", nil)
   345  	if err != nil {
   346  		t.Fatalf("Error making request: %v", err)
   347  	}
   348  	rr := httptest.NewRecorder()
   349  	handler.ServeHTTP(rr, req)
   350  	if rr.Code != http.StatusOK {
   351  		t.Fatalf("Bad error code: %d", rr.Code)
   352  	}
   353  	resp := rr.Result()
   354  	defer resp.Body.Close()
   355  	body, err := ioutil.ReadAll(resp.Body)
   356  	if err != nil {
   357  		t.Fatalf("Error reading response body: %v", err)
   358  	}
   359  	var res tideHistory
   360  	if err := json.Unmarshal(body, &res); err != nil {
   361  		t.Fatalf("Error unmarshaling: %v", err)
   362  	}
   363  	if !reflect.DeepEqual(res.History, testHist) {
   364  		t.Fatalf("Expected /tide-history.js:\n%#v\n,but got:\n%#v\n", testHist, res.History)
   365  	}
   366  }
   367  
   368  func TestHelp(t *testing.T) {
   369  	hitCount := 0
   370  	help := pluginhelp.Help{
   371  		AllRepos:            []string{"org/repo"},
   372  		RepoPlugins:         map[string][]string{"org": {"plugin"}},
   373  		RepoExternalPlugins: map[string][]string{"org/repo": {"external-plugin"}},
   374  		PluginHelp:          map[string]pluginhelp.PluginHelp{"plugin": {Description: "plugin"}},
   375  		ExternalPluginHelp:  map[string]pluginhelp.PluginHelp{"external-plugin": {Description: "external-plugin"}},
   376  	}
   377  	s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   378  		hitCount++
   379  		b, err := json.Marshal(help)
   380  		if err != nil {
   381  			t.Fatalf("Marshaling: %v", err)
   382  		}
   383  		fmt.Fprintf(w, string(b))
   384  	}))
   385  	ha := &helpAgent{
   386  		path: s.URL,
   387  	}
   388  	handler := handlePluginHelp(ha)
   389  	handleAndCheck := func() {
   390  		req, err := http.NewRequest(http.MethodGet, "/plugin-help.js", nil)
   391  		if err != nil {
   392  			t.Fatalf("Error making request: %v", err)
   393  		}
   394  		rr := httptest.NewRecorder()
   395  		handler.ServeHTTP(rr, req)
   396  		if rr.Code != http.StatusOK {
   397  			t.Fatalf("Bad error code: %d", rr.Code)
   398  		}
   399  		resp := rr.Result()
   400  		defer resp.Body.Close()
   401  		body, err := ioutil.ReadAll(resp.Body)
   402  		if err != nil {
   403  			t.Fatalf("Error reading response body: %v", err)
   404  		}
   405  		var res pluginhelp.Help
   406  		if err := yaml.Unmarshal(body, &res); err != nil {
   407  			t.Fatalf("Error unmarshaling: %v", err)
   408  		}
   409  		if !reflect.DeepEqual(help, res) {
   410  			t.Errorf("Invalid plugin help. Got %v, expected %v", res, help)
   411  		}
   412  		if hitCount != 1 {
   413  			t.Errorf("Expected fake hook endpoint to be hit once, but endpoint was hit %d times.", hitCount)
   414  		}
   415  	}
   416  	handleAndCheck()
   417  	handleAndCheck()
   418  }