feat(campaigns): derive chatwoot scheduling

This commit is contained in:
2026-06-06 03:20:51 +08:00
parent ca3c045f21
commit a02d11e73c
4 changed files with 235 additions and 20 deletions
@@ -141,6 +141,12 @@ func (s *CampaignHandlerTestSuite) accountURL() string {
return "/api/v1/accounts/" + strconv.FormatUint(uint64(s.account.ID), 10) + "/campaigns"
}
func (s *CampaignHandlerTestSuite) seedInbox(channelType string) *model.Inbox {
inbox := &model.Inbox{AccountID: s.account.ID, Name: "Campaign " + channelType + " Inbox", ChannelType: channelType, ChannelID: 1}
s.Require().NoError(s.db.Create(inbox).Error)
return inbox
}
// Helper: seed a campaign directly into the DB for Get/List/Delete/Update tests
func (s *CampaignHandlerTestSuite) seedCampaign(title, message, campaignType string) *campaign.Campaign {
c := &campaign.Campaign{
@@ -256,12 +262,13 @@ func (s *CampaignHandlerTestSuite) TestGet_Unauthorized() {
// ========== Create Tests ==========
func (s *CampaignHandlerTestSuite) TestCreate_Success() {
smsInbox := s.seedInbox("Channel::Sms")
body := map[string]interface{}{
"inbox_id": s.inbox.ID,
"inbox_id": smsInbox.ID,
"title": "New Campaign",
"message": "Hello from campaign",
"campaign_type": "one_off",
"enabled": true,
"scheduled_at": "2026-06-07T10:30:00Z",
"audience": []map[string]interface{}{{"type": "Label", "id": 1}},
"trigger_rules": map[string]interface{}{"url": "https://example.com"},
"template_params": map[string]interface{}{"name": "value"},
@@ -281,11 +288,40 @@ func (s *CampaignHandlerTestSuite) TestCreate_Success() {
s.NotContains(resp, "data")
s.Equal("New Campaign", resp["title"])
s.Equal(float64(s.account.ID), resp["account_id"])
s.Equal("one_off", resp["campaign_type"])
s.Equal(float64(1780828200), resp["scheduled_at"])
s.Greater(resp["id"].(float64), float64(0))
s.NotNil(resp["inbox"])
s.Contains(resp, "audience")
s.Contains(resp, "template_params")
}
func (s *CampaignHandlerTestSuite) TestCreate_LiveChatDefaultsOngoingWithoutCampaignType() {
body := map[string]interface{}{
"inbox_id": s.inbox.ID,
"title": "Live Chat Campaign",
"message": "Hello from live chat",
"enabled": true,
"scheduled_at": "2026-06-07T10:30:00Z",
"trigger_rules": map[string]interface{}{"url": "https://example.com", "time_on_page": 10},
}
bodyBytes := marshalNested("campaign", body)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", s.accountURL(), bytes.NewReader(bodyBytes))
req.Header.Set("Content-Type", "application/json")
s.router.ServeHTTP(w, req)
s.Equal(http.StatusOK, w.Code)
var resp map[string]interface{}
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.Equal("Live Chat Campaign", resp["title"])
s.Equal("ongoing", resp["campaign_type"])
s.NotContains(resp, "scheduled_at")
s.NotContains(resp, "audience")
}
func (s *CampaignHandlerTestSuite) TestCreate_ValidationError() {
// Missing required fields (title, message, inbox_id)
body := map[string]interface{}{
@@ -335,10 +371,13 @@ func (s *CampaignHandlerTestSuite) TestCreate_Unauthorized() {
func (s *CampaignHandlerTestSuite) TestUpdate_Success() {
c := s.seedCampaign("Original Title", "Original message", "ongoing")
smsInbox := s.seedInbox("Channel::Sms")
body := map[string]interface{}{
"title": "Updated Title",
"message": "Updated message",
"title": "Updated Title",
"message": "Updated message",
"inbox_id": smsInbox.ID,
"scheduled_at": "2026-06-07T10:30:00Z",
}
bodyBytes := marshalNested("campaign", body)
@@ -353,6 +392,14 @@ func (s *CampaignHandlerTestSuite) TestUpdate_Success() {
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &resp))
s.NotContains(resp, "success")
s.Equal("Updated Title", resp["title"])
s.Equal("one_off", resp["campaign_type"])
s.Equal(float64(1780828200), resp["scheduled_at"])
var updated campaign.Campaign
s.Require().NoError(s.db.First(&updated, c.ID).Error)
s.Equal(smsInbox.ID, updated.InboxID)
s.Require().NotNil(updated.ScheduledAt)
s.Equal(int64(1780828200), updated.ScheduledAt.Unix())
}
func (s *CampaignHandlerTestSuite) TestUpdate_NotFound() {