feat(crm): align contact merge action
This commit is contained in:
@@ -28,6 +28,7 @@ var criticalRoutes = []route{
|
||||
{Method: "PUT", Path: "/api/v1/accounts/:account_id", Controller: "api/v1/accounts#update", Source: "routes.rb:47"},
|
||||
{Method: "POST", Path: "/api/v1/accounts/:account_id/update_active_at", Controller: "api/v1/accounts#update_active_at", Source: "routes.rb:49"},
|
||||
{Method: "GET", Path: "/api/v1/accounts/:account_id/cache_keys", Controller: "api/v1/accounts#cache_keys", Source: "routes.rb:50"},
|
||||
{Method: "POST", Path: "/api/v1/accounts/:account_id/actions/contact_merge", Controller: "api/v1/accounts/actions/contact_merges#create", Source: "routes.rb:55"},
|
||||
|
||||
{Method: "GET", Path: "/api/v1/accounts/:account_id/agents", Controller: "api/v1/accounts/agents#index", Source: "routes.rb:59"},
|
||||
{Method: "POST", Path: "/api/v1/accounts/:account_id/agents", Controller: "api/v1/accounts/agents#create", Source: "routes.rb:59"},
|
||||
|
||||
@@ -476,6 +476,7 @@ PATCH /public/api/v1/inboxes/:inbox_id/contacts/:contact_id
|
||||
PATCH /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id
|
||||
PATCH /widget/contact
|
||||
POST /api/v1/accounts/
|
||||
POST /api/v1/accounts/:account_id/actions/contact_merge
|
||||
POST /api/v1/accounts/:account_id/agent_bot_inboxes/
|
||||
POST /api/v1/accounts/:account_id/agent_bots/
|
||||
POST /api/v1/accounts/:account_id/agent_bots/:agent_bot_id/bot_rules/
|
||||
@@ -815,4 +816,4 @@ PUT /public/api/v1/csat_survey/:id
|
||||
PUT /public/api/v1/inboxes/:inbox_id/contacts/:contact_id
|
||||
PUT /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id
|
||||
PUT /widget/direct_uploads/:upload_uuid
|
||||
TOTAL: 817
|
||||
TOTAL: 818
|
||||
|
||||
@@ -7,7 +7,7 @@ Generated from:
|
||||
|
||||
This report covers tracked frontend-critical Chatwoot routes from `reference/chatwoot/config/routes.rb`, including API v1 account routes, Captain/Copilot, assignment policies, widget/public APIs, and API v2 reports. Ruby is not installed in the workspace, so Chatwoot routes are sourced from static route declarations instead of `bin/rails routes`.
|
||||
|
||||
Summary: 261 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 261 tracked critical routes.
|
||||
Summary: 262 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 262 tracked critical routes.
|
||||
|
||||
## Missing Critical Routes
|
||||
|
||||
@@ -183,6 +183,7 @@ These routes exist with equivalent method and path shape but different parameter
|
||||
| PATCH | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `api/v1/accounts/teams/team_members#update` | `routes.rb:300` | exact |
|
||||
| PATCH | `/api/v1/widget/contact/set_user` | `/api/v1/widget/contact/set_user` | `api/v1/widget/contact#set_user` | `routes.rb:461` | exact |
|
||||
| POST | `/api/v1/accounts/` | `/api/v1/accounts/` | `api/v1/accounts#create` | `routes.rb:47` | exact |
|
||||
| POST | `/api/v1/accounts/:account_id/actions/contact_merge` | `/api/v1/accounts/:account_id/actions/contact_merge` | `api/v1/accounts/actions/contact_merges#create` | `routes.rb:55` | exact |
|
||||
| POST | `/api/v1/accounts/:account_id/agents` | `/api/v1/accounts/:account_id/agents` | `api/v1/accounts/agents#create` | `routes.rb:59` | exact |
|
||||
| POST | `/api/v1/accounts/:account_id/agents/bulk_create` | `/api/v1/accounts/:account_id/agents/bulk_create` | `api/v1/accounts/agents#bulk_create` | `routes.rb:60` | exact |
|
||||
| POST | `/api/v1/accounts/:account_id/assignment_policies/` | `/api/v1/accounts/:account_id/assignment_policies/` | `api/v1/accounts/assignment_policies#create` | `routes.rb:306` | exact |
|
||||
|
||||
@@ -818,7 +818,7 @@ func (h *ContactHandler) Merge(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, result)
|
||||
c.JSON(http.StatusOK, serializeCRMContact(c.Request.Context(), h.svc.DB(), result, false))
|
||||
}
|
||||
|
||||
// Filter retrieves contacts matching advanced filter criteria.
|
||||
|
||||
@@ -52,6 +52,7 @@ func (s *ContactHandlerCRUDTestSuite) SetupSuite() {
|
||||
&model.Tag{},
|
||||
&model.ContactLabel{},
|
||||
&model.Conversation{},
|
||||
&model.Message{},
|
||||
&model.ContactInbox{},
|
||||
&model.InboxMember{},
|
||||
&model.ContactNote{},
|
||||
@@ -92,6 +93,7 @@ func (s *ContactHandlerCRUDTestSuite) SetupSuite() {
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/:contact_id/contact_inboxes", s.handler.ListContactInboxes)
|
||||
s.router.GET("/api/v1/accounts/:id/contacts/:contact_id/notes", s.handler.ListNotes)
|
||||
s.router.POST("/api/v1/accounts/:id/contacts/:contact_id/notes", s.handler.CreateNote)
|
||||
s.router.POST("/api/v1/accounts/:id/actions/contact_merge", s.handler.Merge)
|
||||
|
||||
// Create test data
|
||||
s.account = &model.Account{Name: "Test Account", Locale: "en", Status: "active"}
|
||||
@@ -118,6 +120,7 @@ func (s *ContactHandlerCRUDTestSuite) SetupTest() {
|
||||
s.db.Exec("DELETE FROM contact_notes")
|
||||
s.db.Exec("DELETE FROM contact_labels")
|
||||
s.db.Exec("DELETE FROM tags")
|
||||
s.db.Exec("DELETE FROM messages")
|
||||
s.db.Exec("DELETE FROM notes")
|
||||
s.db.Exec("DELETE FROM contact_inboxes")
|
||||
s.db.Exec("DELETE FROM conversations")
|
||||
@@ -559,6 +562,41 @@ func (s *ContactHandlerCRUDTestSuite) TestDeleteAvatar_Success() {
|
||||
s.Equal("", found.AvatarURL)
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestMerge_ChatwootActionsPathReturnsRawContact() {
|
||||
base := &model.Contact{AccountID: s.account.ID, Name: "Base Contact", Email: "base@example.com"}
|
||||
mergee := &model.Contact{AccountID: s.account.ID, Name: "Mergee Contact", PhoneNumber: "+12212345"}
|
||||
s.Require().NoError(s.db.Create(base).Error)
|
||||
s.Require().NoError(s.db.Create(mergee).Error)
|
||||
s.Require().NoError(s.db.Create(&model.Conversation{AccountID: s.account.ID, InboxID: 1, ContactID: mergee.ID, Status: "open", ChannelType: "Channel::WebWidget", Channel: "web_widget"}).Error)
|
||||
s.Require().NoError(s.db.Create(&model.Note{AccountID: s.account.ID, ContactID: mergee.ID, Content: "mergee note", UserID: &s.user.ID}).Error)
|
||||
|
||||
bodyBytes, _ := json.Marshal(map[string]uint{"base_contact_id": base.ID, "mergee_contact_id": mergee.ID})
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST",
|
||||
fmt.Sprintf("/api/v1/accounts/%d/actions/contact_merge", s.account.ID),
|
||||
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.NoError(json.Unmarshal(w.Body.Bytes(), &resp))
|
||||
s.Equal(float64(base.ID), resp["id"])
|
||||
s.Equal("Base Contact", resp["name"])
|
||||
s.Equal("base@example.com", resp["email"])
|
||||
s.Equal("+12212345", resp["phone_number"])
|
||||
s.NotContains(resp, "payload")
|
||||
s.NotContains(resp, "success")
|
||||
|
||||
var count int64
|
||||
s.db.Model(&model.Contact{}).Where("id = ?", mergee.ID).Count(&count)
|
||||
s.Equal(int64(0), count)
|
||||
s.db.Model(&model.Conversation{}).Where("contact_id = ?", base.ID).Count(&count)
|
||||
s.Equal(int64(1), count)
|
||||
s.db.Model(&model.Note{}).Where("contact_id = ?", base.ID).Count(&count)
|
||||
s.Equal(int64(1), count)
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestLabels_UpdateListAndFilter() {
|
||||
bodyBytes, _ := json.Marshal(map[string]interface{}{"labels": []string{"vip", "trial"}})
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ package v1
|
||||
// Chatwoot's response: returns the base contact after merge on success, 200 OK
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -32,12 +33,13 @@ func NewContactMergeHandler(mergeService *service.ContactMergeService) *ContactM
|
||||
|
||||
// Create merges the mergee contact into the base contact.
|
||||
// Reference: Chatwoot `def create`
|
||||
// contact_merge_action = ContactMergeAction.new(
|
||||
// account: Current.account,
|
||||
// base_contact: @base_contact,
|
||||
// mergee_contact: @mergee_contact
|
||||
// )
|
||||
// contact_merge_action.perform
|
||||
//
|
||||
// contact_merge_action = ContactMergeAction.new(
|
||||
// account: Current.account,
|
||||
// base_contact: @base_contact,
|
||||
// mergee_contact: @mergee_contact
|
||||
// )
|
||||
// contact_merge_action.perform
|
||||
//
|
||||
// Chatwoot returns the base contact on success with 200 OK.
|
||||
func (h *ContactMergeHandler) Create(c *gin.Context) {
|
||||
@@ -75,5 +77,5 @@ func (h *ContactMergeHandler) Create(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
response.OK(c, contact)
|
||||
}
|
||||
c.JSON(http.StatusOK, serializeCRMContact(c.Request.Context(), nil, contact, false))
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ type ContactMergeHandlerTestSuite struct {
|
||||
db *gorm.DB
|
||||
handler *ContactMergeHandler
|
||||
|
||||
account *model.Account
|
||||
account *model.Account
|
||||
contact1 *model.Contact
|
||||
contact2 *model.Contact
|
||||
}
|
||||
@@ -63,31 +63,42 @@ func TestContactMergeHandlerSuite(t *testing.T) {
|
||||
|
||||
func (s *ContactMergeHandlerTestSuite) TestCreate_Success() {
|
||||
r := gin.New()
|
||||
r.POST("/api/v1/accounts/:account_id/actions/contact_merges", func(c *gin.Context) {
|
||||
r.POST("/api/v1/accounts/:account_id/actions/contact_merge", func(c *gin.Context) {
|
||||
c.Set("account_id", float64(s.account.ID))
|
||||
c.Next()
|
||||
}, s.handler.Create)
|
||||
|
||||
body := map[string]uint{"base_contact_id": s.contact1.ID, "mergee_contact_id": s.contact2.ID}
|
||||
base := &model.Contact{Name: "merge-base", Email: "base@example.com", AccountID: s.account.ID}
|
||||
mergee := &model.Contact{Name: "mergee-contact", PhoneNumber: "+12212345", AccountID: s.account.ID}
|
||||
s.Require().NoError(s.db.Create(base).Error)
|
||||
s.Require().NoError(s.db.Create(mergee).Error)
|
||||
|
||||
body := map[string]uint{"base_contact_id": base.ID, "mergee_contact_id": mergee.ID}
|
||||
b, _ := json.Marshal(body)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/actions/contact_merges", s.account.ID), bytes.NewBuffer(b))
|
||||
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/actions/contact_merge", s.account.ID), bytes.NewBuffer(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(s.T(), http.StatusOK, w.Code)
|
||||
var payload map[string]any
|
||||
s.Require().NoError(json.Unmarshal(w.Body.Bytes(), &payload))
|
||||
assert.Equal(s.T(), float64(base.ID), payload["id"])
|
||||
assert.Equal(s.T(), "base@example.com", payload["email"])
|
||||
assert.NotContains(s.T(), payload, "success")
|
||||
assert.NotContains(s.T(), payload, "data")
|
||||
}
|
||||
|
||||
func (s *ContactMergeHandlerTestSuite) TestCreate_BadRequest_NoBody() {
|
||||
r := gin.New()
|
||||
r.POST("/api/v1/accounts/:account_id/actions/contact_merges", func(c *gin.Context) {
|
||||
r.POST("/api/v1/accounts/:account_id/actions/contact_merge", func(c *gin.Context) {
|
||||
c.Set("account_id", float64(s.account.ID))
|
||||
c.Next()
|
||||
}, s.handler.Create)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/actions/contact_merges", s.account.ID), nil)
|
||||
req, _ := http.NewRequest("POST", fmt.Sprintf("/api/v1/accounts/%d/actions/contact_merge", s.account.ID), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
@@ -96,15 +107,15 @@ func (s *ContactMergeHandlerTestSuite) TestCreate_BadRequest_NoBody() {
|
||||
|
||||
func (s *ContactMergeHandlerTestSuite) TestCreate_BadRequest_InvalidAccountID() {
|
||||
r := gin.New()
|
||||
r.POST("/api/v1/accounts/:account_id/actions/contact_merges", s.handler.Create)
|
||||
r.POST("/api/v1/accounts/:account_id/actions/contact_merge", s.handler.Create)
|
||||
|
||||
body := map[string]uint{"base_contact_id": s.contact1.ID, "mergee_contact_id": s.contact2.ID}
|
||||
b, _ := json.Marshal(body)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/actions/contact_merges", bytes.NewBuffer(b))
|
||||
req, _ := http.NewRequest("POST", "/api/v1/accounts/abc/actions/contact_merge", bytes.NewBuffer(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(s.T(), http.StatusBadRequest, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrContactNotInAccount = errors.New("contact does not belong to the account")
|
||||
ErrSameContactMerge = errors.New("cannot merge the same contact")
|
||||
ErrBaseContactNotFound = errors.New("base contact not found")
|
||||
ErrContactNotInAccount = errors.New("contact does not belong to the account")
|
||||
ErrSameContactMerge = errors.New("cannot merge the same contact")
|
||||
ErrBaseContactNotFound = errors.New("base contact not found")
|
||||
ErrMergeeContactNotFound = errors.New("mergee contact not found")
|
||||
)
|
||||
|
||||
@@ -73,10 +73,11 @@ func (r *ContactMergeRepo) MergeConversations(baseContactID, mergeeContactID uin
|
||||
|
||||
// MergeMessages moves all messages where sender was the mergee contact to base contact.
|
||||
// Reference: Chatwoot `Message.where(sender: @mergee_contact).update(sender: @base_contact)`
|
||||
// In our model, messages have sender_id and sender_type. For contact senders, sender_type = "Contact".
|
||||
// In our model, older rows may store either Chatwoot polymorphic "Contact" or
|
||||
// local lowercase "contact". Merge both so legacy messages move with the contact.
|
||||
func (r *ContactMergeRepo) MergeMessages(baseContactID, mergeeContactID uint) error {
|
||||
return r.db.Model(&model.Message{}).
|
||||
Where("sender_id = ? AND sender_type = ?", mergeeContactID, "Contact").
|
||||
Where("sender_id = ? AND sender_type IN ?", mergeeContactID, []string{"Contact", "contact"}).
|
||||
Update("sender_id", baseContactID).Error
|
||||
}
|
||||
|
||||
@@ -100,12 +101,13 @@ func (r *ContactMergeRepo) MergeContactNotes(baseContactID, mergeeContactID, acc
|
||||
// MergeAttributesAndRemoveMergee merges attributes from mergee into base, then destroys mergee.
|
||||
// 1:1 Chatwoot: merge_and_remove_mergee_contact
|
||||
// Logic:
|
||||
// mergable_attribute_keys = [identifier, name, email, phone_number, additional_attributes, custom_attributes]
|
||||
// base_attrs = base_contact.attributes.slice(keys).compact_blank
|
||||
// mergee_attrs = mergee_contact.attributes.slice(keys).compact_blank
|
||||
// merged_attrs = mergee_attrs.deep_merge(base_attrs) // base takes precedence
|
||||
// mergee.destroy!
|
||||
// base_contact.update!(merged_attrs)
|
||||
//
|
||||
// mergable_attribute_keys = [identifier, name, email, phone_number, additional_attributes, custom_attributes]
|
||||
// base_attrs = base_contact.attributes.slice(keys).compact_blank
|
||||
// mergee_attrs = mergee_contact.attributes.slice(keys).compact_blank
|
||||
// merged_attrs = mergee_attrs.deep_merge(base_attrs) // base takes precedence
|
||||
// mergee.destroy!
|
||||
// base_contact.update!(merged_attrs)
|
||||
func (r *ContactMergeRepo) MergeAttributesAndRemoveMergee(baseContactID, mergeeContactID, accountID uint) error {
|
||||
var baseContact, mergeeContact model.Contact
|
||||
if err := r.db.Where("id = ? AND account_id = ?", baseContactID, accountID).First(&baseContact).Error; err != nil {
|
||||
@@ -211,4 +213,4 @@ func (r *ContactMergeRepo) GetBaseContact(accountID, baseContactID uint) (*model
|
||||
return nil, err
|
||||
}
|
||||
return &contact, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,7 +704,8 @@ func registerV1Routes(g *gin.RouterGroup, h *Handlers) {
|
||||
|
||||
// Contact merge (ref: Chatwoot ContactsController #merge)
|
||||
// POST /api/v1/accounts/:account_id/contacts/merge — merge two contacts
|
||||
// Also: POST /api/v1/accounts/:account_id/contact_merge — Chatwoot resource path
|
||||
// Also: POST /api/v1/accounts/:account_id/actions/contact_merge — Chatwoot resource path
|
||||
accountScoped.POST("/actions/contact_merge", h.Contact.Merge)
|
||||
accountScoped.POST("/contacts/merge", h.Contact.Merge)
|
||||
accountScoped.POST("/contact_merge", h.Contact.Merge)
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/repository"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -59,17 +61,64 @@ func (s *ContactMergeServiceTestSuite) TestMerge_BaseNotFound() {
|
||||
}
|
||||
|
||||
func (s *ContactMergeServiceTestSuite) TestMerge_Success() {
|
||||
base := &model.Contact{Base: model.Base{}, AccountID: 1, Name: "base"}
|
||||
mergee := &model.Contact{Base: model.Base{}, AccountID: 1, Name: "mergee"}
|
||||
base := &model.Contact{
|
||||
Base: model.Base{},
|
||||
AccountID: 1,
|
||||
Name: "base",
|
||||
Email: "base@example.com",
|
||||
Identifier: "base-id",
|
||||
AdditionalAttributes: datatypes.JSON(`{"city":"base","nested":{"keep":"base"}}`),
|
||||
CustomAttributes: datatypes.JSON(`{"tier":"gold","base_only":"yes"}`),
|
||||
}
|
||||
mergee := &model.Contact{
|
||||
Base: model.Base{},
|
||||
AccountID: 1,
|
||||
Name: "mergee",
|
||||
Email: "mergee@example.com",
|
||||
PhoneNumber: "+12212345",
|
||||
AdditionalAttributes: datatypes.JSON(`{"city":"mergee","mergee_only":"yes","nested":{"new":"mergee"}}`),
|
||||
CustomAttributes: datatypes.JSON(`{"tier":"silver","mergee_only":"yes"}`),
|
||||
}
|
||||
s.db.Create(base)
|
||||
s.db.Create(mergee)
|
||||
s.db.Create(&model.Conversation{AccountID: 1, InboxID: 1, ContactID: mergee.ID, Status: "open", ChannelType: "Channel::WebWidget", Channel: "web_widget"})
|
||||
s.db.Create(&model.ContactInbox{ContactID: mergee.ID, InboxID: 1, SourceID: "source-mergee"})
|
||||
s.db.Create(&model.Note{AccountID: 1, ContactID: mergee.ID, Content: "mergee note"})
|
||||
s.db.Create(&model.Message{AccountID: 1, InboxID: 1, ConversationID: 1, SenderID: &mergee.ID, SenderType: "contact", Content: "lowercase sender", ContentType: "text"})
|
||||
s.db.Create(&model.Message{AccountID: 1, InboxID: 1, ConversationID: 1, SenderID: &mergee.ID, SenderType: string(model.SenderTypeContact), Content: "polymorphic sender", ContentType: "text"})
|
||||
|
||||
result, err := s.svc.Merge(1, base.ID, mergee.ID)
|
||||
assert.NoError(s.T(), err)
|
||||
assert.Equal(s.T(), "base", result.Name)
|
||||
assert.Equal(s.T(), "base@example.com", result.Email)
|
||||
assert.Equal(s.T(), "base-id", result.Identifier)
|
||||
assert.Equal(s.T(), "+12212345", result.PhoneNumber)
|
||||
|
||||
var attrs map[string]any
|
||||
assert.NoError(s.T(), json.Unmarshal(result.CustomAttributes, &attrs))
|
||||
assert.Equal(s.T(), "gold", attrs["tier"])
|
||||
assert.Equal(s.T(), "yes", attrs["base_only"])
|
||||
assert.Equal(s.T(), "yes", attrs["mergee_only"])
|
||||
|
||||
var additional map[string]any
|
||||
assert.NoError(s.T(), json.Unmarshal(result.AdditionalAttributes, &additional))
|
||||
assert.Equal(s.T(), "base", additional["city"])
|
||||
assert.Equal(s.T(), "yes", additional["mergee_only"])
|
||||
nested := additional["nested"].(map[string]any)
|
||||
assert.Equal(s.T(), "base", nested["keep"])
|
||||
assert.Equal(s.T(), "mergee", nested["new"])
|
||||
|
||||
// Verify mergee is soft-deleted — GORM Delete sets deleted_at
|
||||
var count int64
|
||||
s.db.Model(&model.Contact{}).Where("id = ?", mergee.ID).Count(&count)
|
||||
assert.Equal(s.T(), int64(0), count) // GORM soft-delete: filtered out by default
|
||||
}
|
||||
|
||||
s.db.Model(&model.Conversation{}).Where("contact_id = ?", base.ID).Count(&count)
|
||||
assert.Equal(s.T(), int64(1), count)
|
||||
s.db.Model(&model.ContactInbox{}).Where("contact_id = ?", base.ID).Count(&count)
|
||||
assert.Equal(s.T(), int64(1), count)
|
||||
s.db.Model(&model.Note{}).Where("contact_id = ?", base.ID).Count(&count)
|
||||
assert.Equal(s.T(), int64(1), count)
|
||||
s.db.Model(&model.Message{}).Where("sender_id = ?", base.ID).Count(&count)
|
||||
assert.Equal(s.T(), int64(2), count)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user