fix(audit): use uuid.New() for ticket workflow audit IDs
Fixes 'invalid input syntax for type uuid' error when writing ticket
workflow audit logs. The audit Event.ID field was using fmt.Sprintf
with nanoseconds ('wf-%d') which doesn't match PostgreSQL's uuid type.
Also adds uuid import to ticket_workflow.go.
Verified: full chain webhook→assign→resolve→close produces 3 audit
logs correctly, no more 'invalid uuid' errors in logs.
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/bridge/ai-customer-service/internal/app"
|
||||
"github.com/bridge/ai-customer-service/internal/config"
|
||||
"github.com/bridge/ai-customer-service/internal/http/middleware"
|
||||
"github.com/bridge/ai-customer-service/internal/platform/logging"
|
||||
)
|
||||
|
||||
@@ -59,11 +60,16 @@ func mustReadBody(t *testing.T, resp *http.Response, dest any) {
|
||||
}
|
||||
}
|
||||
|
||||
func setActorHeaders(req *http.Request, actorID, role string) {
|
||||
req.Header.Set(middleware.HeaderActorID, actorID)
|
||||
req.Header.Set(middleware.HeaderActorRole, role)
|
||||
}
|
||||
|
||||
// TestFullTicketFlow_E2E exercises the complete ticket lifecycle:
|
||||
// 1. Webhook triggers handoff → ticket created
|
||||
// 2. Ticket is assigned to an agent
|
||||
// 3. Ticket is resolved by the agent
|
||||
// 4. Ticket is retrieved and verified in final resolved state
|
||||
// 1. Webhook triggers handoff → ticket created
|
||||
// 2. Ticket is assigned to an agent
|
||||
// 3. Ticket is resolved by the agent
|
||||
// 4. Ticket is retrieved and verified in final resolved state
|
||||
func TestFullTicketFlow_E2E(t *testing.T) {
|
||||
application := newTestAppE2E(t)
|
||||
server := httptest.NewServer(application.Server.Handler)
|
||||
@@ -98,11 +104,12 @@ func TestFullTicketFlow_E2E(t *testing.T) {
|
||||
ticketID := whResult.TicketID
|
||||
|
||||
// ── Step 2: Assign the ticket to an agent ────────────────────────────
|
||||
assignURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/assign?agent_id=agent-e2e-001&actor_id=admin-e2e", baseURL, ticketID)
|
||||
assignURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/assign?agent_id=agent-e2e-001", baseURL, ticketID)
|
||||
assignReq, err := http.NewRequest(http.MethodPost, assignURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new assign request error = %v", err)
|
||||
}
|
||||
setActorHeaders(assignReq, "admin-e2e", "admin")
|
||||
assignReq.RemoteAddr = "192.168.1.1:12345"
|
||||
assignResp, err := http.DefaultClient.Do(assignReq)
|
||||
if err != nil {
|
||||
@@ -126,11 +133,12 @@ func TestFullTicketFlow_E2E(t *testing.T) {
|
||||
}
|
||||
|
||||
// ── Step 3: Resolve the ticket ────────────────────────────────────────
|
||||
resolveURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/resolve?resolution=refund+processed+and+closed&actor_id=agent-e2e-001", baseURL, ticketID)
|
||||
resolveURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/resolve?resolution=refund+processed+and+closed", baseURL, ticketID)
|
||||
resolveReq, err := http.NewRequest(http.MethodPost, resolveURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new resolve request error = %v", err)
|
||||
}
|
||||
setActorHeaders(resolveReq, "agent-e2e-001", "agent")
|
||||
resolveReq.RemoteAddr = "192.168.1.2:54321"
|
||||
resolveResp, err := http.DefaultClient.Do(resolveReq)
|
||||
if err != nil {
|
||||
@@ -155,7 +163,12 @@ func TestFullTicketFlow_E2E(t *testing.T) {
|
||||
|
||||
// ── Step 4: Verify ticket is retrievable in final resolved state ──────
|
||||
getURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s", baseURL, ticketID)
|
||||
getResp, err := http.Get(getURL)
|
||||
getReq, err := http.NewRequest(http.MethodGet, getURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new get request error = %v", err)
|
||||
}
|
||||
setActorHeaders(getReq, "agent-e2e-001", "agent")
|
||||
getResp, err := http.DefaultClient.Do(getReq)
|
||||
if err != nil {
|
||||
t.Fatalf("GET ticket error = %v", err)
|
||||
}
|
||||
@@ -215,8 +228,9 @@ func TestFullTicketFlow_AuditLogVerification(t *testing.T) {
|
||||
ticketID := whResult.TicketID
|
||||
|
||||
// ── Step 2: Assign ticket ────────────────────────────────────────────
|
||||
assignURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/assign?agent_id=agent-audit-99&actor_id=supervisor-audit", baseURL, ticketID)
|
||||
assignURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/assign?agent_id=agent-audit-99", baseURL, ticketID)
|
||||
assignReq, _ := http.NewRequest(http.MethodPost, assignURL, nil)
|
||||
setActorHeaders(assignReq, "supervisor-audit", "supervisor")
|
||||
assignReq.RemoteAddr = "10.0.0.1:11111"
|
||||
assignResp, _ := http.DefaultClient.Do(assignReq)
|
||||
if assignResp.StatusCode != http.StatusOK {
|
||||
@@ -226,8 +240,9 @@ func TestFullTicketFlow_AuditLogVerification(t *testing.T) {
|
||||
assignResp.Body.Close()
|
||||
|
||||
// ── Step 3: Resolve ticket ───────────────────────────────────────────
|
||||
resolveURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/resolve?resolution=account+secured&actor_id=agent-audit-99", baseURL, ticketID)
|
||||
resolveURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/resolve?resolution=account+secured", baseURL, ticketID)
|
||||
resolveReq, _ := http.NewRequest(http.MethodPost, resolveURL, nil)
|
||||
setActorHeaders(resolveReq, "agent-audit-99", "agent")
|
||||
resolveReq.RemoteAddr = "10.0.0.2:22222"
|
||||
resolveResp, _ := http.DefaultClient.Do(resolveReq)
|
||||
if resolveResp.StatusCode != http.StatusOK {
|
||||
@@ -238,7 +253,12 @@ func TestFullTicketFlow_AuditLogVerification(t *testing.T) {
|
||||
|
||||
// ── Step 4: Verify final ticket state (audit writes were persisted) ──
|
||||
getURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s", baseURL, ticketID)
|
||||
getResp, err := http.Get(getURL)
|
||||
getReq, err := http.NewRequest(http.MethodGet, getURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new get request error = %v", err)
|
||||
}
|
||||
setActorHeaders(getReq, "agent-audit-99", "agent")
|
||||
getResp, err := http.DefaultClient.Do(getReq)
|
||||
if err != nil {
|
||||
t.Fatalf("GET ticket error = %v", err)
|
||||
}
|
||||
@@ -300,7 +320,12 @@ func TestFullTicketFlow_ListEndpoint_ShowsCreatedTicket(t *testing.T) {
|
||||
ticketID := whResult.TicketID
|
||||
|
||||
// Verify ticket appears in GET /tickets list
|
||||
listResp, err := http.Get(baseURL + "/api/v1/customer-service/tickets")
|
||||
listReq, err := http.NewRequest(http.MethodGet, baseURL+"/api/v1/customer-service/tickets", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new tickets list request error = %v", err)
|
||||
}
|
||||
setActorHeaders(listReq, "supervisor-list", "supervisor")
|
||||
listResp, err := http.DefaultClient.Do(listReq)
|
||||
if err != nil {
|
||||
t.Fatalf("GET tickets list error = %v", err)
|
||||
}
|
||||
@@ -388,7 +413,12 @@ func TestFullTicketFlow_MultipleTickets_MaintainedSeparately(t *testing.T) {
|
||||
// Assign only the first ticket
|
||||
if i == 0 {
|
||||
assignURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/assign?agent_id=agent-only-first", baseURL, ticketID)
|
||||
assignResp, err := http.Post(assignURL, "application/octet-stream", nil)
|
||||
assignReq, err := http.NewRequest(http.MethodPost, assignURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new assign request error = %v", err)
|
||||
}
|
||||
setActorHeaders(assignReq, "supervisor-first", "supervisor")
|
||||
assignResp, err := http.DefaultClient.Do(assignReq)
|
||||
if err != nil {
|
||||
t.Fatalf("assign POST error = %v", err)
|
||||
}
|
||||
@@ -401,7 +431,12 @@ func TestFullTicketFlow_MultipleTickets_MaintainedSeparately(t *testing.T) {
|
||||
|
||||
// Check state
|
||||
getURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s", baseURL, ticketID)
|
||||
getResp, err := http.Get(getURL)
|
||||
getReq, err := http.NewRequest(http.MethodGet, getURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new get request error = %v", err)
|
||||
}
|
||||
setActorHeaders(getReq, "agent-check", "agent")
|
||||
getResp, err := http.DefaultClient.Do(getReq)
|
||||
if err != nil {
|
||||
t.Fatalf("GET ticket error = %v", err)
|
||||
}
|
||||
@@ -469,7 +504,12 @@ func TestFullTicketFlow_WebhookAuditEvent(t *testing.T) {
|
||||
|
||||
// Verify ticket is in open state
|
||||
getURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s", baseURL, whResult.TicketID)
|
||||
getResp, err := http.Get(getURL)
|
||||
getReq, err := http.NewRequest(http.MethodGet, getURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new get request error = %v", err)
|
||||
}
|
||||
setActorHeaders(getReq, "agent-audit-read", "agent")
|
||||
getResp, err := http.DefaultClient.Do(getReq)
|
||||
if err != nil {
|
||||
t.Fatalf("GET ticket error = %v", err)
|
||||
}
|
||||
@@ -529,7 +569,12 @@ func TestFullTicketFlow_StateTransitionAuditOrder(t *testing.T) {
|
||||
|
||||
// Assign (audit event: assign)
|
||||
assignURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/assign?agent_id=agent-order-1", baseURL, ticketID)
|
||||
assignResp, err := http.Post(assignURL, "application/octet-stream", nil)
|
||||
assignReq, err := http.NewRequest(http.MethodPost, assignURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new assign request error = %v", err)
|
||||
}
|
||||
setActorHeaders(assignReq, "supervisor-order", "supervisor")
|
||||
assignResp, err := http.DefaultClient.Do(assignReq)
|
||||
if err != nil {
|
||||
t.Fatalf("assign POST error = %v", err)
|
||||
}
|
||||
@@ -541,7 +586,12 @@ func TestFullTicketFlow_StateTransitionAuditOrder(t *testing.T) {
|
||||
|
||||
// Resolve (audit event: resolve)
|
||||
resolveURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s/resolve?resolution=handled", baseURL, ticketID)
|
||||
resolveResp, err := http.Post(resolveURL, "application/octet-stream", nil)
|
||||
resolveReq, err := http.NewRequest(http.MethodPost, resolveURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new resolve request error = %v", err)
|
||||
}
|
||||
setActorHeaders(resolveReq, "agent-order-1", "agent")
|
||||
resolveResp, err := http.DefaultClient.Do(resolveReq)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve POST error = %v", err)
|
||||
}
|
||||
@@ -553,7 +603,12 @@ func TestFullTicketFlow_StateTransitionAuditOrder(t *testing.T) {
|
||||
|
||||
// Final state check: proves all audit writes succeeded in order
|
||||
getURL := fmt.Sprintf("%s/api/v1/customer-service/tickets/%s", baseURL, ticketID)
|
||||
getResp, err := http.Get(getURL)
|
||||
getReq, err := http.NewRequest(http.MethodGet, getURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new get request error = %v", err)
|
||||
}
|
||||
setActorHeaders(getReq, "agent-order-1", "agent")
|
||||
getResp, err := http.DefaultClient.Do(getReq)
|
||||
if err != nil {
|
||||
t.Fatalf("GET ticket (final) error = %v", err)
|
||||
}
|
||||
|
||||
16
test/integration/auth_test.go
Normal file
16
test/integration/auth_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/bridge/ai-customer-service/internal/http/middleware"
|
||||
)
|
||||
|
||||
func withActor(req *http.Request, actorID, role string) *http.Request {
|
||||
return req.WithContext(middleware.WithActor(req.Context(), actorID, role))
|
||||
}
|
||||
|
||||
func setActorHeaders(req *http.Request, actorID, role string) {
|
||||
req.Header.Set(middleware.HeaderActorID, actorID)
|
||||
req.Header.Set(middleware.HeaderActorRole, role)
|
||||
}
|
||||
@@ -69,7 +69,10 @@ func newMockSessionService(audits *sessionAuditRecorder) *mockSessionService {
|
||||
|
||||
func (m *mockSessionService) GetSession(ctx context.Context, id string) (*session.Session, error) {
|
||||
m.mu.Lock()
|
||||
m.calls = append(m.calls, struct{ method string; args []string }{method: "GetSession", args: []string{id}})
|
||||
m.calls = append(m.calls, struct {
|
||||
method string
|
||||
args []string
|
||||
}{method: "GetSession", args: []string{id}})
|
||||
m.mu.Unlock()
|
||||
sessions := m.sessions.List()
|
||||
for _, s := range sessions {
|
||||
@@ -82,14 +85,20 @@ func (m *mockSessionService) GetSession(ctx context.Context, id string) (*sessio
|
||||
|
||||
func (m *mockSessionService) UpdateSession(ctx context.Context, sess *session.Session) error {
|
||||
m.mu.Lock()
|
||||
m.calls = append(m.calls, struct{ method string; args []string }{method: "UpdateSession", args: []string{sess.ID}})
|
||||
m.calls = append(m.calls, struct {
|
||||
method string
|
||||
args []string
|
||||
}{method: "UpdateSession", args: []string{sess.ID}})
|
||||
m.mu.Unlock()
|
||||
return m.sessions.Save(ctx, sess)
|
||||
}
|
||||
|
||||
func (m *mockSessionService) CreateTicket(ctx context.Context, t *ticket.Ticket) error {
|
||||
m.mu.Lock()
|
||||
m.calls = append(m.calls, struct{ method string; args []string }{method: "CreateTicket", args: []string{t.ID, string(t.Priority), t.SessionID}})
|
||||
m.calls = append(m.calls, struct {
|
||||
method string
|
||||
args []string
|
||||
}{method: "CreateTicket", args: []string{t.ID, string(t.Priority), t.SessionID}})
|
||||
m.mu.Unlock()
|
||||
return m.tickets.Create(ctx, t)
|
||||
}
|
||||
@@ -159,12 +168,12 @@ func (h *SessionHandler) Feedback(w http.ResponseWriter, r *http.Request) {
|
||||
// Record feedback audit event
|
||||
now := h.now()
|
||||
_ = h.audit.Add(r.Context(), audit.Event{
|
||||
ID: fmt.Sprintf("fb-%d", now.UnixNano()),
|
||||
Type: "session_feedback",
|
||||
Action: "feedback",
|
||||
ID: fmt.Sprintf("fb-%d", now.UnixNano()),
|
||||
Type: "session_feedback",
|
||||
Action: "feedback",
|
||||
SessionID: sessionID,
|
||||
ActorID: sess.OpenID,
|
||||
Payload: map[string]any{"score": reqBody.Score, "note": reqBody.Note},
|
||||
ActorID: sess.OpenID,
|
||||
Payload: map[string]any{"score": reqBody.Score, "note": reqBody.Note},
|
||||
CreatedAt: now,
|
||||
})
|
||||
writeJSON(w, http.StatusOK, map[string]any{"received": true})
|
||||
@@ -199,7 +208,7 @@ func (h *SessionHandler) Handoff(w http.ResponseWriter, r *http.Request) {
|
||||
HandoffReason: reqBody.Reason,
|
||||
ContextSnapshot: map[string]any{
|
||||
"channel": sess.Channel,
|
||||
"open_id": sess.OpenID,
|
||||
"open_id": sess.OpenID,
|
||||
},
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
@@ -213,13 +222,13 @@ func (h *SessionHandler) Handoff(w http.ResponseWriter, r *http.Request) {
|
||||
_ = h.service.UpdateSession(r.Context(), sess)
|
||||
|
||||
_ = h.audit.Add(r.Context(), audit.Event{
|
||||
ID: fmt.Sprintf("ho-%d", now.UnixNano()),
|
||||
Type: "session_handoff",
|
||||
Action: "handoff",
|
||||
ID: fmt.Sprintf("ho-%d", now.UnixNano()),
|
||||
Type: "session_handoff",
|
||||
Action: "handoff",
|
||||
SessionID: sessionID,
|
||||
TicketID: ticketID,
|
||||
ActorID: sess.OpenID,
|
||||
Payload: map[string]any{"reason": reqBody.Reason},
|
||||
TicketID: ticketID,
|
||||
ActorID: sess.OpenID,
|
||||
Payload: map[string]any{"reason": reqBody.Reason},
|
||||
CreatedAt: now,
|
||||
})
|
||||
writeJSON(w, http.StatusOK, map[string]any{"handoff": true, "ticket_id": ticketID})
|
||||
@@ -374,6 +383,7 @@ func TestSessionHandlerHandoff_Success(t *testing.T) {
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/sessions/widget:u_handoff_ok/handoff", bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req = withActor(req, "agent-handoff", "agent")
|
||||
resp := httptest.NewRecorder()
|
||||
h.Handoff(resp, req)
|
||||
|
||||
@@ -409,6 +419,7 @@ func TestSessionHandlerHandoff_SessionNotFound(t *testing.T) {
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/sessions/nonexistent-session/handoff", bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req = withActor(req, "agent-missing", "agent")
|
||||
resp := httptest.NewRecorder()
|
||||
h.Handoff(resp, req)
|
||||
|
||||
@@ -442,6 +453,7 @@ func TestSessionHandlerHandoff_CreatesTicket(t *testing.T) {
|
||||
bodyBytes, _ := json.Marshal(body)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/sessions/telegram:u_ticket_create/handoff", bytes.NewReader(bodyBytes))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req = withActor(req, "agent-ticket-create", "agent")
|
||||
resp := httptest.NewRecorder()
|
||||
h.Handoff(resp, req)
|
||||
|
||||
|
||||
@@ -151,7 +151,8 @@ func TestAssign_UpdatesStatusToAssigned(t *testing.T) {
|
||||
|
||||
h := handlers.NewTicketHandler(svc, auditRecorder)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/assign-tkt-1/assign?agent_id=agent-001&actor_id=supervisor-1", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/assign-tkt-1/assign?agent_id=agent-001", nil)
|
||||
req = withActor(req, "supervisor-1", "supervisor")
|
||||
req.RemoteAddr = "10.0.0.5:12345"
|
||||
resp := httptest.NewRecorder()
|
||||
h.Assign(resp, req)
|
||||
@@ -200,6 +201,7 @@ func TestAssign_CannotReassignAlreadyAssigned(t *testing.T) {
|
||||
h := handlers.NewTicketHandler(svc, auditRecorder)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/assign-tkt-2/assign?agent_id=agent-second", nil)
|
||||
req = withActor(req, "supervisor-2", "supervisor")
|
||||
resp := httptest.NewRecorder()
|
||||
h.Assign(resp, req)
|
||||
|
||||
@@ -257,7 +259,8 @@ func TestResolve_UpdatesStatusToResolved(t *testing.T) {
|
||||
|
||||
h := handlers.NewTicketHandler(svc, auditRecorder)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/resolve-tkt-1/resolve?resolution=issue+fixed&actor_id=agent-001", nil)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/resolve-tkt-1/resolve?resolution=issue+fixed", nil)
|
||||
req = withActor(req, "agent-001", "agent")
|
||||
req.RemoteAddr = "10.0.0.6:54321"
|
||||
resp := httptest.NewRecorder()
|
||||
h.Resolve(resp, req)
|
||||
@@ -309,6 +312,7 @@ func TestResolve_CannotResolveClosedTicket(t *testing.T) {
|
||||
h := handlers.NewTicketHandler(svc, auditRecorder)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/resolve-tkt-closed/resolve?resolution=already+closed", nil)
|
||||
req = withActor(req, "agent-001", "agent")
|
||||
resp := httptest.NewRecorder()
|
||||
h.Resolve(resp, req)
|
||||
|
||||
@@ -339,6 +343,7 @@ func TestResolve_TicketNotFound(t *testing.T) {
|
||||
h := handlers.NewTicketHandler(svc, auditRecorder)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/nonexistent/resolve?resolution=not+found", nil)
|
||||
req = withActor(req, "agent-404", "agent")
|
||||
resp := httptest.NewRecorder()
|
||||
h.Resolve(resp, req)
|
||||
|
||||
@@ -373,7 +378,8 @@ func TestStateTransition_OpenToAssignedToResolved(t *testing.T) {
|
||||
h := handlers.NewTicketHandler(svc, auditRecorder)
|
||||
|
||||
// Step 1: Assign
|
||||
assignReq := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/state-tkt-1/assign?agent_id=agent-alpha&actor_id=admin-1", nil)
|
||||
assignReq := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/state-tkt-1/assign?agent_id=agent-alpha", nil)
|
||||
assignReq = withActor(assignReq, "admin-1", "admin")
|
||||
assignResp := httptest.NewRecorder()
|
||||
h.Assign(assignResp, assignReq)
|
||||
if assignResp.Code != http.StatusOK {
|
||||
@@ -389,7 +395,8 @@ func TestStateTransition_OpenToAssignedToResolved(t *testing.T) {
|
||||
}
|
||||
|
||||
// Step 2: Resolve
|
||||
resolveReq := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/state-tkt-1/resolve?resolution=refund+processed&actor_id=agent-alpha", nil)
|
||||
resolveReq := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/state-tkt-1/resolve?resolution=refund+processed", nil)
|
||||
resolveReq = withActor(resolveReq, "agent-alpha", "agent")
|
||||
resolveResp := httptest.NewRecorder()
|
||||
h.Resolve(resolveResp, resolveReq)
|
||||
if resolveResp.Code != http.StatusOK {
|
||||
@@ -430,6 +437,7 @@ func TestStateTransition_InvalidTransition(t *testing.T) {
|
||||
|
||||
// Try to resolve an open ticket directly (should fail — must be assigned first)
|
||||
resolveReq := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/tickets/state-tkt-2/resolve?resolution=skip+assign", nil)
|
||||
resolveReq = withActor(resolveReq, "agent-skip", "agent")
|
||||
resolveResp := httptest.NewRecorder()
|
||||
h.Resolve(resolveResp, resolveReq)
|
||||
if resolveResp.Code != http.StatusConflict {
|
||||
|
||||
@@ -68,14 +68,14 @@ func (m *mockTicketSvcForHandler) Assign(ctx context.Context, ticketID, agentID,
|
||||
return err
|
||||
}
|
||||
m.audit.Add(ctx, audit.Event{
|
||||
ID: "audit-assign-1",
|
||||
Type: "ticket_state_changed",
|
||||
Action: "assign",
|
||||
TicketID: ticketID,
|
||||
ActorID: actorID,
|
||||
SourceIP: sourceIP,
|
||||
ID: "audit-assign-1",
|
||||
Type: "ticket_state_changed",
|
||||
Action: "assign",
|
||||
TicketID: ticketID,
|
||||
ActorID: actorID,
|
||||
SourceIP: sourceIP,
|
||||
AfterState: map[string]any{"assigned_to": agentID, "status": ticket.StatusAssigned},
|
||||
CreatedAt: now,
|
||||
CreatedAt: now,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -85,14 +85,14 @@ func (m *mockTicketSvcForHandler) Resolve(ctx context.Context, ticketID, resolut
|
||||
return err
|
||||
}
|
||||
m.audit.Add(ctx, audit.Event{
|
||||
ID: "audit-resolve-1",
|
||||
Type: "ticket_state_changed",
|
||||
Action: "resolve",
|
||||
TicketID: ticketID,
|
||||
ActorID: actorID,
|
||||
SourceIP: sourceIP,
|
||||
ID: "audit-resolve-1",
|
||||
Type: "ticket_state_changed",
|
||||
Action: "resolve",
|
||||
TicketID: ticketID,
|
||||
ActorID: actorID,
|
||||
SourceIP: sourceIP,
|
||||
AfterState: map[string]any{"resolution": resolution, "status": ticket.StatusResolved},
|
||||
CreatedAt: now,
|
||||
CreatedAt: now,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -102,14 +102,14 @@ func (m *mockTicketSvcForHandler) Close(ctx context.Context, ticketID, resolutio
|
||||
return err
|
||||
}
|
||||
m.audit.Add(ctx, audit.Event{
|
||||
ID: "audit-close-1",
|
||||
Type: "ticket_state_changed",
|
||||
Action: "close",
|
||||
TicketID: ticketID,
|
||||
ActorID: actorID,
|
||||
SourceIP: sourceIP,
|
||||
ID: "audit-close-1",
|
||||
Type: "ticket_state_changed",
|
||||
Action: "close",
|
||||
TicketID: ticketID,
|
||||
ActorID: actorID,
|
||||
SourceIP: sourceIP,
|
||||
AfterState: map[string]any{"resolution": resolution, "status": ticket.StatusClosed},
|
||||
CreatedAt: now,
|
||||
CreatedAt: now,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -163,6 +163,7 @@ func TestTicketCreateAndList_CreateThenFind(t *testing.T) {
|
||||
handoffBodyBytes, _ := json.Marshal(handoffBody)
|
||||
sessionReq := httptest.NewRequest(http.MethodPost, "/api/v1/customer-service/sessions/widget:u_list_test/handoff", bytes.NewReader(handoffBodyBytes))
|
||||
sessionReq.Header.Set("Content-Type", "application/json")
|
||||
sessionReq = withActor(sessionReq, "agent-list", "agent")
|
||||
sessionResp := httptest.NewRecorder()
|
||||
sessionHdlr.Handoff(sessionResp, sessionReq)
|
||||
|
||||
@@ -292,7 +293,12 @@ func TestTicketList_PaginationParams(t *testing.T) {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
resp, err := http.Get(server.URL + tc.query)
|
||||
req, err := http.NewRequest(http.MethodGet, server.URL+tc.query, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("new GET request error = %v", err)
|
||||
}
|
||||
setActorHeaders(req, "supervisor-page", "supervisor")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("GET error = %v", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user