419 lines
13 KiB
Go
419 lines
13 KiB
Go
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/datatypes"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
)
|
|
|
|
// ========== ListActive ==========
|
|
|
|
func TestContactService_ListActive_ReturnsContactsWithActivity(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// Create contacts: one with last_activity_at set, one without
|
|
now := time.Now().Unix()
|
|
activeContact := &model.Contact{
|
|
AccountID: account.ID,
|
|
Name: "Active Contact",
|
|
Email: "active@test.com",
|
|
LastActivityAt: &now,
|
|
}
|
|
inactiveContact := &model.Contact{
|
|
AccountID: account.ID,
|
|
Name: "Inactive Contact",
|
|
Email: "inactive@test.com",
|
|
// LastActivityAt is nil
|
|
}
|
|
require.NoError(t, db.Create(activeContact).Error)
|
|
require.NoError(t, db.Create(inactiveContact).Error)
|
|
|
|
contacts, total, err := svc.ListActive(context.Background(), account.ID, 0, 10, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), total)
|
|
assert.Len(t, contacts, 1)
|
|
assert.Equal(t, "Active Contact", contacts[0].Name)
|
|
}
|
|
|
|
func TestContactService_ListActive_EmptyWhenNoActiveContacts(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// Create a contact without last_activity_at
|
|
contact := &model.Contact{
|
|
AccountID: account.ID,
|
|
Name: "No Activity Contact",
|
|
Email: "noact@test.com",
|
|
}
|
|
require.NoError(t, db.Create(contact).Error)
|
|
|
|
contacts, total, err := svc.ListActive(context.Background(), account.ID, 0, 10, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), total)
|
|
assert.Len(t, contacts, 0)
|
|
}
|
|
|
|
func TestContactService_ListActive_EmptyWhenNoContacts(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
contacts, total, err := svc.ListActive(context.Background(), account.ID, 0, 10, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(0), total)
|
|
assert.Len(t, contacts, 0)
|
|
}
|
|
|
|
func TestContactService_ListActive_Pagination(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
now := time.Now().Unix()
|
|
// Create 3 active contacts
|
|
for i := 0; i < 3; i++ {
|
|
c := &model.Contact{
|
|
AccountID: account.ID,
|
|
Name: fmt.Sprintf("Contact %c", 'A'+i),
|
|
Email: fmt.Sprintf("page%c@test.com", 'A'+i),
|
|
LastActivityAt: &now,
|
|
}
|
|
require.NoError(t, db.Create(c).Error)
|
|
}
|
|
|
|
// Get first page with limit=2
|
|
contacts, total, err := svc.ListActive(context.Background(), account.ID, 0, 2, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), total)
|
|
assert.Len(t, contacts, 2)
|
|
|
|
// Get second page
|
|
contacts2, total2, err := svc.ListActive(context.Background(), account.ID, 2, 2, "")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(3), total2)
|
|
assert.Len(t, contacts2, 1)
|
|
}
|
|
|
|
// ========== ExportCSV ==========
|
|
|
|
func TestContactService_ExportCSV_WritesCorrectHeadersAndRows(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// Create a contact
|
|
contact := &model.Contact{
|
|
AccountID: account.ID,
|
|
Name: "Export Contact",
|
|
Email: "export@test.com",
|
|
PhoneNumber: "+1234567890",
|
|
Identifier: "id123",
|
|
CountryCode: "US",
|
|
Location: "New York",
|
|
ContactType: "visitor",
|
|
Blocked: false,
|
|
}
|
|
require.NoError(t, db.Create(contact).Error)
|
|
|
|
var buf bytes.Buffer
|
|
err := svc.ExportCSV(context.Background(), account.ID, &buf)
|
|
require.NoError(t, err)
|
|
|
|
csvOutput := buf.String()
|
|
lines := strings.Split(csvOutput, "\n")
|
|
|
|
// Verify header
|
|
assert.Equal(t, "id,name,email,phone_number,identifier,country_code,location,contact_type,blocked,created_at", lines[0])
|
|
|
|
// Verify data row contains the contact info
|
|
dataLine := lines[1]
|
|
assert.Contains(t, dataLine, "Export Contact")
|
|
assert.Contains(t, dataLine, "export@test.com")
|
|
assert.Contains(t, dataLine, "+1234567890")
|
|
assert.Contains(t, dataLine, "id123")
|
|
assert.Contains(t, dataLine, "US")
|
|
assert.Contains(t, dataLine, "New York")
|
|
assert.Contains(t, dataLine, "lead")
|
|
assert.Contains(t, dataLine, "false")
|
|
}
|
|
|
|
func TestContactService_ExportCSV_EmptyAccount(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
var buf bytes.Buffer
|
|
err := svc.ExportCSV(context.Background(), account.ID, &buf)
|
|
require.NoError(t, err)
|
|
|
|
csvOutput := buf.String()
|
|
lines := strings.Split(csvOutput, "\n")
|
|
|
|
// Should have header only (plus trailing empty line from csv writer)
|
|
assert.Equal(t, "id,name,email,phone_number,identifier,country_code,location,contact_type,blocked,created_at", lines[0])
|
|
// No data rows
|
|
assert.Empty(t, strings.TrimSpace(lines[1]))
|
|
}
|
|
|
|
func TestContactService_ExportCSV_MultipleContacts(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
for i := 0; i < 3; i++ {
|
|
c := &model.Contact{
|
|
AccountID: account.ID,
|
|
Name: fmt.Sprintf("Multi Contact %c", 'A'+i),
|
|
Email: fmt.Sprintf("multi%c@test.com", 'A'+i),
|
|
}
|
|
require.NoError(t, db.Create(c).Error)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err := svc.ExportCSV(context.Background(), account.ID, &buf)
|
|
require.NoError(t, err)
|
|
|
|
csvOutput := buf.String()
|
|
lines := strings.Split(strings.TrimSpace(csvOutput), "\n")
|
|
|
|
// Header + 3 data rows
|
|
assert.Len(t, lines, 4)
|
|
}
|
|
|
|
// ========== ImportCSV ==========
|
|
|
|
func TestContactService_ImportCSV_ImportsValidRows(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
csvData := "name,email,phone_number\nAlice,alice@test.com,+111\nBob,bob@test.com,+222\n"
|
|
result, err := svc.ImportCSV(context.Background(), account.ID, strings.NewReader(csvData))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, result.Imported)
|
|
assert.Equal(t, 0, result.Skipped)
|
|
assert.Equal(t, 0, result.Failed)
|
|
|
|
// Verify contacts were created
|
|
var count int64
|
|
db.Model(&model.Contact{}).Where("account_id = ?", account.ID).Count(&count)
|
|
assert.Equal(t, int64(2), count)
|
|
}
|
|
|
|
func TestContactService_ImportCSV_SkipsRowsWithoutName(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
csvData := "name,email\nAlice,alice@test.com\n,bob@test.com\n"
|
|
result, err := svc.ImportCSV(context.Background(), account.ID, strings.NewReader(csvData))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.Imported)
|
|
assert.Equal(t, 1, result.Skipped) // row without name is skipped
|
|
assert.Equal(t, 0, result.Failed)
|
|
}
|
|
|
|
func TestContactService_ImportCSV_SkipsDuplicateEmail(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// Pre-create a contact with the same email
|
|
existing := &model.Contact{
|
|
AccountID: account.ID,
|
|
Name: "Existing",
|
|
Email: "dup@test.com",
|
|
}
|
|
require.NoError(t, db.Create(existing).Error)
|
|
|
|
csvData := "name,email\nNewDup,dup@test.com\nUnique,unique@test.com\n"
|
|
result, err := svc.ImportCSV(context.Background(), account.ID, strings.NewReader(csvData))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.Imported) // unique@test.com
|
|
assert.Equal(t, 1, result.Skipped) // dup@test.com is skipped
|
|
assert.Equal(t, 0, result.Failed)
|
|
}
|
|
|
|
func TestContactService_ImportCSV_FailsOnBadCSV(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// Empty input should fail on header read
|
|
result, err := svc.ImportCSV(context.Background(), account.ID, strings.NewReader(""))
|
|
assert.Error(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestContactService_ImportCSV_AllColumns(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
csvData := "name,email,phone_number,identifier,country_code,location,contact_type\nFull,full@test.com,+999,fid001,DE,Berlin,visitor\n"
|
|
result, err := svc.ImportCSV(context.Background(), account.ID, strings.NewReader(csvData))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, result.Imported)
|
|
|
|
// Verify all fields were imported
|
|
var contact model.Contact
|
|
require.NoError(t, db.Where("account_id = ? AND email = ?", account.ID, "full@test.com").First(&contact).Error)
|
|
assert.Equal(t, "Full", contact.Name)
|
|
assert.Equal(t, "+999", contact.PhoneNumber)
|
|
assert.Equal(t, "fid001", contact.Identifier)
|
|
assert.Equal(t, "DE", contact.CountryCode)
|
|
assert.Equal(t, "Berlin", contact.Location)
|
|
assert.Equal(t, "lead", contact.ContactType) // visitor upgraded to lead by BeforeSave (has email+phone)
|
|
}
|
|
|
|
// ========== DeleteCustomAttributes ==========
|
|
|
|
func TestContactService_DeleteCustomAttributes_ClearsCustomAttributes(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
// Create a contact with custom attributes
|
|
contact := &model.Contact{
|
|
AccountID: account.ID,
|
|
Name: "Custom Attr Contact",
|
|
Email: "custom@test.com",
|
|
CustomAttributes: datatypes.JSON(`{"key1":"val1","key2":"val2"}`),
|
|
}
|
|
require.NoError(t, db.Create(contact).Error)
|
|
|
|
err := svc.DeleteCustomAttributes(context.Background(), account.ID, contact.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Verify custom_attributes is now empty
|
|
var updated model.Contact
|
|
require.NoError(t, db.First(&updated, contact.ID).Error)
|
|
assert.Equal(t, datatypes.JSON("{}"), updated.CustomAttributes)
|
|
}
|
|
|
|
func TestContactService_DeleteCustomAttributes_ContactNotFound(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
err := svc.DeleteCustomAttributes(context.Background(), account.ID, 99999)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "contact not found", err.Error())
|
|
}
|
|
|
|
func TestContactService_DeleteCustomAttributes_WrongAccount(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account1 := createTestAccountWithName(t, db, "Account 1")
|
|
account2 := createTestAccountWithName(t, db, "Account 2")
|
|
|
|
// Contact belongs to account1
|
|
contact := &model.Contact{
|
|
AccountID: account1.ID,
|
|
Name: "Scoped Contact",
|
|
Email: "scoped@test.com",
|
|
CustomAttributes: datatypes.JSON(`{"key":"val"}`),
|
|
}
|
|
require.NoError(t, db.Create(contact).Error)
|
|
|
|
// Try to delete custom attrs using account2 — should fail
|
|
err := svc.DeleteCustomAttributes(context.Background(), account2.ID, contact.ID)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "contact not found", err.Error())
|
|
}
|
|
|
|
// ========== GetContactableInboxes ==========
|
|
|
|
func TestContactService_GetContactableInboxes_ReturnsExistingInboxes(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
contact := createTestContact(t, db, account.ID)
|
|
inbox := createTestInbox(t, db, account.ID, "web_widget")
|
|
|
|
// Create a contact_inbox association
|
|
ci := &model.ContactInbox{
|
|
ContactID: contact.ID,
|
|
InboxID: inbox.ID,
|
|
SourceID: "src_test",
|
|
HMACToken: "hmac_test",
|
|
PubsubToken: "pubsub_test",
|
|
}
|
|
require.NoError(t, db.Create(ci).Error)
|
|
|
|
result, err := svc.GetContactableInboxes(context.Background(), account.ID, contact.ID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, result, 1)
|
|
assert.Equal(t, inbox.ID, result[0].Inbox.ID)
|
|
assert.Equal(t, "web_widget", result[0].Inbox.ChannelType)
|
|
assert.Equal(t, ci.ID, result[0].ContactInbox.ID)
|
|
assert.Equal(t, "src_test", result[0].SourceID)
|
|
}
|
|
|
|
func TestContactService_GetContactableInboxes_MultipleInboxes(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
contact := createTestContact(t, db, account.ID)
|
|
inbox1 := createTestInbox(t, db, account.ID, "web_widget")
|
|
inbox2 := createTestInbox(t, db, account.ID, "api")
|
|
|
|
// Create two contact_inbox associations
|
|
ci1 := &model.ContactInbox{
|
|
ContactID: contact.ID,
|
|
InboxID: inbox1.ID,
|
|
SourceID: "src_web",
|
|
HMACToken: "hmac1",
|
|
PubsubToken: "pubsub1",
|
|
}
|
|
ci2 := &model.ContactInbox{
|
|
ContactID: contact.ID,
|
|
InboxID: inbox2.ID,
|
|
SourceID: "src_api",
|
|
HMACToken: "hmac2",
|
|
PubsubToken: "pubsub2",
|
|
}
|
|
require.NoError(t, db.Create(ci1).Error)
|
|
require.NoError(t, db.Create(ci2).Error)
|
|
|
|
result, err := svc.GetContactableInboxes(context.Background(), account.ID, contact.ID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, result, 2)
|
|
|
|
// Check both inboxes are present
|
|
inboxTypes := map[string]bool{}
|
|
for _, r := range result {
|
|
inboxTypes[r.Inbox.ChannelType] = true
|
|
}
|
|
assert.True(t, inboxTypes["web_widget"])
|
|
assert.True(t, inboxTypes["api"])
|
|
}
|
|
|
|
func TestContactService_GetContactableInboxes_ContactNotFound(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
|
|
result, err := svc.GetContactableInboxes(context.Background(), account.ID, 99999)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "contact not found", err.Error())
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestContactService_GetContactableInboxes_WrongAccount(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account1 := createTestAccountWithName(t, db, "Account 1")
|
|
account2 := createTestAccountWithName(t, db, "Account 2")
|
|
contact := createTestContact(t, db, account1.ID)
|
|
|
|
// Try with wrong account
|
|
result, err := svc.GetContactableInboxes(context.Background(), account2.ID, contact.ID)
|
|
assert.Error(t, err)
|
|
assert.Equal(t, "contact not found", err.Error())
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestContactService_GetContactableInboxes_NoInboxes(t *testing.T) {
|
|
db, _, svc := setupContactService(t)
|
|
account := createTestAccount(t, db)
|
|
contact := createTestContact(t, db, account.ID)
|
|
|
|
// No contact_inbox associations created
|
|
result, err := svc.GetContactableInboxes(context.Background(), account.ID, contact.ID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, result, 0)
|
|
} |