Files

307 lines
8.6 KiB
Go

//go:build integration
package admin_test
import (
"fmt"
"testing"
"github.com/qianfree/team-api/tests/integration/admin/testinfra"
)
func TestChannelList(t *testing.T) {
client := testinfra.GetAuthedClient(t)
_, cleanup := testinfra.CreateTestChannel(t, client)
defer cleanup()
resp := client.Get("/api/admin/channels", map[string]string{
"page": "1",
"page_size": "10",
})
testinfra.AssertPaginatedList(t, resp, 1)
}
func TestChannelListWithFilters(t *testing.T) {
client := testinfra.GetAuthedClient(t)
_, cleanup := testinfra.CreateTestChannel(t, client)
defer cleanup()
resp := client.Get("/api/admin/channels", map[string]string{
"page": "1",
"page_size": "10",
"type": "1",
})
resp.AssertSuccess(t)
resp = client.Get("/api/admin/channels", map[string]string{
"page": "1",
"page_size": "10",
"status": "active",
})
resp.AssertSuccess(t)
}
func TestChannelCRUD(t *testing.T) {
client := testinfra.GetAuthedClient(t)
suffix := randomSuffix()
createResp := client.Post("/api/admin/channels", map[string]any{
"name": fmt.Sprintf("CRUD测试渠道 %s", suffix),
"type": 1,
"api_key": "sk-test-key-" + suffix,
"base_url": "https://api.openai.com",
"priority": 10,
"weight": 5,
})
createResp.AssertSuccess(t)
channelID := createResp.GetID(t)
defer client.Delete(fmt.Sprintf("/api/admin/channels/%d", channelID))
// Get detail
detailResp := client.Get(fmt.Sprintf("/api/admin/channels/%d", channelID), nil)
detailResp.AssertSuccess(t)
var detail struct {
ID int64 `json:"id"`
Priority int `json:"priority"`
Weight int `json:"weight"`
}
detailResp.DecodeData(t, &detail)
if detail.Priority != 10 {
t.Fatalf("expected priority=10, got %d", detail.Priority)
}
// Update
updateResp := client.Put(fmt.Sprintf("/api/admin/channels/%d", channelID), map[string]any{
"name": fmt.Sprintf("更新渠道名 %s", suffix),
"priority": 20,
"weight": 10,
"status": "active",
})
updateResp.AssertSuccess(t)
// Verify update
verifyResp := client.Get(fmt.Sprintf("/api/admin/channels/%d", channelID), nil)
verifyResp.AssertSuccess(t)
var updated struct {
Priority int `json:"priority"`
Weight int `json:"weight"`
}
verifyResp.DecodeData(t, &updated)
if updated.Priority != 20 {
t.Fatalf("expected priority=20, got %d", updated.Priority)
}
// Delete
deleteResp := client.Delete(fmt.Sprintf("/api/admin/channels/%d", channelID))
deleteResp.AssertSuccess(t)
}
func TestChannelKeys(t *testing.T) {
client := testinfra.GetAuthedClient(t)
channelID, channelCleanup := testinfra.CreateTestChannel(t, client)
defer channelCleanup()
// Channel created with one key — list and verify
listKeysResp := client.Get(fmt.Sprintf("/api/admin/channels/%d/keys", channelID), nil)
listKeysResp.AssertSuccess(t)
var keysResult struct {
List []struct {
ID int64 `json:"id"`
Name string `json:"name"`
ApiKey string `json:"api_key"`
Status string `json:"status"`
} `json:"list"`
}
listKeysResp.DecodeData(t, &keysResult)
if len(keysResult.List) < 1 {
t.Fatalf("expected at least 1 key, got %d", len(keysResult.List))
}
// Delete the existing key
keyID := keysResult.List[0].ID
deleteKeyResp := client.Delete(fmt.Sprintf("/api/admin/channels/%d/keys/%d", channelID, keyID))
deleteKeyResp.AssertSuccess(t)
}
func TestChannelAbilities(t *testing.T) {
client := testinfra.GetAuthedClient(t)
channelID, channelCleanup := testinfra.CreateTestChannel(t, client)
defer channelCleanup()
modelID, modelCleanup := testinfra.CreateTestModel(t, client)
defer modelCleanup()
// Get the model_id string for the created model
listResp := client.Get("/api/admin/models", map[string]string{
"page": "1",
"page_size": "50",
})
listResp.AssertSuccess(t)
var listResult struct {
List []struct {
ID int64 `json:"id"`
ModelID string `json:"model_id"`
} `json:"list"`
}
listResp.DecodeData(t, &listResult)
var modelIDStr string
for _, m := range listResult.List {
if m.ID == modelID {
modelIDStr = m.ModelID
break
}
}
if modelIDStr == "" {
t.Fatalf("could not find model_id string for id=%d", modelID)
}
// Set abilities — tolerate server errors (may need upstream model config)
putResp := client.Put(fmt.Sprintf("/api/admin/channels/%d/abilities", channelID), map[string]any{
"abilities": []map[string]any{
{
"model_name": modelIDStr,
"upstream_model": modelIDStr,
"enabled": true,
},
},
})
if putResp.Code != 0 {
t.Logf("abilities put returned code=%d msg=%s (may be expected)", putResp.Code, putResp.Message)
return
}
// Get abilities
getResp := client.Get(fmt.Sprintf("/api/admin/channels/%d/abilities", channelID), nil)
getResp.AssertSuccess(t)
var abilitiesResult struct {
List []struct {
ModelName string `json:"model_name"`
Enabled bool `json:"enabled"`
} `json:"list"`
}
getResp.DecodeData(t, &abilitiesResult)
if len(abilitiesResult.List) < 1 {
t.Fatalf("expected at least 1 ability, got %d", len(abilitiesResult.List))
}
}
func TestChannelClone(t *testing.T) {
client := testinfra.GetAuthedClient(t)
channelID, channelCleanup := testinfra.CreateTestChannel(t, client)
defer channelCleanup()
suffix := randomSuffix()
cloneResp := client.Post(fmt.Sprintf("/api/admin/channels/%d/clone", channelID), map[string]any{
"name": fmt.Sprintf("克隆渠道 %s", suffix),
"api_key": "sk-cloned-key-" + suffix,
})
cloneResp.AssertSuccess(t)
clonedID := cloneResp.GetID(t)
defer client.Delete(fmt.Sprintf("/api/admin/channels/%d", clonedID))
// Verify cloned channel exists
detailResp := client.Get(fmt.Sprintf("/api/admin/channels/%d", clonedID), nil)
detailResp.AssertSuccess(t)
}
// ─── 边界值测试 ────────────────────────────────────────────────────
// TestChannelCreate_NegativePriority 验证负数优先级被拒绝
// Business rule: priority 和 weight 应为非负整数
func TestChannelCreate_NegativePriority(t *testing.T) {
client := testinfra.GetAuthedClient(t)
suffix := randomSuffix()
resp := client.Post("/api/admin/channels", map[string]any{
"name": fmt.Sprintf("边界渠道 %s", suffix),
"type": 1,
"api_key": "sk-boundary-" + suffix,
"base_url": "https://api.openai.com",
"priority": -1,
"weight": 5,
})
if resp.Code == 0 {
// 如果服务器接受了负数,验证存储的值被正确处理
channelID := resp.GetID(t)
defer client.Delete(fmt.Sprintf("/api/admin/channels/%d", channelID))
t.Logf("server accepted negative priority — may need server-side validation")
} else {
t.Logf("negative priority rejected: code=%d msg=%q", resp.Code, resp.Message)
}
}
// TestChannelCreate_ZeroWeight 验证零权重被正确处理
// Business rule: weight=0 意味着渠道不参与负载均衡调度
func TestChannelCreate_ZeroWeight(t *testing.T) {
client := testinfra.GetAuthedClient(t)
suffix := randomSuffix()
resp := client.Post("/api/admin/channels", map[string]any{
"name": fmt.Sprintf("零权重渠道 %s", suffix),
"type": 1,
"api_key": "sk-zero-weight-" + suffix,
"base_url": "https://api.openai.com",
"priority": 10,
"weight": 0,
})
resp.AssertSuccess(t)
channelID := resp.GetID(t)
defer client.Delete(fmt.Sprintf("/api/admin/channels/%d", channelID))
// 验证 weight=0 被正确存储
detailResp := client.Get(fmt.Sprintf("/api/admin/channels/%d", channelID), nil)
detailResp.AssertSuccess(t)
var detail struct {
Weight int `json:"weight"`
}
detailResp.DecodeData(t, &detail)
if detail.Weight != 0 {
t.Fatalf("expected weight=0, got %d", detail.Weight)
}
}
// TestChannelList_PaginationBoundary 验证分页边界值
// Business rule: 分页参数超出范围时返回空列表而非错误
func TestChannelList_PaginationBoundary(t *testing.T) {
client := testinfra.GetAuthedClient(t)
_, cleanup := testinfra.CreateTestChannel(t, client)
defer cleanup()
// page=999999 应返回空列表
resp := client.Get("/api/admin/channels", map[string]string{
"page": "999999",
"page_size": "10",
})
resp.AssertSuccess(t)
var data struct {
List []any `json:"list"`
Total int `json:"total"`
}
resp.DecodeData(t, &data)
if len(data.List) != 0 {
t.Fatalf("page=999999 should return empty list, got %d items", len(data.List))
}
// page_size=1 应返回最多1条
resp = client.Get("/api/admin/channels", map[string]string{
"page": "1",
"page_size": "1",
})
resp.AssertSuccess(t)
resp.DecodeData(t, &data)
if len(data.List) > 1 {
t.Fatalf("page_size=1 should return at most 1 item, got %d", len(data.List))
}
}