Files
gochat/internal/handler/api/v1/message_handler_edge_test.go
T
2026-06-04 15:44:48 +08:00

267 lines
9.0 KiB
Go

package v1
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/gochat/gochat/internal/service"
)
// setupMessageEdgeRouter creates a test router for message handler edge-case tests.
func setupMessageEdgeRouter(handler *MessageHandler) *gin.Engine {
gin.SetMode(gin.TestMode)
router := gin.New()
router.Use(gin.Recovery(), mockAuthMiddleware())
router.POST("/api/v1/accounts/:account_id/conversations/:conversation_id/messages", handler.Create)
router.GET("/api/v1/accounts/:account_id/conversations/:conversation_id/messages", handler.List)
router.GET("/api/v1/accounts/:account_id/messages/search", handler.Search)
router.PATCH("/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:id", handler.Update)
router.DELETE("/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:id", handler.Delete)
return router
}
func newMessageEdgeHandler() *MessageHandler {
return NewMessageHandler(&service.MessageService{})
}
// ===========================
// Create Message edge cases
// ===========================
func TestMessageCreate_EmptyBody(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", nil)
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
// ShouldBindJSON on empty body returns EOF → 400
assert.Equal(t, http.StatusBadRequest, w.Code)
var resp map[string]interface{}
json.Unmarshal(w.Body.Bytes(), &resp)
assert.Contains(t, resp, "error")
}
func TestMessageCreate_InvalidJSON(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewBufferString(`{broken json`))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageCreate_MissingRequiredFields(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
// content is required (validate:"required,min=1"); message_type is required (validate:"required,oneof=...")
w := httptest.NewRecorder()
body := `{"conversation_id":1}`
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
// ShouldBindJSON succeeds, but service.Create rejects via validation → nil service panics → 500
assert.NotEqual(t, http.StatusOK, w.Code)
assert.NotEqual(t, http.StatusCreated, w.Code)
}
func TestMessageCreate_InvalidMessageType(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
// message_type must be oneof=outgoing incoming activity template private_note; "invalid" is not valid
w := httptest.NewRecorder()
body := `{"conversation_id":1,"content":"hello","message_type":"invalid_type"}`
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
// ShouldBindJSON succeeds, but service rejects via validation → nil service panics → 500
assert.NotEqual(t, http.StatusCreated, w.Code)
}
func TestMessageCreate_InvalidContentType(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
// content_type must be oneof=text input_text input_email input_phone select card private_note; "video" is not valid
w := httptest.NewRecorder()
body := `{"conversation_id":1,"content":"hello","message_type":"outgoing","content_type":"video"}`
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/1/messages", bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.NotEqual(t, http.StatusCreated, w.Code)
}
func TestMessageCreate_InvalidAccountID(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/conversations/1/messages", bytes.NewBufferString(`{"content":"hi","message_type":"outgoing","conversation_id":1}`))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageCreate_InvalidConversationID(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
// conversation_id in URL is not a uint
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/conversations/xyz/messages", bytes.NewBufferString(`{"content":"hi","message_type":"outgoing"}`))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageCreate_WrongMethod(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
// Create is POST-only; GET should hit List route (different behavior)
// Let's test DELETE on a POST-only sub-route → 404 for messages/:id with DELETE is valid
// Let's test PATCH on the Create route → 404
w := httptest.NewRecorder()
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1/messages", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
// ===========================
// Search Message edge cases
// ===========================
func TestMessageSearch_InvalidAccountID(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/messages/search?q=test", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageSearch_MissingQueryParam(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
// q is required for search
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/1/messages/search", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageSearch_WrongMethod(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
// Search is GET-only; POST should return 404
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/1/messages/search?q=test", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
// ===========================
// Update Message edge cases
// ===========================
func TestMessageUpdate_EmptyBody(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1/messages/1", nil)
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageUpdate_InvalidJSON(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1/messages/1", bytes.NewBufferString(`{broken`))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageUpdate_InvalidAccountID(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/notvalid/conversations/1/messages/1", bytes.NewBufferString(`{"content":"updated"}`))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageUpdate_InvalidMessageID(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PATCH", "/api/v1/accounts/1/conversations/1/messages/abc", bytes.NewBufferString(`{"content":"updated"}`))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ===========================
// Delete Message edge cases
// ===========================
func TestMessageDelete_InvalidAccountID(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/xyz/conversations/1/messages/1", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestMessageDelete_InvalidMessageID(t *testing.T) {
handler := newMessageEdgeHandler()
router := setupMessageEdgeRouter(handler)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/1/conversations/1/messages/abc", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}