package server import ( "bytes" "encoding/json" "io" "mime/multipart" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "task/internal/sqlite" ) func TestAttachmentUploadListDownloadPutDelete(t *testing.T) { tmp := t.TempDir() db, err := sqlite.Open(filepath.Join(tmp, "t.db"), []byte("secret")) if err != nil { t.Fatal(err) } defer db.Close() if _, err := db.CreateUser("admin", "pass", true); err != nil { t.Fatal(err) } dataDir := filepath.Join(tmp, "data") srv := New(db) srv.DataDir = dataDir handler := srv.Handler() loginBody, _ := json.Marshal(map[string]string{"username": "admin", "password": "pass"}) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login", bytes.NewReader(loginBody)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("login %d %s", rec.Code, rec.Body.String()) } cookie := rec.Result().Cookies()[0] createBody, _ := json.Marshal(map[string]string{"name": "Demo"}) req = httptest.NewRequest(http.MethodPost, "/api/v1/projects", bytes.NewReader(createBody)) req.Header.Set("Content-Type", "application/json") req.AddCookie(cookie) rec = httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("create project %d %s", rec.Code, rec.Body.String()) } var proj map[string]any _ = json.Unmarshal(rec.Body.Bytes(), &proj) pid, _ := proj["id"].(string) ticketBody, _ := json.Marshal(map[string]string{"title": "Work"}) req = httptest.NewRequest(http.MethodPost, "/api/v1/projects/"+pid+"/tickets", bytes.NewReader(ticketBody)) req.Header.Set("Content-Type", "application/json") req.AddCookie(cookie) rec = httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("create ticket %d %s", rec.Code, rec.Body.String()) } var ticket map[string]any _ = json.Unmarshal(rec.Body.Bytes(), &ticket) tid, _ := ticket["id"].(string) var body bytes.Buffer mw := multipart.NewWriter(&body) fw, err := mw.CreateFormFile("file", "notes.txt") if err != nil { t.Fatal(err) } if _, err := fw.Write([]byte("hello attach")); err != nil { t.Fatal(err) } _ = mw.Close() req = httptest.NewRequest(http.MethodPost, "/api/v1/projects/"+pid+"/tickets/"+tid+"/attachments", &body) req.Header.Set("Content-Type", mw.FormDataContentType()) req.AddCookie(cookie) rec = httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusCreated { t.Fatalf("upload %d %s", rec.Code, rec.Body.String()) } var att map[string]any _ = json.Unmarshal(rec.Body.Bytes(), &att) aid, _ := att["id"].(string) if aid == "" { t.Fatal("no attachment id") } if att["filename"] != "notes.txt" { t.Fatalf("filename %v", att["filename"]) } if int64(att["sizeBytes"].(float64)) != 12 { t.Fatalf("size %v", att["sizeBytes"]) } diskPath := filepath.Join(dataDir, "attachments", tid, aid) if _, err := os.Stat(diskPath); err != nil { t.Fatalf("disk file: %v", err) } req = httptest.NewRequest(http.MethodGet, "/api/v1/projects/"+pid+"/tickets/"+tid+"/attachments", nil) req.AddCookie(cookie) rec = httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("list %d %s", rec.Code, rec.Body.String()) } var list []map[string]any _ = json.Unmarshal(rec.Body.Bytes(), &list) if len(list) != 1 { t.Fatalf("list len %d", len(list)) } req = httptest.NewRequest(http.MethodGet, "/api/v1/projects/"+pid+"/tickets/"+tid+"/attachments/"+aid, nil) req.AddCookie(cookie) rec = httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("download %d %s", rec.Code, rec.Body.String()) } got, _ := io.ReadAll(rec.Body) if string(got) != "hello attach" { t.Fatalf("download body %q", got) } putBody := []byte("updated content!!") req = httptest.NewRequest(http.MethodPut, "/api/v1/projects/"+pid+"/tickets/"+tid+"/attachments/"+aid, bytes.NewReader(putBody)) req.Header.Set("Content-Type", "text/plain") req.AddCookie(cookie) rec = httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("put %d %s", rec.Code, rec.Body.String()) } _ = json.Unmarshal(rec.Body.Bytes(), &att) if int64(att["sizeBytes"].(float64)) != int64(len(putBody)) { t.Fatalf("put size %v", att["sizeBytes"]) } disk, err := os.ReadFile(diskPath) if err != nil { t.Fatal(err) } if !bytes.Equal(disk, putBody) { t.Fatalf("disk after put %q", disk) } req = httptest.NewRequest(http.MethodDelete, "/api/v1/projects/"+pid+"/tickets/"+tid+"/attachments/"+aid, nil) req.AddCookie(cookie) rec = httptest.NewRecorder() handler.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("delete %d %s", rec.Code, rec.Body.String()) } if _, err := os.Stat(diskPath); !os.IsNotExist(err) { t.Fatalf("file should be gone: %v", err) } req = httptest.NewRequest(http.MethodGet, "/api/v1/projects/"+pid+"/tickets/"+tid+"/attachments", nil) req.AddCookie(cookie) rec = httptest.NewRecorder() handler.ServeHTTP(rec, req) _ = json.Unmarshal(rec.Body.Bytes(), &list) if len(list) != 0 { t.Fatalf("list after delete %d", len(list)) } }