package handler_test import ( "context" "errors" "net/http" "net/http/httptest" "strings" "testing" "lyra/internal/handler" "lyra/internal/model" "github.com/gin-gonic/gin" ) type fakeCategoryModel struct { categories []*model.Category err error calls int gotCtx context.Context } func (f *fakeCategoryModel) FindAll(ctx context.Context) ([]*model.Category, error) { f.calls++ f.gotCtx = ctx return f.categories, f.err } func serveHello(t *testing.T, fake *fakeCategoryModel, req *http.Request) *httptest.ResponseRecorder { t.Helper() gin.SetMode(gin.TestMode) engine := gin.New() engine.GET("/test", handler.NewTestHandler(fake).Hello()) recorder := httptest.NewRecorder() engine.ServeHTTP(recorder, req) return recorder } func newHelloRequest() *http.Request { return httptest.NewRequest(http.MethodGet, "/test", nil) } func TestHelloMapsCategoriesToResponse(t *testing.T) { fake := &fakeCategoryModel{categories: []*model.Category{ {ID: 1, Slug: "go-basics", Title: "Go 基础", Sort: 1}, {ID: 2, Slug: "sql-practice", Title: "SQL 练习", Sort: 2}, }} recorder := serveHello(t, fake, newHelloRequest()) if fake.calls != 1 { t.Fatalf("模型调用次数 = %d, want 1", fake.calls) } if recorder.Code != http.StatusOK { t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK) } want := `{"success":true,"data":[{"id":1,"slug":"go-basics","title":"Go 基础","sort":1},{"id":2,"slug":"sql-practice","title":"SQL 练习","sort":2}]}` if got := recorder.Body.String(); got != want { t.Fatalf("body = %q, want %q", got, want) } } func TestHelloReturnsEmptyArrayForNoCategories(t *testing.T) { tests := []struct { name string categories []*model.Category }{ {name: "空切片", categories: []*model.Category{}}, {name: "nil结果", categories: nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { fake := &fakeCategoryModel{categories: tt.categories} recorder := serveHello(t, fake, newHelloRequest()) if fake.calls != 1 { t.Fatalf("模型调用次数 = %d, want 1", fake.calls) } if recorder.Code != http.StatusOK { t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK) } want := `{"success":true,"data":[]}` if got := recorder.Body.String(); got != want { t.Fatalf("body = %q, want %q", got, want) } }) } } func TestHelloReturnsInternalErrorOnModelFailure(t *testing.T) { modelErr := errors.New("数据库连接被拒绝: 10.0.0.8:5432") fake := &fakeCategoryModel{err: modelErr} recorder := serveHello(t, fake, newHelloRequest()) if fake.calls != 1 { t.Fatalf("模型调用次数 = %d, want 1", fake.calls) } if recorder.Code != http.StatusInternalServerError { t.Fatalf("status = %d, want %d", recorder.Code, http.StatusInternalServerError) } want := `{"success":false,"error":{"code":10002,"message":"获取分类列表失败"}}` if got := recorder.Body.String(); got != want { t.Fatalf("body = %q, want %q", got, want) } if strings.Contains(recorder.Body.String(), modelErr.Error()) { t.Fatalf("响应泄漏了内部错误信息: %q", recorder.Body.String()) } } type contextKey struct{} func TestHelloPassesRequestContextToModel(t *testing.T) { fake := &fakeCategoryModel{categories: []*model.Category{}} reqCtx := context.WithValue(context.Background(), contextKey{}, "req-42") req := newHelloRequest().WithContext(reqCtx) serveHello(t, fake, req) if fake.gotCtx != reqCtx { t.Fatalf("模型收到非请求 context: %v", fake.gotCtx) } if got, _ := fake.gotCtx.Value(contextKey{}).(string); got != "req-42" { t.Fatalf("context 值 = %q, want %q", got, "req-42") } }