package handler import ( "encoding/json" "fmt" "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/wangjia/jiu/backend/testutil" ) func TestProductHandler_CRUD(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PROD001") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 1. Create w := makeRequest(r, "POST", "/api/v1/products", token, map[string]interface{}{ "name": "Test Beer", "code": "BEER001", "unit": "瓶", }) assert.Equal(t, http.StatusCreated, w.Code) productID := extractID(w) assert.NotZero(t, productID) // 2. List w = makeRequest(r, "GET", "/api/v1/products", token, nil) assert.Equal(t, http.StatusOK, w.Code) resp := parseResponse(w) total := resp["total"].(float64) assert.Equal(t, float64(1), total) // 3. Update w = makeRequest(r, "PUT", fmt.Sprintf("/api/v1/products/%d", productID), token, map[string]interface{}{ "name": "Updated Beer", "code": "BEER001", "unit": "瓶", }) assert.Equal(t, http.StatusOK, w.Code) updatedData := parseResponse(w)["data"].(map[string]interface{}) assert.Equal(t, "Updated Beer", updatedData["name"]) // 4. List with keyword w = makeRequest(r, "GET", "/api/v1/products?keyword=Updated", token, nil) assert.Equal(t, http.StatusOK, w.Code) resp = parseResponse(w) assert.Equal(t, float64(1), resp["total"].(float64)) // 5. Delete (soft) w = makeRequest(r, "DELETE", fmt.Sprintf("/api/v1/products/%d", productID), token, nil) assert.Equal(t, http.StatusOK, w.Code) // 6. List after delete - should be 0 w = makeRequest(r, "GET", "/api/v1/products", token, nil) resp = parseResponse(w) assert.Equal(t, float64(0), resp["total"].(float64)) } func TestProductHandler_HotelIsolation(t *testing.T) { db := testutil.SetupTestDB() // 酒店 A shopA := testutil.CreateTestShop(db, "ISOL_A") userA := testutil.CreateTestUser(db, shopA.ID, "adminA", "pass", "admin") tokenA := getAuthToken(userA.ID, shopA.ID, "admin") // 酒店 B shopB := testutil.CreateTestShop(db, "ISOL_B") userB := testutil.CreateTestUser(db, shopB.ID, "adminB", "pass", "admin") tokenB := getAuthToken(userB.ID, shopB.ID, "admin") r := setupProtectedRouter(db) // 酒店 A 创建商品 w := makeRequest(r, "POST", "/api/v1/products", tokenA, map[string]interface{}{ "name": "Hotel A Beer", "unit": "瓶", }) require.Equal(t, http.StatusCreated, w.Code) productAID := extractID(w) // 酒店 B 创建商品 w = makeRequest(r, "POST", "/api/v1/products", tokenB, map[string]interface{}{ "name": "Hotel B Wine", "unit": "瓶", }) require.Equal(t, http.StatusCreated, w.Code) // 酒店 A 只能看到自己的商品 w = makeRequest(r, "GET", "/api/v1/products", tokenA, nil) respA := parseResponse(w) assert.Equal(t, float64(1), respA["total"].(float64)) dataA := respA["data"].([]interface{}) assert.Equal(t, "Hotel A Beer", dataA[0].(map[string]interface{})["name"]) // 酒店 B 只能看到自己的商品 w = makeRequest(r, "GET", "/api/v1/products", tokenB, nil) respB := parseResponse(w) assert.Equal(t, float64(1), respB["total"].(float64)) dataB := respB["data"].([]interface{}) assert.Equal(t, "Hotel B Wine", dataB[0].(map[string]interface{})["name"]) // 酒店 B 不能修改酒店 A 的商品 w = makeRequest(r, "PUT", fmt.Sprintf("/api/v1/products/%d", productAID), tokenB, map[string]interface{}{ "name": "Hacked Product", }) assert.Equal(t, http.StatusNotFound, w.Code) // 酒店 B 不能删除酒店 A 的商品 w = makeRequest(r, "DELETE", fmt.Sprintf("/api/v1/products/%d", productAID), tokenB, nil) assert.Equal(t, http.StatusNotFound, w.Code) } func TestProductHandler_NoAuth(t *testing.T) { db := testutil.SetupTestDB() r := setupProtectedRouter(db) // 无 token 访问 w := makeRequest(r, "GET", "/api/v1/products", "", nil) assert.Equal(t, http.StatusUnauthorized, w.Code) } func TestProductHandler_List_Pagination(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PROD002") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 创建 5 个商品 for i := 1; i <= 5; i++ { makeRequest(r, "POST", "/api/v1/products", token, map[string]interface{}{ "name": fmt.Sprintf("Product %d", i), "unit": "个", }) } // 每页 2 个,第 1 页 w := makeRequest(r, "GET", "/api/v1/products?page=1&page_size=2", token, nil) assert.Equal(t, http.StatusOK, w.Code) resp := parseResponse(w) data := resp["data"].([]interface{}) assert.Len(t, data, 2) assert.Equal(t, float64(5), resp["total"].(float64)) } func TestProductHandler_UpdateNotFound(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PROD003") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) w := makeRequest(r, "PUT", "/api/v1/products/99999", token, map[string]interface{}{ "name": "Nonexistent", }) assert.Equal(t, http.StatusNotFound, w.Code) } func TestProductHandler_DeleteNotFound(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PROD004") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) w := makeRequest(r, "DELETE", "/api/v1/products/99999", token, nil) assert.Equal(t, http.StatusNotFound, w.Code) } func TestProductHandler_Create_ShopIDFromToken(t *testing.T) { db := testutil.SetupTestDB() shop := testutil.CreateTestShop(db, "PROD005") user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin") token := getAuthToken(user.ID, shop.ID, "admin") r := setupProtectedRouter(db) // 尝试在请求体中传入不同的 shop_id w := makeRequest(r, "POST", "/api/v1/products", token, map[string]interface{}{ "name": "Test Product", "shop_id": 9999, // 尝试注入其他门店 ID "unit": "个", }) require.Equal(t, http.StatusCreated, w.Code) resp := parseResponse(w) data := resp["data"].(map[string]interface{}) // shop_id 应该是从 token 中获取的,而不是请求体中的 createdShopID := uint64(data["shop_id"].(float64)) assert.Equal(t, shop.ID, createdShopID) // 反序列化验证 dataBytes, _ := json.Marshal(data) _ = dataBytes }