Files
gochat/backend/internal/service/conversation_assignment_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

556 lines
21 KiB
Go

package service
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"github.com/gochat/gochat/internal/channel"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
)
// setupAssignmentTestDB creates a SQLite in-memory DB with all models needed
// for assignment tests: Account, User, AccountUser, Inbox, InboxMember,
// Contact, Conversation, Team, TeamMember.
func setupAssignmentTestDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
require.NoError(t, err)
models := []interface{}{
&model.Account{},
&model.User{},
&model.AccountUser{},
&model.Inbox{},
&model.InboxMember{},
&model.Contact{},
&model.Conversation{},
&model.Team{},
&model.TeamMember{},
&model.AgentCapacityPolicy{},
&model.InboxCapacityLimit{},
}
require.NoError(t, db.AutoMigrate(models...))
return db
}
// setupAssignmentService creates a ConversationService with all repos needed
// for deep assignment logic testing.
func setupAssignmentService(t *testing.T) (*ConversationService, *gorm.DB) {
t.Helper()
db := setupAssignmentTestDB(t)
convRepo := repository.NewConversationRepo(db)
msgRepo := repository.NewMessageRepo(db)
dispatcher := channel.NewDispatcher()
inboxMemberRepo := repository.NewInboxMemberRepo(db)
inboxMemberSvc := NewInboxMemberService(inboxMemberRepo)
accountUserRepo := repository.NewAccountUserRepo(db)
teamRepo := repository.NewTeamRepo(db)
teamMemberRepo := repository.NewTeamMemberRepo(db)
svc := NewConversationService(convRepo, msgRepo, dispatcher, inboxMemberSvc, accountUserRepo, teamRepo, teamMemberRepo)
return svc, db
}
// ========== Helper functions for creating test data ==========
func createAssignmentAccount(t *testing.T, db *gorm.DB) *model.Account {
t.Helper()
acc := &model.Account{Name: "Test Account"}
require.NoError(t, db.Create(acc).Error)
return acc
}
func createAssignmentUser(t *testing.T, db *gorm.DB, name, email string) *model.User {
t.Helper()
u := &model.User{Name: name, Email: email, AccountID: 1, Password: "hashed"}
require.NoError(t, db.Create(u).Error)
return u
}
func createAssignmentAccountUser(t *testing.T, db *gorm.DB, accountID, userID uint, role, availability string) *model.AccountUser {
t.Helper()
au := &model.AccountUser{AccountID: accountID, UserID: userID, Role: role, Availability: availability}
require.NoError(t, db.Create(au).Error)
return au
}
func createAssignmentInbox(t *testing.T, db *gorm.DB, accountID uint) *model.Inbox {
t.Helper()
inbox := &model.Inbox{AccountID: accountID, Name: "Test Inbox", ChannelType: string(model.InboxChannelTypeWebWidget)}
require.NoError(t, db.Create(inbox).Error)
return inbox
}
func createAssignmentInboxMember(t *testing.T, db *gorm.DB, inboxID, userID uint) {
t.Helper()
im := &model.InboxMember{InboxID: inboxID, UserID: userID}
require.NoError(t, db.Create(im).Error)
}
func createAssignmentContact(t *testing.T, db *gorm.DB, accountID uint) *model.Contact {
t.Helper()
c := &model.Contact{AccountID: accountID, Name: "Test Contact"}
require.NoError(t, db.Create(c).Error)
return c
}
func createAssignmentConversation(t *testing.T, db *gorm.DB, accountID, inboxID, contactID uint) *model.Conversation {
t.Helper()
conv := &model.Conversation{
AccountID: accountID,
InboxID: inboxID,
ContactID: contactID,
Status: "open",
ChannelType: "web_widget",
Channel: "web_widget",
}
require.NoError(t, db.Create(conv).Error)
return conv
}
func createAssignmentCapacityLimit(t *testing.T, db *gorm.DB, accountID, inboxID, userID uint, conversationLimit int) *model.AgentCapacityPolicy {
t.Helper()
policy := &model.AgentCapacityPolicy{AccountID: accountID, Name: "Capacity", AssignmentLogic: "round_robin", ExclusionRules: []byte(`{}`)}
require.NoError(t, db.Create(policy).Error)
require.NoError(t, db.Model(&model.AccountUser{}).
Where("account_id = ? AND user_id = ?", accountID, userID).
Update("agent_capacity_policy_id", policy.ID).Error)
require.NoError(t, db.Create(&model.InboxCapacityLimit{
AgentCapacityPolicyID: policy.ID,
InboxID: inboxID,
ConversationLimit: conversationLimit,
}).Error)
return policy
}
func createAssignmentTeam(t *testing.T, db *gorm.DB, accountID uint, name string, allowAutoAssignment bool) *model.Team {
t.Helper()
team := &model.Team{AccountID: accountID, Name: name, AllowAutoAssignment: true} // SQLite default:true workaround — create as true first
require.NoError(t, db.Create(team).Error)
if !allowAutoAssignment {
require.NoError(t, db.Model(team).Update("allow_auto_assignment", false).Error)
team.AllowAutoAssignment = false
}
return team
}
func createAssignmentTeamMember(t *testing.T, db *gorm.DB, teamID, userID uint, availability string) *model.TeamMember {
t.Helper()
tm := &model.TeamMember{TeamID: teamID, UserID: userID, AvailabilityStatus: availability}
require.NoError(t, db.Create(tm).Error)
return tm
}
// ========== AssignAgent Authorization Tests ==========
func TestAssignAgent_Authorization_RejectsNonAccountMember(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create a user who is NOT an agent/admin in this account
outsider := createAssignmentUser(t, db, "Outsider", "outsider@test.com")
_, err := svc.AssignAgent(ctx, account.ID, conv.ID, outsider.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not an agent or administrator")
}
func TestAssignAgent_Authorization_AcceptsAgent(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create an agent who IS a member of this account AND inbox
agent := createAssignmentUser(t, db, "Agent1", "agent1@test.com")
createAssignmentAccountUser(t, db, account.ID, agent.ID, "agent", "online")
createAssignmentInboxMember(t, db, inbox.ID, agent.ID)
result, err := svc.AssignAgent(ctx, account.ID, conv.ID, agent.ID)
assert.NoError(t, err)
assert.Equal(t, agent.ID, *result.AssigneeID)
}
func TestAssignAgent_Authorization_AcceptsAdministrator(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create an admin who IS a member of this account AND inbox
admin := createAssignmentUser(t, db, "Admin1", "admin1@test.com")
createAssignmentAccountUser(t, db, account.ID, admin.ID, "administrator", "online")
createAssignmentInboxMember(t, db, inbox.ID, admin.ID)
result, err := svc.AssignAgent(ctx, account.ID, conv.ID, admin.ID)
assert.NoError(t, err)
assert.Equal(t, admin.ID, *result.AssigneeID)
}
func TestAssignAgent_RejectsInboxNonMember(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create an agent in the account but NOT in the inbox
agent := createAssignmentUser(t, db, "NoInboxAgent", "noinbox@test.com")
createAssignmentAccountUser(t, db, account.ID, agent.ID, "agent", "online")
// Do NOT add to inbox members
_, err := svc.AssignAgent(ctx, account.ID, conv.ID, agent.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not a member of the conversation's inbox")
}
func TestAssignAgent_RejectsAgentAtInboxCapacity(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
existing := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
agent := createAssignmentUser(t, db, "CapacityAgent", "capacity@test.com")
createAssignmentAccountUser(t, db, account.ID, agent.ID, "agent", "online")
createAssignmentInboxMember(t, db, inbox.ID, agent.ID)
createAssignmentCapacityLimit(t, db, account.ID, inbox.ID, agent.ID, 1)
require.NoError(t, db.Model(existing).Update("assignee_id", agent.ID).Error)
_, err := svc.AssignAgent(ctx, account.ID, conv.ID, agent.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "reached capacity")
}
func TestAssignAgent_IgnoresResolvedConversationsForCapacity(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
resolved := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
agent := createAssignmentUser(t, db, "CapacityAgent2", "capacity2@test.com")
createAssignmentAccountUser(t, db, account.ID, agent.ID, "agent", "online")
createAssignmentInboxMember(t, db, inbox.ID, agent.ID)
createAssignmentCapacityLimit(t, db, account.ID, inbox.ID, agent.ID, 1)
require.NoError(t, db.Model(resolved).Updates(map[string]any{"assignee_id": agent.ID, "status": model.ConversationStatusResolved}).Error)
result, err := svc.AssignAgent(ctx, account.ID, conv.ID, agent.ID)
assert.NoError(t, err)
require.NotNil(t, result.AssigneeID)
assert.Equal(t, agent.ID, *result.AssigneeID)
}
func TestAssignAgent_Unassign_SetsNilAssigneeID(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// First assign an agent
agent := createAssignmentUser(t, db, "Agent1", "agent1@test.com")
createAssignmentAccountUser(t, db, account.ID, agent.ID, "agent", "online")
createAssignmentInboxMember(t, db, inbox.ID, agent.ID)
result, err := svc.AssignAgent(ctx, account.ID, conv.ID, agent.ID)
require.NoError(t, err)
require.NotNil(t, result.AssigneeID)
// Then unassign (assigneeID = 0)
result, err = svc.AssignAgent(ctx, account.ID, conv.ID, 0)
assert.NoError(t, err)
assert.Nil(t, result.AssigneeID)
}
// ========== AssignTeam Validation Tests ==========
func TestAssignTeam_RejectsTeamNotInAccount(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
// Create two accounts
account1 := createAssignmentAccount(t, db)
account2 := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account1.ID)
contact := createAssignmentContact(t, db, account1.ID)
conv := createAssignmentConversation(t, db, account1.ID, inbox.ID, contact.ID)
// Create a team in account2 (wrong account!)
team2 := createAssignmentTeam(t, db, account2.ID, "Wrong Team", true)
_, err := svc.AssignTeam(ctx, account1.ID, conv.ID, nil, &team2.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "team not found in this account")
}
func TestAssignTeam_RejectsTeamWithoutAutoAssignment(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create a team with AllowAutoAssignment = false
team := createAssignmentTeam(t, db, account.ID, "NoAuto Team", false)
// Try to assign team without specifying an agent — should be rejected
_, err := svc.AssignTeam(ctx, account.ID, conv.ID, nil, &team.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "does not allow auto-assignment")
}
func TestAssignTeam_AllowsTeamWithAutoAssignment(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create a team with AllowAutoAssignment = true
team := createAssignmentTeam(t, db, account.ID, "Auto Team", true)
result, err := svc.AssignTeam(ctx, account.ID, conv.ID, nil, &team.ID)
assert.NoError(t, err)
assert.Equal(t, team.ID, *result.TeamID)
}
func TestAssignTeam_WithAgentAndTeam(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
team := createAssignmentTeam(t, db, account.ID, "Team1", true)
agent := createAssignmentUser(t, db, "Agent1", "agent1@test.com")
createAssignmentAccountUser(t, db, account.ID, agent.ID, "agent", "online")
createAssignmentInboxMember(t, db, inbox.ID, agent.ID)
result, err := svc.AssignTeam(ctx, account.ID, conv.ID, &agent.ID, &team.ID)
assert.NoError(t, err)
assert.Equal(t, team.ID, *result.TeamID)
assert.Equal(t, agent.ID, *result.AssigneeID)
}
func TestAssignTeam_RejectsAgentNotInAccount(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
team := createAssignmentTeam(t, db, account.ID, "Team1", true)
outsideAgent := createAssignmentUser(t, db, "Outside", "outside@test.com")
// NOT adding AccountUser for this agent
_, err := svc.AssignTeam(ctx, account.ID, conv.ID, &outsideAgent.ID, &team.ID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not an agent or administrator")
}
func TestAssignTeam_Overflow_FallbackToAccountOnlineAgent(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create a team
team := createAssignmentTeam(t, db, account.ID, "Overflow Team", true)
// Add 2 team members — both OFFLINE
member1 := createAssignmentUser(t, db, "Member1", "member1@test.com")
member2 := createAssignmentUser(t, db, "Member2", "member2@test.com")
createAssignmentAccountUser(t, db, account.ID, member1.ID, "agent", "offline")
createAssignmentAccountUser(t, db, account.ID, member2.ID, "agent", "offline")
createAssignmentTeamMember(t, db, team.ID, member1.ID, "offline")
createAssignmentTeamMember(t, db, team.ID, member2.ID, "offline")
// Add an account-level online agent (NOT in the team) for overflow fallback
fallbackAgent := createAssignmentUser(t, db, "Fallback", "fallback@test.com")
createAssignmentAccountUser(t, db, account.ID, fallbackAgent.ID, "agent", "online")
createAssignmentInboxMember(t, db, inbox.ID, fallbackAgent.ID)
// Assign team without specifying agent — should overflow to fallback
result, err := svc.AssignTeam(ctx, account.ID, conv.ID, nil, &team.ID)
assert.NoError(t, err)
assert.Equal(t, team.ID, *result.TeamID)
// Overflow should assign the fallback agent
assert.NotNil(t, result.AssigneeID)
assert.Equal(t, fallbackAgent.ID, *result.AssigneeID)
}
func TestAssignTeam_Overflow_NoOnlineAgents_NoFallback(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create a team
team := createAssignmentTeam(t, db, account.ID, "NoFallback Team", true)
// Add team members — all OFFLINE
member1 := createAssignmentUser(t, db, "Member1", "member1@test.com")
createAssignmentAccountUser(t, db, account.ID, member1.ID, "agent", "offline")
createAssignmentTeamMember(t, db, team.ID, member1.ID, "offline")
// No online agents in the account at all
// Assign team without agent — overflow should NOT assign anyone
result, err := svc.AssignTeam(ctx, account.ID, conv.ID, nil, &team.ID)
assert.NoError(t, err)
assert.Equal(t, team.ID, *result.TeamID)
// No overflow agent available, so AssigneeID should remain nil
assert.Nil(t, result.AssigneeID)
}
func TestAssignTeam_OnlineTeamMember_NoOverflow(t *testing.T) {
svc, db := setupAssignmentService(t)
ctx := context.Background()
account := createAssignmentAccount(t, db)
inbox := createAssignmentInbox(t, db, account.ID)
contact := createAssignmentContact(t, db, account.ID)
conv := createAssignmentConversation(t, db, account.ID, inbox.ID, contact.ID)
// Create a team with online members
team := createAssignmentTeam(t, db, account.ID, "Online Team", true)
onlineMember := createAssignmentUser(t, db, "OnlineMember", "online@test.com")
createAssignmentAccountUser(t, db, account.ID, onlineMember.ID, "agent", "online")
createAssignmentTeamMember(t, db, team.ID, onlineMember.ID, "online")
// Assign team without agent — no overflow needed (member is online)
result, err := svc.AssignTeam(ctx, account.ID, conv.ID, nil, &team.ID)
assert.NoError(t, err)
assert.Equal(t, team.ID, *result.TeamID)
// No overflow triggered since team has online members
assert.Nil(t, result.AssigneeID)
}
// ========== AccountUserRepo Tests ==========
func TestAccountUserRepo_IsAgentOrAdmin(t *testing.T) {
db := setupAssignmentTestDB(t)
repo := repository.NewAccountUserRepo(db)
ctx := context.Background()
account := createAssignmentAccount(t, db)
user := createAssignmentUser(t, db, "TestUser", "test@test.com")
// User not in account yet
isMember, err := repo.IsAgentOrAdmin(ctx, account.ID, user.ID)
assert.NoError(t, err)
assert.False(t, isMember)
// Add as agent
createAssignmentAccountUser(t, db, account.ID, user.ID, "agent", "online")
isMember, err = repo.IsAgentOrAdmin(ctx, account.ID, user.ID)
assert.NoError(t, err)
assert.True(t, isMember)
}
func TestAccountUserRepo_IsAdministrator(t *testing.T) {
db := setupAssignmentTestDB(t)
repo := repository.NewAccountUserRepo(db)
ctx := context.Background()
account := createAssignmentAccount(t, db)
agentUser := createAssignmentUser(t, db, "Agent", "agent@test.com")
adminUser := createAssignmentUser(t, db, "Admin", "admin@test.com")
createAssignmentAccountUser(t, db, account.ID, agentUser.ID, "agent", "online")
createAssignmentAccountUser(t, db, account.ID, adminUser.ID, "administrator", "online")
isAdmin, err := repo.IsAdministrator(ctx, account.ID, agentUser.ID)
assert.NoError(t, err)
assert.False(t, isAdmin)
isAdmin, err = repo.IsAdministrator(ctx, account.ID, adminUser.ID)
assert.NoError(t, err)
assert.True(t, isAdmin)
}
func TestAccountUserRepo_FindOnlineAgentsByAccount(t *testing.T) {
db := setupAssignmentTestDB(t)
repo := repository.NewAccountUserRepo(db)
ctx := context.Background()
account := createAssignmentAccount(t, db)
onlineAgent := createAssignmentUser(t, db, "Online1", "online1@test.com")
offlineAgent := createAssignmentUser(t, db, "Offline1", "offline1@test.com")
createAssignmentAccountUser(t, db, account.ID, onlineAgent.ID, "agent", "online")
createAssignmentAccountUser(t, db, account.ID, offlineAgent.ID, "agent", "offline")
onlineAgents, err := repo.FindOnlineAgentsByAccount(ctx, account.ID)
assert.NoError(t, err)
assert.Len(t, onlineAgents, 1)
assert.Equal(t, onlineAgent.ID, onlineAgents[0].UserID)
}
func TestAccountUserRepo_FindByAccountAndUser(t *testing.T) {
db := setupAssignmentTestDB(t)
repo := repository.NewAccountUserRepo(db)
ctx := context.Background()
account := createAssignmentAccount(t, db)
user := createAssignmentUser(t, db, "TestUser", "test@test.com")
// Not in account yet
au, err := repo.FindByAccountAndUser(ctx, account.ID, user.ID)
assert.NoError(t, err)
assert.Nil(t, au)
// Add to account
createAssignmentAccountUser(t, db, account.ID, user.ID, "agent", "online")
au, err = repo.FindByAccountAndUser(ctx, account.ID, user.ID)
assert.NoError(t, err)
assert.NotNil(t, au)
assert.Equal(t, "agent", au.Role)
}