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.
408 lines
12 KiB
Go
408 lines
12 KiB
Go
package model_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
)
|
|
|
|
// --- TableName Method Tests ---
|
|
|
|
func TestAccountTableName(t *testing.T) {
|
|
assert.Equal(t, "accounts", model.Account{}.TableName())
|
|
}
|
|
|
|
func TestUserTableName(t *testing.T) {
|
|
assert.Equal(t, "users", model.User{}.TableName())
|
|
}
|
|
|
|
func TestInboxTableName(t *testing.T) {
|
|
assert.Equal(t, "inboxes", model.Inbox{}.TableName())
|
|
}
|
|
|
|
func TestConversationTableName(t *testing.T) {
|
|
assert.Equal(t, "conversations", model.Conversation{}.TableName())
|
|
}
|
|
|
|
func TestMessageTableName(t *testing.T) {
|
|
assert.Equal(t, "messages", model.Message{}.TableName())
|
|
}
|
|
|
|
func TestContactTableName(t *testing.T) {
|
|
assert.Equal(t, "contacts", model.Contact{}.TableName())
|
|
}
|
|
|
|
func TestAccountUserTableName(t *testing.T) {
|
|
assert.Equal(t, "account_users", model.AccountUser{}.TableName())
|
|
}
|
|
|
|
func TestInboxMemberTableName(t *testing.T) {
|
|
assert.Equal(t, "inbox_members", model.InboxMember{}.TableName())
|
|
}
|
|
|
|
func TestCustomRoleTableName(t *testing.T) {
|
|
assert.Equal(t, "custom_roles", model.CustomRole{}.TableName())
|
|
}
|
|
|
|
func TestAttachmentTableName(t *testing.T) {
|
|
assert.Equal(t, "attachments", model.Attachment{}.TableName())
|
|
}
|
|
|
|
func TestNotificationTableName(t *testing.T) {
|
|
assert.Equal(t, "notifications", model.Notification{}.TableName())
|
|
}
|
|
|
|
func TestNotificationPreferenceTableName(t *testing.T) {
|
|
assert.Equal(t, "notification_preferences", model.NotificationPreference{}.TableName())
|
|
}
|
|
|
|
func TestPlatformAppTableName(t *testing.T) {
|
|
assert.Equal(t, "platform_apps", model.PlatformApp{}.TableName())
|
|
}
|
|
|
|
func TestContactInboxTableName(t *testing.T) {
|
|
assert.Equal(t, "contact_inboxes", model.ContactInbox{}.TableName())
|
|
}
|
|
|
|
// --- AccountUser Method Tests ---
|
|
|
|
func TestAccountUserIsAdministrator(t *testing.T) {
|
|
au := &model.AccountUser{Role: "administrator"}
|
|
assert.True(t, au.IsAdministrator())
|
|
|
|
au.Role = "agent"
|
|
assert.False(t, au.IsAdministrator())
|
|
|
|
au.Role = "custom_role"
|
|
assert.False(t, au.IsAdministrator())
|
|
}
|
|
|
|
func TestAccountUserIsAgent(t *testing.T) {
|
|
au := &model.AccountUser{Role: "agent"}
|
|
assert.True(t, au.IsAgent())
|
|
|
|
au.Role = "administrator"
|
|
assert.False(t, au.IsAgent())
|
|
}
|
|
|
|
func TestAccountUserHasCustomRole(t *testing.T) {
|
|
au := &model.AccountUser{Role: "agent", CustomRoleID: 5}
|
|
assert.True(t, au.HasCustomRole())
|
|
|
|
au.Role = "agent"
|
|
assert.True(t, au.HasCustomRole())
|
|
|
|
au.Role = "custom_role"
|
|
au.CustomRoleID = 0
|
|
assert.False(t, au.HasCustomRole(), "custom_role with CustomRoleID=0 should not count")
|
|
}
|
|
|
|
func TestAccountUserIsOnline(t *testing.T) {
|
|
au := &model.AccountUser{Availability: "online"}
|
|
assert.True(t, au.IsOnline())
|
|
|
|
au.Availability = "offline"
|
|
assert.False(t, au.IsOnline())
|
|
|
|
au.Availability = "busy"
|
|
assert.False(t, au.IsOnline())
|
|
}
|
|
|
|
// --- CustomRole Method Tests ---
|
|
|
|
func TestCustomRoleGetPermissionMap(t *testing.T) {
|
|
cr := &model.CustomRole{
|
|
Permissions: `["conversation_manage","contact_manage"]`,
|
|
}
|
|
pm, err := cr.GetPermissionMap()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, model.PermissionLevelFull, pm[model.DimensionConversationManage])
|
|
assert.Equal(t, model.PermissionLevelFull, pm[model.DimensionContactManage])
|
|
assert.Empty(t, pm[model.DimensionAutomationManage])
|
|
}
|
|
|
|
func TestCustomRoleGetPermissionMapEmpty(t *testing.T) {
|
|
cr := &model.CustomRole{Permissions: "{}"}
|
|
pm, err := cr.GetPermissionMap()
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, pm)
|
|
|
|
cr.Permissions = ""
|
|
pm, err = cr.GetPermissionMap()
|
|
assert.NoError(t, err)
|
|
assert.Empty(t, pm)
|
|
}
|
|
|
|
func TestCustomRoleGetPermissionMapInvalid(t *testing.T) {
|
|
cr := &model.CustomRole{Permissions: "not json"}
|
|
pm, err := cr.GetPermissionMap()
|
|
assert.Error(t, err)
|
|
assert.Nil(t, pm)
|
|
}
|
|
|
|
func TestCustomRoleSetPermissionMap(t *testing.T) {
|
|
cr := &model.CustomRole{}
|
|
pm := map[model.PermissionDimension]model.PermissionLevel{
|
|
model.DimensionConversationManage: model.PermissionLevelFull,
|
|
model.DimensionContactManage: model.PermissionLevelRead,
|
|
}
|
|
err := cr.SetPermissionMap(pm)
|
|
assert.NoError(t, err)
|
|
assert.Contains(t, cr.Permissions, "conversation_manage")
|
|
assert.Contains(t, cr.Permissions, "contact_manage")
|
|
|
|
// Verify round-trip
|
|
pm2, err := cr.GetPermissionMap()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, model.PermissionLevelFull, pm2[model.DimensionConversationManage])
|
|
assert.Equal(t, model.PermissionLevelFull, pm2[model.DimensionContactManage])
|
|
}
|
|
|
|
func TestCustomRoleValidate(t *testing.T) {
|
|
cr := &model.CustomRole{
|
|
Name: "Valid Role",
|
|
Permissions: `["conversation_manage","contact_manage"]`,
|
|
}
|
|
assert.NoError(t, cr.Validate())
|
|
|
|
// Empty name
|
|
cr.Name = ""
|
|
assert.Error(t, cr.Validate())
|
|
|
|
// Invalid permission level
|
|
cr.Name = "Invalid Role"
|
|
cr.Permissions = `["conversation_delete"]`
|
|
assert.Error(t, cr.Validate())
|
|
|
|
// Invalid JSON
|
|
cr.Permissions = "bad json"
|
|
cr.Name = "BadJSON Role"
|
|
assert.Error(t, cr.Validate())
|
|
}
|
|
|
|
// --- CopilotMessage Method Tests ---
|
|
|
|
func TestCopilotMessageGetMessageContent(t *testing.T) {
|
|
msg := &model.CopilotMessage{
|
|
Message: json.RawMessage(`{"content": "Hello from copilot", "role": "assistant"}`),
|
|
}
|
|
assert.Equal(t, "Hello from copilot", msg.GetMessageContent())
|
|
|
|
// No content field
|
|
msg.Message = json.RawMessage(`{"role": "assistant"}`)
|
|
assert.Equal(t, "", msg.GetMessageContent())
|
|
|
|
// Invalid JSON
|
|
msg.Message = json.RawMessage(`invalid`)
|
|
assert.Equal(t, "", msg.GetMessageContent())
|
|
}
|
|
|
|
// --- CopilotThread PreviousHistory Method Test ---
|
|
|
|
func TestCopilotThreadPreviousHistory(t *testing.T) {
|
|
thread := &model.CopilotThread{}
|
|
|
|
messages := []model.CopilotMessage{
|
|
{
|
|
MessageType: model.CopilotMessageTypeUser,
|
|
Message: json.RawMessage(`{"content": "User question"}`),
|
|
},
|
|
{
|
|
MessageType: model.CopilotMessageTypeAssistant,
|
|
Message: json.RawMessage(`{"content": "AI answer"}`),
|
|
},
|
|
{
|
|
MessageType: model.CopilotMessageTypeAssistantThinking,
|
|
Message: json.RawMessage(`{"content": "Thinking trace"}`),
|
|
},
|
|
}
|
|
|
|
history := thread.PreviousHistory(messages)
|
|
assert.Len(t, history, 2, "assistant_thinking should be filtered out")
|
|
assert.Equal(t, "user", history[0].Role)
|
|
assert.Equal(t, "User question", history[0].Content)
|
|
assert.Equal(t, "assistant", history[1].Role)
|
|
assert.Equal(t, "AI answer", history[1].Content)
|
|
}
|
|
|
|
// --- Enum Constant Tests ---
|
|
|
|
func TestConversationStatusConstants(t *testing.T) {
|
|
assert.Equal(t, model.ConversationStatus("open"), model.ConversationStatusOpen)
|
|
assert.Equal(t, model.ConversationStatus("resolved"), model.ConversationStatusResolved)
|
|
assert.Equal(t, model.ConversationStatus("pending"), model.ConversationStatusPending)
|
|
assert.Equal(t, model.ConversationStatus("snoozed"), model.ConversationStatusSnoozed)
|
|
}
|
|
|
|
func TestMessageTypeConstants(t *testing.T) {
|
|
assert.Equal(t, model.MessageType("incoming"), model.MessageTypeIncoming)
|
|
assert.Equal(t, model.MessageType("outgoing"), model.MessageTypeOutgoing)
|
|
assert.Equal(t, model.MessageType("activity"), model.MessageTypeActivity)
|
|
assert.Equal(t, model.MessageType("template"), model.MessageTypeTemplate)
|
|
}
|
|
|
|
func TestAccountUserRoleConstants(t *testing.T) {
|
|
assert.Equal(t, model.AccountUserRole("agent"), model.AccountUserRoleAgent)
|
|
assert.Equal(t, model.AccountUserRole("administrator"), model.AccountUserRoleAdministrator)
|
|
}
|
|
|
|
func TestUserAvailabilityConstants(t *testing.T) {
|
|
assert.Equal(t, model.UserAvailability("online"), model.UserAvailabilityOnline)
|
|
assert.Equal(t, model.UserAvailability("offline"), model.UserAvailabilityOffline)
|
|
assert.Equal(t, model.UserAvailability("busy"), model.UserAvailabilityBusy)
|
|
}
|
|
|
|
func TestPermissionDimensionConstants(t *testing.T) {
|
|
assert.Equal(t, model.PermissionDimension("conversation_manage"), model.DimensionConversationManage)
|
|
assert.Equal(t, model.PermissionDimension("conversation_delete"), model.DimensionConversationDelete)
|
|
assert.Equal(t, model.PermissionDimension("contact_manage"), model.DimensionContactManage)
|
|
assert.Equal(t, model.PermissionDimension("report_manage"), model.DimensionReportManage)
|
|
assert.Equal(t, model.PermissionDimension("knowledge_base_manage"), model.DimensionKnowledgeBaseManage)
|
|
assert.Equal(t, model.PermissionDimension("automation_manage"), model.DimensionAutomationManage)
|
|
}
|
|
|
|
func TestPermissionLevelConstants(t *testing.T) {
|
|
assert.Equal(t, model.PermissionLevel("full"), model.PermissionLevelFull)
|
|
assert.Equal(t, model.PermissionLevel("read"), model.PermissionLevelRead)
|
|
assert.Equal(t, model.PermissionLevel("none"), model.PermissionLevelNone)
|
|
}
|
|
|
|
// --- Model Struct Field Verification Tests ---
|
|
|
|
func TestAccountStructFields(t *testing.T) {
|
|
acc := model.Account{
|
|
Name: "Test",
|
|
Domain: "test.com",
|
|
Locale: "en",
|
|
Timezone: "UTC",
|
|
Active: true,
|
|
Status: "active",
|
|
FeatureFlags: `{"feature_x":true}`,
|
|
AutoResolveDuration: 7,
|
|
}
|
|
assert.Equal(t, "Test", acc.Name)
|
|
assert.Equal(t, "test.com", acc.Domain)
|
|
assert.Equal(t, "en", acc.Locale)
|
|
assert.Equal(t, "UTC", acc.Timezone)
|
|
assert.True(t, acc.Active)
|
|
assert.Equal(t, "active", acc.Status)
|
|
assert.Contains(t, acc.FeatureFlags, "feature_x")
|
|
assert.Equal(t, 7, acc.AutoResolveDuration)
|
|
}
|
|
|
|
func TestUserStructFields(t *testing.T) {
|
|
user := model.User{
|
|
AccountID: 1,
|
|
Name: "Agent",
|
|
Email: "agent@test.com",
|
|
Password: "secret",
|
|
Provider: "email",
|
|
UID: "ext123",
|
|
AvatarURL: "https://img.test.com/avatar.png",
|
|
Role: "agent",
|
|
Active: true,
|
|
Available: false,
|
|
TOTPSecret: "otp_secret",
|
|
TOTPEnabled: false,
|
|
CustomRoleID: nil,
|
|
SignInCount: 5,
|
|
}
|
|
assert.Equal(t, uint(1), user.AccountID)
|
|
assert.Equal(t, "Agent", user.Name)
|
|
assert.Equal(t, "agent@test.com", user.Email)
|
|
assert.Equal(t, "email", user.Provider)
|
|
assert.Nil(t, user.CustomRoleID)
|
|
assert.Equal(t, 5, user.SignInCount)
|
|
}
|
|
|
|
func TestConversationStructFields(t *testing.T) {
|
|
conv := model.Conversation{
|
|
AccountID: 1,
|
|
InboxID: 2,
|
|
ContactID: 3,
|
|
Status: "open",
|
|
ChannelType: "web_widget",
|
|
Channel: "web_widget",
|
|
}
|
|
assert.Equal(t, uint(1), conv.AccountID)
|
|
assert.Equal(t, uint(2), conv.InboxID)
|
|
assert.Equal(t, uint(3), conv.ContactID)
|
|
assert.Equal(t, "open", conv.Status)
|
|
assert.Equal(t, "web_widget", conv.ChannelType)
|
|
}
|
|
|
|
func TestMessageStructFields(t *testing.T) {
|
|
senderID := uint(10)
|
|
msg := model.Message{
|
|
ConversationID: 1,
|
|
AccountID: 2,
|
|
InboxID: 3,
|
|
SenderID: &senderID,
|
|
SenderType: "agent",
|
|
Content: "Hello",
|
|
ContentType: "text",
|
|
MessageType: "outgoing",
|
|
Private: false,
|
|
SourceID: "ext_src_1",
|
|
External: true,
|
|
}
|
|
assert.Equal(t, uint(1), msg.ConversationID)
|
|
assert.Equal(t, "agent", msg.SenderType)
|
|
assert.Equal(t, "Hello", msg.Content)
|
|
assert.Equal(t, uint(10), *msg.SenderID)
|
|
assert.True(t, msg.External)
|
|
}
|
|
|
|
func TestContactStructFields(t *testing.T) {
|
|
contact := model.Contact{
|
|
AccountID: 1,
|
|
Name: "Alice",
|
|
Email: "alice@test.com",
|
|
PhoneNumber: "+1234567890",
|
|
AvatarURL: "https://img.test.com/alice.png",
|
|
Identifier: "ext_id_123",
|
|
}
|
|
assert.Equal(t, uint(1), contact.AccountID)
|
|
assert.Equal(t, "Alice", contact.Name)
|
|
assert.Equal(t, "alice@test.com", contact.Email)
|
|
assert.Equal(t, "+1234567890", contact.PhoneNumber)
|
|
}
|
|
|
|
func TestInboxStructFields(t *testing.T) {
|
|
inbox := model.Inbox{
|
|
AccountID: 1,
|
|
Name: "Support",
|
|
ChannelType: "web_widget",
|
|
ChannelID: 5,
|
|
EnableAutoAssignment: true,
|
|
AutoAssignmentLimit: 10,
|
|
Enabled: true,
|
|
ChannelConfig: `{"color":"#1f93ff"}`,
|
|
}
|
|
assert.Equal(t, uint(1), inbox.AccountID)
|
|
assert.Equal(t, "Support", inbox.Name)
|
|
assert.True(t, inbox.EnableAutoAssignment)
|
|
assert.Equal(t, 10, inbox.AutoAssignmentLimit)
|
|
}
|
|
|
|
func TestBaseStructFields(t *testing.T) {
|
|
base := model.Base{}
|
|
assert.Equal(t, uint(0), base.ID)
|
|
assert.True(t, base.CreatedAt.IsZero())
|
|
assert.True(t, base.UpdatedAt.IsZero())
|
|
}
|
|
|
|
func TestCustomRoleValidationError(t *testing.T) {
|
|
err := &model.CustomRoleValidationError{
|
|
Dimension: "conversation_manage",
|
|
Level: "invalid",
|
|
Message: "not a valid level",
|
|
}
|
|
assert.Contains(t, err.Error(), "conversation_manage")
|
|
assert.Contains(t, err.Error(), "invalid")
|
|
assert.Contains(t, err.Error(), "not a valid level")
|
|
}
|