chore: import clean Agent Desk snapshot (2/7)
This commit is contained in:
313
internal/handlers/dashboard/conversation_handler_test.go
Normal file
313
internal/handlers/dashboard/conversation_handler_test.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"agent-desk/internal/ai"
|
||||
"agent-desk/internal/models"
|
||||
"agent-desk/internal/pkg/constants"
|
||||
"agent-desk/internal/pkg/dto"
|
||||
"agent-desk/internal/pkg/dto/request"
|
||||
"agent-desk/internal/pkg/dto/response"
|
||||
"agent-desk/internal/pkg/enums"
|
||||
"agent-desk/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func setupConversationDashboardTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
dbName := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
|
||||
db, err := gorm.Open(sqlite.Open("file:"+dbName+"?mode=memory&cache=shared"), &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: "t_",
|
||||
SingularTable: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite error = %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if sqlDB, err := db.DB(); err == nil {
|
||||
_ = sqlDB.Close()
|
||||
}
|
||||
})
|
||||
|
||||
if err := db.AutoMigrate(
|
||||
&models.Conversation{},
|
||||
&models.Message{},
|
||||
&models.User{},
|
||||
); err != nil {
|
||||
t.Fatalf("auto migrate error = %v", err)
|
||||
}
|
||||
sqls.SetDB(db)
|
||||
return db
|
||||
}
|
||||
|
||||
func TestConversationSuggestReply(t *testing.T) {
|
||||
db := setupConversationDashboardTestDB(t)
|
||||
|
||||
conv := &models.Conversation{
|
||||
ID: 101,
|
||||
Subject: "Suggest Reply Test",
|
||||
}
|
||||
if err := db.Create(conv).Error; err != nil {
|
||||
t.Fatalf("create conv error = %v", err)
|
||||
}
|
||||
msg := &models.Message{
|
||||
ID: 1,
|
||||
ConversationID: conv.ID,
|
||||
ClientMsgID: "msg_1",
|
||||
SenderType: enums.IMSenderTypeCustomer,
|
||||
MessageType: enums.IMMessageTypeText,
|
||||
Content: "Can you help me reset my password?",
|
||||
}
|
||||
if err := db.Create(msg).Error; err != nil {
|
||||
t.Fatalf("create msg error = %v", err)
|
||||
}
|
||||
|
||||
t.Run("unauthorized when no auth principal", func(t *testing.T) {
|
||||
router := gin.New()
|
||||
router.POST("/api/dashboard/conversation/suggest_reply", ConversationPostSuggest_reply)
|
||||
|
||||
body, _ := json.Marshal(request.SuggestReplyRequest{ConversationID: conv.ID})
|
||||
req, _ := http.NewRequest(http.MethodPost, "/api/dashboard/conversation/suggest_reply", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||
if resp["success"] == true {
|
||||
t.Fatalf("expected failure without auth, got success: true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("forbidden when principal lacks permission", func(t *testing.T) {
|
||||
router := gin.New()
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Set("authPrincipal", &dto.AuthPrincipal{
|
||||
UserID: 1,
|
||||
Username: "agent",
|
||||
Permissions: []string{"unrelated.permission"},
|
||||
})
|
||||
c.Next()
|
||||
})
|
||||
router.POST("/api/dashboard/conversation/suggest_reply", ConversationPostSuggest_reply)
|
||||
|
||||
body, _ := json.Marshal(request.SuggestReplyRequest{ConversationID: conv.ID})
|
||||
req, _ := http.NewRequest(http.MethodPost, "/api/dashboard/conversation/suggest_reply", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||
if resp["success"] == true {
|
||||
t.Fatalf("expected forbidden error, got success: true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid params when conversationId <= 0", func(t *testing.T) {
|
||||
router := gin.New()
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Set("authPrincipal", &dto.AuthPrincipal{
|
||||
UserID: 1,
|
||||
Username: "agent",
|
||||
Permissions: []string{constants.PermissionConversationView.Code},
|
||||
})
|
||||
c.Next()
|
||||
})
|
||||
router.POST("/api/dashboard/conversation/suggest_reply", ConversationPostSuggest_reply)
|
||||
|
||||
body, _ := json.Marshal(request.SuggestReplyRequest{ConversationID: 0})
|
||||
req, _ := http.NewRequest(http.MethodPost, "/api/dashboard/conversation/suggest_reply", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||
if resp["success"] == true {
|
||||
t.Fatalf("expected error for conversationId <= 0, got success: true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("successful suggestion", func(t *testing.T) {
|
||||
restore := services.AICopilotService.SetChatFnForTest(func(ctx context.Context, systemPrompt, userPrompt string) (*ai.ChatCompletionResult, error) {
|
||||
return &ai.ChatCompletionResult{
|
||||
Content: "Click 'Forgot Password' to reset your credentials.",
|
||||
ModelName: "mock-model",
|
||||
}, nil
|
||||
})
|
||||
defer restore()
|
||||
|
||||
router := gin.New()
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Set("authPrincipal", &dto.AuthPrincipal{
|
||||
UserID: 1,
|
||||
Username: "agent",
|
||||
Permissions: []string{constants.PermissionConversationView.Code},
|
||||
})
|
||||
c.Next()
|
||||
})
|
||||
router.POST("/api/dashboard/conversation/suggest_reply", ConversationPostSuggest_reply)
|
||||
|
||||
body, _ := json.Marshal(request.SuggestReplyRequest{ConversationID: conv.ID})
|
||||
req, _ := http.NewRequest(http.MethodPost, "/api/dashboard/conversation/suggest_reply", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
Code int `json:"code"`
|
||||
Success bool `json:"success"`
|
||||
Data response.SuggestReplyResponse `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("unmarshal response error = %v", err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Fatalf("expected success: true, got false. Body: %s", rec.Body.String())
|
||||
}
|
||||
expectedSuggestion := "Click 'Forgot Password' to reset your credentials."
|
||||
if resp.Data.Suggestion != expectedSuggestion {
|
||||
t.Fatalf("expected suggestion %q, got %q", expectedSuggestion, resp.Data.Suggestion)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestConversationSummarize(t *testing.T) {
|
||||
db := setupConversationDashboardTestDB(t)
|
||||
|
||||
conv := &models.Conversation{
|
||||
ID: 201,
|
||||
Subject: "Summarize Test",
|
||||
}
|
||||
if err := db.Create(conv).Error; err != nil {
|
||||
t.Fatalf("create conv error = %v", err)
|
||||
}
|
||||
msg := &models.Message{
|
||||
ID: 20,
|
||||
ConversationID: conv.ID,
|
||||
ClientMsgID: "msg_20",
|
||||
SenderType: enums.IMSenderTypeCustomer,
|
||||
MessageType: enums.IMMessageTypeText,
|
||||
Content: "Billing dispute for invoice #1234",
|
||||
}
|
||||
if err := db.Create(msg).Error; err != nil {
|
||||
t.Fatalf("create msg error = %v", err)
|
||||
}
|
||||
|
||||
t.Run("unauthorized when no auth principal", func(t *testing.T) {
|
||||
router := gin.New()
|
||||
router.POST("/api/dashboard/conversation/summarize", ConversationPostSummarize)
|
||||
|
||||
body, _ := json.Marshal(request.SummarizeConversationRequest{ConversationID: conv.ID})
|
||||
req, _ := http.NewRequest(http.MethodPost, "/api/dashboard/conversation/summarize", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||
if resp["success"] == true {
|
||||
t.Fatalf("expected failure without auth, got success: true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid params when conversationId <= 0", func(t *testing.T) {
|
||||
router := gin.New()
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Set("authPrincipal", &dto.AuthPrincipal{
|
||||
UserID: 1,
|
||||
Username: "agent",
|
||||
Permissions: []string{constants.PermissionConversationView.Code},
|
||||
})
|
||||
c.Next()
|
||||
})
|
||||
router.POST("/api/dashboard/conversation/summarize", ConversationPostSummarize)
|
||||
|
||||
body, _ := json.Marshal(request.SummarizeConversationRequest{ConversationID: -5})
|
||||
req, _ := http.NewRequest(http.MethodPost, "/api/dashboard/conversation/summarize", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(rec.Body.Bytes(), &resp)
|
||||
if resp["success"] == true {
|
||||
t.Fatalf("expected error for conversationId <= 0, got success: true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("successful summary", func(t *testing.T) {
|
||||
restore := services.AICopilotService.SetChatFnForTest(func(ctx context.Context, systemPrompt, userPrompt string) (*ai.ChatCompletionResult, error) {
|
||||
return &ai.ChatCompletionResult{
|
||||
Content: "Summary: Customer inquired about invoice #1234.",
|
||||
ModelName: "mock-model",
|
||||
}, nil
|
||||
})
|
||||
defer restore()
|
||||
|
||||
router := gin.New()
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Set("authPrincipal", &dto.AuthPrincipal{
|
||||
UserID: 1,
|
||||
Username: "agent",
|
||||
Permissions: []string{constants.PermissionConversationView.Code},
|
||||
})
|
||||
c.Next()
|
||||
})
|
||||
router.POST("/api/dashboard/conversation/summarize", ConversationPostSummarize)
|
||||
|
||||
body, _ := json.Marshal(request.SummarizeConversationRequest{ConversationID: conv.ID})
|
||||
req, _ := http.NewRequest(http.MethodPost, "/api/dashboard/conversation/summarize", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
router.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
var resp struct {
|
||||
Code int `json:"code"`
|
||||
Success bool `json:"success"`
|
||||
Data response.ConversationSummaryResponse `json:"data"`
|
||||
}
|
||||
if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("unmarshal response error = %v", err)
|
||||
}
|
||||
if !resp.Success {
|
||||
t.Fatalf("expected success: true, got false. Body: %s", rec.Body.String())
|
||||
}
|
||||
expectedSummary := "Summary: Customer inquired about invoice #1234."
|
||||
if resp.Data.Summary != expectedSummary {
|
||||
t.Fatalf("expected summary %q, got %q", expectedSummary, resp.Data.Summary)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user