package api_test import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "net/http/httptest" "strings" "testing" "time" "git.t-juice.club/torjus/gpaste" "git.t-juice.club/torjus/gpaste/api" "git.t-juice.club/torjus/gpaste/files" "git.t-juice.club/torjus/gpaste/users" "github.com/google/go-cmp/cmp" "github.com/google/uuid" ) func TestHandlers(t *testing.T) { //cfg := &gpaste.ServerConfig{ // SigningSecret: "abc123", // Store: &gpaste.ServerStoreConfig{ // Type: "memory", // }, // URL: "http://localhost:8080", //} //hs := api.NewHTTPServer(cfg) t.Run("index", func(t *testing.T) { hs := newServer() rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) hs.Handler.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Returned unexpected status") } expectedBody := "index" if body := rr.Body.String(); body != expectedBody { t.Errorf("Body does not match expected. Got %s want %s", body, expectedBody) } }) t.Run("api", func(t *testing.T) { t.Run("file", func(t *testing.T) { // POST /api/file t.Run("POST", func(t *testing.T) { hs := newServer() rr := httptest.NewRecorder() buf := &bytes.Buffer{} mw := multipart.NewWriter(buf) fw, err := mw.CreateFormFile("test", "test.txt") if err != nil { t.Fatalf("Unable to create form file: %s", err) } expectedData := "Test OMEGALUL PLS." if _, err := io.WriteString(fw, expectedData); err != nil { t.Fatalf("Unable to write body to buffer: %s", err) } mw.Close() req := httptest.NewRequest(http.MethodPost, "/api/file?max_views=99", buf) req.Header.Add("Content-Type", mw.FormDataContentType()) hs.Handler.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusAccepted { t.Errorf("Returned unexpected status. Got %d want %d", status, http.StatusAccepted) } var expectedResp []struct { Message string `json:"message"` ID string `json:"id"` URL string `json:"url"` } decoder := json.NewDecoder(rr.Result().Body) if err := decoder.Decode(&expectedResp); err != nil { t.Fatalf("error decoding response: %s", err) } if l := len(expectedResp); l != 1 { t.Errorf("Response has wrong length. Got %d want %d", l, 1) } uploadID := expectedResp[0].ID if uploadID == "" { t.Errorf("Response has empty id") } retrieved, err := hs.Files.Get(uploadID) if err != nil { t.Errorf("Error retrieving file: %s", err) } defer retrieved.Body.Close() retBuf := new(bytes.Buffer) io.Copy(retBuf, retrieved.Body) if diff := cmp.Diff(retBuf.String(), expectedData); diff != "" { t.Errorf("Retrieved file mismatch: %s", diff) } if retrieved.MaxViews != 99 { t.Errorf("Uploaded file has wrong max_views: %d", retrieved.MaxViews) } }) // GET /api/file/id t.Run("GET", func(t *testing.T) { hs := newServer() fileData := "abc123456" sr := io.NopCloser(strings.NewReader(fileData)) file := &files.File{ ID: uuid.NewString(), OriginalFilename: "test-file.txt", MaxViews: 99, ExpiresOn: time.Now().Add(90 * time.Second), Body: sr, } hs.Files.Store(file) rr := httptest.NewRecorder() url := fmt.Sprintf("/api/file/%s", file.ID) req := httptest.NewRequest(http.MethodGet, url, nil) hs.Handler.ServeHTTP(rr, req) if status := rr.Code; status != http.StatusOK { t.Errorf("Returned unexpected status. Got %d want %d", status, http.StatusAccepted) t.Logf(url) } if diff := cmp.Diff(rr.Body.String(), fileData); diff != "" { t.Errorf("Returned body does not match expected: %s", diff) } }) // DELETE /api/file/id t.Run("DELETE", func(t *testing.T) { hs := newServer() fileBody := io.NopCloser(strings.NewReader("roflcopter")) file := &files.File{ ID: uuid.NewString(), OriginalFilename: "testpls.txt", MaxViews: 9, ExpiresOn: time.Now().Add(10 * time.Hour), Body: fileBody, } if err := hs.Files.Store(file); err != nil { t.Fatalf("Error storing file: %s", err) } rr := httptest.NewRecorder() url := fmt.Sprintf("/api/file/%s", file.ID) req := httptest.NewRequest(http.MethodDelete, url, nil) hs.Handler.ServeHTTP(rr, req) if rr.Result().StatusCode != http.StatusOK { t.Fatalf("Delete returned wrong status: %s", rr.Result().Status) } if _, err := hs.Files.Get(file.ID); err == nil { t.Errorf("Getting after delete returned no error") } }) }) // /api/user t.Run("user", func(t *testing.T) { t.Run("POST", func(t *testing.T) { hs := newServer() adminPw := "admin" admin := &users.User{ Username: "admin", Role: users.RoleAdmin, } _ = admin.SetPassword(adminPw) _ = hs.Users.Store(admin) token, err := hs.Auth.Login(admin.Username, adminPw) if err != nil { t.Fatalf("error getting admin token: %s", err) } requestData := &api.RequestAPIUserCreate{ Username: "test", Password: "test", } body := new(bytes.Buffer) encoder := json.NewEncoder(body) if err := encoder.Encode(requestData); err != nil { t.Fatalf("Error encoding data: %s", err) } rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/api/user", body) req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token)) hs.Handler.ServeHTTP(rr, req) if rr.Result().StatusCode != http.StatusAccepted { t.Fatalf("Create returned wrong status: %s", rr.Result().Status) } user, err := hs.Users.Get(requestData.Username) if err != nil { t.Fatalf("Unable to get user after create: %s", err) } expectedUser := &users.User{ Username: requestData.Username, Role: users.RoleUser, } ignorePW := cmp.FilterPath(func(p cmp.Path) bool { return p.String() == "HashedPassword" }, cmp.Ignore()) if diff := cmp.Diff(user, expectedUser, ignorePW); diff != "" { t.Errorf("User does not match expected: %s", diff) } }) }) // /api/login t.Run("Login", func(t *testing.T) { hs := newServer() // TODO: Add test username := "admin" password := "admin" user := &users.User{Username: username} if err := user.SetPassword(password); err != nil { t.Fatalf("Error setting user password: %s", err) } if err := hs.Users.Store(user); err != nil { t.Fatalf("Error storing user: %s", err) } requestData := struct { Username string `json:"username"` Password string `json:"password"` }{ Username: username, Password: password, } body := new(bytes.Buffer) encoder := json.NewEncoder(body) if err := encoder.Encode(&requestData); err != nil { t.Fatalf("Error encoding request body: %s", err) } rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodPost, "/api/login", body) hs.Handler.ServeHTTP(rr, req) responseData := struct { Token string `json:"token"` }{} decoder := json.NewDecoder(rr.Body) if err := decoder.Decode(&responseData); err != nil { t.Fatalf("Error decoding response: %s", err) } if _, err := hs.Auth.ValidateToken(responseData.Token); err != nil { t.Fatalf("Unable to validate received token: %s", err) } }) }) } func newServer() *api.HTTPServer { cfg := &gpaste.ServerConfig{ SigningSecret: "abc123", Store: &gpaste.ServerStoreConfig{ Type: "memory", }, URL: "http://localhost:8080", } return api.NewHTTPServer(cfg) }