From 9def7d0cd8b8edf2b1d516948c5c503fdb8c2dee Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 1 May 2026 09:08:59 +0800 Subject: [PATCH] =?UTF-8?q?test(P0-1):=20=E8=A1=A5=E9=BD=90=20memory=20sto?= =?UTF-8?q?re=20ticket=20workflow=20=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 internal/store/memory/ticket_workflow_test.go: - TestTicketStore_ListOpen: 测试开放工单过滤逻辑 - TestTicketStore_Assign: 测试工单分配(正常+已分配冲突) - TestTicketStore_Resolve: 测试工单解决 - TestTicketStore_Close: 测试工单关闭(正常+非resolved错误) **覆盖率提升**: - internal/store/memory: 59.1% → **88.3%** (+29.2%) - 整体覆盖率: 62.6% → **66.2%** (+3.6%) Ref: test/PHASE2_TEST_PLAN.md P0-1 --- internal/store/memory/ticket_workflow_test.go | 167 ++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 internal/store/memory/ticket_workflow_test.go diff --git a/internal/store/memory/ticket_workflow_test.go b/internal/store/memory/ticket_workflow_test.go new file mode 100644 index 0000000..ab60ee8 --- /dev/null +++ b/internal/store/memory/ticket_workflow_test.go @@ -0,0 +1,167 @@ +package memory + +import ( + "context" + "testing" + "time" + + "github.com/bridge/ai-customer-service/internal/domain/ticket" +) + +func TestTicketStore_ListOpen(t *testing.T) { + store := NewTicketStore() + ctx := context.Background() + now := time.Now().Truncate(time.Second) + + // Create tickets with different statuses + tickets := []ticket.Ticket{ + {ID: "t1", Status: ticket.StatusOpen, CreatedAt: now, UpdatedAt: now}, + {ID: "t2", Status: ticket.StatusAssigned, CreatedAt: now, UpdatedAt: now}, + {ID: "t3", Status: ticket.StatusResolved, CreatedAt: now, UpdatedAt: now}, + {ID: "t4", Status: ticket.StatusClosed, CreatedAt: now, UpdatedAt: now}, + } + for i := range tickets { + store.Create(ctx, &tickets[i]) + } + + // ListOpen should return open + assigned (not resolved/closed) + open, err := store.ListOpen(ctx, 10) + if err != nil { + t.Fatalf("ListOpen() error = %v", err) + } + if len(open) != 2 { + t.Errorf("ListOpen() len = %d, want 2 (open + assigned)", len(open)) + } +} + +func TestTicketStore_Assign(t *testing.T) { + store := NewTicketStore() + ctx := context.Background() + now := time.Now().Truncate(time.Second) + + // Create an open ticket + store.Create(ctx, &ticket.Ticket{ + ID: "t1", + Status: ticket.StatusOpen, + CreatedAt: now, + UpdatedAt: now, + }) + + // Assign it + err := store.Assign(ctx, "t1", "agent1", "admin", "127.0.0.1", now) + if err != nil { + t.Fatalf("Assign() error = %v", err) + } + + // Verify assigned + tkt, _ := store.GetByID(ctx, "t1") + if tkt.Status != ticket.StatusAssigned { + t.Errorf("ticket.Status = %s, want %s", tkt.Status, ticket.StatusAssigned) + } + if tkt.AssignedTo != "agent1" { + t.Errorf("ticket.AssignedTo = %s, want agent1", tkt.AssignedTo) + } +} + +func TestTicketStore_Assign_AlreadyAssigned(t *testing.T) { + store := NewTicketStore() + ctx := context.Background() + now := time.Now().Truncate(time.Second) + + // Create an already-assigned ticket + store.Create(ctx, &ticket.Ticket{ + ID: "t1", + Status: ticket.StatusAssigned, + AssignedTo: "agent1", + CreatedAt: now, + UpdatedAt: now, + }) + + // Try to assign again — should return error + err := store.Assign(ctx, "t1", "agent2", "admin", "127.0.0.1", now) + if err == nil { + t.Fatal("Assign() on already-assigned ticket should return error") + } +} + +func TestTicketStore_Resolve(t *testing.T) { + store := NewTicketStore() + ctx := context.Background() + now := time.Now().Truncate(time.Second) + + // Create an assigned ticket + store.Create(ctx, &ticket.Ticket{ + ID: "t1", + Status: ticket.StatusAssigned, + AssignedTo: "agent1", + CreatedAt: now, + UpdatedAt: now, + }) + + // Resolve it + err := store.Resolve(ctx, "t1", "fixed by agent", "admin", "127.0.0.1", now) + if err != nil { + t.Fatalf("Resolve() error = %v", err) + } + + // Verify resolved + tkt, _ := store.GetByID(ctx, "t1") + if tkt.Status != ticket.StatusResolved { + t.Errorf("ticket.Status = %s, want %s", tkt.Status, ticket.StatusResolved) + } + if tkt.Resolution != "fixed by agent" { + t.Errorf("ticket.Resolution = %s, want 'fixed by agent'", tkt.Resolution) + } + if tkt.ResolvedAt == nil { + t.Error("ticket.ResolvedAt should be set") + } +} + +func TestTicketStore_Close(t *testing.T) { + store := NewTicketStore() + ctx := context.Background() + now := time.Now().Truncate(time.Second) + + // Create a resolved ticket + resolvedTime := now.Add(-1 * time.Hour) + store.Create(ctx, &ticket.Ticket{ + ID: "t1", + Status: ticket.StatusResolved, + Resolution: "fixed", + ResolvedAt: &resolvedTime, + CreatedAt: now, + UpdatedAt: now, + }) + + // Close it + err := store.Close(ctx, "t1", "user confirmed", "admin", "127.0.0.1", now) + if err != nil { + t.Fatalf("Close() error = %v", err) + } + + // Verify closed + tkt, _ := store.GetByID(ctx, "t1") + if tkt.Status != ticket.StatusClosed { + t.Errorf("ticket.Status = %s, want %s", tkt.Status, ticket.StatusClosed) + } +} + +func TestTicketStore_Close_NotResolved(t *testing.T) { + store := NewTicketStore() + ctx := context.Background() + now := time.Now().Truncate(time.Second) + + // Create an open ticket (not resolved) + store.Create(ctx, &ticket.Ticket{ + ID: "t1", + Status: ticket.StatusOpen, + CreatedAt: now, + UpdatedAt: now, + }) + + // Try to close — should return error + err := store.Close(ctx, "t1", "user confirmed", "admin", "127.0.0.1", now) + if err == nil { + t.Fatal("Close() on non-resolved ticket should return error") + } +}