package note_test import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "strings" "testing" "rill/internal/model" "rill/internal/testutil" ) func decodeNote(t *testing.T, w *httptest.ResponseRecorder) model.Note { t.Helper() var note model.Note if err := json.Unmarshal(w.Body.Bytes(), ¬e); err != nil { t.Fatalf("解析响应失败: %v, body=%s", err, w.Body.String()) } return note } func TestNoteCRUD(t *testing.T) { env := testutil.Setup(t) r := env.AdminRouter() w := testutil.Call(t, r, http.MethodPost, "/api/notes", map[string]string{"title": "第一条", "content": "内容"}) if w.Code != http.StatusCreated { t.Fatalf("创建状态码 = %d, 期望 %d, body=%s", w.Code, http.StatusCreated, w.Body.String()) } created := decodeNote(t, w) if created.ID == 0 || created.Title != "第一条" || created.Content != "内容" { t.Fatalf("创建结果异常: %+v", created) } detailPath := fmt.Sprintf("/api/notes/%d", created.ID) w = testutil.Call(t, r, http.MethodGet, detailPath, nil) if w.Code != http.StatusOK { t.Fatalf("详情状态码 = %d, 期望 %d", w.Code, http.StatusOK) } if got := decodeNote(t, w); got.ID != created.ID { t.Errorf("详情 ID = %d, 期望 %d", got.ID, created.ID) } w = testutil.Call(t, r, http.MethodPut, detailPath, map[string]string{"title": "已更新", "content": "新内容"}) if w.Code != http.StatusOK { t.Fatalf("更新状态码 = %d, 期望 %d, body=%s", w.Code, http.StatusOK, w.Body.String()) } updated := decodeNote(t, w) if updated.Title != "已更新" || updated.Content != "新内容" { t.Errorf("更新结果异常: %+v", updated) } w = testutil.Call(t, r, http.MethodGet, "/api/notes?page=1&page_size=10", nil) if w.Code != http.StatusOK { t.Fatalf("列表状态码 = %d, 期望 %d", w.Code, http.StatusOK) } var list struct { Items []model.Note `json:"items"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` } if err := json.Unmarshal(w.Body.Bytes(), &list); err != nil { t.Fatalf("解析列表响应失败: %v", err) } if list.Total != 1 || len(list.Items) != 1 { t.Fatalf("列表结果异常: total=%d, items=%d", list.Total, len(list.Items)) } if list.Items[0].Title != "已更新" { t.Errorf("列表项标题 = %q, 期望 已更新", list.Items[0].Title) } w = testutil.Call(t, r, http.MethodDelete, detailPath, nil) if w.Code != http.StatusNoContent { t.Fatalf("删除状态码 = %d, 期望 %d", w.Code, http.StatusNoContent) } w = testutil.Call(t, r, http.MethodGet, detailPath, nil) if w.Code != http.StatusNotFound { t.Errorf("删除后详情状态码 = %d, 期望 %d", w.Code, http.StatusNotFound) } if !strings.Contains(w.Body.String(), "record not found") { t.Errorf("错误文案应为英文: %s", w.Body.String()) } } func TestNoteValidation(t *testing.T) { env := testutil.Setup(t) r := env.AdminRouter() w := testutil.Call(t, r, http.MethodPost, "/api/notes", map[string]string{"content": "缺少标题"}) if w.Code != http.StatusBadRequest { t.Errorf("缺少标题状态码 = %d, 期望 %d", w.Code, http.StatusBadRequest) } w = testutil.Call(t, r, http.MethodGet, "/api/notes/abc", nil) if w.Code != http.StatusBadRequest { t.Errorf("非法 id 状态码 = %d, 期望 %d", w.Code, http.StatusBadRequest) } w = testutil.Call(t, r, http.MethodGet, "/api/notes/9999", nil) if w.Code != http.StatusNotFound { t.Errorf("不存在记录状态码 = %d, 期望 %d", w.Code, http.StatusNotFound) } }