Files
team-api/tests/integration/admin/ticket_test.go
T

292 lines
8.1 KiB
Go

//go:build integration
package admin_test
import (
"fmt"
"testing"
"github.com/qianfree/team-api/tests/integration/admin/testinfra"
admintest "github.com/qianfree/team-api/tests/integration/admin/testinfra"
tenanttest "github.com/qianfree/team-api/tests/integration/tenant/testinfra"
)
func TestTicketList(t *testing.T) {
client := testinfra.GetAuthedClient(t)
resp := client.Get("/api/admin/tickets", map[string]string{
"page": "1",
"page_size": "10",
})
testinfra.AssertPaginatedList(t, resp, 0)
}
func TestTicketListWithFilters(t *testing.T) {
client := testinfra.GetAuthedClient(t)
// Filter by status
resp := client.Get("/api/admin/tickets", map[string]string{
"page": "1",
"page_size": "10",
"status": "pending",
})
resp.AssertSuccess(t)
// Filter by category
resp = client.Get("/api/admin/tickets", map[string]string{
"page": "1",
"page_size": "10",
"category": "billing",
})
resp.AssertSuccess(t)
// Filter by tenant_id
tenantID, cleanup := createTestTenantWithTicket(t, client)
defer cleanup()
resp = client.Get("/api/admin/tickets", map[string]string{
"page": "1",
"page_size": "10",
"tenant_id": fmt.Sprintf("%d", tenantID),
})
resp.AssertSuccess(t)
// Multiple filters combined
resp = client.Get("/api/admin/tickets", map[string]string{
"page": "1",
"page_size": "10",
"status": "pending",
"category": "technical",
})
resp.AssertSuccess(t)
}
func TestTicketDetail(t *testing.T) {
client := testinfra.GetAuthedClient(t)
// Create test ticket via tenant
ticketID, cleanup := createTestTicketViaTenant(t, client)
defer cleanup()
// Get ticket detail
detailResp := client.Get(fmt.Sprintf("/api/admin/tickets/%d", ticketID), nil)
detailResp.AssertSuccess(t)
var detail map[string]any
detailResp.DecodeData(t, &detail)
// Verify essential fields exist
requiredFields := []string{"id", "title", "status", "category"}
for _, field := range requiredFields {
if _, ok := detail[field]; !ok {
t.Fatalf("ticket detail missing required field: %s", field)
}
}
// Verify id matches
if id, ok := detail["id"].(float64); !ok || int64(id) != ticketID {
t.Fatalf("expected ticket id=%d in detail response", ticketID)
}
t.Logf("Ticket detail retrieved: id=%d, status=%v", ticketID, detail["status"])
}
func TestTicketReply(t *testing.T) {
client := testinfra.GetAuthedClient(t)
// Create test ticket via tenant
ticketID, cleanup := createTestTicketViaTenant(t, client)
defer cleanup()
// Reply to the ticket
replyContent := fmt.Sprintf("管理员回复(集成测试)%s", randomSuffix())
replyResp := client.Post(fmt.Sprintf("/api/admin/tickets/%d/reply", ticketID), map[string]any{
"content": replyContent,
})
replyResp.AssertSuccess(t)
t.Logf("Replied to ticket %d", ticketID)
}
func TestTicketAssign(t *testing.T) {
client := testinfra.GetAuthedClient(t)
// Create test ticket via tenant
ticketID, cleanup := createTestTicketViaTenant(t, client)
defer cleanup()
// Get current admin user list to find an admin ID
adminListResp := client.Get("/api/admin/users", map[string]string{
"page": "1",
"page_size": "10",
})
adminListResp.AssertSuccess(t)
var adminData struct {
List []struct {
Id int64 `json:"id"`
} `json:"list"`
}
adminListResp.DecodeData(t, &adminData)
if len(adminData.List) == 0 {
t.Skip("No admin users found, skipping assign test")
}
adminID := adminData.List[0].Id
// Assign the ticket
assignResp := client.Put(fmt.Sprintf("/api/admin/tickets/%d/assign", ticketID), map[string]any{
"admin_id": adminID,
})
assignResp.AssertSuccess(t)
// Verify assignment via detail
detailResp := client.Get(fmt.Sprintf("/api/admin/tickets/%d", ticketID), nil)
detailResp.AssertSuccess(t)
var detail map[string]any
detailResp.DecodeData(t, &detail)
if assignedAdmin, ok := detail["assigned_admin_id"]; ok {
if adminIDFloat, ok := assignedAdmin.(float64); ok && int64(adminIDFloat) != adminID {
t.Fatalf("expected assigned_admin_id=%d, got %v", adminID, assignedAdmin)
}
}
t.Logf("Ticket %d assigned to admin %d", ticketID, adminID)
}
func TestTicketStatusUpdate(t *testing.T) {
client := testinfra.GetAuthedClient(t)
// Create test ticket via tenant
ticketID, cleanup := createTestTicketViaTenant(t, client)
defer cleanup()
// Update status to processing
updateResp := client.Put(fmt.Sprintf("/api/admin/tickets/%d/status", ticketID), map[string]any{
"status": "processing",
})
updateResp.AssertSuccess(t)
// Verify status change via detail
detailResp := client.Get(fmt.Sprintf("/api/admin/tickets/%d", ticketID), nil)
detailResp.AssertSuccess(t)
var detail struct {
Status string `json:"status"`
}
detailResp.DecodeData(t, &detail)
if detail.Status != "processing" {
t.Fatalf("expected status=processing after update, got %q", detail.Status)
}
// Update status to closed
closeResp := client.Put(fmt.Sprintf("/api/admin/tickets/%d/status", ticketID), map[string]any{
"status": "closed",
})
closeResp.AssertSuccess(t)
// Verify closed status
verifyResp := client.Get(fmt.Sprintf("/api/admin/tickets/%d", ticketID), nil)
verifyResp.AssertSuccess(t)
var verify struct {
Status string `json:"status"`
}
verifyResp.DecodeData(t, &verify)
if verify.Status != "closed" {
t.Fatalf("expected status=closed, got %q", verify.Status)
}
t.Logf("Ticket %d status updated: pending -> processing -> closed", ticketID)
}
func TestTicketNegative(t *testing.T) {
client := testinfra.GetAuthedClient(t)
// Detail for non-existent ticket
detailNonExistResp := client.Get("/api/admin/tickets/999999999", nil)
if detailNonExistResp.Code == 0 {
t.Fatal("expected error for non-existent ticket detail, got success")
}
// Reply to non-existent ticket
replyNonExistResp := client.Post("/api/admin/tickets/999999999/reply", map[string]any{
"content": "test",
})
if replyNonExistResp.Code == 0 {
t.Fatal("expected error when replying to non-existent ticket, got success")
}
// Reply with empty content — test against a real ticket
ticketID, cleanup := createTestTicketViaTenant(t, client)
defer cleanup()
emptyReplyResp := client.Post(fmt.Sprintf("/api/admin/tickets/%d/reply", ticketID), map[string]any{
"content": "",
})
if emptyReplyResp.Code == 0 {
t.Fatal("expected error for empty reply content, got success")
}
// Invalid status value
invalidStatusResp := client.Put(fmt.Sprintf("/api/admin/tickets/%d/status", ticketID), map[string]any{
"status": "invalid_status",
})
if invalidStatusResp.Code == 0 {
t.Fatal("expected error for invalid ticket status, got success")
}
}
// createTestTicketViaTenant creates a test tenant, creates a ticket via the tenant API,
// and returns the ticket ID and cleanup.
func createTestTicketViaTenant(t *testing.T, adminClient *admintest.APIClient) (ticketID int64, cleanup func()) {
t.Helper()
result := tenanttest.RegisterTestTenant(t)
tenantClient := admintest.NewAPIClient(tenanttest.DefaultBaseURL).WithToken(result.AccessToken)
suffix := tenanttest.RandomSuffix()
resp := tenantClient.Post("/api/tenant/tickets", map[string]any{
"category": "technical",
"title": fmt.Sprintf("测试工单 %s", suffix),
"description": "集成测试自动创建的工单",
"urgency": "normal",
})
resp.AssertSuccess(t)
var data struct {
ID int64 `json:"id"`
}
resp.DecodeData(t, &data)
return data.ID, func() {
// Tenant cleanup is handled by t.Cleanup in RegisterTestTenant
}
}
// createTestTenantWithTicket creates a test tenant with a ticket for filter tests.
func createTestTenantWithTicket(t *testing.T, adminClient *admintest.APIClient) (tenantID int64, cleanup func()) {
t.Helper()
result := tenanttest.RegisterTestTenant(t)
tenantClient := admintest.NewAPIClient(tenanttest.DefaultBaseURL).WithToken(result.AccessToken)
suffix := tenanttest.RandomSuffix()
resp := tenantClient.Post("/api/tenant/tickets", map[string]any{
"category": "billing",
"title": fmt.Sprintf("过滤测试工单 %s", suffix),
"description": "过滤测试自动创建的工单",
"urgency": "normal",
})
resp.AssertSuccess(t)
return result.Tenant.ID, func() {
// Tenant cleanup is handled by t.Cleanup in RegisterTestTenant
}
}