Files
gochat/backend/internal/handler/api/v1/message_handler_test.go_BAK
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
Restructure the monorepo into clear top-level directories:
- backend/: Go module root (cmd, internal, pkg, configs, migrations,
  docs/swagger, scripts, tests, go.mod, Makefile, .air.toml)
- deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd
- docs/: project documentation + reports/ (moved from repo root)
- AGENTS.md: new AI coding-agent guide at repo root

Update all references to the new layout:
- Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root)
- docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile,
  env_file ../../.env, volume mounts ../../backend:/app
- deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile
- CI: working-directory: backend for go commands, file deploy/docker/Dockerfile,
  coverage path backend/coverage.out, health_check backend/scripts/
- backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../
- README: architecture tree, quickstart, config paths updated

Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh,
gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to
preserve history. Build, vet, SQLite tests, and docker compose config verified.
2026-07-07 14:44:12 +08:00

219 lines
6.5 KiB
Plaintext

package v1
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"github.com/gochat/gochat/internal/channel"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
"github.com/gochat/gochat/internal/service"
)
// --- Message Handler Test Suite ---
type MessageHandlerTestSuite struct {
suite.Suite
router *gin.Engine
handler *MessageHandler
db *gorm.DB
}
func (s *MessageHandlerTestSuite) SetupSuite() {
gin.SetMode(gin.TestMode)
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
s.Require().NoError(err)
s.db = db
// AutoMigrate all required models
err = db.AutoMigrate(
&model.Account{},
&model.Inbox{},
&model.Contact{},
&model.Conversation{},
&model.Message{},
)
s.Require().NoError(err)
// Create repositories and services
msgRepo := repository.NewMessageRepo(db)
dispatcher := channel.NewDispatcher()
msgSvc := service.NewMessageService(msgRepo, dispatcher)
// Create handler
s.handler = NewMessageHandler(msgSvc)
// Setup router with message routes
s.router = gin.New()
s.router.GET("/api/v1/accounts/:account_id/conversations/:conversation_id/messages", s.handler.List)
s.router.POST("/api/v1/accounts/:account_id/conversations/:conversation_id/messages", s.handler.Create)
s.router.GET("/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:id", s.handler.Get)
}
func (s *MessageHandlerTestSuite) TearDownSuite() {
if s.db != nil {
sqlDB, _ := s.db.DB()
sqlDB.Close()
}
}
func (s *MessageHandlerTestSuite) SetupTest() {
// Seed data for each test
s.db.Create(&model.Account{Name: "Test Account"})
s.db.Create(&model.Inbox{AccountID: 1, Name: "Test Inbox", ChannelType: "web_widget", ChannelID: 1})
s.db.Create(&model.Contact{AccountID: 1, Name: "Test Contact"})
s.db.Create(&model.Conversation{
AccountID: 1, InboxID: 1, ContactID: 1,
Status: string(model.ConversationStatusOpen), Priority: string(model.ConversationPriorityMedium),
ChannelType: "web_widget", Channel: "web_widget",
})
s.db.Create(&model.Message{
ConversationID: 1, AccountID: 1, InboxID: 1,
Content: "Hello world", ContentType: "text",
MessageType: "outgoing",
})
}
func (s *MessageHandlerTestSuite) TearDownTest() {
s.db.Exec("DELETE FROM messages")
s.db.Exec("DELETE FROM conversations")
s.db.Exec("DELETE FROM contacts")
s.db.Exec("DELETE FROM inboxes")
s.db.Exec("DELETE FROM accounts")
}
func TestMessageHandlerTestSuite(t *testing.T) {
suite.Run(t, new(MessageHandlerTestSuite))
}
// --- Test Cases ---
func (s *MessageHandlerTestSuite) TestList_Success() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages", nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
assert.True(s.T(), resp["success"].(bool))
data, ok := resp["data"].([]interface{})
assert.True(s.T(), ok)
assert.GreaterOrEqual(s.T(), len(data), 1)
}
func (s *MessageHandlerTestSuite) TestList_Empty() {
// Delete seeded messages to test empty list
s.db.Exec("DELETE FROM messages")
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages", nil)
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"].([]interface{})
assert.True(s.T(), ok)
assert.Equal(s.T(), 0, len(data))
}
func (s *MessageHandlerTestSuite) TestList_InvalidConversationID() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/abc/messages", nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *MessageHandlerTestSuite) TestCreate_Success() {
payload := map[string]interface{}{
"content": "New message",
"content_type": "text",
"message_type": "outgoing",
"private": false,
}
body, _ := json.Marshal(payload)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusCreated, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
assert.True(s.T(), resp["success"].(bool))
data := resp["data"].(map[string]interface{})
assert.NotNil(s.T(), data["id"])
assert.Equal(s.T(), "New message", data["content"])
}
func (s *MessageHandlerTestSuite) TestCreate_MissingContent() {
payload := map[string]interface{}{
"content_type": "text",
}
body, _ := json.Marshal(payload)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *MessageHandlerTestSuite) TestCreate_InvalidConversationID() {
payload := map[string]interface{}{
"content": "Test",
"content_type": "text",
"message_type": "outgoing",
}
body, _ := json.Marshal(payload)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/abc/messages", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}
func (s *MessageHandlerTestSuite) TestGet_Success() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages/1", nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusOK, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
assert.True(s.T(), resp["success"].(bool))
data := resp["data"].(map[string]interface{})
assert.Equal(s.T(), float64(1), data["id"])
assert.Equal(s.T(), "Hello world", data["content"])
}
func (s *MessageHandlerTestSuite) TestGet_NotFound() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages/999", nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusNotFound, w.Code)
}
func (s *MessageHandlerTestSuite) TestGet_InvalidID() {
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/conversations/1/messages/abc", nil)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
}