github.com/replicatedhq/ship@v0.55.0/pkg/lifecycle/render/web/step_test.go (about)

     1  package web
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"testing"
     9  
    10  	"github.com/jarcoal/httpmock"
    11  	"github.com/pkg/errors"
    12  	"github.com/replicatedhq/libyaml"
    13  	"github.com/replicatedhq/ship/pkg/api"
    14  	"github.com/replicatedhq/ship/pkg/lifecycle/render/root"
    15  	"github.com/replicatedhq/ship/pkg/templates"
    16  	"github.com/replicatedhq/ship/pkg/testing/logger"
    17  	"github.com/spf13/afero"
    18  	"github.com/spf13/viper"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  type TestWebAsset struct {
    23  	Name               string
    24  	Asset              api.WebAsset
    25  	ExpectFiles        map[string]interface{}
    26  	ExpectedErr        error
    27  	MockedRespBody     string
    28  	RegisterResponders func()
    29  }
    30  
    31  func TestWebStep(t *testing.T) {
    32  	tests := []TestWebAsset{
    33  		{
    34  			Name: "get",
    35  			Asset: api.WebAsset{
    36  				AssetShared: api.AssetShared{
    37  					Dest: "asset.txt",
    38  				},
    39  				URL: "http://foo.bar",
    40  			},
    41  			RegisterResponders: func() {
    42  				httpmock.RegisterResponder("GET", "http://foo.bar",
    43  					httpmock.NewStringResponder(200, "hi from foo.bar"))
    44  			},
    45  			ExpectFiles: map[string]interface{}{
    46  				"asset.txt": "hi from foo.bar",
    47  			},
    48  			ExpectedErr: nil,
    49  		},
    50  		{
    51  			Name: "get error",
    52  			Asset: api.WebAsset{
    53  				AssetShared: api.AssetShared{
    54  					Dest: "asset.txt",
    55  				},
    56  				URL: "http://foo.bar",
    57  			},
    58  			RegisterResponders: func() {
    59  				httpmock.RegisterResponder("GET", "http://foo.bar",
    60  					httpmock.NewStringResponder(500, "NOPE!"))
    61  			},
    62  			ExpectFiles: map[string]interface{}{},
    63  			ExpectedErr: errors.New("Get web asset from http://foo.bar: received response with status 500"),
    64  		},
    65  		{
    66  			Name: "post",
    67  			Asset: api.WebAsset{
    68  				AssetShared: api.AssetShared{
    69  					Dest: "asset.txt",
    70  				},
    71  				Body:       "stuff to post",
    72  				Method:     "POST",
    73  				URL:        "http://foo.bar",
    74  				BodyFormat: "text/plain",
    75  			},
    76  			RegisterResponders: func() {
    77  				httpmock.RegisterResponder("POST", "http://foo.bar",
    78  					httpmock.NewStringResponder(200, "hi from foo.bar"))
    79  			},
    80  			ExpectFiles: map[string]interface{}{
    81  				"asset.txt": "hi from foo.bar",
    82  			},
    83  			ExpectedErr: nil,
    84  		},
    85  		{
    86  			Name: "post error",
    87  			Asset: api.WebAsset{
    88  				AssetShared: api.AssetShared{
    89  					Dest: "asset.txt",
    90  				},
    91  				Body:       "stuff to post",
    92  				Method:     "POST",
    93  				URL:        "http://foo.bar",
    94  				BodyFormat: "text/plain",
    95  			},
    96  			RegisterResponders: func() {
    97  				httpmock.RegisterResponder("POST", "http://foo.bar",
    98  					httpmock.NewStringResponder(500, "NOPE!"))
    99  			},
   100  			ExpectFiles: map[string]interface{}{},
   101  			ExpectedErr: errors.New("Get web asset from http://foo.bar: received response with status 500"),
   102  		},
   103  		{
   104  			Name: "headers",
   105  			Asset: api.WebAsset{
   106  				AssetShared: api.AssetShared{
   107  					Dest: "asset.txt",
   108  				},
   109  				Body: "some stuff to post",
   110  				Headers: map[string][]string{
   111  					"Authorization": {"my auth"},
   112  				},
   113  				Method:     "POST",
   114  				URL:        "http://foo.bar",
   115  				BodyFormat: "text/plain",
   116  			},
   117  			RegisterResponders: func() {
   118  				httpmock.RegisterResponder("POST", "http://foo.bar",
   119  					func(req *http.Request) (*http.Response, error) {
   120  						header := req.Header.Get("Authorization")
   121  
   122  						if header != "my auth" {
   123  							return httpmock.NewStringResponse(500, "mock headers != test headers"), nil
   124  						}
   125  
   126  						return httpmock.NewStringResponse(200, "hi from foo.bar"), nil
   127  					})
   128  			},
   129  			ExpectFiles: map[string]interface{}{
   130  				"asset.txt": "hi from foo.bar",
   131  			},
   132  			ExpectedErr: nil,
   133  		},
   134  		{
   135  			Name: "headers error",
   136  			Asset: api.WebAsset{
   137  				AssetShared: api.AssetShared{
   138  					Dest: "asset.txt",
   139  				},
   140  				Body: "some stuff to post",
   141  				Headers: map[string][]string{
   142  					"Authorization": {"my auth"},
   143  				},
   144  				Method:     "POST",
   145  				URL:        "http://foo.bar",
   146  				BodyFormat: "text/plain",
   147  			},
   148  			RegisterResponders: func() {
   149  				httpmock.RegisterResponder("POST", "http://foo.bar",
   150  					func(req *http.Request) (*http.Response, error) {
   151  						header := req.Header.Get("Authorization")
   152  
   153  						decoded, _ := base64.StdEncoding.DecodeString(header)
   154  						if string(decoded) != "NOT my auth" {
   155  							return httpmock.NewStringResponse(500, "mock headers != test headers"), nil
   156  						}
   157  
   158  						return httpmock.NewStringResponse(200, "hi from foo.bar"), nil
   159  					})
   160  			},
   161  			ExpectFiles: map[string]interface{}{},
   162  			ExpectedErr: errors.New("Get web asset from http://foo.bar: received response with status 500"),
   163  		},
   164  		{
   165  			Name: "advanced post with headers",
   166  			Asset: api.WebAsset{
   167  				AssetShared: api.AssetShared{
   168  					Dest: "asset.txt",
   169  				},
   170  				Body: "some stuff to post",
   171  				Headers: map[string][]string{
   172  					"Authorization": {"my auth"},
   173  				},
   174  				Method:     "POST",
   175  				URL:        "http://foo.bar",
   176  				BodyFormat: "text/plain",
   177  			},
   178  			RegisterResponders: func() {
   179  				httpmock.RegisterResponder("POST", "http://foo.bar",
   180  					func(req *http.Request) (*http.Response, error) {
   181  						header := req.Header.Get("Authorization")
   182  
   183  						decoded, _ := base64.StdEncoding.DecodeString(header)
   184  						if string(decoded) != "my auth" {
   185  							return httpmock.NewStringResponse(500, "mock headers != test headers"), nil
   186  						}
   187  
   188  						body, _ := ioutil.ReadAll(req.Body)
   189  						if string(body) != "some stuff to post" {
   190  							return httpmock.NewStringResponse(500, "mock body != test body"), nil
   191  						}
   192  
   193  						resp, err := httpmock.NewJsonResponse(200, "some stuff to post")
   194  						if err != nil {
   195  							return httpmock.NewStringResponse(500, "NOPE!"), nil
   196  						}
   197  						return resp, nil
   198  					})
   199  			},
   200  			ExpectFiles: map[string]interface{}{},
   201  			ExpectedErr: errors.New("Get web asset from http://foo.bar: received response with status 500"),
   202  		},
   203  		{
   204  			Name: "illegal dest path",
   205  			Asset: api.WebAsset{
   206  				AssetShared: api.AssetShared{
   207  					Dest: "/bin/runc",
   208  				},
   209  				URL: "http://foo.bar",
   210  			},
   211  			RegisterResponders: func() {
   212  				httpmock.RegisterResponder("GET", "http://foo.bar",
   213  					httpmock.NewStringResponder(200, "hi from foo.bar"))
   214  			},
   215  			ExpectFiles: map[string]interface{}{},
   216  			ExpectedErr: errors.Wrap(errors.New("cannot write to an absolute path: /bin/runc"), "write web asset"),
   217  		},
   218  		{
   219  			Name: "illegal dest path",
   220  			Asset: api.WebAsset{
   221  				AssetShared: api.AssetShared{
   222  					Dest: "../../../bin/runc",
   223  				},
   224  				URL: "http://foo.bar",
   225  			},
   226  			RegisterResponders: func() {
   227  				httpmock.RegisterResponder("GET", "http://foo.bar",
   228  					httpmock.NewStringResponder(200, "hi from foo.bar"))
   229  			},
   230  			ExpectFiles: map[string]interface{}{},
   231  			ExpectedErr: errors.Wrap(errors.New("cannot write to a path that is a parent of the working dir: ../../../bin/runc"), "write web asset"),
   232  		},
   233  	}
   234  
   235  	for _, test := range tests {
   236  		t.Run(test.Name, func(t *testing.T) {
   237  			req := require.New(t)
   238  
   239  			v := viper.New()
   240  			testLogger := &logger.TestLogger{T: t}
   241  			rootFs := root.Fs{
   242  				Afero:    afero.Afero{Fs: afero.NewMemMapFs()},
   243  				RootPath: "",
   244  			}
   245  			client := &http.Client{}
   246  
   247  			step := &DefaultStep{
   248  				Logger: testLogger,
   249  				Fs:     rootFs.Afero,
   250  				Viper:  v,
   251  				BuilderBuilder: &templates.BuilderBuilder{
   252  					Logger: testLogger,
   253  					Viper:  v,
   254  				},
   255  				Client: client,
   256  			}
   257  
   258  			httpmock.Activate()
   259  			defer httpmock.DeactivateAndReset()
   260  			test.RegisterResponders()
   261  
   262  			err := step.Execute(
   263  				rootFs,
   264  				test.Asset,
   265  				api.ReleaseMetadata{},
   266  				map[string]interface{}{},
   267  				[]libyaml.ConfigGroup{},
   268  			)(context.Background())
   269  
   270  			if test.ExpectedErr == nil {
   271  				req.NoError(err)
   272  			} else {
   273  				req.Error(err, "expected error "+test.ExpectedErr.Error())
   274  				req.Equal(test.ExpectedErr.Error(), err.Error())
   275  			}
   276  
   277  			for name, contents := range test.ExpectFiles {
   278  				actual, err := rootFs.ReadFile(name)
   279  				req.NoError(err)
   280  				req.Equal(contents, string(actual))
   281  			}
   282  
   283  		})
   284  	}
   285  }