feat(test):新增用户端API自动化测试
This commit is contained in:
1 parent
104f1de0b3
commit
76d35c9d7d
18 files changed
+1932
No files matched your search
@@ -0,0 +1,117 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
admintest "github.com/qianfree/team-api/tests/integration/admin/testinfra"
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestApiKeyList(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
_, cleanup := testinfra.CreateTestApiKey(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resp := client.Get("/api/tenant/api-keys", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
admintest.AssertPaginatedList(t, resp, 1)
|
||||
}
|
||||
|
||||
func TestApiKeyCRUD(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// --- Create ---
|
||||
suffix := testinfra.RandomSuffix()
|
||||
createResp := client.Post("/api/tenant/api-keys", map[string]any{
|
||||
"name": fmt.Sprintf("crud-key-%s", suffix),
|
||||
})
|
||||
createResp.AssertSuccess(t)
|
||||
keyID := createResp.GetID(t)
|
||||
defer func() {
|
||||
client.Delete(fmt.Sprintf("/api/tenant/api-keys/%d", keyID))
|
||||
}()
|
||||
|
||||
var createData struct {
|
||||
ID int64 `json:"id"`
|
||||
Key string `json:"key"`
|
||||
KeyPrefix string `json:"key_prefix"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
createResp.DecodeData(t, &createData)
|
||||
|
||||
if createData.Key == "" {
|
||||
t.Fatal("expected non-empty key on create")
|
||||
}
|
||||
if createData.KeyPrefix == "" {
|
||||
t.Fatal("expected non-empty key_prefix")
|
||||
}
|
||||
|
||||
// --- List should contain the key ---
|
||||
listResp := client.Get("/api/tenant/api-keys", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "50",
|
||||
})
|
||||
listResp.AssertSuccess(t)
|
||||
|
||||
// --- Update ---
|
||||
updateResp := client.Put(fmt.Sprintf("/api/tenant/api-keys/%d", keyID), map[string]any{
|
||||
"name": fmt.Sprintf("updated-key-%s", suffix),
|
||||
"status": "disabled",
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
|
||||
// Verify update via list
|
||||
verifyResp := client.Get("/api/tenant/api-keys", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "50",
|
||||
})
|
||||
verifyResp.AssertSuccess(t)
|
||||
|
||||
// --- Delete ---
|
||||
deleteResp := client.Delete(fmt.Sprintf("/api/tenant/api-keys/%d", keyID))
|
||||
deleteResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestApiKeyUpdateScopes(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
keyID, cleanup := testinfra.CreateTestApiKey(t, client)
|
||||
defer cleanup()
|
||||
|
||||
// Update scopes
|
||||
scopesResp := client.Put(fmt.Sprintf("/api/tenant/api-keys/%d/scopes", keyID), map[string]any{
|
||||
"model_names": []string{"gpt-4", "claude-3"},
|
||||
})
|
||||
scopesResp.AssertSuccess(t)
|
||||
|
||||
// Verify model scopes
|
||||
getScopesResp := client.Get(fmt.Sprintf("/api/tenant/api-keys/%d/model-scopes", keyID), nil)
|
||||
getScopesResp.AssertSuccess(t)
|
||||
|
||||
var scopesData struct {
|
||||
ModelNames []string `json:"model_names"`
|
||||
}
|
||||
getScopesResp.DecodeData(t, &scopesData)
|
||||
|
||||
if len(scopesData.ModelNames) != 2 {
|
||||
t.Fatalf("expected 2 model names, got %d", len(scopesData.ModelNames))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApiKeyExport(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
_, cleanup := testinfra.CreateTestApiKey(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resp := client.Get("/api/tenant/api-keys/export", map[string]string{
|
||||
"format": "csv",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestAuditConfigGet(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/audit/config", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
AuditLevel string `json:"audit_level"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestAuditConfigUpdate(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Get current level
|
||||
getResp := client.Get("/api/tenant/audit/config", nil)
|
||||
getResp.AssertSuccess(t)
|
||||
|
||||
var getData struct {
|
||||
AuditLevel string `json:"audit_level"`
|
||||
}
|
||||
getResp.DecodeData(t, &getData)
|
||||
|
||||
// Update to a different level
|
||||
newLevel := "full_text"
|
||||
if getData.AuditLevel == "full_text" {
|
||||
newLevel = "masked"
|
||||
}
|
||||
|
||||
updateResp := client.Put("/api/tenant/audit/config", map[string]any{
|
||||
"audit_level": newLevel,
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
|
||||
// Restore original level
|
||||
client.Put("/api/tenant/audit/config", map[string]any{
|
||||
"audit_level": getData.AuditLevel,
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuditLogs(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/audit/logs", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestRequestAuditLogs(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/audit/request-logs", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestRequestAuditLogsWithFilters(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Filter by username
|
||||
resp := client.Get("/api/tenant/audit/request-logs", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"username": "owner",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
// Filter by date range
|
||||
resp = client.Get("/api/tenant/audit/request-logs", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"start_date": "2025-01-01",
|
||||
"end_date": "2026-12-31",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
admintest "github.com/qianfree/team-api/tests/integration/admin/testinfra"
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestTenantRegister_Success(t *testing.T) {
|
||||
result := testinfra.RegisterTestTenant(t)
|
||||
|
||||
if result.AccessToken == "" {
|
||||
t.Fatal("expected non-empty access_token")
|
||||
}
|
||||
if result.RefreshToken == "" {
|
||||
t.Fatal("expected non-empty refresh_token")
|
||||
}
|
||||
if result.ExpiresAt == "" {
|
||||
t.Fatal("expected non-empty expires_at")
|
||||
}
|
||||
if result.Tenant.ID == 0 {
|
||||
t.Fatal("expected tenant.id > 0")
|
||||
}
|
||||
if result.Tenant.Code == "" {
|
||||
t.Fatal("expected tenant.code")
|
||||
}
|
||||
if result.User.Role != "owner" {
|
||||
t.Fatalf("expected role=owner, got %s", result.User.Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantRegister_DuplicateTenantCode(t *testing.T) {
|
||||
result := testinfra.RegisterTestTenant(t)
|
||||
|
||||
// Try registering again with the same tenant_code
|
||||
captchaKey, captchaX := testinfra.SolveCaptcha(t)
|
||||
client := admintest.NewAPIClient(testinfra.DefaultBaseURL)
|
||||
resp := client.Post("/api/tenant/auth/register", map[string]any{
|
||||
"email": fmt.Sprintf("dup-%s@test.com", result.TenantCode),
|
||||
"password": testinfra.TestPassword,
|
||||
"tenant_name": "重复租户",
|
||||
"tenant_code": result.TenantCode,
|
||||
"username": "anotheruser",
|
||||
"captcha_key": captchaKey,
|
||||
"captcha_x": captchaX,
|
||||
})
|
||||
if resp.Code == 0 {
|
||||
t.Fatal("expected error for duplicate tenant_code, but got success")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantLogin_RAM_Success(t *testing.T) {
|
||||
regResult := testinfra.RegisterTestTenant(t)
|
||||
|
||||
loginResult := testinfra.LoginTenant(t, regResult.Username, regResult.TenantCode, regResult.Password)
|
||||
|
||||
if loginResult.AccessToken == "" {
|
||||
t.Fatal("expected non-empty access_token after RAM login")
|
||||
}
|
||||
if loginResult.RefreshToken == "" {
|
||||
t.Fatal("expected non-empty refresh_token after RAM login")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantLogin_InvalidPassword(t *testing.T) {
|
||||
regResult := testinfra.RegisterTestTenant(t)
|
||||
|
||||
captchaKey, captchaX := testinfra.SolveCaptcha(t)
|
||||
client := admintest.NewAPIClient(testinfra.DefaultBaseURL)
|
||||
account := fmt.Sprintf("%s@%s", regResult.Username, regResult.TenantCode)
|
||||
resp := client.Post("/api/tenant/auth/login", map[string]any{
|
||||
"account": account,
|
||||
"password": "WrongPassword123!",
|
||||
"type": "ram",
|
||||
"captcha_key": captchaKey,
|
||||
"captcha_x": captchaX,
|
||||
})
|
||||
if resp.Code == 0 {
|
||||
t.Fatal("expected login to fail with wrong password, but got success")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantLogout_Success(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
logoutResp := client.Post("/api/tenant/auth/logout", nil)
|
||||
logoutResp.AssertSuccess(t)
|
||||
|
||||
// Verify token is invalidated
|
||||
sessionResp := client.Get("/api/tenant/auth/sessions", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
sessionResp.AssertHTTPStatus(t, 401)
|
||||
}
|
||||
|
||||
func TestTenantTokenRefresh(t *testing.T) {
|
||||
regResult := testinfra.RegisterTestTenant(t)
|
||||
|
||||
client := admintest.NewAPIClient(testinfra.DefaultBaseURL)
|
||||
refreshResp := client.Post("/api/tenant/auth/refresh", map[string]any{
|
||||
"refresh_token": regResult.RefreshToken,
|
||||
})
|
||||
refreshResp.AssertSuccess(t)
|
||||
|
||||
var refreshData struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
}
|
||||
refreshResp.DecodeData(t, &refreshData)
|
||||
|
||||
if refreshData.AccessToken == "" {
|
||||
t.Fatal("expected non-empty access_token after refresh")
|
||||
}
|
||||
|
||||
// Verify new token works
|
||||
authedClient := admintest.NewAPIClient(testinfra.DefaultBaseURL).WithToken(refreshData.AccessToken)
|
||||
sessionsResp := authedClient.Get("/api/tenant/auth/sessions", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
sessionsResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestTenantChangePassword(t *testing.T) {
|
||||
regResult := testinfra.RegisterTestTenant(t)
|
||||
client := admintest.NewAPIClient(testinfra.DefaultBaseURL).WithToken(regResult.AccessToken)
|
||||
|
||||
newPassword := "NewPass456!"
|
||||
changeResp := client.Put("/api/tenant/auth/change-password", map[string]any{
|
||||
"old_password": regResult.Password,
|
||||
"new_password": newPassword,
|
||||
})
|
||||
changeResp.AssertSuccess(t)
|
||||
|
||||
// Verify can login with new password
|
||||
loginResult := testinfra.LoginTenant(t, regResult.Username, regResult.TenantCode, newPassword)
|
||||
if loginResult.AccessToken == "" {
|
||||
t.Fatal("expected login with new password to succeed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTenantSessionList(t *testing.T) {
|
||||
regResult := testinfra.RegisterTestTenant(t)
|
||||
client := admintest.NewAPIClient(testinfra.DefaultBaseURL).WithToken(regResult.AccessToken)
|
||||
|
||||
resp := client.Get("/api/tenant/auth/sessions", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
// Verify paginated response structure exists
|
||||
var data struct {
|
||||
List []any `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestTenantSessionRevoke(t *testing.T) {
|
||||
regResult := testinfra.RegisterTestTenant(t)
|
||||
client := admintest.NewAPIClient(testinfra.DefaultBaseURL).WithToken(regResult.AccessToken)
|
||||
|
||||
// List sessions
|
||||
listResp := client.Get("/api/tenant/auth/sessions", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
listResp.AssertSuccess(t)
|
||||
|
||||
var listData struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
listResp.DecodeData(t, &listData)
|
||||
|
||||
if len(listData.List) == 0 {
|
||||
t.Skip("no sessions to revoke, session list empty")
|
||||
}
|
||||
|
||||
// Revoke the first session
|
||||
revokeResp := client.Delete(fmt.Sprintf("/api/tenant/auth/sessions/%d", listData.List[0].ID))
|
||||
revokeResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestTenantAuth_Unauthorized(t *testing.T) {
|
||||
client := admintest.NewAPIClient(testinfra.DefaultBaseURL)
|
||||
resp := client.Get("/api/tenant/auth/sessions", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertHTTPStatus(t, 401)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestUsageLogs(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/usage-logs", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestUsageLogsWithFilters(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Filter by status
|
||||
resp := client.Get("/api/tenant/usage-logs", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"status": "success",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
// Filter by date range
|
||||
resp = client.Get("/api/tenant/usage-logs", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"start_date": "2025-01-01",
|
||||
"end_date": "2026-12-31",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
// Filter by username
|
||||
resp = client.Get("/api/tenant/usage-logs", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"username": "owner",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestUsageLogsExport(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/usage-logs/export", map[string]string{
|
||||
"format": "csv",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestDashboardOverview(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/dashboard", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
Today map[string]any `json:"today"`
|
||||
Month map[string]any `json:"month"`
|
||||
Wallet map[string]any `json:"wallet"`
|
||||
ActiveKeys int `json:"active_keys"`
|
||||
MemberCount int `json:"member_count"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestDashboardTokenTrends(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/dashboard/token-trends", map[string]string{
|
||||
"days": "7",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestDashboardModelDistribution(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/dashboard/model-distribution", map[string]string{
|
||||
"days": "7",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestDashboardBalancePrediction(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/dashboard/balance-prediction", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
DailyAvgCost float64 `json:"daily_avg_cost"`
|
||||
AvailableBalance float64 `json:"available_balance"`
|
||||
WillExhaust bool `json:"will_exhaust"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestDashboardBudgetAlerts(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/dashboard/budget-alerts", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
Members []map[string]any `json:"members"`
|
||||
Projects []map[string]any `json:"projects"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestDashboardMemberUsageRanking(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/dashboard/member-usage-ranking", map[string]string{
|
||||
"days": "7",
|
||||
"limit": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestFeedbackCreate(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Post("/api/tenant/feedbacks", map[string]any{
|
||||
"category": "bug_report",
|
||||
"title": fmt.Sprintf("测试反馈 %s", testinfra.RandomSuffix()),
|
||||
"description": "这是一个集成测试创建的反馈",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
if data.ID == 0 {
|
||||
t.Fatal("expected non-zero feedback id")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeedbackList(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Create a feedback first
|
||||
createResp := client.Post("/api/tenant/feedbacks", map[string]any{
|
||||
"category": "suggestion",
|
||||
"title": fmt.Sprintf("列表测试反馈 %s", testinfra.RandomSuffix()),
|
||||
"description": "测试反馈列表功能",
|
||||
})
|
||||
createResp.AssertSuccess(t)
|
||||
|
||||
// List feedbacks
|
||||
resp := client.Get("/api/tenant/feedbacks", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
Category string `json:"category"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
} `json:"list"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
if data.Total < 1 {
|
||||
t.Fatalf("expected at least 1 feedback, got total=%d", data.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeedbackListWithFilters(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Create feedbacks with different categories
|
||||
client.Post("/api/tenant/feedbacks", map[string]any{
|
||||
"category": "bug_report",
|
||||
"title": "Bug反馈过滤测试",
|
||||
"description": "测试按分类过滤",
|
||||
})
|
||||
client.Post("/api/tenant/feedbacks", map[string]any{
|
||||
"category": "feature_request",
|
||||
"title": "功能请求过滤测试",
|
||||
"description": "测试按分类过滤",
|
||||
})
|
||||
|
||||
// Filter by category
|
||||
resp := client.Get("/api/tenant/feedbacks", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"category": "bug_report",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestFeedbackGet(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Create a feedback
|
||||
createResp := client.Post("/api/tenant/feedbacks", map[string]any{
|
||||
"category": "complaint",
|
||||
"title": "详情测试反馈",
|
||||
"description": "测试获取反馈详情",
|
||||
})
|
||||
createResp.AssertSuccess(t)
|
||||
|
||||
var createData struct {
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
createResp.DecodeData(t, &createData)
|
||||
|
||||
// Get detail
|
||||
detailResp := client.Get(fmt.Sprintf("/api/tenant/feedbacks/%d", createData.ID), nil)
|
||||
detailResp.AssertSuccess(t)
|
||||
|
||||
var detail struct {
|
||||
ID int64 `json:"id"`
|
||||
Category string `json:"category"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
detailResp.DecodeData(t, &detail)
|
||||
|
||||
if detail.ID != createData.ID {
|
||||
t.Fatalf("expected id=%d, got %d", createData.ID, detail.ID)
|
||||
}
|
||||
if detail.Category != "complaint" {
|
||||
t.Fatalf("expected category=complaint, got %s", detail.Category)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
admintest "github.com/qianfree/team-api/tests/integration/admin/testinfra"
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestMemberList(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
memberID, cleanup := testinfra.CreateTestTenantMember(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resp := client.Get("/api/tenant/members", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
admintest.AssertPaginatedList(t, resp, 2) // owner + member
|
||||
|
||||
// Verify the created member appears
|
||||
var listData struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
} `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &listData)
|
||||
|
||||
found := false
|
||||
for _, m := range listData.List {
|
||||
if m.ID == memberID {
|
||||
found = true
|
||||
if m.Role != "member" {
|
||||
t.Fatalf("expected role=member, got %s", m.Role)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("created member %d not found in list", memberID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberListWithFilters(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
_, cleanup := testinfra.CreateTestTenantMember(t, client)
|
||||
defer cleanup()
|
||||
|
||||
// Filter by role
|
||||
resp := client.Get("/api/tenant/members", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"role": "member",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
var memberList struct {
|
||||
List []struct {
|
||||
Role string `json:"role"`
|
||||
} `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &memberList)
|
||||
for _, m := range memberList.List {
|
||||
if m.Role != "member" {
|
||||
t.Fatalf("expected role=member when filtering, got %s", m.Role)
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by keyword
|
||||
resp = client.Get("/api/tenant/members", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"keyword": "member",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestMemberCreate(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
memberID, cleanup := testinfra.CreateTestTenantMember(t, client)
|
||||
defer cleanup()
|
||||
|
||||
// Get detail
|
||||
detailResp := client.Get(fmt.Sprintf("/api/tenant/members/%d", memberID), nil)
|
||||
detailResp.AssertSuccess(t)
|
||||
|
||||
var detail struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
detailResp.DecodeData(t, &detail)
|
||||
|
||||
if detail.ID != memberID {
|
||||
t.Fatalf("expected id=%d, got %d", memberID, detail.ID)
|
||||
}
|
||||
if detail.Role != "member" {
|
||||
t.Fatalf("expected role=member, got %s", detail.Role)
|
||||
}
|
||||
if detail.Status != "active" {
|
||||
t.Fatalf("expected status=active, got %s", detail.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberInvite(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Create invitation
|
||||
inviteResp := client.Post("/api/tenant/members/invite", map[string]any{
|
||||
"role": "member",
|
||||
"expires_days": 7,
|
||||
"max_uses": 5,
|
||||
})
|
||||
inviteResp.AssertSuccess(t)
|
||||
|
||||
var inviteData struct {
|
||||
Code string `json:"code"`
|
||||
InviteURL string `json:"invite_url"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
MaxUses int `json:"max_uses"`
|
||||
}
|
||||
inviteResp.DecodeData(t, &inviteData)
|
||||
|
||||
if inviteData.Code == "" {
|
||||
t.Fatal("expected non-empty invite code")
|
||||
}
|
||||
if inviteData.MaxUses != 5 {
|
||||
t.Fatalf("expected max_uses=5, got %d", inviteData.MaxUses)
|
||||
}
|
||||
|
||||
// List invitations
|
||||
listResp := client.Get("/api/tenant/members/invitations", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
admintest.AssertPaginatedList(t, listResp, 1)
|
||||
}
|
||||
|
||||
func TestMemberInviteInfo(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
inviteResp := client.Post("/api/tenant/members/invite", map[string]any{
|
||||
"role": "member",
|
||||
})
|
||||
inviteResp.AssertSuccess(t)
|
||||
|
||||
var inviteData struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
inviteResp.DecodeData(t, &inviteData)
|
||||
|
||||
// Query invite info (public endpoint)
|
||||
pubClient := admintest.NewAPIClient(testinfra.DefaultBaseURL)
|
||||
infoResp := pubClient.Get("/api/tenant/members/invite-info", map[string]string{
|
||||
"code": inviteData.Code,
|
||||
})
|
||||
infoResp.AssertSuccess(t)
|
||||
|
||||
var info struct {
|
||||
TenantName string `json:"tenant_name"`
|
||||
Role string `json:"role"`
|
||||
Valid bool `json:"valid"`
|
||||
}
|
||||
infoResp.DecodeData(t, &info)
|
||||
|
||||
if !info.Valid {
|
||||
t.Fatal("expected invite to be valid")
|
||||
}
|
||||
if info.Role != "member" {
|
||||
t.Fatalf("expected role=member, got %s", info.Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberInviteRevoke(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
inviteResp := client.Post("/api/tenant/members/invite", map[string]any{
|
||||
"role": "member",
|
||||
})
|
||||
inviteResp.AssertSuccess(t)
|
||||
|
||||
// List invitations to get the ID
|
||||
listResp := client.Get("/api/tenant/members/invitations", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
listResp.AssertSuccess(t)
|
||||
|
||||
var listData struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"list"`
|
||||
}
|
||||
listResp.DecodeData(t, &listData)
|
||||
if len(listData.List) == 0 {
|
||||
t.Fatal("expected at least 1 invitation")
|
||||
}
|
||||
|
||||
// Revoke
|
||||
revokeResp := client.Delete(fmt.Sprintf("/api/tenant/members/invitations/%d", listData.List[0].ID))
|
||||
revokeResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestMemberUpdateRole(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
memberID, cleanup := testinfra.CreateTestTenantMember(t, client)
|
||||
defer cleanup()
|
||||
|
||||
// Update role to admin
|
||||
updateResp := client.Put(fmt.Sprintf("/api/tenant/members/%d/role", memberID), map[string]any{
|
||||
"role": "admin",
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
|
||||
// Verify
|
||||
detailResp := client.Get(fmt.Sprintf("/api/tenant/members/%d", memberID), nil)
|
||||
detailResp.AssertSuccess(t)
|
||||
|
||||
var detail struct {
|
||||
Role string `json:"role"`
|
||||
}
|
||||
detailResp.DecodeData(t, &detail)
|
||||
if detail.Role != "admin" {
|
||||
t.Fatalf("expected role=admin after update, got %s", detail.Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberResetPassword(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
memberID, cleanup := testinfra.CreateTestTenantMember(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resetResp := client.Put(fmt.Sprintf("/api/tenant/members/%d/reset-password", memberID), map[string]any{
|
||||
"password": "NewPass789!",
|
||||
})
|
||||
resetResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestMemberRemove(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
memberID, _ := testinfra.CreateTestTenantMember(t, client)
|
||||
|
||||
// Remove
|
||||
removeResp := client.Delete(fmt.Sprintf("/api/tenant/members/%d", memberID))
|
||||
removeResp.AssertSuccess(t)
|
||||
|
||||
// Verify removed - get detail should fail or return inactive status
|
||||
detailResp := client.Get(fmt.Sprintf("/api/tenant/members/%d", memberID), nil)
|
||||
detailResp.AssertSuccess(t)
|
||||
|
||||
var detail struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
detailResp.DecodeData(t, &detail)
|
||||
if detail.Status == "active" {
|
||||
t.Fatal("expected member to not be active after removal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberUsage(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
memberID, cleanup := testinfra.CreateTestTenantMember(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resp := client.Get(fmt.Sprintf("/api/tenant/members/%d/usage", memberID), nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var usage struct {
|
||||
TodayRequests float64 `json:"today_requests"`
|
||||
MonthRequests float64 `json:"month_requests"`
|
||||
MonthInputTok float64 `json:"month_input_tokens"`
|
||||
MonthOutputTok float64 `json:"month_output_tokens"`
|
||||
MonthTotalCost float64 `json:"month_total_cost"`
|
||||
}
|
||||
resp.DecodeData(t, &usage)
|
||||
// New member should have zero usage
|
||||
if usage.TodayRequests != 0 {
|
||||
t.Fatalf("expected today_requests=0, got %f", usage.TodayRequests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberApiKeys(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
memberID, cleanup := testinfra.CreateTestTenantMember(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resp := client.Get(fmt.Sprintf("/api/tenant/members/%d/api-keys", memberID), map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []any `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
if data.Total != 0 {
|
||||
t.Fatalf("expected 0 api keys for new member, got %d", data.Total)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemberExport(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/members/export", map[string]string{
|
||||
"format": "csv",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestAvailableModels(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/models", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
ModelID string `json:"model_id"`
|
||||
ModelName string `json:"model_name"`
|
||||
Category string `json:"category"`
|
||||
} `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestAvailableModelsFilterByCategory(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/models", map[string]string{
|
||||
"category": "chat",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestAvailableModelsSearch(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/models", map[string]string{
|
||||
"search": "gpt",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestNotificationList(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/notifications", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestUnreadCount(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/notifications/unread-count", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
UnreadCount int `json:"unread_count"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestMarkAllRead(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Post("/api/tenant/notifications/read-all", nil)
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestNotificationPreferences(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Get preferences
|
||||
getResp := client.Get("/api/tenant/notification-preferences", nil)
|
||||
getResp.AssertSuccess(t)
|
||||
|
||||
// Update preferences (user scope)
|
||||
updateResp := client.Put("/api/tenant/notification-preferences", map[string]any{
|
||||
"scope": "user",
|
||||
"preferences": map[string]any{
|
||||
"billing": map[string]any{
|
||||
"email": true,
|
||||
"in_app": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
|
||||
// Verify update
|
||||
verifyResp := client.Get("/api/tenant/notification-preferences", nil)
|
||||
verifyResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestNotificationPreferencesOrg(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Update preferences (org scope, owner can do this)
|
||||
updateResp := client.Put("/api/tenant/notification-preferences", map[string]any{
|
||||
"scope": "org",
|
||||
"preferences": map[string]any{
|
||||
"security": map[string]any{
|
||||
"email": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestAnnouncements(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/announcements", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestNotificationsExport(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/notifications/export", map[string]string{
|
||||
"format": "csv",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestOrgInfo(t *testing.T) {
|
||||
client, regResult := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/organization", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
Status string `json:"status"`
|
||||
MemberCount int `json:"member_count"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
|
||||
if data.Code != regResult.TenantCode {
|
||||
t.Fatalf("expected code=%s, got %s", regResult.TenantCode, data.Code)
|
||||
}
|
||||
if data.Status != "active" {
|
||||
t.Fatalf("expected status=active, got %s", data.Status)
|
||||
}
|
||||
if data.MemberCount < 1 {
|
||||
t.Fatalf("expected member_count >= 1, got %d", data.MemberCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgUpdate(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
newName := fmt.Sprintf("更新组织名 %s", testinfra.RandomSuffix())
|
||||
updateResp := client.Put("/api/tenant/organization", map[string]any{
|
||||
"name": newName,
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
|
||||
// Verify update
|
||||
verifyResp := client.Get("/api/tenant/organization", nil)
|
||||
verifyResp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
verifyResp.DecodeData(t, &data)
|
||||
if data.Name != newName {
|
||||
t.Fatalf("expected name=%s, got %s", newName, data.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfile(t *testing.T) {
|
||||
client, regResult := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/profile", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
|
||||
if data.Username != regResult.Username {
|
||||
t.Fatalf("expected username=%s, got %s", regResult.Username, data.Username)
|
||||
}
|
||||
if data.Role != "owner" {
|
||||
t.Fatalf("expected role=owner, got %s", data.Role)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfileUpdate(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
newDisplayName := fmt.Sprintf("测试用户 %s", testinfra.RandomSuffix())
|
||||
updateResp := client.Put("/api/tenant/profile", map[string]any{
|
||||
"display_name": newDisplayName,
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
|
||||
// Verify update
|
||||
verifyResp := client.Get("/api/tenant/profile", nil)
|
||||
verifyResp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
DisplayName string `json:"display_name"`
|
||||
}
|
||||
verifyResp.DecodeData(t, &data)
|
||||
if data.DisplayName != newDisplayName {
|
||||
t.Fatalf("expected display_name=%s, got %s", newDisplayName, data.DisplayName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPWhitelist(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Get current whitelist
|
||||
getResp := client.Get("/api/tenant/security/ip-whitelist", nil)
|
||||
getResp.AssertSuccess(t)
|
||||
|
||||
var getData struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Whitelist []string `json:"whitelist"`
|
||||
}
|
||||
getResp.DecodeData(t, &getData)
|
||||
|
||||
// Update whitelist (disable it to avoid locking ourselves out)
|
||||
updateResp := client.Put("/api/tenant/security/ip-whitelist", map[string]any{
|
||||
"enabled": false,
|
||||
"whitelist": []string{},
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
|
||||
// Verify update
|
||||
verifyResp := client.Get("/api/tenant/security/ip-whitelist", nil)
|
||||
verifyResp.AssertSuccess(t)
|
||||
|
||||
var verifyData struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
verifyResp.DecodeData(t, &verifyData)
|
||||
if verifyData.Enabled {
|
||||
t.Fatal("expected ip whitelist to be disabled after update")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestPersonalDashboardOverview(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/personal-dashboard", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
Today map[string]any `json:"today"`
|
||||
Month map[string]any `json:"month"`
|
||||
ErrorRate map[string]any `json:"error_rate"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestPersonalDashboardTrends(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/personal-dashboard/trends", map[string]string{
|
||||
"days": "7",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestPersonalDashboardModels(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/personal-dashboard/models", map[string]string{
|
||||
"days": "7",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestPersonalDashboardApiKeyUsage(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/personal-dashboard/api-key-usage", map[string]string{
|
||||
"days": "7",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestPlanList(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/plans", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Identifier string `json:"identifier"`
|
||||
} `json:"list"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestCurrentPlan(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/plan/current", nil)
|
||||
// New tenant may not have a plan, accept either success or error
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
ID int64 `json:"id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestCancelAutoRenew(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Put("/api/tenant/plan/cancel-auto-renew", nil)
|
||||
// New tenant likely has no active plan, so this may fail gracefully
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
admintest "github.com/qianfree/team-api/tests/integration/admin/testinfra"
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestProjectList(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
_, cleanup := testinfra.CreateTestProject(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resp := client.Get("/api/tenant/projects", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
admintest.AssertPaginatedList(t, resp, 1)
|
||||
}
|
||||
|
||||
func TestProjectCRUD(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// --- Create ---
|
||||
suffix := testinfra.RandomSuffix()
|
||||
createResp := client.Post("/api/tenant/projects", map[string]any{
|
||||
"name": fmt.Sprintf("crud-project-%s", suffix),
|
||||
"description": "Integration test project",
|
||||
"budget": 100.0,
|
||||
})
|
||||
createResp.AssertSuccess(t)
|
||||
projectID := createResp.GetID(t)
|
||||
defer func() {
|
||||
client.Post(fmt.Sprintf("/api/tenant/projects/%d/archive", projectID), nil)
|
||||
}()
|
||||
|
||||
// --- Get detail ---
|
||||
detailResp := client.Get(fmt.Sprintf("/api/tenant/projects/%d", projectID), nil)
|
||||
detailResp.AssertSuccess(t)
|
||||
|
||||
var detail struct {
|
||||
Data struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Budget string `json:"budget"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
detailResp.DecodeData(t, &detail)
|
||||
|
||||
if detail.Data.ID != projectID {
|
||||
t.Fatalf("expected id=%d, got %d", projectID, detail.Data.ID)
|
||||
}
|
||||
|
||||
// --- Update ---
|
||||
updateResp := client.Put(fmt.Sprintf("/api/tenant/projects/%d", projectID), map[string]any{
|
||||
"name": fmt.Sprintf("updated-project-%s", suffix),
|
||||
"description": "Updated description",
|
||||
"budget": 200.0,
|
||||
})
|
||||
updateResp.AssertSuccess(t)
|
||||
|
||||
// Verify update
|
||||
verifyResp := client.Get(fmt.Sprintf("/api/tenant/projects/%d", projectID), nil)
|
||||
verifyResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestProjectArchiveUnarchive(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
projectID, _ := testinfra.CreateTestProject(t, client)
|
||||
|
||||
// Archive
|
||||
archiveResp := client.Post(fmt.Sprintf("/api/tenant/projects/%d/archive", projectID), nil)
|
||||
archiveResp.AssertSuccess(t)
|
||||
|
||||
// Verify archived via list
|
||||
listResp := client.Get("/api/tenant/projects", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "50",
|
||||
})
|
||||
listResp.AssertSuccess(t)
|
||||
|
||||
// Unarchive
|
||||
unarchiveResp := client.Post(fmt.Sprintf("/api/tenant/projects/%d/unarchive", projectID), nil)
|
||||
unarchiveResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestProjectApiKeys(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
projectID, cleanup := testinfra.CreateTestProject(t, client)
|
||||
defer cleanup()
|
||||
|
||||
// Create project API key
|
||||
createKeyResp := client.Post(fmt.Sprintf("/api/tenant/projects/%d/api-keys", projectID), map[string]any{
|
||||
"name": "project-test-key",
|
||||
})
|
||||
createKeyResp.AssertSuccess(t)
|
||||
|
||||
// List project API keys
|
||||
listResp := client.Get(fmt.Sprintf("/api/tenant/projects/%d/api-keys", projectID), map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
admintest.AssertPaginatedList(t, listResp, 1)
|
||||
|
||||
var listData struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
} `json:"list"`
|
||||
}
|
||||
listResp.DecodeData(t, &listData)
|
||||
if len(listData.List) == 0 {
|
||||
t.Fatal("expected at least 1 project API key")
|
||||
}
|
||||
|
||||
// Delete project API key
|
||||
keyID := listData.List[0].ID
|
||||
deleteResp := client.Delete(fmt.Sprintf("/api/tenant/projects/%d/api-keys/%d", projectID, keyID))
|
||||
deleteResp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestProjectUsageStats(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
projectID, cleanup := testinfra.CreateTestProject(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resp := client.Get(fmt.Sprintf("/api/tenant/projects/%d/usage-stats", projectID), nil)
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
|
||||
func TestProjectUsageLogs(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
projectID, cleanup := testinfra.CreateTestProject(t, client)
|
||||
defer cleanup()
|
||||
|
||||
resp := client.Get(fmt.Sprintf("/api/tenant/projects/%d/usage-logs", projectID), map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestLoginHistory(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/security/login-history", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
List []struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
LoginMethod string `json:"login_method"`
|
||||
IPAddress string `json:"ip_address"`
|
||||
Success bool `json:"success"`
|
||||
} `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestLoginHistoryWithFilters(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
// Filter by IP
|
||||
resp := client.Get("/api/tenant/security/login-history", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"ip_address": "127.0.0.1",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
// Filter by success status
|
||||
resp = client.Get("/api/tenant/security/login-history", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"success": "true",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
// Filter by date range
|
||||
resp = client.Get("/api/tenant/security/login-history", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
"start_time": "2025-01-01",
|
||||
"end_time": "2026-12-31",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
//go:build integration
|
||||
|
||||
package testinfra
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
admintest "github.com/qianfree/team-api/tests/integration/admin/testinfra"
|
||||
)
|
||||
|
||||
func RandomSuffix() string {
|
||||
b := make([]byte, 4)
|
||||
_, _ = rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
func getRedisClient() *redis.Client {
|
||||
return redis.NewClient(&redis.Options{
|
||||
Addr: DefaultRedisAddr,
|
||||
DB: 0,
|
||||
DialTimeout: 3 * time.Second,
|
||||
ReadTimeout: 3 * time.Second,
|
||||
WriteTimeout: 3 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
func SolveCaptcha(t *testing.T) (captchaKey string, captchaX int) {
|
||||
t.Helper()
|
||||
client := admintest.NewAPIClient(DefaultBaseURL)
|
||||
|
||||
captchaResp := client.Get("/api/captcha/", nil)
|
||||
captchaResp.AssertSuccess(t)
|
||||
|
||||
var captchaData struct {
|
||||
CaptchaKey string `json:"captcha_key"`
|
||||
}
|
||||
captchaResp.DecodeData(t, &captchaData)
|
||||
|
||||
rdb := getRedisClient()
|
||||
defer rdb.Close()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
redisKey := fmt.Sprintf("captcha:state:%s", captchaData.CaptchaKey)
|
||||
val, err := rdb.Get(ctx, redisKey).Result()
|
||||
if err != nil {
|
||||
t.Fatalf("read captcha from redis error: %v", err)
|
||||
}
|
||||
|
||||
var answer struct {
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(val), &answer); err != nil {
|
||||
t.Fatalf("unmarshal captcha answer error: %v", err)
|
||||
}
|
||||
|
||||
return captchaData.CaptchaKey, answer.X
|
||||
}
|
||||
|
||||
type TenantRegisterResult struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
Tenant struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
} `json:"tenant"`
|
||||
User struct {
|
||||
ID int64 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Role string `json:"role"`
|
||||
} `json:"user"`
|
||||
|
||||
// Stored for re-login tests
|
||||
TenantCode string
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
}
|
||||
|
||||
func RegisterTestTenant(t *testing.T) *TenantRegisterResult {
|
||||
t.Helper()
|
||||
|
||||
suffix := RandomSuffix()
|
||||
tenantCode := fmt.Sprintf("t-%s", suffix)
|
||||
username := fmt.Sprintf("owner%s", suffix)
|
||||
email := fmt.Sprintf("%s@test.com", username)
|
||||
|
||||
captchaKey, captchaX := SolveCaptcha(t)
|
||||
|
||||
client := admintest.NewAPIClient(DefaultBaseURL)
|
||||
resp := client.Post("/api/tenant/auth/register", map[string]any{
|
||||
"email": email,
|
||||
"password": TestPassword,
|
||||
"tenant_name": fmt.Sprintf("测试租户 %s", suffix),
|
||||
"tenant_code": tenantCode,
|
||||
"username": username,
|
||||
"captcha_key": captchaKey,
|
||||
"captcha_x": captchaX,
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var result TenantRegisterResult
|
||||
resp.DecodeData(t, &result)
|
||||
result.TenantCode = tenantCode
|
||||
result.Username = username
|
||||
result.Password = TestPassword
|
||||
result.Email = email
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func GetAuthedClient(t *testing.T) (*admintest.APIClient, *TenantRegisterResult) {
|
||||
t.Helper()
|
||||
result := RegisterTestTenant(t)
|
||||
client := admintest.NewAPIClient(DefaultBaseURL).WithToken(result.AccessToken)
|
||||
return client, result
|
||||
}
|
||||
|
||||
func LoginTenant(t *testing.T, username, tenantCode, password string) *TenantRegisterResult {
|
||||
t.Helper()
|
||||
captchaKey, captchaX := SolveCaptcha(t)
|
||||
|
||||
client := admintest.NewAPIClient(DefaultBaseURL)
|
||||
account := fmt.Sprintf("%s@%s", username, tenantCode)
|
||||
resp := client.Post("/api/tenant/auth/login", map[string]any{
|
||||
"account": account,
|
||||
"password": password,
|
||||
"type": "ram",
|
||||
"captcha_key": captchaKey,
|
||||
"captcha_x": captchaX,
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var result TenantRegisterResult
|
||||
resp.DecodeData(t, &result)
|
||||
result.TenantCode = tenantCode
|
||||
result.Username = username
|
||||
result.Password = password
|
||||
|
||||
return &result
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build integration
|
||||
|
||||
package testinfra
|
||||
|
||||
const (
|
||||
DefaultBaseURL = "http://127.0.0.1:18888"
|
||||
DefaultRedisAddr = "192.168.50.22:16380"
|
||||
TestPassword = "TestPass123!"
|
||||
)
|
||||
@@ -0,0 +1,53 @@
|
||||
//go:build integration
|
||||
|
||||
package testinfra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
admintest "github.com/qianfree/team-api/tests/integration/admin/testinfra"
|
||||
)
|
||||
|
||||
func CreateTestTenantMember(t *testing.T, client *admintest.APIClient) (id int64, cleanup func()) {
|
||||
t.Helper()
|
||||
suffix := RandomSuffix()
|
||||
username := fmt.Sprintf("member%s", suffix)
|
||||
resp := client.Post("/api/tenant/members/create", map[string]any{
|
||||
"username": username,
|
||||
"password": TestPassword,
|
||||
"email": fmt.Sprintf("%s@test.com", username),
|
||||
"role": "member",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
id = resp.GetID(t)
|
||||
return id, func() {
|
||||
client.Delete(fmt.Sprintf("/api/tenant/members/%d", id))
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTestApiKey(t *testing.T, client *admintest.APIClient) (id int64, cleanup func()) {
|
||||
t.Helper()
|
||||
suffix := RandomSuffix()
|
||||
resp := client.Post("/api/tenant/api-keys", map[string]any{
|
||||
"name": fmt.Sprintf("test-key-%s", suffix),
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
id = resp.GetID(t)
|
||||
return id, func() {
|
||||
client.Delete(fmt.Sprintf("/api/tenant/api-keys/%d", id))
|
||||
}
|
||||
}
|
||||
|
||||
func CreateTestProject(t *testing.T, client *admintest.APIClient) (id int64, cleanup func()) {
|
||||
t.Helper()
|
||||
suffix := RandomSuffix()
|
||||
resp := client.Post("/api/tenant/projects", map[string]any{
|
||||
"name": fmt.Sprintf("test-project-%s", suffix),
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
id = resp.GetID(t)
|
||||
return id, func() {
|
||||
client.Post(fmt.Sprintf("/api/tenant/projects/%d/archive", id), nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
//go:build integration
|
||||
|
||||
package tenant_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/qianfree/team-api/tests/integration/tenant/testinfra"
|
||||
)
|
||||
|
||||
func TestWalletInfo(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/wallet", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
Balance float64 `json:"balance"`
|
||||
FrozenBalance float64 `json:"frozen_balance"`
|
||||
AvailableBalance float64 `json:"available_balance"`
|
||||
Currency string `json:"currency"`
|
||||
Level int `json:"level"`
|
||||
LevelName string `json:"level_name"`
|
||||
CumulativeRecharge float64 `json:"cumulative_recharge"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
|
||||
// New tenant should have zero balance
|
||||
if data.Currency == "" {
|
||||
t.Fatal("expected non-empty currency")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalletTransactions(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/wallet/transactions", map[string]string{
|
||||
"page": "1",
|
||||
"page_size": "10",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
// New tenant likely has no transactions, just verify structure
|
||||
var data struct {
|
||||
List []map[string]any `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestWalletFrozenItems(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/wallet/frozen-items", nil)
|
||||
resp.AssertSuccess(t)
|
||||
|
||||
var data struct {
|
||||
Items []map[string]any `json:"items"`
|
||||
}
|
||||
resp.DecodeData(t, &data)
|
||||
}
|
||||
|
||||
func TestWalletTransactionsExport(t *testing.T) {
|
||||
client, _ := testinfra.GetAuthedClient(t)
|
||||
|
||||
resp := client.Get("/api/tenant/wallet/transactions/export", map[string]string{
|
||||
"format": "csv",
|
||||
})
|
||||
resp.AssertSuccess(t)
|
||||
}
|
||||
Reference in new issue
Block a user