github.com/spinnaker/spin@v1.30.0/cmd/pipeline/save_test.go (about)

     1  // Copyright (c) 2018, Google, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //   http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  //   Unless required by applicable law or agreed to in writing, software
    10  //   distributed under the License is distributed on an "AS IS" BASIS,
    11  //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  //   See the License for the specific language governing permissions and
    13  //   limitations under the License.
    14  
    15  package pipeline
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"os"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/spinnaker/spin/cmd"
    29  	"github.com/spinnaker/spin/util"
    30  )
    31  
    32  func TestPipelineSave_json(t *testing.T) {
    33  	saveBuffer := new(bytes.Buffer)
    34  	ts := testGatePipelineSaveSuccess(saveBuffer)
    35  	defer ts.Close()
    36  
    37  	tempFile := tempPipelineFile(testPipelineJsonStr)
    38  	if tempFile == nil {
    39  		t.Fatal("Could not create temp pipeline file.")
    40  	}
    41  	defer os.Remove(tempFile.Name())
    42  
    43  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
    44  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
    45  	rootCmd.AddCommand(pipelineCmd)
    46  
    47  	args := []string{"pipeline", "save", "--file", tempFile.Name(), "--gate-endpoint", ts.URL}
    48  	rootCmd.SetArgs(args)
    49  	err := rootCmd.Execute()
    50  	if err != nil {
    51  		t.Fatalf("Command failed with: %s", err)
    52  	}
    53  
    54  	expected := strings.TrimSpace(testPipelineJsonStr)
    55  	recieved := saveBuffer.Bytes()
    56  	util.TestPrettyJsonDiff(t, "save request body", expected, recieved)
    57  }
    58  
    59  func TestPipelineSave_yaml(t *testing.T) {
    60  	saveBuffer := new(bytes.Buffer)
    61  	ts := testGatePipelineSaveSuccess(saveBuffer)
    62  	defer ts.Close()
    63  
    64  	tempFile := tempPipelineFile(testPipelineYamlStr)
    65  	if tempFile == nil {
    66  		t.Fatal("Could not create temp pipeline file.")
    67  	}
    68  	defer os.Remove(tempFile.Name())
    69  
    70  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
    71  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
    72  	rootCmd.AddCommand(pipelineCmd)
    73  
    74  	args := []string{"pipeline", "save", "--file", tempFile.Name(), "--gate-endpoint", ts.URL}
    75  	rootCmd.SetArgs(args)
    76  	err := rootCmd.Execute()
    77  	if err != nil {
    78  		t.Fatalf("Command failed with: %s", err)
    79  	}
    80  
    81  	expected := strings.TrimSpace(testPipelineJsonStr)
    82  	recieved := saveBuffer.Bytes()
    83  	util.TestPrettyJsonDiff(t, "save request body", expected, recieved)
    84  }
    85  
    86  func TestPipelineSave_stdin(t *testing.T) {
    87  	saveBuffer := new(bytes.Buffer)
    88  	ts := testGatePipelineSaveSuccess(saveBuffer)
    89  	defer ts.Close()
    90  
    91  	tempFile := tempPipelineFile(testPipelineJsonStr)
    92  	if tempFile == nil {
    93  		t.Fatal("Could not create temp pipeline file.")
    94  	}
    95  	defer os.Remove(tempFile.Name())
    96  
    97  	// Prepare Stdin for test reading.
    98  	tempFile.Seek(0, 0)
    99  	oldStdin := os.Stdin
   100  	defer func() { os.Stdin = oldStdin }()
   101  	os.Stdin = tempFile
   102  
   103  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
   104  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
   105  	rootCmd.AddCommand(pipelineCmd)
   106  
   107  	args := []string{"pipeline", "save", "--gate-endpoint", ts.URL}
   108  	rootCmd.SetArgs(args)
   109  	err := rootCmd.Execute()
   110  	if err != nil {
   111  		t.Fatalf("Command failed with: %s", err)
   112  	}
   113  
   114  	expected := strings.TrimSpace(testPipelineJsonStr)
   115  	recieved := saveBuffer.Bytes()
   116  	util.TestPrettyJsonDiff(t, "save request body", expected, recieved)
   117  }
   118  
   119  func TestPipelineSave_fail(t *testing.T) {
   120  	ts := testGateFail()
   121  	defer ts.Close()
   122  
   123  	tempFile := tempPipelineFile(testPipelineJsonStr)
   124  	if tempFile == nil {
   125  		t.Fatal("Could not create temp pipeline file.")
   126  	}
   127  	defer os.Remove(tempFile.Name())
   128  
   129  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
   130  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
   131  	rootCmd.AddCommand(pipelineCmd)
   132  
   133  	args := []string{"pipeline", "save", "--file", tempFile.Name(), "--gate-endpoint", ts.URL}
   134  	rootCmd.SetArgs(args)
   135  	err := rootCmd.Execute()
   136  	if err == nil {
   137  		t.Fatalf("Command failed with: %s", err)
   138  	}
   139  }
   140  
   141  func TestPipelineSave_accessdenied(t *testing.T) {
   142  	ts := testGateReadOnly()
   143  	defer ts.Close()
   144  
   145  	tempFile := tempPipelineFile(testPipelineJsonStr)
   146  	if tempFile == nil {
   147  		t.Fatal("Could not create temp pipeline file.")
   148  	}
   149  	defer os.Remove(tempFile.Name())
   150  
   151  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
   152  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
   153  	rootCmd.AddCommand(pipelineCmd)
   154  
   155  	args := []string{"pipeline", "save", "--file", tempFile.Name(), "--gate-endpoint", ts.URL}
   156  	rootCmd.SetArgs(args)
   157  	err := rootCmd.Execute()
   158  	if err == nil {
   159  		t.Fatalf("Command failed with: %s", err)
   160  	}
   161  }
   162  
   163  func TestPipelineSave_flags(t *testing.T) {
   164  	ts := testGateSuccess()
   165  	defer ts.Close()
   166  
   167  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
   168  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
   169  	rootCmd.AddCommand(pipelineCmd)
   170  
   171  	args := []string{"pipeline", "save", "--gate-endpoint", ts.URL} // Missing pipeline spec file.
   172  	rootCmd.SetArgs(args)
   173  	err := rootCmd.Execute()
   174  	if err == nil {
   175  		t.Fatalf("Command failed with: %s", err)
   176  	}
   177  }
   178  
   179  func TestPipelineSave_missingname(t *testing.T) {
   180  	ts := testGateSuccess()
   181  	defer ts.Close()
   182  
   183  	tempFile := tempPipelineFile(missingNameJsonStr)
   184  	if tempFile == nil {
   185  		t.Fatal("Could not create temp pipeline file.")
   186  	}
   187  	defer os.Remove(tempFile.Name())
   188  
   189  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
   190  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
   191  	rootCmd.AddCommand(pipelineCmd)
   192  
   193  	args := []string{"pipeline", "save", "--file", tempFile.Name(), "--gate-endpoint", ts.URL}
   194  	rootCmd.SetArgs(args)
   195  	err := rootCmd.Execute()
   196  	if err == nil {
   197  		t.Fatalf("Command failed with: %s", err)
   198  	}
   199  }
   200  
   201  func TestPipelineSave_missingid(t *testing.T) {
   202  	ts := testGateSuccess()
   203  	defer ts.Close()
   204  
   205  	tempFile := tempPipelineFile(missingIdJsonStr)
   206  	if tempFile == nil {
   207  		t.Fatal("Could not create temp pipeline file.")
   208  	}
   209  	defer os.Remove(tempFile.Name())
   210  
   211  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
   212  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
   213  	rootCmd.AddCommand(pipelineCmd)
   214  
   215  	args := []string{"pipeline", "save", "--file", tempFile.Name(), "--gate-endpoint", ts.URL}
   216  	rootCmd.SetArgs(args)
   217  	err := rootCmd.Execute()
   218  	if err != nil {
   219  		t.Fatalf("Command failed with: %s", err)
   220  	}
   221  }
   222  
   223  func TestPipelineSave_missingapp(t *testing.T) {
   224  	ts := testGateSuccess()
   225  	defer ts.Close()
   226  
   227  	tempFile := tempPipelineFile(missingAppJsonStr)
   228  	if tempFile == nil {
   229  		t.Fatal("Could not create temp pipeline file.")
   230  	}
   231  	defer os.Remove(tempFile.Name())
   232  
   233  	rootCmd, rootOpts := cmd.NewCmdRoot(ioutil.Discard, ioutil.Discard)
   234  	pipelineCmd, _ := NewPipelineCmd(rootOpts)
   235  	rootCmd.AddCommand(pipelineCmd)
   236  
   237  	args := []string{"pipeline", "save", "--file", tempFile.Name(), "--gate-endpoint", ts.URL}
   238  	rootCmd.SetArgs(args)
   239  	err := rootCmd.Execute()
   240  	if err == nil {
   241  		t.Fatalf("Command failed with: %s", err)
   242  	}
   243  }
   244  
   245  func tempPipelineFile(pipelineContent string) *os.File {
   246  	tempFile, _ := ioutil.TempFile("" /* /tmp dir. */, "pipeline-spec")
   247  	bytes, err := tempFile.Write([]byte(pipelineContent))
   248  	if err != nil || bytes == 0 {
   249  		fmt.Println("Could not write temp file.")
   250  		return nil
   251  	}
   252  	return tempFile
   253  }
   254  
   255  // testGatePipelineSaveSuccess spins up a local http server that we will configure the
   256  // GateClient to direct requests to. Responds with a 200 OK.
   257  // Writes pipeline body to buffer for testing.
   258  func testGatePipelineSaveSuccess(buffer io.Writer) *httptest.Server {
   259  	mux := util.TestGateMuxWithVersionHandler()
   260  	mux.Handle(
   261  		"/applications/app/pipelineConfigs/pipeline1",
   262  		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   263  			// Confirm the pipeline exists.
   264  			fmt.Fprintln(w, "")
   265  		}),
   266  	)
   267  	mux.Handle(
   268  		"/pipelines",
   269  		util.NewTestBufferHandlerFunc(http.MethodPost, buffer, http.StatusOK, ""),
   270  	)
   271  	return httptest.NewServer(mux)
   272  }
   273  
   274  // testGateSuccess spins up a local http server that we will configure the GateClient
   275  // to direct requests to. Responds with a 200 OK.
   276  func testGateSuccess() *httptest.Server {
   277  	mux := util.TestGateMuxWithVersionHandler()
   278  	mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   279  		fmt.Fprintln(w, "") // Just write an empty 200 success on save.
   280  	}))
   281  	return httptest.NewServer(mux)
   282  }
   283  
   284  // testGateReadOnly spins up a local http server that we will configure the GateClient
   285  // to direct requests to. Responds with a 200 OK for READ-type requests (GET), 400 otherwise.
   286  func testGateReadOnly() *httptest.Server {
   287  	mux := util.TestGateMuxWithVersionHandler()
   288  	mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   289  		switch method := r.Method; method {
   290  		case "GET":
   291  			fmt.Fprintln(w, "") // Just write an empty 200 success on GET.
   292  		default:
   293  			// Return "400 Access is denied" (Bad Request)
   294  			http.Error(w, "Access is denied", http.StatusBadRequest)
   295  		}
   296  	}))
   297  	return httptest.NewServer(mux)
   298  }
   299  
   300  // testGateFail spins up a local http server that we will configure the GateClient
   301  // to direct requests to. Responds with a 500 InternalServerError.
   302  func testGateFail() *httptest.Server {
   303  	mux := util.TestGateMuxWithVersionHandler()
   304  	mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   305  		// TODO(jacobkiefer): Mock more robust errors once implemented upstream.
   306  		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
   307  	}))
   308  	return httptest.NewServer(mux)
   309  }
   310  
   311  const missingNameJsonStr = `
   312  {
   313    "id": "pipeline1",
   314    "application": "app",
   315    "keepWaitingPipelines": false,
   316    "lastModifiedBy": "anonymous",
   317    "limitConcurrent": true,
   318    "stages": [
   319      {
   320        "name": "Wait",
   321        "refId": "1",
   322        "requisiteStageRefIds": [],
   323        "type": "wait",
   324        "waitTime": 30
   325      }
   326    ],
   327    "triggers": [],
   328    "updateTs": "1520879791608"
   329  }
   330  `
   331  
   332  const missingIdJsonStr = `
   333  {
   334    "name": "pipeline1",
   335    "application": "app",
   336    "keepWaitingPipelines": false,
   337    "lastModifiedBy": "anonymous",
   338    "limitConcurrent": true,
   339    "stages": [
   340      {
   341        "name": "Wait",
   342        "refId": "1",
   343        "requisiteStageRefIds": [],
   344        "type": "wait",
   345        "waitTime": 30
   346      }
   347    ],
   348    "triggers": [],
   349    "updateTs": "1520879791608"
   350  }
   351  `
   352  
   353  const missingAppJsonStr = `
   354  {
   355    "name": "pipeline1",
   356    "id": "pipeline1",
   357    "keepWaitingPipelines": false,
   358    "lastModifiedBy": "anonymous",
   359    "limitConcurrent": true,
   360    "stages": [
   361      {
   362        "name": "Wait",
   363        "refId": "1",
   364        "requisiteStageRefIds": [],
   365        "type": "wait",
   366        "waitTime": 30
   367      }
   368    ],
   369    "triggers": [],
   370    "updateTs": "1520879791608"
   371  }
   372  `
   373  
   374  const testPipelineJsonStr = `
   375  {
   376   "application": "app",
   377   "id": "pipeline1",
   378   "keepWaitingPipelines": false,
   379   "lastModifiedBy": "anonymous",
   380   "limitConcurrent": true,
   381   "name": "pipeline1",
   382   "parameterConfig": [
   383    {
   384     "default": "bar",
   385     "description": "A foo.",
   386     "name": "foo",
   387     "required": true
   388    }
   389   ],
   390   "stages": [
   391    {
   392     "comments": "${ parameters.derp }",
   393     "name": "Wait",
   394     "refId": "1",
   395     "requisiteStageRefIds": [],
   396     "type": "wait",
   397     "waitTime": 30
   398    }
   399   ],
   400   "triggers": [],
   401   "updateTs": "1520879791608"
   402  }
   403  `
   404  
   405  const testPipelineYamlStr = `
   406  name: pipeline1
   407  id: pipeline1
   408  application: app
   409  keepWaitingPipelines: false
   410  lastModifiedBy: anonymous
   411  limitConcurrent: true
   412  parameterConfig:
   413  - default: bar
   414    description: A foo.
   415    name: foo
   416    required: true
   417  stages:
   418  - comments: ${ parameters.derp }
   419    name: Wait
   420    refId: "1"
   421    requisiteStageRefIds: []
   422    type: wait
   423    waitTime: 30
   424  triggers: []
   425  updateTs: "1520879791608"
   426  `