493 lines
15 KiB
Go
493 lines
15 KiB
Go
package v1
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"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"
|
|
)
|
|
|
|
// --- Account Handler Test Suite ---
|
|
// Uses real SQLite DB + real repo + real service.
|
|
// AccountHandler stores *service.AccountService (concrete type), so mocks can't be injected.
|
|
|
|
type AccountHandlerTestSuite struct {
|
|
suite.Suite
|
|
router *gin.Engine
|
|
handler *AccountHandler
|
|
db *gorm.DB
|
|
testUserID uint
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) 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.AccountUser{},
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// Create a persistent test user for authentication
|
|
user := &model.User{
|
|
Name: "Test Auth User",
|
|
Email: "auth@test.com",
|
|
Password: "hashedpassword",
|
|
Role: "administrator",
|
|
Active: true,
|
|
}
|
|
s.Require().NoError(db.Create(user).Error)
|
|
s.testUserID = user.ID
|
|
|
|
// Create real repo + service
|
|
accountRepo := repository.NewAccountRepo(db)
|
|
accountSvc := service.NewAccountService(accountRepo)
|
|
|
|
// Create handler
|
|
s.handler = NewAccountHandler(accountSvc)
|
|
|
|
// Setup router with middleware that injects userID into context
|
|
// (simulating what the auth middleware does in production)
|
|
s.router = gin.New()
|
|
s.router.Use(func(c *gin.Context) {
|
|
c.Set("user_id", s.testUserID)
|
|
c.Next()
|
|
})
|
|
|
|
accountsGroup := s.router.Group("/api/v1/accounts")
|
|
{
|
|
accountsGroup.GET("/all", s.handler.GetAll)
|
|
accountsGroup.GET("", s.handler.List)
|
|
accountsGroup.GET("/:account_id", s.handler.Get)
|
|
accountsGroup.POST("", s.handler.Create)
|
|
accountsGroup.PUT("/:account_id", s.handler.Update)
|
|
accountsGroup.DELETE("/:account_id", s.handler.Delete)
|
|
accountsGroup.PUT("/:account_id/settings", s.handler.UpdateSettings)
|
|
accountsGroup.GET("/:account_id/agents", s.handler.GetAgents)
|
|
accountsGroup.GET("/:account_id/users", s.handler.ListUsers)
|
|
accountsGroup.POST("/:account_id/users", s.handler.AddUser)
|
|
accountsGroup.DELETE("/:account_id/users/:user_id", s.handler.RemoveUser)
|
|
// G8 extension routes
|
|
accountsGroup.POST("/:account_id/update_active_at", s.handler.UpdateActiveAt)
|
|
accountsGroup.GET("/:account_id/cache_keys", s.handler.CacheKeys)
|
|
}
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TearDownSuite() {
|
|
if s.db != nil {
|
|
sqlDB, _ := s.db.DB()
|
|
sqlDB.Close()
|
|
}
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) SetupTest() {
|
|
// Clean tables before each test
|
|
s.db.Exec("DELETE FROM account_users")
|
|
s.db.Exec("DELETE FROM accounts")
|
|
s.db.Exec("DELETE FROM sqlite_sequence WHERE name IN ('account_users','accounts')")
|
|
}
|
|
|
|
func TestAccountHandlerSuite(t *testing.T) {
|
|
suite.Run(t, new(AccountHandlerTestSuite))
|
|
}
|
|
|
|
// --- Helper: create a test account directly in DB ---
|
|
func (s *AccountHandlerTestSuite) seedAccount(name string) *model.Account {
|
|
acc := &model.Account{Name: name, Active: true, Status: "active"}
|
|
s.Require().NoError(s.db.Create(acc).Error)
|
|
return acc
|
|
}
|
|
|
|
// --- Helper: create a test user directly in DB ---
|
|
func (s *AccountHandlerTestSuite) seedUser(email string) *model.User {
|
|
user := &model.User{
|
|
Name: "Seeded User",
|
|
Email: email,
|
|
Password: "hashedpassword",
|
|
Role: "agent",
|
|
Active: true,
|
|
}
|
|
s.Require().NoError(s.db.Create(user).Error)
|
|
return user
|
|
}
|
|
|
|
// --- Helper: link user to account in DB ---
|
|
func (s *AccountHandlerTestSuite) seedAccountUser(userID, accountID uint, role string) {
|
|
au := &model.AccountUser{
|
|
UserID: userID,
|
|
AccountID: accountID,
|
|
Role: role,
|
|
}
|
|
s.Require().NoError(s.db.Create(au).Error)
|
|
}
|
|
|
|
// --- Helper: unmarshal response ---
|
|
func (s *AccountHandlerTestSuite) unmarshalResponse(w *httptest.ResponseRecorder) map[string]interface{} {
|
|
var resp map[string]interface{}
|
|
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
|
return resp
|
|
}
|
|
|
|
// ====== List Accounts ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestList_Success() {
|
|
// Seed accounts linked to the test user
|
|
acc1 := s.seedAccount("List Account 1")
|
|
acc2 := s.seedAccount("List Account 2")
|
|
s.seedAccountUser(s.testUserID, acc1.ID, "administrator")
|
|
s.seedAccountUser(s.testUserID, acc2.ID, "agent")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts?page=1&per_page=25", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
assert.NotNil(s.T(), resp["data"])
|
|
assert.NotNil(s.T(), resp["meta"])
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestList_Empty() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts?page=1&per_page=25", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
data := resp["data"].([]interface{})
|
|
assert.Equal(s.T(), 0, len(data))
|
|
}
|
|
|
|
// ====== Get Account ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestGet_Success() {
|
|
acc := s.seedAccount("Get Account")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10), nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
assert.NotContains(s.T(), resp, "success")
|
|
assert.Equal(s.T(), "Get Account", resp["name"])
|
|
assert.Contains(s.T(), resp, "settings")
|
|
assert.Contains(s.T(), resp, "features")
|
|
assert.Contains(s.T(), resp, "cache_keys")
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestGet_NotFound() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/99999", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
// Should return error status
|
|
assert.True(s.T(), w.Code >= 400)
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestGet_InvalidID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/abc", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
|
|
}
|
|
|
|
// ====== Create Account ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestCreate_Success() {
|
|
body := `{"name":"New Test Account"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
data := resp["data"].(map[string]interface{})
|
|
assert.NotZero(s.T(), data["account_id"])
|
|
assert.NotContains(s.T(), resp, "success")
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestCreate_ChatwootAccountName() {
|
|
body := `{"account_name":"Chatwoot Account"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
data := resp["data"].(map[string]interface{})
|
|
assert.NotZero(s.T(), data["account_id"])
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestCreate_MissingName() {
|
|
body := `{}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
// Should fail with 400 when name is missing
|
|
assert.True(s.T(), w.Code >= 400)
|
|
}
|
|
|
|
// ====== Update Account ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestUpdate_Success() {
|
|
acc := s.seedAccount("Before Update")
|
|
|
|
body := `{"name":"After Update"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PUT", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10), bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
assert.NotContains(s.T(), resp, "success")
|
|
assert.Equal(s.T(), "After Update", resp["name"])
|
|
}
|
|
|
|
// ====== Delete Account ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestDelete_Success() {
|
|
acc := s.seedAccount("Delete Me")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10), nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusNoContent, w.Code)
|
|
}
|
|
|
|
// ====== Update Settings ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestUpdateSettings() {
|
|
acc := s.seedAccount("Settings Account")
|
|
|
|
body := `{"auto_assignment":true,"custom_settings":"value"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("PUT", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10)+"/settings", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
assert.NotContains(s.T(), resp, "success")
|
|
assert.Contains(s.T(), resp, "settings")
|
|
}
|
|
|
|
// ====== List Users ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestListUsers() {
|
|
acc := s.seedAccount("Users List Account")
|
|
user1 := s.seedUser("listu1@test.com")
|
|
user2 := s.seedUser("listu2@test.com")
|
|
s.seedAccountUser(user1.ID, acc.ID, "administrator")
|
|
s.seedAccountUser(user2.ID, acc.ID, "agent")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10)+"/users?page=1&per_page=25", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
assert.NotNil(s.T(), resp["data"])
|
|
}
|
|
|
|
// ====== Add User ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestAddUser() {
|
|
acc := s.seedAccount("Add User Account")
|
|
newUser := s.seedUser("adduser@test.com")
|
|
|
|
body := `{"user_id":` + strconv.FormatUint(uint64(newUser.ID), 10) + `,"role":"agent"}`
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10)+"/users", bytes.NewBufferString(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.True(s.T(), w.Code == http.StatusOK || w.Code < 500)
|
|
}
|
|
|
|
// ====== Remove User ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestRemoveUser() {
|
|
acc := s.seedAccount("Remove User Account")
|
|
existingUser := s.seedUser("rmuser@test.com")
|
|
s.seedAccountUser(existingUser.ID, acc.ID, "agent")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("DELETE", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10)+"/users/"+strconv.FormatUint(uint64(existingUser.ID), 10), nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.True(s.T(), w.Code == http.StatusOK || w.Code == http.StatusNoContent || w.Code < 500)
|
|
}
|
|
|
|
// ====== GetAll ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestGetAll_Success() {
|
|
s.seedAccount("All Account 1")
|
|
s.seedAccount("All Account 2")
|
|
s.seedAccount("All Account 3")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/all?page=1&per_page=25", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
data := resp["data"].([]interface{})
|
|
assert.Equal(s.T(), 3, len(data))
|
|
assert.NotNil(s.T(), resp["meta"])
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestGetAll_Empty() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/all?page=1&per_page=25", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
data := resp["data"].([]interface{})
|
|
assert.Equal(s.T(), 0, len(data))
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestGetAll_Pagination() {
|
|
s.seedAccount("Pag Account 1")
|
|
s.seedAccount("Pag Account 2")
|
|
s.seedAccount("Pag Account 3")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/all?page=1&per_page=2", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
data := resp["data"].([]interface{})
|
|
assert.Equal(s.T(), 2, len(data))
|
|
meta := resp["meta"].(map[string]interface{})
|
|
assert.Equal(s.T(), float64(3), meta["total_count"])
|
|
}
|
|
|
|
// ====== GetAgents ======
|
|
|
|
func (s *AccountHandlerTestSuite) TestGetAgents_Success() {
|
|
acc := s.seedAccount("Agents Account")
|
|
user1 := s.seedUser("agent1@test.com")
|
|
user2 := s.seedUser("agent2@test.com")
|
|
s.seedAccountUser(user1.ID, acc.ID, "agent")
|
|
s.seedAccountUser(user2.ID, acc.ID, "administrator")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10)+"/agents?page=1&per_page=25", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
data := resp["data"].([]interface{})
|
|
assert.Equal(s.T(), 2, len(data))
|
|
assert.NotNil(s.T(), resp["meta"])
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestGetAgents_Empty() {
|
|
acc := s.seedAccount("Empty Agents Account")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/"+strconv.FormatUint(uint64(acc.ID), 10)+"/agents?page=1&per_page=25", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
resp := s.unmarshalResponse(w)
|
|
data := resp["data"].([]interface{})
|
|
assert.Equal(s.T(), 0, len(data))
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestGetAgents_InvalidID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/agents?page=1&per_page=25", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.True(s.T(), w.Code >= 400)
|
|
}
|
|
|
|
// --- G8 extension handler tests: UpdateActiveAt, CacheKeys ---
|
|
// Reference: Chatwoot accounts_controller.rb#update_active_at, #cache_keys
|
|
|
|
func (s *AccountHandlerTestSuite) TestUpdateActiveAt_Success() {
|
|
account := s.seedAccount("G8 ActiveAt Account")
|
|
// Use the suite's testUserID (set by middleware) and link it to the seeded account
|
|
s.seedAccountUser(s.testUserID, account.ID, "administrator")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/update_active_at", account.ID), nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
assert.Empty(s.T(), w.Body.String())
|
|
|
|
// Verify active_at was updated in DB
|
|
var au model.AccountUser
|
|
require.NoError(s.T(), s.db.Where("account_id = ? AND user_id = ?", account.ID, s.testUserID).First(&au).Error)
|
|
assert.NotNil(s.T(), au.ActiveAt)
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestUpdateActiveAt_InvalidID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/update_active_at", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.True(s.T(), w.Code >= 400)
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestCacheKeys_Success() {
|
|
account := s.seedAccount("G8 CacheKeys Account")
|
|
// Use the suite's testUserID (set by middleware) and link it to the seeded account
|
|
s.seedAccountUser(s.testUserID, account.ID, "administrator")
|
|
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/accounts/%d/cache_keys", account.ID), nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.Equal(s.T(), http.StatusOK, w.Code)
|
|
|
|
data := s.unmarshalResponse(w)
|
|
keys := data["cache_keys"].(map[string]interface{})
|
|
assert.Equal(s.T(), "0000000000", keys["label"])
|
|
assert.Equal(s.T(), "0000000000", keys["inbox"])
|
|
assert.Equal(s.T(), "0000000000", keys["team"])
|
|
assert.NotContains(s.T(), data, "success")
|
|
}
|
|
|
|
func (s *AccountHandlerTestSuite) TestCacheKeys_InvalidID() {
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest("GET", "/api/v1/accounts/abc/cache_keys", nil)
|
|
s.router.ServeHTTP(w, req)
|
|
|
|
assert.True(s.T(), w.Code >= 400)
|
|
}
|