package v1 import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/service" "github.com/gochat/gochat/internal/worker" ) func TestBulkActionHandler_ConversationEnqueuesChatwootPayload(t *testing.T) { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open("file:bulk-action-handler?mode=memory&cache=private"), &gorm.Config{}) require.NoError(t, err) require.NoError(t, db.AutoMigrate(&model.BackgroundJob{})) sqlDB, _ := db.DB() t.Cleanup(func() { _ = sqlDB.Close() }) wp := worker.NewWorkerPool(db) handler := NewBulkActionHandler(nil, nil).WithWorkerPool(wp) router := gin.New() router.POST("/api/v1/accounts/:account_id/bulk_actions", handler.Create) body := []byte(`{"type":"Conversation","ids":[101,102],"fields":{"status":"resolved"},"labels":{"add":["vip"],"remove":["old"]}}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/accounts/7/bulk_actions", bytes.NewReader(body)) request.Header.Set("Content-Type", "application/json") request.Header.Set("X-User-ID", "42") response := httptest.NewRecorder() router.ServeHTTP(response, request) require.Equal(t, http.StatusOK, response.Code) require.Empty(t, response.Body.String()) var job model.BackgroundJob require.NoError(t, db.Where("job_type = ? AND queue = ?", service.TaskTypeConversationBulkAction, "medium").First(&job).Error) require.Equal(t, model.BackgroundJobStatusQueued, job.Status) require.NotZero(t, job.ScheduledAt) require.Contains(t, string(job.Payload), `"account_id":7`) require.Contains(t, string(job.Payload), `"user_id":42`) require.Contains(t, string(job.Payload), `"ids":[101,102]`) require.Contains(t, string(job.Payload), `"status":"resolved"`) require.Contains(t, string(job.Payload), `"add":["vip"]`) var payload struct { AccountID uint `json:"account_id"` UserID uint `json:"user_id"` Params struct { Type string `json:"type"` IDs []uint `json:"ids"` Fields struct { Status *string `json:"status"` } `json:"fields"` Labels struct { Add []string `json:"add"` Remove []string `json:"remove"` } `json:"labels"` } `json:"params"` } require.NoError(t, json.Unmarshal(job.Payload, &payload)) require.Equal(t, uint(7), payload.AccountID) require.Equal(t, uint(42), payload.UserID) require.Equal(t, "Conversation", payload.Params.Type) require.Equal(t, []uint{101, 102}, payload.Params.IDs) require.NotNil(t, payload.Params.Fields.Status) require.Equal(t, "resolved", *payload.Params.Fields.Status) require.Equal(t, []string{"vip"}, payload.Params.Labels.Add) require.Equal(t, []string{"old"}, payload.Params.Labels.Remove) } func TestBulkActionHandler_ContactEnqueuesChatwootPayload(t *testing.T) { gin.SetMode(gin.TestMode) db, err := gorm.Open(sqlite.Open("file:bulk-action-contact-handler?mode=memory&cache=private"), &gorm.Config{}) require.NoError(t, err) require.NoError(t, db.AutoMigrate(&model.BackgroundJob{})) sqlDB, _ := db.DB() t.Cleanup(func() { _ = sqlDB.Close() }) wp := worker.NewWorkerPool(db) handler := NewBulkActionHandler(nil, nil).WithWorkerPool(wp) router := gin.New() router.POST("/api/v1/accounts/:account_id/bulk_actions", handler.Create) body := []byte(`{"type":"contact","ids":[11,12],"labels":{"add":["vip"],"remove":["old"]}}`) request := httptest.NewRequest(http.MethodPost, "/api/v1/accounts/7/bulk_actions", bytes.NewReader(body)) request.Header.Set("Content-Type", "application/json") request.Header.Set("X-User-ID", "42") response := httptest.NewRecorder() router.ServeHTTP(response, request) require.Equal(t, http.StatusOK, response.Code) require.Empty(t, response.Body.String()) var job model.BackgroundJob require.NoError(t, db.Where("job_type = ? AND queue = ?", service.TaskTypeContactBulkAction, "medium").First(&job).Error) require.Contains(t, string(job.Payload), `"account_id":7`) require.Contains(t, string(job.Payload), `"user_id":42`) require.Contains(t, string(job.Payload), `"type":"Contact"`) require.Contains(t, string(job.Payload), `"ids":[11,12]`) require.Contains(t, string(job.Payload), `"add":["vip"]`) } func TestBulkActionHandler_InvalidTypeMatchesChatwootPayload(t *testing.T) { gin.SetMode(gin.TestMode) handler := NewBulkActionHandler(nil, nil) router := gin.New() router.POST("/api/v1/accounts/:account_id/bulk_actions", handler.Create) request := httptest.NewRequest(http.MethodPost, "/api/v1/accounts/7/bulk_actions", bytes.NewReader([]byte(`{"type":"Ticket"}`))) request.Header.Set("Content-Type", "application/json") response := httptest.NewRecorder() router.ServeHTTP(response, request) require.Equal(t, http.StatusUnprocessableEntity, response.Code) require.JSONEq(t, `{"success":false}`, response.Body.String()) }