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

379 lines
13 KiB
Go

package v1
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"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"
)
// marshalNestedDef wraps body under "custom_attribute_definition" key
// to match Chatwoot params.require(:custom_attribute_definition) format.
func marshalNestedDef(body interface{}) []byte {
wrapped := map[string]interface{}{"custom_attribute_definition": body}
b, _ := json.Marshal(wrapped)
return b
}
// ========== Test Setup ==========
func setupCustomAttrDefHandlerTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
t.Fatalf("failed to open SQLite test database: %v", err)
}
if err := db.AutoMigrate(
&model.Account{},
&model.User{},
&model.CustomAttributeDefinition{},
); err != nil {
t.Fatalf("failed to auto-migrate models: %v", err)
}
t.Cleanup(func() {
sqlDB, _ := db.DB()
sqlDB.Close()
})
return db
}
func setupCustomAttrDefHandlerTest(t *testing.T) (*gin.Engine, *CustomAttributeDefinitionHandler, *gorm.DB, uint) {
t.Helper()
gin.SetMode(gin.TestMode)
db := setupCustomAttrDefHandlerTestDB(t)
repo := repository.NewCustomAttributeDefinitionRepo(db)
svc := service.NewCustomAttributeDefinitionService(repo)
handler := NewCustomAttributeDefinitionHandler(svc)
r := gin.New()
account := createHandlerTestAccount(t, db)
return r, handler, db, account.ID
}
func createHandlerTestAccount(t *testing.T, db *gorm.DB) *model.Account {
t.Helper()
account := &model.Account{Name: "Test Account"}
if err := db.Create(account).Error; err != nil {
t.Fatalf("failed to create test account: %v", err)
}
return account
}
func seedCustomAttrDef(t *testing.T, db *gorm.DB, accountID uint, name, displayName, attrType, attrModel string) *model.CustomAttributeDefinition {
t.Helper()
repo := repository.NewCustomAttributeDefinitionRepo(db)
svc := service.NewCustomAttributeDefinitionService(repo)
def, err := svc.Create(context.Background(), accountID, &service.CreateCustomAttributeDefinitionRequest{
AttributeKey: name,
AttributeDisplayName: displayName,
AttributeDisplayType: attrType,
AttributeModel: attrModel,
})
require.NoError(t, err)
return def
}
func fmtUint(id uint) string {
return fmt.Sprintf("%d", id)
}
// ========== List ==========
func TestCustomAttributeDefinitionHandler_List(t *testing.T) {
r, handler, db, accountID := setupCustomAttrDefHandlerTest(t)
seedCustomAttrDef(t, db, accountID, "custom_priority_score", "Custom Priority Score", "number", "conversation")
seedCustomAttrDef(t, db, accountID, "source_link", "Source Link", "link", "conversation")
r.GET("/api/v1/accounts/:account_id/custom_attribute_definitions", handler.List)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions", accountID), nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp []map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Len(t, resp, 2)
}
func TestCustomAttributeDefinitionHandler_List_FilterByModel(t *testing.T) {
r, handler, db, accountID := setupCustomAttrDefHandlerTest(t)
seedCustomAttrDef(t, db, accountID, "conv_attr", "Conv Attr", "text", "conversation")
seedCustomAttrDef(t, db, accountID, "contact_attr", "Contact Attr", "text", "contact")
r.GET("/api/v1/accounts/:account_id/custom_attribute_definitions", handler.List)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions?attribute_model=conversation_attribute", accountID), nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp []map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Len(t, resp, 1)
}
func TestCustomAttributeDefinitionHandler_List_InvalidAccountID(t *testing.T) {
r, handler, _, _ := setupCustomAttrDefHandlerTest(t)
r.GET("/api/v1/accounts/:account_id/custom_attribute_definitions", handler.List)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/api/v1/accounts/invalid/custom_attribute_definitions", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== Get ==========
func TestCustomAttributeDefinitionHandler_Get(t *testing.T) {
r, handler, db, accountID := setupCustomAttrDefHandlerTest(t)
def := seedCustomAttrDef(t, db, accountID, "custom_priority_score", "Custom Priority Score", "number", "conversation")
r.GET("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Get)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions/%d", accountID, def.ID), nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Equal(t, "custom_priority_score", resp["attribute_key"])
}
func TestCustomAttributeDefinitionHandler_Get_NotFound(t *testing.T) {
r, handler, _, accountID := setupCustomAttrDefHandlerTest(t)
r.GET("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Get)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions/9999", accountID), nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestCustomAttributeDefinitionHandler_Get_InvalidID(t *testing.T) {
r, handler, _, accountID := setupCustomAttrDefHandlerTest(t)
r.GET("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Get)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions/invalid", accountID), nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== Create ==========
func TestCustomAttributeDefinitionHandler_Create(t *testing.T) {
r, handler, _, accountID := setupCustomAttrDefHandlerTest(t)
r.POST("/api/v1/accounts/:account_id/custom_attribute_definitions", handler.Create)
bodyBytes := []byte(`{"attribute_key":"custom_priority_score","attribute_display_name":"Custom Priority Score","attribute_description":"Score","attribute_display_type":1,"attribute_model":0}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions", accountID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Equal(t, "custom_priority_score", resp["attribute_key"])
assert.Equal(t, "number", resp["attribute_display_type"])
assert.Equal(t, "conversation_attribute", resp["attribute_model"])
}
func TestCustomAttributeDefinitionHandler_Create_InvalidType(t *testing.T) {
r, handler, _, accountID := setupCustomAttrDefHandlerTest(t)
r.POST("/api/v1/accounts/:account_id/custom_attribute_definitions", handler.Create)
body := service.CreateCustomAttributeDefinitionRequest{
AttributeKey: "custom_priority_score",
AttributeDisplayName: "Custom Priority Score",
AttributeDisplayType: "invalid_type",
AttributeModel: "conversation",
}
bodyBytes := marshalNestedDef(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions", accountID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestCustomAttributeDefinitionHandler_Create_InvalidAccountID(t *testing.T) {
r, handler, _, _ := setupCustomAttrDefHandlerTest(t)
r.POST("/api/v1/accounts/:account_id/custom_attribute_definitions", handler.Create)
body := service.CreateCustomAttributeDefinitionRequest{
AttributeKey: "custom_priority_score",
AttributeDisplayName: "Custom Priority Score",
AttributeDisplayType: "number",
AttributeModel: "conversation",
}
bodyBytes := marshalNestedDef(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/v1/accounts/invalid/custom_attribute_definitions", bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestCustomAttributeDefinitionHandler_Create_EmptyBody(t *testing.T) {
r, handler, _, accountID := setupCustomAttrDefHandlerTest(t)
r.POST("/api/v1/accounts/:account_id/custom_attribute_definitions", handler.Create)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions", accountID), bytes.NewReader([]byte("{}")))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== Update ==========
func TestCustomAttributeDefinitionHandler_Update(t *testing.T) {
r, handler, db, accountID := setupCustomAttrDefHandlerTest(t)
def := seedCustomAttrDef(t, db, accountID, "custom_priority_score", "Custom Priority Score", "number", "conversation")
r.PUT("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Update)
body := service.UpdateCustomAttributeDefinitionRequest{
AttributeDisplayName: "Priority Level",
AttributeDescription: "Updated description",
}
bodyBytes := marshalNestedDef(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions/%d", accountID, def.ID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &resp)
require.NoError(t, err)
assert.Equal(t, "Priority Level", resp["attribute_display_name"])
}
func TestCustomAttributeDefinitionHandler_Update_NotFound(t *testing.T) {
r, handler, _, accountID := setupCustomAttrDefHandlerTest(t)
r.PUT("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Update)
body := service.UpdateCustomAttributeDefinitionRequest{
AttributeDisplayName: "New Name",
}
bodyBytes := marshalNestedDef(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions/9999", accountID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestCustomAttributeDefinitionHandler_Update_InvalidID(t *testing.T) {
r, handler, _, accountID := setupCustomAttrDefHandlerTest(t)
r.PUT("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Update)
body := service.UpdateCustomAttributeDefinitionRequest{
AttributeDisplayName: "New Name",
}
bodyBytes := marshalNestedDef(body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions/invalid", accountID), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
// ========== Delete ==========
func TestCustomAttributeDefinitionHandler_Delete(t *testing.T) {
r, handler, db, accountID := setupCustomAttrDefHandlerTest(t)
def := seedCustomAttrDef(t, db, accountID, "custom_priority_score", "Custom Priority Score", "number", "conversation")
r.DELETE("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Delete)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions/%d", accountID, def.ID), nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNoContent, w.Code)
}
func TestCustomAttributeDefinitionHandler_Delete_NotFound(t *testing.T) {
r, handler, _, accountID := setupCustomAttrDefHandlerTest(t)
r.DELETE("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Delete)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", fmt.Sprintf("/api/v1/accounts/%d/custom_attribute_definitions/9999", accountID), nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestCustomAttributeDefinitionHandler_Delete_InvalidAccountID(t *testing.T) {
r, handler, _, _ := setupCustomAttrDefHandlerTest(t)
r.DELETE("/api/v1/accounts/:account_id/custom_attribute_definitions/:id", handler.Delete)
w := httptest.NewRecorder()
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/invalid/custom_attribute_definitions/1", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}