Restructure the monorepo into clear top-level directories: - backend/: Go module root (cmd, internal, pkg, configs, migrations, docs/swagger, scripts, tests, go.mod, Makefile, .air.toml) - deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd - docs/: project documentation + reports/ (moved from repo root) - AGENTS.md: new AI coding-agent guide at repo root Update all references to the new layout: - Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root) - docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile, env_file ../../.env, volume mounts ../../backend:/app - deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile - CI: working-directory: backend for go commands, file deploy/docker/Dockerfile, coverage path backend/coverage.out, health_check backend/scripts/ - backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../ - README: architecture tree, quickstart, config paths updated Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh, gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to preserve history. Build, vet, SQLite tests, and docker compose config verified.
307 lines
9.6 KiB
Go
307 lines
9.6 KiB
Go
package repository
|
||
|
||
import (
|
||
"context"
|
||
"testing"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/stretchr/testify/require"
|
||
"gorm.io/gorm"
|
||
|
||
"github.com/gochat/gochat/internal/model"
|
||
)
|
||
|
||
// Helper: create a test ContactInbox with prerequisites (Account, Contact, Inbox).
|
||
func createTestContactInbox(t *testing.T, db *gorm.DB, sourceID, pubsubToken string) *model.ContactInbox {
|
||
t.Helper()
|
||
|
||
account := &model.Account{Name: "CITestOrg", Locale: "en", Active: true}
|
||
require.NoError(t, db.Create(account).Error)
|
||
|
||
contact := &model.Contact{AccountID: account.ID, Name: "CIContact", Email: "ci@example.com"}
|
||
require.NoError(t, db.Create(contact).Error)
|
||
|
||
inbox := &model.Inbox{AccountID: account.ID, Name: "CIInbox", ChannelType: "web_widget", ChannelID: 1}
|
||
require.NoError(t, db.Create(inbox).Error)
|
||
|
||
ci := &model.ContactInbox{
|
||
ContactID: contact.ID,
|
||
InboxID: inbox.ID,
|
||
SourceID: sourceID,
|
||
PubsubToken: pubsubToken,
|
||
}
|
||
require.NoError(t, db.Create(ci).Error)
|
||
return ci
|
||
}
|
||
|
||
// 1. FindByID – success
|
||
func TestContactInboxRepo_FindByID(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
ci := createTestContactInbox(t, db, "src-001", "pub-001")
|
||
|
||
found, err := repo.FindByID(context.Background(), ci.ID)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, ci.ID, found.ID)
|
||
assert.Equal(t, ci.ContactID, found.ContactID)
|
||
assert.Equal(t, ci.InboxID, found.InboxID)
|
||
assert.Equal(t, "src-001", found.SourceID)
|
||
assert.Equal(t, "pub-001", found.PubsubToken)
|
||
}
|
||
|
||
// 2. FindByID – not found
|
||
func TestContactInboxRepo_FindByID_NotFound(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
found, err := repo.FindByID(context.Background(), 9999)
|
||
assert.Error(t, err)
|
||
assert.Nil(t, found)
|
||
}
|
||
|
||
// 3. FindByContactAndInbox – success
|
||
func TestContactInboxRepo_FindByContactAndInbox(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
ci := createTestContactInbox(t, db, "src-002", "pub-002")
|
||
|
||
found, err := repo.FindByContactAndInbox(context.Background(), ci.ContactID, ci.InboxID)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, ci.ID, found.ID)
|
||
assert.Equal(t, ci.ContactID, found.ContactID)
|
||
assert.Equal(t, ci.InboxID, found.InboxID)
|
||
}
|
||
|
||
// 4. FindByContactAndInbox – not found
|
||
func TestContactInboxRepo_FindByContactAndInbox_NotFound(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
found, err := repo.FindByContactAndInbox(context.Background(), 8888, 9999)
|
||
assert.Error(t, err)
|
||
assert.Nil(t, found)
|
||
}
|
||
|
||
func TestContactInboxRepo_FindByContactInboxSource(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
ci := createTestContactInbox(t, db, "browser-abc", "pub-source")
|
||
|
||
found, err := repo.FindByContactInboxSource(context.Background(), ci.ContactID, ci.InboxID, "browser-abc")
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, ci.ID, found.ID)
|
||
assert.Equal(t, ci.SourceID, found.SourceID)
|
||
assert.Equal(t, ci.InboxID, found.Inbox.ID)
|
||
}
|
||
|
||
// 5. FindByPubsubToken – success (with Contact preloaded)
|
||
func TestContactInboxRepo_FindByPubsubToken(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
ci := createTestContactInbox(t, db, "src-003", "pub-003")
|
||
|
||
found, err := repo.FindByPubsubToken(context.Background(), "pub-003")
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, ci.ID, found.ID)
|
||
assert.Equal(t, "pub-003", found.PubsubToken)
|
||
// Verify Contact is preloaded
|
||
assert.Equal(t, ci.ContactID, found.Contact.ID)
|
||
assert.Equal(t, "CIContact", found.Contact.Name)
|
||
}
|
||
|
||
// 6. FindByPubsubToken – not found
|
||
func TestContactInboxRepo_FindByPubsubToken_NotFound(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
found, err := repo.FindByPubsubToken(context.Background(), "nonexistent-token")
|
||
assert.Error(t, err)
|
||
assert.Nil(t, found)
|
||
}
|
||
|
||
// 7. FindByContact – returns multiple contact_inboxes for same contact
|
||
func TestContactInboxRepo_FindByContact(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
account := &model.Account{Name: "CIListOrg", Locale: "en", Active: true}
|
||
require.NoError(t, db.Create(account).Error)
|
||
|
||
contact := &model.Contact{AccountID: account.ID, Name: "CIListContact"}
|
||
require.NoError(t, db.Create(contact).Error)
|
||
|
||
inbox1 := &model.Inbox{AccountID: account.ID, Name: "CIListInbox1", ChannelType: "web_widget", ChannelID: 1}
|
||
require.NoError(t, db.Create(inbox1).Error)
|
||
inbox2 := &model.Inbox{AccountID: account.ID, Name: "CIListInbox2", ChannelType: "facebook", ChannelID: 2}
|
||
require.NoError(t, db.Create(inbox2).Error)
|
||
|
||
ci1 := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox1.ID, SourceID: "src-a", PubsubToken: "pub-a"}
|
||
require.NoError(t, db.Create(ci1).Error)
|
||
ci2 := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox2.ID, SourceID: "src-b", PubsubToken: "pub-b"}
|
||
require.NoError(t, db.Create(ci2).Error)
|
||
|
||
results, err := repo.FindByContact(context.Background(), contact.ID)
|
||
assert.NoError(t, err)
|
||
assert.Len(t, results, 2)
|
||
// Ordered by id ASC, so ci1 comes first
|
||
assert.Equal(t, ci1.ID, results[0].ID)
|
||
assert.Equal(t, ci2.ID, results[1].ID)
|
||
}
|
||
|
||
// 8. FindByContact – no results
|
||
func TestContactInboxRepo_FindByContact_Empty(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
results, err := repo.FindByContact(context.Background(), 9999)
|
||
assert.NoError(t, err)
|
||
assert.Len(t, results, 0)
|
||
}
|
||
|
||
// 9. FindByInbox – with pagination
|
||
func TestContactInboxRepo_FindByInbox(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
account := &model.Account{Name: "CIInboxOrg", Locale: "en", Active: true}
|
||
require.NoError(t, db.Create(account).Error)
|
||
|
||
inbox := &model.Inbox{AccountID: account.ID, Name: "CIInboxPag", ChannelType: "web_widget", ChannelID: 1}
|
||
require.NoError(t, db.Create(inbox).Error)
|
||
|
||
// Create 3 contacts with contact_inboxes in the same inbox
|
||
for i := 0; i < 3; i++ {
|
||
contact := &model.Contact{AccountID: account.ID, Name: "CIInboxContact" + string(rune('A'+i))}
|
||
require.NoError(t, db.Create(contact).Error)
|
||
ci := &model.ContactInbox{ContactID: contact.ID, InboxID: inbox.ID, SourceID: "src-inbox-" + string(rune('A'+i))}
|
||
require.NoError(t, db.Create(ci).Error)
|
||
}
|
||
|
||
// Page 1: offset=0, limit=2
|
||
results, total, err := repo.FindByInbox(context.Background(), inbox.ID, 0, 2)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, int64(3), total)
|
||
assert.Len(t, results, 2)
|
||
|
||
// Page 2: offset=2, limit=2
|
||
results2, total2, err := repo.FindByInbox(context.Background(), inbox.ID, 2, 2)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, int64(3), total2)
|
||
assert.Len(t, results2, 1)
|
||
}
|
||
|
||
// 10. FindBySourceID – success
|
||
func TestContactInboxRepo_FindBySourceID(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
ci := createTestContactInbox(t, db, "src-unique-007", "pub-007")
|
||
|
||
found, err := repo.FindBySourceID(context.Background(), ci.InboxID, "src-unique-007")
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, ci.ID, found.ID)
|
||
assert.Equal(t, "src-unique-007", found.SourceID)
|
||
assert.Equal(t, ci.InboxID, found.InboxID)
|
||
}
|
||
|
||
// 11. FindBySourceID – not found
|
||
func TestContactInboxRepo_FindBySourceID_NotFound(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
found, err := repo.FindBySourceID(context.Background(), 9999, "nonexistent-source")
|
||
assert.Error(t, err)
|
||
assert.Nil(t, found)
|
||
}
|
||
|
||
// 12. Create – success
|
||
func TestContactInboxRepo_Create(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
account := &model.Account{Name: "CICreateOrg", Locale: "en", Active: true}
|
||
require.NoError(t, db.Create(account).Error)
|
||
|
||
contact := &model.Contact{AccountID: account.ID, Name: "CICreateContact"}
|
||
require.NoError(t, db.Create(contact).Error)
|
||
|
||
inbox := &model.Inbox{AccountID: account.ID, Name: "CICreateInbox", ChannelType: "web_widget", ChannelID: 1}
|
||
require.NoError(t, db.Create(inbox).Error)
|
||
|
||
ci := &model.ContactInbox{
|
||
ContactID: contact.ID,
|
||
InboxID: inbox.ID,
|
||
SourceID: "src-create",
|
||
PubsubToken: "pub-create",
|
||
HMACToken: "hmac-create",
|
||
}
|
||
|
||
err := repo.Create(context.Background(), ci)
|
||
assert.NoError(t, err)
|
||
assert.NotZero(t, ci.ID)
|
||
|
||
// Verify persisted
|
||
found, err := repo.FindByID(context.Background(), ci.ID)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, "src-create", found.SourceID)
|
||
assert.Equal(t, "pub-create", found.PubsubToken)
|
||
assert.Equal(t, "hmac-create", found.HMACToken)
|
||
}
|
||
|
||
// 13. Update – success
|
||
func TestContactInboxRepo_Update(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
ci := createTestContactInbox(t, db, "src-update-old", "pub-update-old")
|
||
|
||
// Modify fields
|
||
ci.SourceID = "src-update-new"
|
||
ci.PubsubToken = "pub-update-new"
|
||
|
||
err := repo.Update(context.Background(), ci)
|
||
assert.NoError(t, err)
|
||
|
||
// Verify persisted
|
||
found, err := repo.FindByID(context.Background(), ci.ID)
|
||
assert.NoError(t, err)
|
||
assert.Equal(t, "src-update-new", found.SourceID)
|
||
assert.Equal(t, "pub-update-new", found.PubsubToken)
|
||
}
|
||
|
||
// 14. Delete – success
|
||
func TestContactInboxRepo_Delete(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
ci := createTestContactInbox(t, db, "src-delete", "pub-delete")
|
||
|
||
err := repo.Delete(context.Background(), ci.ID)
|
||
assert.NoError(t, err)
|
||
|
||
// Verify soft-deleted (FindByID should fail due to soft delete)
|
||
found, err := repo.FindByID(context.Background(), ci.ID)
|
||
assert.Error(t, err)
|
||
assert.Nil(t, found)
|
||
}
|
||
|
||
// 15. DeleteByContactAndInbox – success
|
||
func TestContactInboxRepo_DeleteByContactAndInbox(t *testing.T) {
|
||
db := setupTestDB(t)
|
||
repo := NewContactInboxRepo(db)
|
||
|
||
ci := createTestContactInbox(t, db, "src-del-ci", "pub-del-ci")
|
||
|
||
err := repo.DeleteByContactAndInbox(context.Background(), ci.ContactID, ci.InboxID)
|
||
assert.NoError(t, err)
|
||
|
||
// Verify soft-deleted
|
||
found, err := repo.FindByContactAndInbox(context.Background(), ci.ContactID, ci.InboxID)
|
||
assert.Error(t, err)
|
||
assert.Nil(t, found)
|
||
}
|