Files
safebin/internal/app/server_test.go
T
x 8b638275b8 fix: unhandled errors
Signed-off-by: skidoodle <contact@albert.lol>
2026-01-18 21:19:42 +01:00

230 lines
6.1 KiB
Go

package app
import (
"bytes"
"fmt"
"io"
"log/slog"
"mime/multipart"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
func setupTestApp(t *testing.T) (*App, string) {
storageDir := t.TempDir()
if err := os.MkdirAll(filepath.Join(storageDir, TempDirName), 0700); err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
webDir := filepath.Join(storageDir, "web")
if err := os.MkdirAll(webDir, 0700); err != nil {
t.Fatalf("Failed to create web dir: %v", err)
}
if err := os.WriteFile(filepath.Join(webDir, "layout.html"), []byte(`{{define "layout"}}{{template "content" .}}{{end}}`), 0600); err != nil {
t.Fatalf("Failed to write layout.html: %v", err)
}
if err := os.WriteFile(filepath.Join(webDir, "home.html"), []byte(`{{define "content"}}OK{{end}}`), 0600); err != nil {
t.Fatalf("Failed to write home.html: %v", err)
}
testFS := os.DirFS(webDir)
tmpl := ParseTemplates(testFS)
db, err := InitDB(storageDir)
if err != nil {
t.Fatalf("Failed to init db: %v", err)
}
t.Cleanup(func() {
if err := db.Close(); err != nil {
t.Errorf("Failed to close DB: %v", err)
}
})
app := &App{
Conf: Config{
StorageDir: storageDir,
MaxMB: 10,
},
Logger: discardLogger(),
Tmpl: tmpl,
Assets: testFS,
DB: db,
}
return app, storageDir
}
func discardLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(io.Discard, nil))
}
func TestIntegration_StandardUploadAndDownload(t *testing.T) {
app, _ := setupTestApp(t)
server := httptest.NewServer(app.Routes())
defer server.Close()
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("file", "test.txt")
if err != nil {
t.Fatalf("CreateFormFile failed: %v", err)
}
content := []byte("Hello Safebin")
if _, err := part.Write(content); err != nil {
t.Fatalf("Write part failed: %v", err)
}
if err := writer.Close(); err != nil {
t.Fatalf("Writer close failed: %v", err)
}
req, _ := http.NewRequest("POST", server.URL+"/", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Upload request failed: %v", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
t.Errorf("Failed to close response body: %v", err)
}
}()
if resp.StatusCode != http.StatusOK {
t.Fatalf("Upload failed status: %d", resp.StatusCode)
}
respBytes, _ := io.ReadAll(resp.Body)
respStr := string(respBytes)
parts := strings.Split(strings.TrimSpace(respStr), "/")
slugWithExt := parts[len(parts)-1]
downloadURL := fmt.Sprintf("%s/%s", server.URL, slugWithExt)
resp, err = http.Get(downloadURL)
if err != nil {
t.Fatalf("Download request failed: %v", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
t.Errorf("Failed to close download response body: %v", err)
}
}()
if resp.StatusCode != http.StatusOK {
t.Fatalf("Download failed status: %d", resp.StatusCode)
}
downloadedContent, _ := io.ReadAll(resp.Body)
if !bytes.Equal(content, downloadedContent) {
t.Errorf("Content mismatch. Want %s, got %s", content, downloadedContent)
}
}
func TestIntegration_ChunkedUpload(t *testing.T) {
app, _ := setupTestApp(t)
server := httptest.NewServer(app.Routes())
defer server.Close()
uploadID := "testchunkid123"
content := []byte("Chunk1Content-Chunk2Content")
chunk1 := content[:13]
chunk2 := content[13:]
uploadChunk(t, server.URL, uploadID, 0, chunk1)
uploadChunk(t, server.URL, uploadID, 1, chunk2)
finishURL := fmt.Sprintf("%s/upload/finish", server.URL)
form := map[string]string{
"upload_id": uploadID,
"total": "2",
"filename": "chunked.txt",
}
resp := postForm(t, finishURL, form)
defer func() {
if err := resp.Body.Close(); err != nil {
t.Errorf("Failed to close finish response body: %v", err)
}
}()
if resp.StatusCode != http.StatusOK {
t.Fatalf("Finish failed: %d", resp.StatusCode)
}
respBytes, _ := io.ReadAll(resp.Body)
respStr := string(respBytes)
parts := strings.Split(strings.TrimSpace(respStr), "/")
slugWithExt := parts[len(parts)-1]
downloadURL := fmt.Sprintf("%s/%s", server.URL, slugWithExt)
dlResp, err := http.Get(downloadURL)
if err != nil {
t.Fatalf("Download request failed: %v", err)
}
dlBytes, _ := io.ReadAll(dlResp.Body)
if err := dlResp.Body.Close(); err != nil {
t.Errorf("Failed to close download response body: %v", err)
}
if !bytes.Equal(content, dlBytes) {
t.Errorf("Chunked reassembly failed. Want %s, got %s", content, dlBytes)
}
}
func uploadChunk(t *testing.T, baseURL, uid string, idx int, data []byte) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
if err := writer.WriteField("upload_id", uid); err != nil {
t.Fatalf("WriteField upload_id failed: %v", err)
}
if err := writer.WriteField("index", fmt.Sprintf("%d", idx)); err != nil {
t.Fatalf("WriteField index failed: %v", err)
}
part, err := writer.CreateFormFile("chunk", "blob")
if err != nil {
t.Fatalf("CreateFormFile failed: %v", err)
}
if _, err := part.Write(data); err != nil {
t.Fatalf("Write part failed: %v", err)
}
if err := writer.Close(); err != nil {
t.Fatalf("Writer close failed: %v", err)
}
req, _ := http.NewRequest("POST", baseURL+"/upload/chunk", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != http.StatusOK {
t.Fatalf("Chunk %d upload failed: %v", idx, err)
}
if err := resp.Body.Close(); err != nil {
t.Errorf("Failed to close chunk response body: %v", err)
}
}
func postForm(t *testing.T, url string, fields map[string]string) *http.Response {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
for k, v := range fields {
if err := writer.WriteField(k, v); err != nil {
t.Fatalf("WriteField %s failed: %v", k, err)
}
}
if err := writer.Close(); err != nil {
t.Fatalf("Writer close failed: %v", err)
}
req, _ := http.NewRequest("POST", url, body)
req.Header.Set("Content-Type", writer.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatalf("Post form failed: %v", err)
}
return resp
}