Ver Fonte

feat: add Gin handlers with integration tests

b há 1 mês atrás
pai
commit
31c4b86c50
2 ficheiros alterados com 283 adições e 0 exclusões
  1. 90 0
      handler/order.go
  2. 193 0
      handler/order_test.go

+ 90 - 0
handler/order.go

@@ -0,0 +1,90 @@
+package handler
+
+import (
+	"net/http"
+
+	"job-cheng-xing/model"
+	"job-cheng-xing/service"
+
+	"github.com/gin-gonic/gin"
+)
+
+type OrderHandler struct {
+	svc *service.OrderService
+}
+
+func NewOrderHandler(svc *service.OrderService) *OrderHandler {
+	return &OrderHandler{svc: svc}
+}
+
+func (h *OrderHandler) CreateOrder(c *gin.Context) {
+	var req model.CreateOrderRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{
+			"error":   "validation",
+			"message": "请求参数无效:" + err.Error(),
+		})
+		return
+	}
+
+	order, err := h.svc.CreateOrder(req)
+	if err != nil {
+		handleError(c, err)
+		return
+	}
+
+	c.JSON(http.StatusCreated, order)
+}
+
+func (h *OrderHandler) AcceptOrder(c *gin.Context) {
+	id := c.Param("id")
+
+	var req model.AcceptOrderRequest
+	if err := c.ShouldBindJSON(&req); err != nil {
+		c.JSON(http.StatusBadRequest, gin.H{
+			"error":   "validation",
+			"message": "请求参数无效:" + err.Error(),
+		})
+		return
+	}
+
+	order, err := h.svc.AcceptOrder(id, req.ProviderID)
+	if err != nil {
+		handleError(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "接单成功",
+		"order":   order,
+	})
+}
+
+func (h *OrderHandler) CancelOrder(c *gin.Context) {
+	id := c.Param("id")
+
+	order, err := h.svc.CancelOrder(id)
+	if err != nil {
+		handleError(c, err)
+		return
+	}
+
+	c.JSON(http.StatusOK, gin.H{
+		"message": "取消成功",
+		"order":   order,
+	})
+}
+
+func handleError(c *gin.Context, err error) {
+	if appErr, ok := err.(*model.AppError); ok {
+		c.JSON(appErr.HTTPStatus, gin.H{
+			"error":   appErr.Code,
+			"message": appErr.Message,
+		})
+		return
+	}
+	c.JSON(http.StatusInternalServerError, gin.H{
+		"error":   "internal",
+		"message": "服务器内部错误",
+	})
+}

+ 193 - 0
handler/order_test.go

@@ -0,0 +1,193 @@
+package handler
+
+import (
+	"bytes"
+	"encoding/json"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+	"time"
+
+	"job-cheng-xing/model"
+	"job-cheng-xing/repository"
+	"job-cheng-xing/service"
+
+	"github.com/gin-gonic/gin"
+)
+
+func setupRouter() *gin.Engine {
+	gin.SetMode(gin.TestMode)
+	repo := repository.NewMemoryRepo()
+	svc := service.NewOrderService(repo)
+	handler := NewOrderHandler(svc)
+
+	r := gin.New()
+	r.POST("/orders", handler.CreateOrder)
+	r.PUT("/orders/:id/accept", handler.AcceptOrder)
+	r.PUT("/orders/:id/cancel", handler.CancelOrder)
+	return r
+}
+
+func TestCreateOrder_Handler(t *testing.T) {
+	r := setupRouter()
+
+	body := map[string]interface{}{
+		"service_time": time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+		"duration":     120,
+		"address":      "北京朝阳区xxx路xxx号",
+	}
+	jsonBody, _ := json.Marshal(body)
+
+	req, _ := http.NewRequest("POST", "/orders", bytes.NewBuffer(jsonBody))
+	req.Header.Set("Content-Type", "application/json")
+	w := httptest.NewRecorder()
+	r.ServeHTTP(w, req)
+
+	if w.Code != http.StatusCreated {
+		t.Errorf("expected 201, got %d: %s", w.Code, w.Body.String())
+	}
+
+	var order model.Order
+	json.Unmarshal(w.Body.Bytes(), &order)
+	if order.ID == "" {
+		t.Error("expected non-empty ID")
+	}
+	if order.Status != model.StatusPending {
+		t.Errorf("expected pending, got %s", order.Status)
+	}
+}
+
+func TestAcceptOrder_Handler_Success(t *testing.T) {
+	r := setupRouter()
+
+	body := map[string]interface{}{
+		"service_time": time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+		"duration":     120,
+		"address":      "北京朝阳",
+	}
+	jsonBody, _ := json.Marshal(body)
+	req, _ := http.NewRequest("POST", "/orders", bytes.NewBuffer(jsonBody))
+	req.Header.Set("Content-Type", "application/json")
+	w := httptest.NewRecorder()
+	r.ServeHTTP(w, req)
+
+	var order model.Order
+	json.Unmarshal(w.Body.Bytes(), &order)
+
+	acceptBody := map[string]string{"provider_id": "prov_001"}
+	acceptJSON, _ := json.Marshal(acceptBody)
+	req2, _ := http.NewRequest("PUT", "/orders/"+order.ID+"/accept", bytes.NewBuffer(acceptJSON))
+	req2.Header.Set("Content-Type", "application/json")
+	w2 := httptest.NewRecorder()
+	r.ServeHTTP(w2, req2)
+
+	if w2.Code != http.StatusOK {
+		t.Errorf("expected 200, got %d: %s", w2.Code, w2.Body.String())
+	}
+}
+
+func TestAcceptOrder_Handler_DoubleAccept(t *testing.T) {
+	r := setupRouter()
+
+	body := map[string]interface{}{
+		"service_time": time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+		"duration":     120,
+		"address":      "北京朝阳",
+	}
+	jsonBody, _ := json.Marshal(body)
+	req, _ := http.NewRequest("POST", "/orders", bytes.NewBuffer(jsonBody))
+	req.Header.Set("Content-Type", "application/json")
+	w := httptest.NewRecorder()
+	r.ServeHTTP(w, req)
+	var order model.Order
+	json.Unmarshal(w.Body.Bytes(), &order)
+
+	acceptBody1 := map[string]string{"provider_id": "prov_001"}
+	acceptJSON1, _ := json.Marshal(acceptBody1)
+	req1, _ := http.NewRequest("PUT", "/orders/"+order.ID+"/accept", bytes.NewBuffer(acceptJSON1))
+	req1.Header.Set("Content-Type", "application/json")
+	w1 := httptest.NewRecorder()
+	r.ServeHTTP(w1, req1)
+
+	if w1.Code != http.StatusOK {
+		t.Fatalf("first accept should succeed, got %d", w1.Code)
+	}
+
+	acceptBody2 := map[string]string{"provider_id": "prov_002"}
+	acceptJSON2, _ := json.Marshal(acceptBody2)
+	req2, _ := http.NewRequest("PUT", "/orders/"+order.ID+"/accept", bytes.NewBuffer(acceptJSON2))
+	req2.Header.Set("Content-Type", "application/json")
+	w2 := httptest.NewRecorder()
+	r.ServeHTTP(w2, req2)
+
+	if w2.Code != http.StatusConflict {
+		t.Errorf("expected 409, got %d: %s", w2.Code, w2.Body.String())
+	}
+}
+
+func TestCancelOrder_Handler_ThenAcceptFails(t *testing.T) {
+	r := setupRouter()
+
+	body := map[string]interface{}{
+		"service_time": time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+		"duration":     120,
+		"address":      "北京朝阳",
+	}
+	jsonBody, _ := json.Marshal(body)
+	req, _ := http.NewRequest("POST", "/orders", bytes.NewBuffer(jsonBody))
+	req.Header.Set("Content-Type", "application/json")
+	w := httptest.NewRecorder()
+	r.ServeHTTP(w, req)
+	var order model.Order
+	json.Unmarshal(w.Body.Bytes(), &order)
+
+	req1, _ := http.NewRequest("PUT", "/orders/"+order.ID+"/cancel", bytes.NewBuffer([]byte("{}")))
+	req1.Header.Set("Content-Type", "application/json")
+	w1 := httptest.NewRecorder()
+	r.ServeHTTP(w1, req1)
+	if w1.Code != http.StatusOK {
+		t.Fatalf("cancel should succeed, got %d", w1.Code)
+	}
+
+	acceptBody := map[string]string{"provider_id": "prov_001"}
+	acceptJSON, _ := json.Marshal(acceptBody)
+	req2, _ := http.NewRequest("PUT", "/orders/"+order.ID+"/accept", bytes.NewBuffer(acceptJSON))
+	req2.Header.Set("Content-Type", "application/json")
+	w2 := httptest.NewRecorder()
+	r.ServeHTTP(w2, req2)
+
+	if w2.Code != http.StatusBadRequest {
+		t.Errorf("expected 400, got %d: %s", w2.Code, w2.Body.String())
+	}
+}
+
+func TestCancelOrder_Handler_DoubleCancel(t *testing.T) {
+	r := setupRouter()
+
+	body := map[string]interface{}{
+		"service_time": time.Now().Add(24 * time.Hour).Format(time.RFC3339),
+		"duration":     120,
+		"address":      "北京朝阳",
+	}
+	jsonBody, _ := json.Marshal(body)
+	req, _ := http.NewRequest("POST", "/orders", bytes.NewBuffer(jsonBody))
+	req.Header.Set("Content-Type", "application/json")
+	w := httptest.NewRecorder()
+	r.ServeHTTP(w, req)
+	var order model.Order
+	json.Unmarshal(w.Body.Bytes(), &order)
+
+	req1, _ := http.NewRequest("PUT", "/orders/"+order.ID+"/cancel", bytes.NewBuffer([]byte("{}")))
+	req1.Header.Set("Content-Type", "application/json")
+	w1 := httptest.NewRecorder()
+	r.ServeHTTP(w1, req1)
+
+	req2, _ := http.NewRequest("PUT", "/orders/"+order.ID+"/cancel", bytes.NewBuffer([]byte("{}")))
+	req2.Header.Set("Content-Type", "application/json")
+	w2 := httptest.NewRecorder()
+	r.ServeHTTP(w2, req2)
+
+	if w2.Code != http.StatusBadRequest {
+		t.Errorf("expected 400, got %d: %s", w2.Code, w2.Body.String())
+	}
+}