Files
gochat/internal/handler/api/v1/draft_message_handler_test.go
T

345 lines
11 KiB
Go

package v1
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
)
// --- DraftMessage Handler Test Suite ---
type DraftMessageHandlerTestSuite struct {
suite.Suite
router *gin.Engine
db *gorm.DB
testAccount *model.Account
testUser *model.User
testConv *model.Conversation
}
func (s *DraftMessageHandlerTestSuite) SetupSuite() {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
s.Require().NoError(err)
s.db = db
err = db.AutoMigrate(
&model.Account{},
&model.User{},
&model.Inbox{},
&model.Contact{},
&model.ContactInbox{},
&model.Conversation{},
&model.DraftMessage{},
)
s.Require().NoError(err)
// Create test account
account := &model.Account{Name: "DraftHandlerOrg", Locale: "en", Active: true}
s.Require().NoError(db.Create(account).Error)
s.testAccount = account
// Create test user
user := &model.User{Name: "DraftHandlerUser", Email: "draft-handler@test.com", Password: "hashed", Role: "agent", Active: true}
s.Require().NoError(db.Create(user).Error)
s.testUser = user
// Create inbox and contact
inbox := &model.Inbox{AccountID: account.ID, Name: "DraftHandlerInbox", ChannelType: "web_widget", ChannelID: 1}
s.Require().NoError(db.Create(inbox).Error)
contact := &model.Contact{AccountID: account.ID, Name: "DraftHandlerContact"}
s.Require().NoError(db.Create(contact).Error)
// Create conversation
conv := &model.Conversation{AccountID: account.ID, InboxID: inbox.ID, ContactID: contact.ID, Status: "open", ChannelType: "web_widget", Channel: "web_widget"}
s.Require().NoError(db.Create(conv).Error)
s.testConv = conv
// Wire up repos, services, handlers
convRepo := repository.NewConversationRepo(db)
draftRepo := repository.NewDraftMessageRepo(db)
draftSvc := service.NewDraftMessageService(draftRepo, convRepo)
handler := NewDraftMessageHandler(draftSvc)
// Setup router
r := gin.New()
s.router = r
// Register routes
accountGroup := r.Group("/api/v1/accounts/:account_id")
{
convGroup := accountGroup.Group("/conversations/:conversation_id")
{
drafts := convGroup.Group("/draft_messages")
{
drafts.GET("", handler.Show)
drafts.PATCH("", handler.UpdateConversationDraft)
drafts.PUT("", handler.UpdateConversationDraft)
drafts.DELETE("", handler.DeleteConversationDraft)
drafts.GET("/", handler.List)
drafts.POST("/", handler.Create)
drafts.GET("/:draft_id", handler.Get)
drafts.PATCH("/:draft_id", handler.Update)
drafts.DELETE("/:draft_id", handler.Delete)
}
}
}
}
func makeDraftURL(accountID, convID uint) string {
return "/api/v1/accounts/" + strconv.FormatUint(uint64(accountID), 10) +
"/conversations/" + strconv.FormatUint(uint64(convID), 10) +
"/draft_messages"
}
func makeDraftCollectionURL(accountID, convID uint) string {
return makeDraftURL(accountID, convID) + "/"
}
func makeDraftURLWithID(accountID, convID, draftID uint) string {
return makeDraftCollectionURL(accountID, convID) + strconv.FormatUint(uint64(draftID), 10)
}
func (s *DraftMessageHandlerTestSuite) Test_CreateDraft() {
body := map[string]interface{}{
"content": "Hello, this is a draft message",
}
b, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", makeDraftCollectionURL(s.testAccount.ID, s.testConv.ID), bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
data, ok := resp["data"]
if ok && data != nil {
item := data.(map[string]interface{})
assert.Equal(s.T(), "Hello, this is a draft message", item["content"])
}
}
func (s *DraftMessageHandlerTestSuite) Test_ShowDraft_ChatwootShapeWithoutDraft() {
s.Require().NoError(s.db.Exec("DELETE FROM draft_messages").Error)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", makeDraftURL(s.testAccount.ID, s.testConv.ID), nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
assert.Equal(s.T(), false, resp["has_draft"])
assert.NotContains(s.T(), resp, "data")
}
func (s *DraftMessageHandlerTestSuite) Test_UpdateAndShowDraft_ChatwootShape() {
s.Require().NoError(s.db.Exec("DELETE FROM draft_messages").Error)
body := map[string]interface{}{"draft_message": map[string]interface{}{"message": "Saved draft"}}
b, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PATCH", makeDraftURL(s.testAccount.ID, s.testConv.ID), bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
assert.Empty(s.T(), w.Body.String())
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", makeDraftURL(s.testAccount.ID, s.testConv.ID), nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
assert.Equal(s.T(), true, resp["has_draft"])
assert.Equal(s.T(), "Saved draft", resp["message"])
assert.NotContains(s.T(), resp, "success")
}
func (s *DraftMessageHandlerTestSuite) Test_DeleteDraft_ChatwootShape() {
s.Require().NoError(s.db.Exec("DELETE FROM draft_messages").Error)
s.Require().NoError(s.db.Create(&model.DraftMessage{ConversationID: s.testConv.ID, UserID: s.testUser.ID, Content: "Delete me"}).Error)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", makeDraftURL(s.testAccount.ID, s.testConv.ID), nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
assert.Empty(s.T(), w.Body.String())
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", makeDraftURL(s.testAccount.ID, s.testConv.ID), nil)
s.router.ServeHTTP(w, req)
var resp map[string]interface{}
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
assert.Equal(s.T(), false, resp["has_draft"])
}
func (s *DraftMessageHandlerTestSuite) Test_ShowDraft_UsesDisplayIDRoute() {
s.Require().NoError(s.db.Exec("DELETE FROM draft_messages").Error)
displayID := uint(91)
s.testConv.DisplayID = &displayID
s.Require().NoError(s.db.Save(s.testConv).Error)
s.Require().NoError(s.db.Create(&model.DraftMessage{ConversationID: s.testConv.ID, UserID: s.testUser.ID, Content: "Display draft"}).Error)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", makeDraftURL(s.testAccount.ID, displayID), nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
assert.Equal(s.T(), true, resp["has_draft"])
assert.Equal(s.T(), "Display draft", resp["message"])
}
func (s *DraftMessageHandlerTestSuite) Test_ListDrafts() {
// First create a draft
body := map[string]interface{}{
"content": "Draft for listing",
}
b, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", makeDraftCollectionURL(s.testAccount.ID, s.testConv.ID), bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
// Now list
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", makeDraftCollectionURL(s.testAccount.ID, s.testConv.ID), nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
}
func (s *DraftMessageHandlerTestSuite) Test_GetDraft() {
// First create a draft
body := map[string]interface{}{
"content": "Draft to get",
}
b, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", makeDraftCollectionURL(s.testAccount.ID, s.testConv.ID), bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
// Try to get the draft by ID - note: the response structure may vary
// We just verify the list endpoint works for round-trip verification
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", makeDraftCollectionURL(s.testAccount.ID, s.testConv.ID), nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
}
func (s *DraftMessageHandlerTestSuite) Test_DeleteDraft() {
// First create a draft
body := map[string]interface{}{
"content": "Draft to delete",
}
b, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", makeDraftCollectionURL(s.testAccount.ID, s.testConv.ID), bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
data, ok := resp["data"]
if ok && data != nil {
item := data.(map[string]interface{})
if id, ok2 := item["id"]; ok2 {
// Delete
draftID := uint(id.(float64))
w = httptest.NewRecorder()
req, _ = http.NewRequest("DELETE", makeDraftURLWithID(s.testAccount.ID, s.testConv.ID, draftID), nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
}
}
}
func (s *DraftMessageHandlerTestSuite) Test_UpdateDraft() {
// First create a draft
body := map[string]interface{}{
"content": "Original draft",
}
b, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", makeDraftCollectionURL(s.testAccount.ID, s.testConv.ID), bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
data, ok := resp["data"]
if ok && data != nil {
item := data.(map[string]interface{})
if id, ok2 := item["id"]; ok2 {
// Update
draftID := uint(id.(float64))
body = map[string]interface{}{
"content": "Updated draft",
}
b, _ = json.Marshal(body)
w = httptest.NewRecorder()
req, _ = http.NewRequest("PATCH", makeDraftURLWithID(s.testAccount.ID, s.testConv.ID, draftID), bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
}
}
}
func (s *DraftMessageHandlerTestSuite) Test_CreateDraft_InvalidAccountID() {
body := map[string]interface{}{
"content": "Bad account",
}
b, _ := json.Marshal(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/invalid/conversations/1/draft_messages/", bytes.NewReader(b))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func TestDraftMessageHandlerTestSuite(t *testing.T) {
suite.Run(t, new(DraftMessageHandlerTestSuite))
}