github.com/saucelabs/saucectl@v0.175.1/internal/http/appstore_test.go (about)

     1  package http
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"os"
    10  	"path"
    11  	"reflect"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/hashicorp/go-retryablehttp"
    17  	"github.com/saucelabs/saucectl/internal/storage"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/xtgo/uuid"
    20  
    21  	"gotest.tools/v3/fs"
    22  )
    23  
    24  func TestAppStore_UploadStream(t *testing.T) {
    25  	// mock test values
    26  	itemID := uuid.NewRandom().String()
    27  	itemName := "hello.txt"
    28  	uploadTimestamp := time.Now().Round(1 * time.Second)
    29  	testUser := "test"
    30  	testPass := "test"
    31  
    32  	dir := fs.NewDir(t, "checksums", fs.WithFile(itemName, "world!"))
    33  	defer dir.Remove()
    34  
    35  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    36  		if r.Method != http.MethodPost {
    37  			w.WriteHeader(http.StatusMethodNotAllowed)
    38  			return
    39  		}
    40  
    41  		if r.URL.Path != "/v1/storage/upload" {
    42  			w.WriteHeader(404)
    43  			_, _ = w.Write([]byte("incorrect path"))
    44  			return
    45  		}
    46  
    47  		user, pass, _ := r.BasicAuth()
    48  		if user != testUser || pass != testPass {
    49  			w.WriteHeader(http.StatusForbidden)
    50  			_, _ = w.Write([]byte(http.StatusText(http.StatusForbidden)))
    51  			return
    52  		}
    53  
    54  		if err := r.ParseForm(); err != nil {
    55  			w.WriteHeader(400)
    56  			_, _ = fmt.Fprintf(w, "failed to parse form post: %v", err)
    57  			return
    58  		}
    59  
    60  		reader, err := r.MultipartReader()
    61  		if err != nil {
    62  			w.WriteHeader(400)
    63  			_, _ = fmt.Fprintf(w, "failed to read multipart form: %v", err)
    64  			return
    65  		}
    66  
    67  		p, err := reader.NextPart()
    68  		if err == io.EOF {
    69  			w.WriteHeader(400)
    70  			_, _ = fmt.Fprintf(w, "unexpected early end of multipart data: %v", err)
    71  			return
    72  		}
    73  		if err != nil {
    74  			w.WriteHeader(400)
    75  			_, _ = fmt.Fprintf(w, "failed to retrieve next part in multipart: %v", err)
    76  			return
    77  		}
    78  
    79  		size, _ := io.Copy(io.Discard, p)
    80  
    81  		w.WriteHeader(201)
    82  		_ = json.NewEncoder(w).Encode(UploadResponse{Item{
    83  			ID:              itemID,
    84  			Name:            p.FileName(),
    85  			UploadTimestamp: uploadTimestamp.Unix(),
    86  			Size:            int(size),
    87  		}})
    88  	}))
    89  	defer server.Close()
    90  
    91  	type fields struct {
    92  		HTTPClient *retryablehttp.Client
    93  		URL        string
    94  		Username   string
    95  		AccessKey  string
    96  	}
    97  	type args struct {
    98  		filename string
    99  	}
   100  	tests := []struct {
   101  		name    string
   102  		fields  fields
   103  		args    args
   104  		want    storage.Item
   105  		wantErr bool
   106  	}{
   107  		{
   108  			name: "successfully upload file",
   109  			fields: fields{
   110  				HTTPClient: NewRetryableClient(10 * time.Second),
   111  				URL:        server.URL,
   112  				Username:   testUser,
   113  				AccessKey:  testPass,
   114  			},
   115  			args: args{dir.Join("hello.txt")},
   116  			want: storage.Item{
   117  				ID:       itemID,
   118  				Name:     "hello.txt",
   119  				Uploaded: uploadTimestamp,
   120  				Size:     6,
   121  			},
   122  			wantErr: false,
   123  		},
   124  		{
   125  			name: "wrong credentials",
   126  			fields: fields{
   127  				HTTPClient: NewRetryableClient(10 * time.Second),
   128  				URL:        server.URL,
   129  				Username:   testUser + "1",
   130  				AccessKey:  testPass + "1",
   131  			},
   132  			args:    args{dir.Join("hello.txt")},
   133  			want:    storage.Item{},
   134  			wantErr: true,
   135  		},
   136  	}
   137  	for _, tt := range tests {
   138  		t.Run(tt.name, func(t *testing.T) {
   139  			s := &AppStore{
   140  				HTTPClient: tt.fields.HTTPClient,
   141  				URL:        tt.fields.URL,
   142  				Username:   tt.fields.Username,
   143  				AccessKey:  tt.fields.AccessKey,
   144  			}
   145  
   146  			f, err := os.Open(tt.args.filename)
   147  			if err != nil {
   148  				t.Error(err)
   149  			}
   150  			defer func(f *os.File) {
   151  				_ = f.Close()
   152  			}(f)
   153  
   154  			got, err := s.UploadStream(tt.args.filename, "", f)
   155  			if (err != nil) != tt.wantErr {
   156  				t.Errorf("UploadStream() error = %v, wantErr %v", err, tt.wantErr)
   157  				return
   158  			}
   159  			if !reflect.DeepEqual(got, tt.want) {
   160  				t.Errorf("UploadStream() got = %v, want %v", got, tt.want)
   161  			}
   162  		})
   163  	}
   164  }
   165  
   166  func TestAppStore_List(t *testing.T) {
   167  	testUser := "test"
   168  	testPass := "test"
   169  
   170  	// Items that are known to the mock server.
   171  	items := []Item{
   172  		{
   173  			ID:              uuid.NewRandom().String(),
   174  			Name:            "hello.app",
   175  			UploadTimestamp: time.Now().Add(-1 * time.Hour).Unix(),
   176  		},
   177  		{
   178  			ID:              uuid.NewRandom().String(),
   179  			Name:            "world.app",
   180  			UploadTimestamp: time.Now().Add(-1 * time.Hour).Unix(),
   181  		},
   182  	}
   183  
   184  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   185  		if r.Method != http.MethodGet {
   186  			w.WriteHeader(http.StatusMethodNotAllowed)
   187  			return
   188  		}
   189  
   190  		if r.URL.Path != "/v1/storage/files" {
   191  			w.WriteHeader(404)
   192  			_, _ = w.Write([]byte("incorrect path"))
   193  			return
   194  		}
   195  
   196  		user, pass, _ := r.BasicAuth()
   197  		if user != testUser || pass != testPass {
   198  			w.WriteHeader(http.StatusForbidden)
   199  			_, _ = w.Write([]byte(http.StatusText(http.StatusForbidden)))
   200  			return
   201  		}
   202  
   203  		var filteredItems []Item
   204  		filtered := false
   205  
   206  		nameQuery := r.URL.Query().Get("name")
   207  		if r.URL.Query().Get("name") != "" {
   208  			filtered = true
   209  			for _, v := range items {
   210  				if v.Name == nameQuery {
   211  					filteredItems = append(filteredItems, v)
   212  				}
   213  			}
   214  		}
   215  
   216  		// Return all items if no filter was applied.
   217  		if !filtered {
   218  			filteredItems = items
   219  		}
   220  
   221  		w.WriteHeader(200)
   222  		_ = json.NewEncoder(w).Encode(ListResponse{Items: filteredItems})
   223  	}))
   224  	defer server.Close()
   225  
   226  	type fields struct {
   227  		HTTPClient *retryablehttp.Client
   228  		URL        string
   229  		Username   string
   230  		AccessKey  string
   231  	}
   232  	type args struct {
   233  		opts storage.ListOptions
   234  	}
   235  	tests := []struct {
   236  		name    string
   237  		fields  fields
   238  		args    args
   239  		want    storage.List
   240  		wantErr bool
   241  	}{
   242  		{
   243  			name: "query all",
   244  			fields: fields{
   245  				HTTPClient: NewRetryableClient(10 * time.Second),
   246  				URL:        server.URL,
   247  				Username:   testUser,
   248  				AccessKey:  testPass,
   249  			},
   250  			args: args{},
   251  			want: storage.List{
   252  				Items: []storage.Item{
   253  					{
   254  						ID:       items[0].ID,
   255  						Name:     items[0].Name,
   256  						Size:     items[0].Size,
   257  						Uploaded: time.Unix(items[0].UploadTimestamp, 0),
   258  					},
   259  					{
   260  						ID:       items[1].ID,
   261  						Name:     items[1].Name,
   262  						Size:     items[1].Size,
   263  						Uploaded: time.Unix(items[1].UploadTimestamp, 0),
   264  					},
   265  				},
   266  			},
   267  			wantErr: false,
   268  		},
   269  		{
   270  			name: "query subset",
   271  			fields: fields{
   272  				HTTPClient: NewRetryableClient(10 * time.Second),
   273  				URL:        server.URL,
   274  				Username:   testUser,
   275  				AccessKey:  testPass,
   276  			},
   277  			args: args{
   278  				opts: storage.ListOptions{Name: items[0].Name},
   279  			},
   280  			want: storage.List{
   281  				Items: []storage.Item{
   282  					{
   283  						ID:       items[0].ID,
   284  						Name:     items[0].Name,
   285  						Size:     items[0].Size,
   286  						Uploaded: time.Unix(items[0].UploadTimestamp, 0),
   287  					},
   288  				},
   289  			},
   290  			wantErr: false,
   291  		},
   292  		{
   293  			name: "wrong credentials",
   294  			fields: fields{
   295  				HTTPClient: NewRetryableClient(10 * time.Second),
   296  				URL:        server.URL,
   297  				Username:   testUser + "1",
   298  				AccessKey:  testPass + "1",
   299  			},
   300  			args:    args{},
   301  			want:    storage.List{},
   302  			wantErr: true,
   303  		},
   304  	}
   305  	for _, tt := range tests {
   306  		t.Run(tt.name, func(t *testing.T) {
   307  			s := &AppStore{
   308  				HTTPClient: tt.fields.HTTPClient,
   309  				URL:        tt.fields.URL,
   310  				Username:   tt.fields.Username,
   311  				AccessKey:  tt.fields.AccessKey,
   312  			}
   313  			got, err := s.List(tt.args.opts)
   314  			if (err != nil) != tt.wantErr {
   315  				t.Errorf("List() error = %v, wantErr %v", err, tt.wantErr)
   316  				return
   317  			}
   318  			if !reflect.DeepEqual(got, tt.want) {
   319  				t.Errorf("List() got = %v, want %v", got, tt.want)
   320  			}
   321  		})
   322  	}
   323  }
   324  
   325  func TestAppStore_Delete(t *testing.T) {
   326  	testUser := "test"
   327  	testPass := "test"
   328  
   329  	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   330  		if r.Method != http.MethodDelete {
   331  			w.WriteHeader(http.StatusMethodNotAllowed)
   332  			return
   333  		}
   334  
   335  		if !strings.HasPrefix(r.URL.Path, "/v1/storage/files/") {
   336  			w.WriteHeader(http.StatusNotImplemented)
   337  			_, _ = w.Write([]byte("incorrect path"))
   338  			return
   339  		}
   340  		println(path.Base(r.URL.Path))
   341  		if path.Base(r.URL.Path) == "" {
   342  			w.WriteHeader(http.StatusBadRequest)
   343  			_, _ = w.Write([]byte("missing file id"))
   344  			return
   345  		}
   346  
   347  		user, pass, _ := r.BasicAuth()
   348  		if user != testUser || pass != testPass {
   349  			w.WriteHeader(http.StatusForbidden)
   350  			_, _ = w.Write([]byte(http.StatusText(http.StatusForbidden)))
   351  			return
   352  		}
   353  
   354  		w.WriteHeader(200)
   355  		// The real server's response body contains a JSON that describes the
   356  		// deleted item. We don't need that for this test.
   357  	}))
   358  	defer server.Close()
   359  
   360  	type fields struct {
   361  		HTTPClient *retryablehttp.Client
   362  		URL        string
   363  		Username   string
   364  		AccessKey  string
   365  	}
   366  	type args struct {
   367  		id string
   368  	}
   369  	tests := []struct {
   370  		name    string
   371  		fields  fields
   372  		args    args
   373  		wantErr assert.ErrorAssertionFunc
   374  	}{
   375  		{
   376  			name: "delete item successfully",
   377  			fields: fields{
   378  				HTTPClient: NewRetryableClient(10 * time.Second),
   379  				URL:        server.URL,
   380  				Username:   testUser,
   381  				AccessKey:  testPass,
   382  			},
   383  			args:    args{id: uuid.NewRandom().String()},
   384  			wantErr: assert.NoError,
   385  		},
   386  		{
   387  			name: "fail on wrong credentials",
   388  			fields: fields{
   389  				HTTPClient: NewRetryableClient(10 * time.Second),
   390  				URL:        server.URL,
   391  				Username:   testUser + "1",
   392  				AccessKey:  testPass + "1",
   393  			},
   394  			args:    args{id: uuid.NewRandom().String()},
   395  			wantErr: assert.Error,
   396  		},
   397  		{
   398  			name: "fail when no ID was specified",
   399  			fields: fields{
   400  				HTTPClient: NewRetryableClient(10 * time.Second),
   401  				URL:        server.URL,
   402  				Username:   testUser,
   403  				AccessKey:  testPass,
   404  			},
   405  			args:    args{id: ""},
   406  			wantErr: assert.Error,
   407  		},
   408  	}
   409  	for _, tt := range tests {
   410  		t.Run(tt.name, func(t *testing.T) {
   411  			s := &AppStore{
   412  				HTTPClient: tt.fields.HTTPClient,
   413  				URL:        tt.fields.URL,
   414  				Username:   tt.fields.Username,
   415  				AccessKey:  tt.fields.AccessKey,
   416  			}
   417  			tt.wantErr(t, s.Delete(tt.args.id), fmt.Sprintf("Delete(%v)", tt.args.id))
   418  		})
   419  	}
   420  }