Files
gochat/backend/internal/model/methods_test.go
T
rogee aeddedf2a3 Reorganize repo: backend/, deploy/, docs/ layout + AGENTS.md
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.
2026-07-07 14:44:12 +08:00

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")
}