package server import ( "crypto/rand" "fmt" "io" "net/http" "net/http/httptest" "net/url" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" mocks "gitlab.com/unboundsoftware/apex-mocks" ) func TestServer(t *testing.T) { type args struct { store func(t *testing.T) StoreFunc url string random io.Reader } type req struct { method string path string body string } tests := []struct { name string args args req req wantStatus int wantResp string wantHeaders map[string]string }{ { name: "unhandled path", args: args{}, req: req{ method: http.MethodPut, path: "/missing", body: "xyz", }, wantStatus: 404, wantResp: "404 page not found\n", }, { name: "health", args: args{}, req: req{ method: http.MethodPut, path: "/health", body: "", }, wantStatus: 200, wantResp: "OK", }, { name: "GET on /put", args: args{}, req: req{ method: http.MethodGet, path: "/put/some/file", body: "abc", }, wantStatus: 400, wantResp: "This endpoint requires PUT", }, { name: "OPTIONS on /put", args: args{}, req: req{ method: http.MethodOptions, path: "/put/some/file", body: "abc", }, wantStatus: 204, wantResp: "", wantHeaders: map[string]string{ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "PUT,OPTIONS", "Access-Control-Allow-Headers": "Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With", "Access-Control-Max-Age": "1728000", }, }, { name: "PUT on /put - error", args: args{ store: func(t *testing.T) StoreFunc { return func(path string, content io.Reader) error { assert.Equal(t, "some/file", path) temp, err := io.ReadAll(content) assert.NoError(t, err) assert.Equal(t, "abc", string(temp)) return fmt.Errorf("error") } }, }, req: req{ method: http.MethodPut, path: "/put/some/file", body: "abc", }, wantStatus: 500, wantResp: "error storing object in bucket", }, { name: "PUT on /put - success", args: args{ store: func(t *testing.T) StoreFunc { return func(path string, content io.Reader) error { return nil } }, url: "https://example.org", }, req: req{ method: http.MethodPut, path: "/put/some/file", body: "abc", }, wantStatus: 200, wantResp: "success", wantHeaders: map[string]string{ "Access-Control-Expose-Headers": "X-File-URL", "X-File-URL": "https://example.org/some/file", }, }, { name: "GET on /upload", args: args{}, req: req{ method: http.MethodGet, path: "/upload", body: "abc", }, wantStatus: 400, wantResp: "This endpoint requires PUT", }, { name: "OPTIONS on /upload", args: args{}, req: req{ method: http.MethodOptions, path: "/upload", body: "abc", }, wantStatus: 204, wantResp: "", wantHeaders: map[string]string{ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "PUT,OPTIONS", "Access-Control-Allow-Headers": "Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With", "Access-Control-Max-Age": "1728000", }, }, { name: "PUT on /upload - error with random", args: args{ random: strings.NewReader("too short"), }, req: req{ method: http.MethodPut, path: "/upload", body: "abc", }, wantStatus: 500, wantResp: "An error occurred", }, { name: "PUT on /upload - error", args: args{ store: func(t *testing.T) StoreFunc { return func(path string, content io.Reader) error { assert.Equal(t, "20211125/588b41ebf261820104615b83201c729bd16016d6e43649b28b0ef77d54ca5aaf8da0ce74ae3f20a4", path) temp, err := io.ReadAll(content) assert.NoError(t, err) assert.Equal(t, "abc", string(temp)) return fmt.Errorf("error") } }, }, req: req{ method: http.MethodPut, path: "/upload", body: "abc", }, wantStatus: 500, wantResp: "error storing object in bucket", }, { name: "PUT on /upload - success", args: args{ store: func(t *testing.T) StoreFunc { return func(path string, content io.Reader) error { return nil } }, url: "https://example.org", }, req: req{ method: http.MethodPut, path: "/upload", body: "abc", }, wantStatus: 200, wantResp: "success", wantHeaders: map[string]string{ "Access-Control-Expose-Headers": "X-File-URL", "X-File-URL": "https://example.org/20211125/588b41ebf261820104615b83201c729bd16016d6e43649b28b0ef77d54ca5aaf8da0ce74ae3f20a4", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if tt.args.random != nil { rand.Reader = tt.args.random } else { rand.Reader = strings.NewReader("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") } logger := mocks.New() var store StoreFunc if tt.args.store != nil { store = tt.args.store(t) } server := New(store, tt.args.url, logger) server.(*Server).now = func() time.Time { return time.Date(2021, 11, 25, 7, 43, 12, 0, time.UTC) } recorder := httptest.NewRecorder() u, err := url.Parse(fmt.Sprintf("https://example.org%s", tt.req.path)) require.NoError(t, err) server.ServeHTTP(recorder, &http.Request{ Method: tt.req.method, URL: u, Body: io.NopCloser(strings.NewReader(tt.req.body)), }) assert.Equal(t, tt.wantStatus, recorder.Code, "StatusCode") assert.Equal(t, tt.wantResp, recorder.Body.String(), "Body") for k, v := range tt.wantHeaders { assert.Equal(t, v, recorder.Header().Get(k), "Header") } }) } } type StoreFunc func(path string, content io.Reader) error func (f StoreFunc) Store(path string, content io.Reader) error { return f(path, content) }