diff options
Diffstat (limited to 'internal/api/drops_test.go')
| -rw-r--r-- | internal/api/drops_test.go | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/internal/api/drops_test.go b/internal/api/drops_test.go new file mode 100644 index 0000000..ab67489 --- /dev/null +++ b/internal/api/drops_test.go @@ -0,0 +1,159 @@ +package api + +import ( + "bytes" + "encoding/json" + "mime/multipart" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" +) + +func testServerWithDrops(t *testing.T) (*Server, string) { + t.Helper() + srv, _ := testServer(t) + dropsDir := t.TempDir() + srv.SetDropsDir(dropsDir) + return srv, dropsDir +} + +func TestHandleListDrops_Empty(t *testing.T) { + srv, _ := testServerWithDrops(t) + + req := httptest.NewRequest("GET", "/api/drops", nil) + rec := httptest.NewRecorder() + srv.mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("want 200, got %d", rec.Code) + } + + var files []map[string]interface{} + if err := json.NewDecoder(rec.Body).Decode(&files); err != nil { + t.Fatalf("decode response: %v", err) + } + if len(files) != 0 { + t.Errorf("want empty list, got %d entries", len(files)) + } +} + +func TestHandleListDrops_WithFile(t *testing.T) { + srv, dropsDir := testServerWithDrops(t) + + // Create a file in the drops dir. + if err := os.WriteFile(filepath.Join(dropsDir, "hello.txt"), []byte("world"), 0600); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/api/drops", nil) + rec := httptest.NewRecorder() + srv.mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("want 200, got %d: %s", rec.Code, rec.Body.String()) + } + + var files []map[string]interface{} + if err := json.NewDecoder(rec.Body).Decode(&files); err != nil { + t.Fatalf("decode response: %v", err) + } + if len(files) != 1 { + t.Fatalf("want 1 file, got %d", len(files)) + } + if files[0]["name"] != "hello.txt" { + t.Errorf("name: want %q, got %v", "hello.txt", files[0]["name"]) + } +} + +func TestHandlePostDrop_Multipart(t *testing.T) { + srv, dropsDir := testServerWithDrops(t) + + var buf bytes.Buffer + w := multipart.NewWriter(&buf) + fw, err := w.CreateFormFile("file", "test.txt") + if err != nil { + t.Fatal(err) + } + fw.Write([]byte("hello world")) //nolint:errcheck + w.Close() + + req := httptest.NewRequest("POST", "/api/drops", &buf) + req.Header.Set("Content-Type", w.FormDataContentType()) + rec := httptest.NewRecorder() + srv.mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusCreated { + t.Fatalf("want 201, got %d: %s", rec.Code, rec.Body.String()) + } + + var resp map[string]interface{} + if err := json.NewDecoder(rec.Body).Decode(&resp); err != nil { + t.Fatalf("decode response: %v", err) + } + if resp["name"] != "test.txt" { + t.Errorf("name: want %q, got %v", "test.txt", resp["name"]) + } + + // Verify file was created on disk. + content, err := os.ReadFile(filepath.Join(dropsDir, "test.txt")) + if err != nil { + t.Fatalf("reading uploaded file: %v", err) + } + if string(content) != "hello world" { + t.Errorf("content: want %q, got %q", "hello world", content) + } +} + +func TestHandleGetDrop_Download(t *testing.T) { + srv, dropsDir := testServerWithDrops(t) + + if err := os.WriteFile(filepath.Join(dropsDir, "download.txt"), []byte("download me"), 0600); err != nil { + t.Fatal(err) + } + + req := httptest.NewRequest("GET", "/api/drops/download.txt", nil) + rec := httptest.NewRecorder() + srv.mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Fatalf("want 200, got %d", rec.Code) + } + + cd := rec.Header().Get("Content-Disposition") + if !strings.Contains(cd, "attachment") { + t.Errorf("want Content-Disposition: attachment, got %q", cd) + } + if rec.Body.String() != "download me" { + t.Errorf("body: want %q, got %q", "download me", rec.Body.String()) + } +} + +func TestHandleGetDrop_PathTraversal(t *testing.T) { + srv, _ := testServerWithDrops(t) + + // Attempt path traversal — should be rejected. + req := httptest.NewRequest("GET", "/api/drops/..%2Fetc%2Fpasswd", nil) + rec := httptest.NewRecorder() + srv.mux.ServeHTTP(rec, req) + + // The Go net/http router will handle %2F-encoded slashes as literal characters, + // so the filename becomes "../etc/passwd". Our handler should reject it. + if rec.Code == http.StatusOK { + t.Error("expected non-200 for path traversal attempt") + } +} + +func TestHandleGetDrop_NotFound(t *testing.T) { + srv, _ := testServerWithDrops(t) + + req := httptest.NewRequest("GET", "/api/drops/notexist.txt", nil) + rec := httptest.NewRecorder() + srv.mux.ServeHTTP(rec, req) + + if rec.Code != http.StatusNotFound { + t.Fatalf("want 404, got %d", rec.Code) + } +} |
