Files
gochat/internal/repository/contact_repo_test.go
T
2026-06-04 15:44:48 +08:00

363 lines
12 KiB
Go

package repository
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/datatypes"
"gorm.io/gorm"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/search"
)
func createTestContact(t *testing.T, db *gorm.DB, accountID uint, name, email string) *model.Contact {
t.Helper()
contact := &model.Contact{
AccountID: accountID,
Name: name,
Email: email,
PhoneNumber: "",
}
require.NoError(t, db.Create(contact).Error)
return contact
}
func TestContactRepo_FindByID(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactFindOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
contact := createTestContact(t, db, account.ID, "John Doe", "john@example.com")
found, err := repo.FindByID(context.Background(), contact.ID)
assert.NoError(t, err)
assert.Equal(t, contact.ID, found.ID)
assert.Equal(t, "John Doe", found.Name)
assert.Equal(t, "john@example.com", found.Email)
}
func TestContactRepo_FindByID_NotFound(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
found, err := repo.FindByID(context.Background(), 9999)
assert.Error(t, err)
assert.Nil(t, found)
}
func TestContactRepo_FindByAccountAndID(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactScopedOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
contact := createTestContact(t, db, account.ID, "Scoped", "scoped@example.com")
found, err := repo.FindByAccountAndID(context.Background(), account.ID, contact.ID)
assert.NoError(t, err)
assert.Equal(t, contact.ID, found.ID)
}
func TestContactRepo_FindByAccountAndID_WrongAccount(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactScopedWrongOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
contact := createTestContact(t, db, account.ID, "WrongScoped", "wrong@example.com")
found, err := repo.FindByAccountAndID(context.Background(), 9999, contact.ID)
assert.Error(t, err)
assert.Nil(t, found)
}
func TestContactRepo_FindByEmail(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactEmailOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
contact := createTestContact(t, db, account.ID, "EmailUser", "unique@example.com")
found, err := repo.FindByEmail(context.Background(), account.ID, "unique@example.com")
assert.NoError(t, err)
assert.Equal(t, contact.ID, found.ID)
}
func TestContactRepo_FindByEmail_NotFound(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactEmailNotFoundOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
found, err := repo.FindByEmail(context.Background(), account.ID, "nonexistent@example.com")
assert.Error(t, err)
assert.Nil(t, found)
}
func TestContactRepo_FindByAccount(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactListOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
for i := 0; i < 5; i++ {
createTestContact(t, db, account.ID, "Contact"+string(rune('A'+i)), "c"+string(rune('A'+i))+"@example.com")
}
contacts, total, err := repo.FindByAccount(context.Background(), account.ID, 0, 10, "")
assert.NoError(t, err)
assert.Equal(t, int64(5), total)
assert.Len(t, contacts, 5)
}
func TestContactRepo_FindByAccount_Empty(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
contacts, total, err := repo.FindByAccount(context.Background(), 1, 0, 10, "")
assert.NoError(t, err)
assert.Equal(t, int64(0), total)
assert.Len(t, contacts, 0)
}
func TestContactRepo_Create(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
contact := &model.Contact{
AccountID: 1,
Name: "New Contact",
Email: "new@example.com",
PhoneNumber: "1234567890",
}
err := repo.Create(context.Background(), contact)
assert.NoError(t, err)
assert.NotZero(t, contact.ID)
}
func TestContactRepo_Update(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactUpdateOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
contact := createTestContact(t, db, account.ID, "Original", "original@example.com")
contact.Name = "Updated"
err := repo.Update(context.Background(), contact)
assert.NoError(t, err)
found, err := repo.FindByID(context.Background(), contact.ID)
assert.NoError(t, err)
assert.Equal(t, "Updated", found.Name)
}
func TestContactRepo_Delete(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactDeleteOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
contact := createTestContact(t, db, account.ID, "DeleteMe", "delete@example.com")
err := repo.Delete(context.Background(), contact.ID)
assert.NoError(t, err)
found, err := repo.FindByID(context.Background(), contact.ID)
assert.Error(t, err)
assert.Nil(t, found)
}
func TestContactRepo_CountByAccount(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactCountOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
for i := 0; i < 3; i++ {
createTestContact(t, db, account.ID, "Count"+string(rune('A'+i)), "count"+string(rune('A'+i))+"@example.com")
}
total, err := repo.CountByAccount(context.Background(), account.ID)
assert.NoError(t, err)
assert.Equal(t, int64(3), total)
}
// Search uses ILIKE which SQLite does not support; skip when running SQLite
func TestContactRepo_Search(t *testing.T) {
skipIfSQLite(t)
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactSearchOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
createTestContact(t, db, account.ID, "Alice Smith", "alice@example.com")
createTestContact(t, db, account.ID, "Bob Jones", "bob@example.com")
createTestContact(t, db, account.ID, "Alice Jones", "alice.jones@example.com")
// Search by name
contacts, total, err := repo.Search(context.Background(), account.ID, "alice", 0, 10, "", search.SearchModeILike)
assert.NoError(t, err)
assert.Equal(t, int64(2), total)
assert.Len(t, contacts, 2)
// Search by email
contacts, total, err = repo.Search(context.Background(), account.ID, "bob", 0, 10, "", search.SearchModeILike)
assert.NoError(t, err)
assert.Equal(t, int64(1), total)
assert.Len(t, contacts, 1)
assert.Equal(t, "Bob Jones", contacts[0].Name)
}
func TestContactRepo_FindActive(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactActiveOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
// Create an active contact (with last_activity_at set)
activeContact := createTestContact(t, db, account.ID, "Active Alice", "active@example.com")
now := int64(1000000)
require.NoError(t, db.Model(activeContact).Update("last_activity_at", &now).Error)
// Create an inactive contact (no last_activity_at)
createTestContact(t, db, account.ID, "Inactive Bob", "inactive@example.com")
contacts, total, err := repo.FindActive(context.Background(), account.ID, 0, 10, "")
assert.NoError(t, err)
assert.Equal(t, int64(1), total)
assert.Len(t, contacts, 1)
assert.Equal(t, "Active Alice", contacts[0].Name)
}
func TestContactRepo_FindActive_Empty(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactActiveEmptyOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
// Create only inactive contacts (no last_activity_at)
createTestContact(t, db, account.ID, "NoActivity", "noactivity@example.com")
contacts, total, err := repo.FindActive(context.Background(), account.ID, 0, 10, "")
assert.NoError(t, err)
assert.Equal(t, int64(0), total)
assert.Len(t, contacts, 0)
}
func TestContactRepo_FindActive_Pagination(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactActivePageOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
// Create 5 active contacts
for i := 0; i < 5; i++ {
c := createTestContact(t, db, account.ID, "Active"+string(rune('A'+i)), "active"+string(rune('A'+i))+"@example.com")
ts := int64(1000000 + i)
require.NoError(t, db.Model(c).Update("last_activity_at", &ts).Error)
}
// Fetch first page (offset=0, limit=2)
contacts, total, err := repo.FindActive(context.Background(), account.ID, 0, 2, "")
assert.NoError(t, err)
assert.Equal(t, int64(5), total)
assert.Len(t, contacts, 2)
// Fetch second page (offset=2, limit=2)
contacts2, total2, err2 := repo.FindActive(context.Background(), account.ID, 2, 2, "")
assert.NoError(t, err2)
assert.Equal(t, int64(5), total2)
assert.Len(t, contacts2, 2)
// Ensure different contacts returned
assert.NotEqual(t, contacts[0].ID, contacts2[0].ID)
}
func TestContactRepo_FindAllForExport(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactExportOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
c1 := createTestContact(t, db, account.ID, "Export Alice", "export1@example.com")
c2 := createTestContact(t, db, account.ID, "Export Bob", "export2@example.com")
c3 := createTestContact(t, db, account.ID, "Export Carol", "export3@example.com")
contacts, err := repo.FindAllForExport(context.Background(), account.ID)
assert.NoError(t, err)
assert.Len(t, contacts, 3)
// Verify ordered by id ASC
assert.Equal(t, c1.ID, contacts[0].ID)
assert.Equal(t, c2.ID, contacts[1].ID)
assert.Equal(t, c3.ID, contacts[2].ID)
}
func TestContactRepo_FindAllForExport_Empty(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactExportEmptyOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
contacts, err := repo.FindAllForExport(context.Background(), account.ID)
assert.NoError(t, err)
assert.Len(t, contacts, 0)
}
func TestContactRepo_DeleteCustomAttributes(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
account := &model.Account{Name: "ContactDeleteAttrOrg", Locale: "en", Active: true}
require.NoError(t, db.Create(account).Error)
contact := createTestContact(t, db, account.ID, "Attr User", "attr@example.com")
// Set custom attributes
require.NoError(t, db.Model(contact).Update("custom_attributes", datatypes.JSON(`{"key":"value"}`)).Error)
// Verify custom attributes are set
found, err := repo.FindByID(context.Background(), contact.ID)
require.NoError(t, err)
assert.Equal(t, datatypes.JSON(`{"key":"value"}`), found.CustomAttributes)
// Delete custom attributes
err = repo.DeleteCustomAttributes(context.Background(), contact.ID)
assert.NoError(t, err)
// Verify custom attributes are now empty
found2, err := repo.FindByID(context.Background(), contact.ID)
require.NoError(t, err)
assert.Equal(t, datatypes.JSON(`{}`), found2.CustomAttributes)
}
func TestContactRepo_DeleteCustomAttributes_NotFound(t *testing.T) {
db := setupTestDB(t)
repo := NewContactRepo(db)
// DeleteCustomAttributes on a non-existent id: GORM Update with Where returns nil
// (no ErrRecordNotFound) but RowsAffected is 0
err := repo.DeleteCustomAttributes(context.Background(), 9999)
// GORM's Update with Where clause on nonexistent record returns nil error
// (it just affects 0 rows), so we verify it doesn't crash and returns no error
assert.NoError(t, err)
}