379 lines
13 KiB
Go
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)
|
|
}
|