package v1 import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "strconv" "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" ) // ========== Test Setup ========== func setupAPV2HandlerTest(t *testing.T) (*AssignmentPolicyV2Handler, *gorm.DB) { t.Helper() db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) require.NoError(t, err) require.NoError(t, db.AutoMigrate( &model.Account{}, &model.AssignmentPolicyV2{}, &model.AssignmentPolicyInbox{}, )) t.Cleanup(func() { sqlDB, _ := db.DB() sqlDB.Close() }) policyRepo := repository.NewAssignmentPolicyV2Repo(db) apInboxRepo := repository.NewAssignmentPolicyInboxRepo(db) svc := service.NewAssignmentPolicyV2Service(policyRepo, apInboxRepo) handler := NewAssignmentPolicyV2Handler(svc) account := &model.Account{Name: "APV2HandlerOrg", Locale: "en", Active: true} require.NoError(t, db.Create(account).Error) return handler, db } func apv2HandlerAccountID(db *gorm.DB) string { var account model.Account db.First(&account) return strconv.FormatUint(uint64(account.ID), 10) } func apv2HandlerAccountIDUint(db *gorm.DB) uint { var account model.Account db.First(&account) return account.ID } func setupAPV2TestRouter(handler *AssignmentPolicyV2Handler) *gin.Engine { gin.SetMode(gin.TestMode) r := gin.New() rg := r.Group("/api/v1/accounts/:account_id") rg.GET("/assignment_policies_v2", handler.List) rg.POST("/assignment_policies_v2", handler.Create) rg.GET("/assignment_policies_v2/:id", handler.Get) rg.PUT("/assignment_policies_v2/:id", handler.Update) rg.DELETE("/assignment_policies_v2/:id", handler.Delete) rg.GET("/assignment_policies_v2/:id/inboxes", handler.ListInboxes) rg.POST("/assignment_policies_v2/:id/inboxes", handler.AddInbox) rg.DELETE("/assignment_policies_v2/:id/inboxes/:inbox_id", handler.RemoveInbox) // Inbox-policy routes (reverse lookup) inboxV2 := rg.Group("/inboxes/:inbox_id/assignment_policy") inboxV2.GET("/", handler.GetInboxPolicy) inboxV2.POST("/", handler.SetInboxPolicy) inboxV2.DELETE("/", handler.DeleteInboxPolicy) return r } // ========== List ========== func TestAPV2Handler_List_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) svc.Create(nil, apv2HandlerAccountIDUint(db), &service.CreatePolicyV2Request{ Name: "Policy-A", Type: model.APV2TypeRoundRobin, }) svc.Create(nil, apv2HandlerAccountIDUint(db), &service.CreatePolicyV2Request{ Name: "Policy-B", Type: model.APV2TypeFair, }) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/"+aid+"/assignment_policies_v2", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestAPV2Handler_List_NoAccountID(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() handler, _ := setupAPV2HandlerTest(t) rg := r.Group("/api/v1/accounts") rg.GET("/assignment_policies_v2", handler.List) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/assignment_policies_v2", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } // ========== Create ========== func TestAPV2Handler_Create_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) body := map[string]interface{}{ "name": "New RoundRobin", "description": "desc", "type": "round_robin", } jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+aid+"/assignment_policies_v2", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) } func TestAPV2Handler_Create_ValidationError(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) body := map[string]interface{}{ "name": "", // empty name — required "type": "invalid_type", } jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+aid+"/assignment_policies_v2", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } func TestAPV2Handler_Create_NoAccountID(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() handler, _ := setupAPV2HandlerTest(t) rg := r.Group("/api/v1/accounts") rg.POST("/assignment_policies_v2", handler.Create) body := map[string]interface{}{ "name": "Nope", "type": "round_robin", } jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/assignment_policies_v2", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } // ========== Get ========== func TestAPV2Handler_Get_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, apv2HandlerAccountIDUint(db), &service.CreatePolicyV2Request{ Name: "GetTest", Type: model.APV2TypeRoundRobin, }) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/"+aid+"/assignment_policies_v2/"+strconv.FormatUint(uint64(policy.ID), 10), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestAPV2Handler_Get_InvalidID(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/"+aid+"/assignment_policies_v2/notanumber", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusBadRequest, w.Code) } // ========== Update ========== func TestAPV2Handler_Update_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, apv2HandlerAccountIDUint(db), &service.CreatePolicyV2Request{ Name: "ToUpdate", Type: model.APV2TypeRoundRobin, }) body := map[string]interface{}{ "name": "Updated Name", "description": "Updated desc", "type": "fair", } jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/"+aid+"/assignment_policies_v2/"+strconv.FormatUint(uint64(policy.ID), 10), bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestAPV2Handler_Update_NoAccountID(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() handler, _ := setupAPV2HandlerTest(t) rg := r.Group("/api/v1/accounts") rg.PUT("/assignment_policies_v2/:id", handler.Update) body := map[string]interface{}{"name": "Nope"} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("PUT", "/api/v1/accounts/assignment_policies_v2/1", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } // ========== Delete ========== func TestAPV2Handler_Delete_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, apv2HandlerAccountIDUint(db), &service.CreatePolicyV2Request{ Name: "ToDelete", Type: model.APV2TypeRoundRobin, }) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/"+aid+"/assignment_policies_v2/"+strconv.FormatUint(uint64(policy.ID), 10), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNoContent, w.Code) } func TestAPV2Handler_Delete_NoAccountID(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() handler, _ := setupAPV2HandlerTest(t) rg := r.Group("/api/v1/accounts") rg.DELETE("/assignment_policies_v2/:id", handler.Delete) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/assignment_policies_v2/1", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } // ========== AddInbox ========== func TestAPV2Handler_AddInbox_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) accountUID := apv2HandlerAccountIDUint(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, accountUID, &service.CreatePolicyV2Request{ Name: "Test Policy", Type: model.APV2TypeRoundRobin, }) inbox := &model.Inbox{Name: "Inbox1", AccountID: accountUID} require.NoError(t, db.Create(inbox).Error) body := map[string]interface{}{ "inbox_id": inbox.ID, } jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+aid+"/assignment_policies_v2/"+strconv.FormatUint(uint64(policy.ID), 10)+"/inboxes", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) } // ========== RemoveInbox ========== func TestAPV2Handler_RemoveInbox_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) accountUID := apv2HandlerAccountIDUint(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, accountUID, &service.CreatePolicyV2Request{ Name: "Test Policy", Type: model.APV2TypeRoundRobin, }) inbox := &model.Inbox{Name: "Inbox1", AccountID: accountUID} require.NoError(t, db.Create(inbox).Error) svc.AddInbox(nil, accountUID, policy.ID, &service.AddInboxRequestV2{InboxID: inbox.ID}) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/"+aid+"/assignment_policies_v2/"+strconv.FormatUint(uint64(policy.ID), 10)+"/inboxes/"+strconv.FormatUint(uint64(inbox.ID), 10), nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNoContent, w.Code) } // ========== ListInboxes ========== func TestAPV2Handler_ListInboxes_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) accountUID := apv2HandlerAccountIDUint(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, accountUID, &service.CreatePolicyV2Request{ Name: "Test Policy", Type: model.APV2TypeRoundRobin, }) inbox := &model.Inbox{Name: "Inbox1", AccountID: accountUID} require.NoError(t, db.Create(inbox).Error) svc.AddInbox(nil, accountUID, policy.ID, &service.AddInboxRequestV2{InboxID: inbox.ID}) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/"+aid+"/assignment_policies_v2/"+strconv.FormatUint(uint64(policy.ID), 10)+"/inboxes", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } // ========== GetInboxPolicy ========== func TestAPV2Handler_GetInboxPolicy_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) accountUID := apv2HandlerAccountIDUint(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, accountUID, &service.CreatePolicyV2Request{ Name: "RoundRobin", Type: model.APV2TypeRoundRobin, }) inbox := &model.Inbox{Name: "Inbox1", AccountID: accountUID} require.NoError(t, db.Create(inbox).Error) svc.AddInbox(nil, accountUID, policy.ID, &service.AddInboxRequestV2{InboxID: inbox.ID}) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/"+aid+"/inboxes/"+strconv.FormatUint(uint64(inbox.ID), 10)+"/assignment_policy/", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestAPV2Handler_GetInboxPolicy_NoAccountID(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() handler, _ := setupAPV2HandlerTest(t) rg := r.Group("/api/v1/accounts") inboxV2 := rg.Group("/inboxes/:inbox_id/assignment_policy") inboxV2.GET("/", handler.GetInboxPolicy) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/api/v1/accounts/inboxes/1/assignment_policy/", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } // ========== SetInboxPolicy ========== func TestAPV2Handler_SetInboxPolicy_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) accountUID := apv2HandlerAccountIDUint(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, accountUID, &service.CreatePolicyV2Request{ Name: "RoundRobin", Type: model.APV2TypeRoundRobin, }) inbox := &model.Inbox{Name: "Inbox1", AccountID: accountUID} require.NoError(t, db.Create(inbox).Error) body := map[string]interface{}{ "policy_id": policy.ID, } jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/"+aid+"/inboxes/"+strconv.FormatUint(uint64(inbox.ID), 10)+"/assignment_policy/", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") router.ServeHTTP(w, req) assert.Equal(t, http.StatusCreated, w.Code) } func TestAPV2Handler_SetInboxPolicy_NoAccountID(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() handler, _ := setupAPV2HandlerTest(t) rg := r.Group("/api/v1/accounts") inboxV2 := rg.Group("/inboxes/:inbox_id/assignment_policy") inboxV2.POST("/", handler.SetInboxPolicy) body := map[string]interface{}{"policy_id": 1} jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/accounts/inboxes/1/assignment_policy/", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) } // ========== DeleteInboxPolicy ========== func TestAPV2Handler_DeleteInboxPolicy_Success(t *testing.T) { handler, db := setupAPV2HandlerTest(t) router := setupAPV2TestRouter(handler) aid := apv2HandlerAccountID(db) accountUID := apv2HandlerAccountIDUint(db) svc := service.NewAssignmentPolicyV2Service( repository.NewAssignmentPolicyV2Repo(db), repository.NewAssignmentPolicyInboxRepo(db), ) policy, _ := svc.Create(nil, accountUID, &service.CreatePolicyV2Request{ Name: "RoundRobin", Type: model.APV2TypeRoundRobin, }) inbox := &model.Inbox{Name: "Inbox1", AccountID: accountUID} require.NoError(t, db.Create(inbox).Error) svc.SetInboxPolicy(nil, accountUID, inbox.ID, policy.ID) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/"+aid+"/inboxes/"+strconv.FormatUint(uint64(inbox.ID), 10)+"/assignment_policy/", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusNoContent, w.Code) } func TestAPV2Handler_DeleteInboxPolicy_NoAccountID(t *testing.T) { gin.SetMode(gin.TestMode) r := gin.New() handler, _ := setupAPV2HandlerTest(t) rg := r.Group("/api/v1/accounts") inboxV2 := rg.Group("/inboxes/:inbox_id/assignment_policy") inboxV2.DELETE("/", handler.DeleteInboxPolicy) w := httptest.NewRecorder() req, _ := http.NewRequest("DELETE", "/api/v1/accounts/inboxes/1/assignment_policy/", nil) r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code) }