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:
Your Name
2026-05-04 13:44:39 +08:00
parent c7cb174c58
commit 087de4e102
23 changed files with 1459 additions and 195 deletions

View File

@@ -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)