Files
jiu/backend/internal/handler/partner_test.go
T
wangjia 31ea370cea fix(backend): JWT config mapstructure tag 修复 + 模型从 hotel 重构为 shop
- 修复 JWTConfig 缺少 mapstructure tag 导致 access_expire_min 解析为 0,
  token 签发即过期,所有 API 请求返回 401
- 全部 config struct 补齐 mapstructure tag(secret/dsn/hmac_secret 等)
- 模型层从 hotel/HotelID 统一重命名为 shop/ShopID
- 删除旧 migrations(001-004),新增 001_init 综合迁移文件
- 更新 schema.sql、testutil、handler/service/model 相关引用

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 22:20:12 +08:00

228 lines
7.3 KiB
Go

package handler
import (
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wangjia/jiu/backend/testutil"
)
func TestPartnerHandler_CRUD(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PT001")
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
token := getAuthToken(user.ID, shop.ID, "admin")
r := setupProtectedRouter(db)
// 1. Create supplier
w := makeRequest(r, "POST", "/api/v1/partners", token, map[string]interface{}{
"name": "Test Supplier",
"type": "supplier",
"code": "SUP001",
})
require.Equal(t, http.StatusCreated, w.Code)
partnerID := extractID(w)
assert.NotZero(t, partnerID)
data := parseResponse(w)["data"].(map[string]interface{})
assert.Equal(t, "Test Supplier", data["name"])
assert.Equal(t, "supplier", data["type"])
// 2. List
w = makeRequest(r, "GET", "/api/v1/partners", token, nil)
assert.Equal(t, http.StatusOK, w.Code)
resp := parseResponse(w)
assert.Equal(t, float64(1), resp["total"].(float64))
// 3. Update
w = makeRequest(r, "PUT", fmt.Sprintf("/api/v1/partners/%d", partnerID), token, map[string]interface{}{
"name": "Updated Supplier",
"type": "supplier",
})
assert.Equal(t, http.StatusOK, w.Code)
updatedData := parseResponse(w)["data"].(map[string]interface{})
assert.Equal(t, "Updated Supplier", updatedData["name"])
// 4. Delete
w = makeRequest(r, "DELETE", fmt.Sprintf("/api/v1/partners/%d", partnerID), token, nil)
assert.Equal(t, http.StatusOK, w.Code)
// 5. List after delete - should be 0
w = makeRequest(r, "GET", "/api/v1/partners", token, nil)
resp = parseResponse(w)
assert.Equal(t, float64(0), resp["total"].(float64))
}
func TestPartnerHandler_NoAuth(t *testing.T) {
db := testutil.SetupTestDB()
r := setupProtectedRouter(db)
w := makeRequest(r, "GET", "/api/v1/partners", "", nil)
assert.Equal(t, http.StatusUnauthorized, w.Code)
}
func TestPartnerHandler_HotelIsolation(t *testing.T) {
db := testutil.SetupTestDB()
shopA := testutil.CreateTestShop(db, "PT_A")
userA := testutil.CreateTestUser(db, shopA.ID, "adminA", "pass", "admin")
tokenA := getAuthToken(userA.ID, shopA.ID, "admin")
shopB := testutil.CreateTestShop(db, "PT_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/partners", tokenA, map[string]interface{}{
"name": "A Supplier",
"type": "supplier",
})
require.Equal(t, http.StatusCreated, w.Code)
partnerAID := extractID(w)
// 门店 B 创建往来单位
makeRequest(r, "POST", "/api/v1/partners", tokenB, map[string]interface{}{
"name": "B Customer",
"type": "customer",
})
// 门店 A 只能看到自己的数据
w = makeRequest(r, "GET", "/api/v1/partners", tokenA, nil)
resp := parseResponse(w)
assert.Equal(t, float64(1), resp["total"].(float64))
listData := resp["data"].([]interface{})
assert.Equal(t, "A Supplier", listData[0].(map[string]interface{})["name"])
// 门店 B 只能看到自己的数据
w = makeRequest(r, "GET", "/api/v1/partners", tokenB, nil)
respB := parseResponse(w)
assert.Equal(t, float64(1), respB["total"].(float64))
// 门店 B 不能修改门店 A 的往来单位
w = makeRequest(r, "PUT", fmt.Sprintf("/api/v1/partners/%d", partnerAID), tokenB, map[string]interface{}{
"name": "Hacked",
"type": "supplier",
})
assert.Equal(t, http.StatusNotFound, w.Code)
// 门店 B 不能删除门店 A 的往来单位
w = makeRequest(r, "DELETE", fmt.Sprintf("/api/v1/partners/%d", partnerAID), tokenB, nil)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestPartnerHandler_UpdateNotFound(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PT002")
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/partners/99999", token, map[string]interface{}{
"name": "Nonexistent",
"type": "supplier",
})
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestPartnerHandler_DeleteNotFound(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PT003")
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/partners/99999", token, nil)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestPartnerHandler_Create_MissingName(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PT004")
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
token := getAuthToken(user.ID, shop.ID, "admin")
r := setupProtectedRouter(db)
// 缺少 name 字段(必填)
w := makeRequest(r, "POST", "/api/v1/partners", token, map[string]interface{}{
"type": "supplier",
})
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestPartnerHandler_List_FilterByType(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PT005")
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
token := getAuthToken(user.ID, shop.ID, "admin")
r := setupProtectedRouter(db)
// 创建供应商和客户
makeRequest(r, "POST", "/api/v1/partners", token, map[string]interface{}{
"name": "Supplier One",
"type": "supplier",
})
makeRequest(r, "POST", "/api/v1/partners", token, map[string]interface{}{
"name": "Customer One",
"type": "customer",
})
// 列出全部
w := makeRequest(r, "GET", "/api/v1/partners", token, nil)
resp := parseResponse(w)
assert.Equal(t, float64(2), resp["total"].(float64))
}
func TestPartnerHandler_List_KeywordSearch(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PT006")
user := testutil.CreateTestUser(db, shop.ID, "admin", "pass", "admin")
token := getAuthToken(user.ID, shop.ID, "admin")
r := setupProtectedRouter(db)
makeRequest(r, "POST", "/api/v1/partners", token, map[string]interface{}{
"name": "Beijing Beer Co",
"type": "supplier",
"phone": "13800001111",
})
makeRequest(r, "POST", "/api/v1/partners", token, map[string]interface{}{
"name": "Shanghai Wine Ltd",
"type": "supplier",
})
// 按名称搜索
w := makeRequest(r, "GET", "/api/v1/partners?keyword=Beijing", token, nil)
resp := parseResponse(w)
assert.Equal(t, float64(1), resp["total"].(float64))
// 搜索不存在的关键词
w = makeRequest(r, "GET", "/api/v1/partners?keyword=Nonexistent", token, nil)
resp = parseResponse(w)
assert.Equal(t, float64(0), resp["total"].(float64))
}
func TestPartnerHandler_ShopIDFromToken(t *testing.T) {
db := testutil.SetupTestDB()
shop := testutil.CreateTestShop(db, "PT007")
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/partners", token, map[string]interface{}{
"name": "Test Partner",
"type": "supplier",
"shop_id": 9999,
})
require.Equal(t, http.StatusCreated, w.Code)
data := parseResponse(w)["data"].(map[string]interface{})
// shop_id 应该来自 token
createdShopID := uint64(data["shop_id"].(float64))
assert.Equal(t, shop.ID, createdShopID)
}