yao/openapi/oauth/providers/user/member_test.go
Max 1e01ed768e Implement robot member update functionality with comprehensive validation
- Added the `UpdateRobotMember` method to handle updates for robot members, including validation for member existence, type checks, and email uniqueness.
- Introduced a new `UpdateRobotMemberRequest` structure to facilitate updates via the API, allowing for modifications to various fields such as `robot_email`, `status`, and `autonomous_mode`.
- Enhanced the `GinMemberUpdateRobot` handler to process update requests, ensuring proper authentication and error handling for various scenarios.
- Expanded test coverage with new test cases for the robot member update functionality, validating successful updates, error conditions, and edge cases.
- Refactored existing member management logic to integrate the new update capabilities, improving overall system robustness and user experience.
2025-10-27 12:05:11 +08:00

1635 lines
51 KiB
Go

package user_test
import (
"context"
"strings"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/yaoapp/gou/model"
"github.com/yaoapp/kun/maps"
)
func TestMemberBasicOperations(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test users
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
memberUser := createTestUser(ctx, t, "member"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "Test Team " + testUUID,
"display_name": "Test Display " + testUUID,
"description": "A test team for member testing",
"owner_id": ownerUser,
"status": "active",
"type": "corporation",
"type_id": "business",
"metadata": map[string]interface{}{"test": true},
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
var businessMemberID string
// Test CreateMember
t.Run("CreateMember", func(t *testing.T) {
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": memberUser,
"member_type": "user",
"role_id": "user",
"status": "active",
}
memberID, err := testProvider.CreateMember(ctx, memberData)
assert.NoError(t, err)
assert.NotEmpty(t, memberID)
businessMemberID = memberID
})
// Test GetMember
t.Run("GetMember", func(t *testing.T) {
member, err := testProvider.GetMember(ctx, teamID, memberUser)
assert.NoError(t, err)
assert.NotNil(t, member)
assert.Equal(t, teamID, member["team_id"])
assert.Equal(t, memberUser, member["user_id"])
assert.Equal(t, "user", member["member_type"])
assert.Equal(t, "user", member["role_id"])
})
// Test GetMemberDetail
t.Run("GetMemberDetail", func(t *testing.T) {
member, err := testProvider.GetMemberDetail(ctx, teamID, memberUser)
assert.NoError(t, err)
assert.NotNil(t, member)
assert.Equal(t, teamID, member["team_id"])
assert.Equal(t, memberUser, member["user_id"])
// Should contain more detailed fields
assert.Contains(t, member, "created_at")
assert.Contains(t, member, "updated_at")
})
// Test GetMemberByMemberID
t.Run("GetMemberByMemberID", func(t *testing.T) {
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.NotNil(t, member)
assert.Equal(t, teamID, member["team_id"])
assert.Equal(t, memberUser, member["user_id"])
// Verify member_id is returned
assert.Equal(t, businessMemberID, member["member_id"])
})
// Test MemberExists
t.Run("MemberExists", func(t *testing.T) {
exists, err := testProvider.MemberExists(ctx, teamID, memberUser)
assert.NoError(t, err)
assert.True(t, exists)
// Test with non-existent member
exists, err = testProvider.MemberExists(ctx, teamID, "non-existent-user")
assert.NoError(t, err)
assert.False(t, exists)
})
// Test UpdateMember
t.Run("UpdateMember", func(t *testing.T) {
updateData := maps.MapStrAny{
"role_id": "admin",
"notes": "Promoted to admin",
}
err := testProvider.UpdateMember(ctx, teamID, memberUser, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMember(ctx, teamID, memberUser)
assert.NoError(t, err)
assert.Equal(t, "admin", member["role_id"])
// Test updating sensitive fields (should be ignored)
sensitiveData := maps.MapStrAny{
"id": 999,
"team_id": "new-team",
"user_id": "new-user",
"invitation_token": "fake-token",
}
err = testProvider.UpdateMember(ctx, teamID, memberUser, sensitiveData)
assert.NoError(t, err) // Should not error, just ignore sensitive fields
})
// Test UpdateMemberByMemberID
t.Run("UpdateMemberByMemberID", func(t *testing.T) {
updateData := maps.MapStrAny{
"status": "inactive",
}
err := testProvider.UpdateMemberByMemberID(ctx, businessMemberID, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.Equal(t, "inactive", member["status"])
// Change back to active for other tests
err = testProvider.UpdateMemberByMemberID(ctx, businessMemberID, maps.MapStrAny{"status": "active"})
assert.NoError(t, err)
})
// Test UpdateMemberRole
t.Run("UpdateMemberRole", func(t *testing.T) {
err := testProvider.UpdateMemberRole(ctx, teamID, memberUser, "moderator")
assert.NoError(t, err)
// Verify role was updated
member, err := testProvider.GetMember(ctx, teamID, memberUser)
assert.NoError(t, err)
assert.Equal(t, "moderator", member["role_id"])
})
// Test UpdateMemberStatus
t.Run("UpdateMemberStatus", func(t *testing.T) {
err := testProvider.UpdateMemberStatus(ctx, teamID, memberUser, "suspended")
assert.NoError(t, err)
// Verify status was updated
member, err := testProvider.GetMember(ctx, teamID, memberUser)
assert.NoError(t, err)
assert.Equal(t, "suspended", member["status"])
// Change back to active
err = testProvider.UpdateMemberStatus(ctx, teamID, memberUser, "active")
assert.NoError(t, err)
})
// Test UpdateMemberLastActivity
t.Run("UpdateMemberLastActivity", func(t *testing.T) {
err := testProvider.UpdateMemberLastActivity(ctx, teamID, memberUser)
assert.NoError(t, err)
// Verify last_active_at was updated and login_count incremented
member, err := testProvider.GetMember(ctx, teamID, memberUser)
assert.NoError(t, err)
assert.NotNil(t, member["last_active_at"])
// login_count should be at least 1 (handle different integer types)
loginCount := member["login_count"]
if loginCount != nil {
switch v := loginCount.(type) {
case int:
assert.True(t, v >= 1, "login_count should be at least 1")
case int64:
assert.True(t, v >= 1, "login_count should be at least 1")
case int32:
assert.True(t, v >= 1, "login_count should be at least 1")
default:
t.Logf("Unexpected login_count type: %T, value: %v", loginCount, loginCount)
assert.True(t, false, "login_count should be a numeric type")
}
} else {
assert.True(t, false, "login_count should not be nil")
}
})
// Test RemoveMember (at the end)
t.Run("RemoveMember", func(t *testing.T) {
err := testProvider.RemoveMember(ctx, teamID, memberUser)
assert.NoError(t, err)
// Verify member was removed
_, err = testProvider.GetMember(ctx, teamID, memberUser)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
}
func TestMemberInvitationFlow(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test users
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
inviteeUser := createTestUser(ctx, t, "invitee"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "Invitation Test Team " + testUUID,
"display_name": "Invitation Test " + testUUID,
"description": "A test team for invitation testing",
"owner_id": ownerUser,
"status": "active",
"type": "corporation",
"type_id": "business",
"metadata": map[string]interface{}{"test": true},
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
var invitationToken string
var invitationID string
// Test AddMember (invitation-based)
t.Run("AddMember", func(t *testing.T) {
memberID, err := testProvider.AddMember(ctx, teamID, inviteeUser, "user", ownerUser)
assert.NoError(t, err)
assert.NotEmpty(t, memberID)
// Verify member was created with pending status
member, err := testProvider.GetMember(ctx, teamID, inviteeUser)
assert.NoError(t, err)
assert.Equal(t, "pending", member["status"])
assert.Equal(t, ownerUser, member["invited_by"])
// Get invitation token and invitation_id for acceptance test
memberDetail, err := testProvider.GetMemberDetail(ctx, teamID, inviteeUser)
assert.NoError(t, err)
invitationToken = memberDetail["invitation_token"].(string)
assert.NotEmpty(t, invitationToken)
invitationID = memberDetail["invitation_id"].(string)
assert.NotEmpty(t, invitationID)
// Verify invitation expiry is set
assert.NotNil(t, memberDetail["invitation_expires_at"])
})
// Test duplicate invitation prevention
t.Run("AddMember_DuplicatePrevention", func(t *testing.T) {
_, err := testProvider.AddMember(ctx, teamID, inviteeUser, "user", ownerUser)
assert.Error(t, err)
assert.Contains(t, err.Error(), "already a member")
})
// Test AcceptInvitation
t.Run("AcceptInvitation", func(t *testing.T) {
err := testProvider.AcceptInvitation(ctx, invitationID, invitationToken, "")
assert.NoError(t, err)
// Verify member status changed to active
member, err := testProvider.GetMember(ctx, teamID, inviteeUser)
assert.NoError(t, err)
assert.Equal(t, "active", member["status"])
assert.NotNil(t, member["joined_at"])
// Verify invitation token was cleared
memberDetail, err := testProvider.GetMemberDetail(ctx, teamID, inviteeUser)
assert.NoError(t, err)
assert.Nil(t, memberDetail["invitation_token"])
})
// Test AcceptInvitation with invalid token
t.Run("AcceptInvitation_InvalidToken", func(t *testing.T) {
err := testProvider.AcceptInvitation(ctx, invitationID, "invalid-token", "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "invitation not found")
})
// Test AcceptInvitation with already accepted token
t.Run("AcceptInvitation_AlreadyAccepted", func(t *testing.T) {
err := testProvider.AcceptInvitation(ctx, invitationID, invitationToken, "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "invitation not found")
})
}
func TestRobotMemberOperations(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test user (team owner)
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "Robot Test Team " + testUUID,
"display_name": "Robot Test " + testUUID,
"description": "A test team for robot testing",
"owner_id": ownerUser,
"status": "active",
"type": "corporation",
"type_id": "business",
"metadata": map[string]interface{}{"test": true},
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
var robotBusinessMemberID string
// Test CreateRobotMember
t.Run("CreateRobotMember", func(t *testing.T) {
robotData := maps.MapStrAny{
"display_name": "TestBot" + testUUID,
"bio": "A test robot for unit testing",
"avatar": "https://example.com/robot.png",
"role_id": "bot",
"autonomous_mode": true,
"robot_status": "idle",
"system_prompt": "You are a helpful test robot",
"language_model": "gpt-4",
"cost_limit": 100.00,
"manager_id": ownerUser,
"robot_email": "testbot" + testUUID + "@robot.example.com",
"authorized_senders": []string{
"admin@example.com",
"manager@example.com",
},
"email_filter_rules": []string{
".*@example\\.com$",
".*@test\\.com$",
},
"robot_config": map[string]interface{}{
"max_tokens": 1000,
},
}
memberID, err := testProvider.CreateRobotMember(ctx, teamID, robotData)
assert.NoError(t, err)
assert.NotEmpty(t, memberID)
robotBusinessMemberID = memberID
// Verify robot member was created
member, err := testProvider.GetMemberByMemberID(ctx, robotBusinessMemberID)
assert.NoError(t, err)
assert.Equal(t, "robot", member["member_type"])
assert.Equal(t, "active", member["status"]) // Robots are active by default
assert.Nil(t, member["user_id"]) // Robots don't have user_id
// Verify new email fields in detail view
memberDetail, err := testProvider.GetMemberDetailByMemberID(ctx, robotBusinessMemberID)
assert.NoError(t, err)
assert.Equal(t, "testbot"+testUUID+"@robot.example.com", memberDetail["robot_email"])
assert.NotNil(t, memberDetail["authorized_senders"])
assert.NotNil(t, memberDetail["email_filter_rules"])
})
// Test GetTeamRobotMembers
t.Run("GetTeamRobotMembers", func(t *testing.T) {
robots, err := testProvider.GetTeamRobotMembers(ctx, teamID)
assert.NoError(t, err)
assert.Len(t, robots, 1)
assert.Equal(t, "robot", robots[0]["member_type"])
assert.Equal(t, "TestBot"+testUUID, robots[0]["display_name"])
assert.Equal(t, "A test robot for unit testing", robots[0]["bio"])
})
// Test UpdateRobotActivity - needs internal database ID
t.Run("UpdateRobotActivity", func(t *testing.T) {
// Get internal database ID from member_id
member, err := testProvider.GetMemberByMemberID(ctx, robotBusinessMemberID)
assert.NoError(t, err)
robotTeamID := member["team_id"].(string)
// UpdateRobotActivity still uses internal database ID
// We need to query the database to get it
m := model.Select("__yao.member")
result, err := m.Get(model.QueryParam{
Select: []interface{}{"id"},
Wheres: []model.QueryWhere{
{Column: "member_id", Value: robotBusinessMemberID},
},
Limit: 1,
})
assert.NoError(t, err)
assert.Len(t, result, 1)
robotDBID := result[0]["id"].(int64)
err = testProvider.UpdateRobotActivity(ctx, robotDBID, "working")
assert.NoError(t, err)
// Get robot members to verify status (robot members don't have user_id)
robots, err := testProvider.GetTeamRobotMembers(ctx, robotTeamID)
assert.NoError(t, err)
assert.Len(t, robots, 1)
robot := robots[0]
assert.Equal(t, "working", robot["robot_status"])
assert.NotNil(t, robot["last_robot_activity"])
})
// Test GetActiveRobotMembers
t.Run("GetActiveRobotMembers", func(t *testing.T) {
// First make sure our robot is active
err := testProvider.UpdateMemberByMemberID(ctx, robotBusinessMemberID, maps.MapStrAny{
"autonomous_mode": true,
"status": "active",
})
assert.NoError(t, err)
robots, err := testProvider.GetActiveRobotMembers(ctx)
assert.NoError(t, err)
assert.True(t, len(robots) >= 1) // At least our test robot
// Find our test robot in the results
found := false
for _, robot := range robots {
if robot["display_name"] == "TestBot"+testUUID {
found = true
assert.Equal(t, "robot", robot["member_type"])
// Handle different boolean types from database
autonomousMode := robot["autonomous_mode"]
assert.True(t, autonomousMode == true || autonomousMode == int64(1) || autonomousMode == 1, "Robot should be autonomous")
break
}
}
assert.True(t, found, "Test robot should be found in active robots")
})
// Test robot member validation
t.Run("CreateRobotMember_ValidationErrors", func(t *testing.T) {
// Missing display_name
robotData := maps.MapStrAny{
"role_id": "bot",
}
_, err := testProvider.CreateRobotMember(ctx, teamID, robotData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "display_name is required")
// Missing role_id
robotData = maps.MapStrAny{
"display_name": "TestBot2",
}
_, err = testProvider.CreateRobotMember(ctx, teamID, robotData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "role_id is required")
})
}
func TestMemberQueryOperations(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test users
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
member1User := createTestUser(ctx, t, "member1"+testUUID)
member2User := createTestUser(ctx, t, "member2"+testUUID)
// Create test teams
team1Map := maps.MapStrAny{
"name": "Query Test Team 1 " + testUUID,
"display_name": "Query Test 1 " + testUUID,
"description": "First test team for query testing",
"owner_id": ownerUser,
"status": "active",
"type": "corporation",
"type_id": "business",
"metadata": map[string]interface{}{"test": true},
}
team1ID, err := testProvider.CreateTeam(ctx, team1Map)
assert.NoError(t, err)
team2Map := maps.MapStrAny{
"name": "Query Test Team 2 " + testUUID,
"display_name": "Query Test 2 " + testUUID,
"description": "Second test team for query testing",
"owner_id": ownerUser,
"status": "active",
"type": "corporation",
"type_id": "business",
"metadata": map[string]interface{}{"test": true},
}
team2ID, err := testProvider.CreateTeam(ctx, team2Map)
assert.NoError(t, err)
// Add members to teams
_, err = testProvider.CreateMember(ctx, maps.MapStrAny{
"team_id": team1ID,
"user_id": member1User,
"member_type": "user",
"role_id": "user",
"status": "active",
})
assert.NoError(t, err)
_, err = testProvider.CreateMember(ctx, maps.MapStrAny{
"team_id": team1ID,
"user_id": member2User,
"member_type": "user",
"role_id": "admin",
"status": "pending",
})
assert.NoError(t, err)
_, err = testProvider.CreateMember(ctx, maps.MapStrAny{
"team_id": team2ID,
"user_id": member1User,
"member_type": "user",
"role_id": "moderator",
"status": "active",
})
assert.NoError(t, err)
// Test GetTeamMembers
t.Run("GetTeamMembers", func(t *testing.T) {
members, err := testProvider.GetTeamMembers(ctx, team1ID)
assert.NoError(t, err)
assert.Len(t, members, 2) // member1 and member2
// Verify members are ordered by joined_at desc, invited_at desc
userIDs := []string{members[0]["user_id"].(string), members[1]["user_id"].(string)}
assert.Contains(t, userIDs, member1User)
assert.Contains(t, userIDs, member2User)
})
// Test GetUserTeams
t.Run("GetUserTeams", func(t *testing.T) {
teams, err := testProvider.GetUserTeams(ctx, member1User)
assert.NoError(t, err)
assert.Len(t, teams, 2) // member1 is in both teams
teamIDs := []string{teams[0]["team_id"].(string), teams[1]["team_id"].(string)}
assert.Contains(t, teamIDs, team1ID)
assert.Contains(t, teamIDs, team2ID)
})
// Test GetTeamMembersByStatus
t.Run("GetTeamMembersByStatus", func(t *testing.T) {
// Get active members
activeMembers, err := testProvider.GetTeamMembersByStatus(ctx, team1ID, "active")
assert.NoError(t, err)
assert.Len(t, activeMembers, 1) // Only member1 is active
assert.Equal(t, member1User, activeMembers[0]["user_id"])
// Get pending members
pendingMembers, err := testProvider.GetTeamMembersByStatus(ctx, team1ID, "pending")
assert.NoError(t, err)
assert.Len(t, pendingMembers, 1) // Only member2 is pending
assert.Equal(t, member2User, pendingMembers[0]["user_id"])
// Get inactive members (should be empty)
inactiveMembers, err := testProvider.GetTeamMembersByStatus(ctx, team1ID, "inactive")
assert.NoError(t, err)
assert.Len(t, inactiveMembers, 0)
})
// Test PaginateMembers
t.Run("PaginateMembers", func(t *testing.T) {
param := model.QueryParam{
Wheres: []model.QueryWhere{
{Column: "team_id", Value: team1ID},
},
}
result, err := testProvider.PaginateMembers(ctx, param, 1, 10)
assert.NoError(t, err)
assert.NotNil(t, result)
// Pagination result may use "data" instead of "items"
assert.True(t, result["data"] != nil || result["items"] != nil)
assert.Contains(t, result, "total")
// Total should be 2 (member1 and member2)
total := result["total"]
assert.True(t, total == 2 || total == int64(2))
})
}
func TestMemberErrorHandling(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
nonExistentTeamID := "non-existent-team-" + testUUID
nonExistentUserID := "non-existent-user-" + testUUID
nonExistentMemberID := int64(999999)
t.Run("GetMember_NotFound", func(t *testing.T) {
_, err := testProvider.GetMember(ctx, nonExistentTeamID, nonExistentUserID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
t.Run("GetMemberDetail_NotFound", func(t *testing.T) {
_, err := testProvider.GetMemberDetail(ctx, nonExistentTeamID, nonExistentUserID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
t.Run("GetMemberByID_NotFound", func(t *testing.T) {
_, err := testProvider.GetMemberByID(ctx, nonExistentMemberID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
t.Run("UpdateMember_NotFound", func(t *testing.T) {
updateData := maps.MapStrAny{"role_id": "admin"}
err := testProvider.UpdateMember(ctx, nonExistentTeamID, nonExistentUserID, updateData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
t.Run("UpdateMemberByID_NotFound", func(t *testing.T) {
updateData := maps.MapStrAny{"role_id": "admin"}
err := testProvider.UpdateMemberByID(ctx, nonExistentMemberID, updateData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
t.Run("RemoveMember_NotFound", func(t *testing.T) {
err := testProvider.RemoveMember(ctx, nonExistentTeamID, nonExistentUserID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
t.Run("CreateMember_MissingRequiredFields", func(t *testing.T) {
// Missing team_id
memberData := maps.MapStrAny{
"user_id": "test-user",
"role_id": "user",
}
_, err := testProvider.CreateMember(ctx, memberData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "team_id is required")
// Missing role_id
memberData = maps.MapStrAny{
"team_id": "test-team",
"user_id": "test-user",
}
_, err = testProvider.CreateMember(ctx, memberData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "role_id is required")
// Missing user_id for active user member
memberData = maps.MapStrAny{
"team_id": "test-team",
"role_id": "user",
"member_type": "user",
"status": "active", // Explicitly set to active to trigger validation
}
_, err = testProvider.CreateMember(ctx, memberData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "user_id is required for active user members")
})
t.Run("UpdateMember_EmptyData", func(t *testing.T) {
// Create a test member first
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
memberUser := createTestUser(ctx, t, "member"+testUUID)
teamMap := maps.MapStrAny{
"name": "Error Test Team " + testUUID,
"display_name": "Error Test " + testUUID,
"description": "A test team for error testing",
"owner_id": ownerUser,
"status": "active",
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
_, err = testProvider.CreateMember(ctx, maps.MapStrAny{
"team_id": teamID,
"user_id": memberUser,
"member_type": "user",
"role_id": "user",
"status": "active",
})
assert.NoError(t, err)
// Test update with empty data (should not error, just do nothing)
err = testProvider.UpdateMember(ctx, teamID, memberUser, maps.MapStrAny{})
assert.NoError(t, err)
// Test update with only sensitive fields (should not error, just ignore them)
err = testProvider.UpdateMember(ctx, teamID, memberUser, maps.MapStrAny{
"id": 999,
"team_id": "new-team",
})
assert.NoError(t, err)
})
}
func TestMemberInvitationExpiry(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test users
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
inviteeUser := createTestUser(ctx, t, "invitee"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "Expiry Test Team " + testUUID,
"display_name": "Expiry Test " + testUUID,
"description": "A test team for invitation expiry testing",
"owner_id": ownerUser,
"status": "active",
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
// Create member with expired invitation
expiredTime := time.Now().Add(-2 * time.Hour) // Expired 2 hours ago to be safe
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": inviteeUser,
"member_type": "user",
"role_id": "user",
"status": "pending",
"invited_by": ownerUser,
"invited_at": expiredTime.Add(-1 * time.Hour), // Invited 3 hours ago
"invitation_token": "expired-token-" + testUUID,
"invitation_expires_at": expiredTime, // Expired 2 hours ago
}
businessMemberID, err := testProvider.CreateMember(ctx, memberData)
assert.NoError(t, err)
// Get the invitation_id
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
invitationID := member["invitation_id"].(string)
assert.NotEmpty(t, invitationID)
// Test AcceptInvitation with expired token
t.Run("AcceptInvitation_ExpiredToken", func(t *testing.T) {
err := testProvider.AcceptInvitation(ctx, invitationID, "expired-token-"+testUUID, "")
assert.Error(t, err)
assert.Contains(t, err.Error(), "invitation has expired")
})
}
func TestMemberInvitationIDOperations(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test users
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
inviteeUser := createTestUser(ctx, t, "invitee"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "Invitation ID Test Team " + testUUID,
"display_name": "Invitation ID Test " + testUUID,
"description": "A test team for invitation_id testing",
"owner_id": ownerUser,
"status": "active",
"type": "corporation",
"type_id": "business",
"metadata": map[string]interface{}{"test": true},
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
var invitationID string
// Test CreateMember with pending status (should generate invitation_id)
t.Run("CreateMember_GeneratesInvitationID", func(t *testing.T) {
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": nil, // Simulate invitation to unregistered user
"member_type": "user",
"role_id": "user",
"status": "pending",
"invited_by": ownerUser,
}
businessMemberID, err := testProvider.CreateMember(ctx, memberData)
assert.NoError(t, err)
assert.NotEmpty(t, businessMemberID)
// Get the created member to verify invitation_id was generated
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.NotNil(t, member["invitation_id"])
assert.NotEmpty(t, member["invitation_id"])
invitationID = member["invitation_id"].(string)
t.Logf("Generated invitation_id: %s", invitationID)
assert.True(t, strings.Contains(invitationID, "inv_"), "invitation_id should contain inv_ prefix, got: "+invitationID)
})
// Test GetMemberByInvitationID
t.Run("GetMemberByInvitationID", func(t *testing.T) {
member, err := testProvider.GetMemberByInvitationID(ctx, invitationID)
assert.NoError(t, err)
assert.NotNil(t, member)
assert.Equal(t, invitationID, member["invitation_id"])
assert.Equal(t, teamID, member["team_id"])
assert.Equal(t, "pending", member["status"])
assert.Equal(t, ownerUser, member["invited_by"])
})
// Test GetMemberByInvitationID with non-existent invitation
t.Run("GetMemberByInvitationID_NotFound", func(t *testing.T) {
_, err := testProvider.GetMemberByInvitationID(ctx, "non-existent-invitation-id")
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
// Test UpdateMemberByInvitationID
t.Run("UpdateMemberByInvitationID", func(t *testing.T) {
updateData := maps.MapStrAny{
"user_id": inviteeUser, // Now associate with a user
"status": "active",
"joined_at": time.Now(),
"notes": "Invitation accepted",
}
err := testProvider.UpdateMemberByInvitationID(ctx, invitationID, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMemberByInvitationID(ctx, invitationID)
assert.NoError(t, err)
assert.Equal(t, inviteeUser, member["user_id"])
assert.Equal(t, "active", member["status"])
assert.NotNil(t, member["joined_at"])
// Test updating sensitive fields (should be ignored except user_id which is allowed)
sensitiveData := maps.MapStrAny{
"id": 999,
"team_id": "new-team",
"invitation_id": "new-invitation-id",
}
err = testProvider.UpdateMemberByInvitationID(ctx, invitationID, sensitiveData)
assert.NoError(t, err) // Should not error, just ignore sensitive fields
// Verify sensitive fields were not updated
member, err = testProvider.GetMemberByInvitationID(ctx, invitationID)
assert.NoError(t, err)
assert.Equal(t, invitationID, member["invitation_id"]) // Should remain unchanged
assert.Equal(t, teamID, member["team_id"]) // Should remain unchanged
assert.Equal(t, inviteeUser, member["user_id"]) // Should remain as updated value
})
// Test UpdateMemberByInvitationID with non-existent invitation
t.Run("UpdateMemberByInvitationID_NotFound", func(t *testing.T) {
updateData := maps.MapStrAny{"notes": "test"}
err := testProvider.UpdateMemberByInvitationID(ctx, "non-existent-invitation-id", updateData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
// Test UpdateMemberByInvitationID with empty data (should not error)
t.Run("UpdateMemberByInvitationID_EmptyData", func(t *testing.T) {
err := testProvider.UpdateMemberByInvitationID(ctx, invitationID, maps.MapStrAny{})
assert.NoError(t, err) // Should not error, just do nothing
})
// Test RemoveMemberByInvitationID (at the end)
t.Run("RemoveMemberByInvitationID", func(t *testing.T) {
err := testProvider.RemoveMemberByInvitationID(ctx, invitationID)
assert.NoError(t, err)
// Verify member was removed
_, err = testProvider.GetMemberByInvitationID(ctx, invitationID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
// Test RemoveMemberByInvitationID with non-existent invitation
t.Run("RemoveMemberByInvitationID_NotFound", func(t *testing.T) {
err := testProvider.RemoveMemberByInvitationID(ctx, "non-existent-invitation-id")
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
}
func TestCreateMemberInvitationIDGeneration(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test user
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "ID Generation Test Team " + testUUID,
"display_name": "ID Generation Test " + testUUID,
"description": "A test team for invitation_id generation testing",
"owner_id": ownerUser,
"status": "active",
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
// Test invitation_id generation for pending members
t.Run("CreateMember_PendingStatus_GeneratesInvitationID", func(t *testing.T) {
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": nil, // No user_id for pending invitation
"member_type": "user",
"role_id": "user",
"status": "pending",
"invited_by": ownerUser,
}
businessMemberID, err := testProvider.CreateMember(ctx, memberData)
assert.NoError(t, err)
// Get the created member
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
// Verify invitation_id was generated
assert.NotNil(t, member["invitation_id"])
assert.NotEmpty(t, member["invitation_id"])
invitationID := member["invitation_id"].(string)
t.Logf("Generated invitation_id: %s", invitationID)
assert.True(t, strings.Contains(invitationID, "inv_"), "invitation_id should contain inv_ prefix, got: "+invitationID)
assert.True(t, len(invitationID) > 4, "invitation_id should be longer than just the prefix")
})
// Test that active members don't get invitation_id
t.Run("CreateMember_ActiveStatus_NoInvitationID", func(t *testing.T) {
activeUser := createTestUser(ctx, t, "active"+testUUID)
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": activeUser,
"member_type": "user",
"role_id": "user",
"status": "active",
}
businessMemberID, err := testProvider.CreateMember(ctx, memberData)
assert.NoError(t, err)
// Get the created member
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
// Verify invitation_id is nil for active members
assert.Nil(t, member["invitation_id"])
})
// Test explicit invitation_id is preserved
t.Run("CreateMember_ExplicitInvitationID_Preserved", func(t *testing.T) {
explicitInvitationID := "inv_explicit_test_" + testUUID
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": nil,
"member_type": "user",
"role_id": "user",
"status": "pending",
"invited_by": ownerUser,
"invitation_id": explicitInvitationID,
}
businessMemberID, err := testProvider.CreateMember(ctx, memberData)
assert.NoError(t, err)
// Get the created member
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
// Verify explicit invitation_id was preserved
assert.Equal(t, explicitInvitationID, member["invitation_id"])
})
}
func TestMemberIDOperations(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test users
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
memberUser := createTestUser(ctx, t, "member"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "Member ID Test Team " + testUUID,
"display_name": "Member ID Test " + testUUID,
"description": "A test team for member_id testing",
"owner_id": ownerUser,
"status": "active",
"type": "corporation",
"type_id": "business",
"metadata": map[string]interface{}{"test": true},
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
var businessMemberID string
// Test CreateMember generates member_id
t.Run("CreateMember_GeneratesMemberID", func(t *testing.T) {
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": memberUser,
"member_type": "user",
"role_id": "user",
"status": "active",
}
_, err := testProvider.CreateMember(ctx, memberData)
assert.NoError(t, err)
// Get the created member to verify member_id was generated
member, err := testProvider.GetMember(ctx, teamID, memberUser)
assert.NoError(t, err)
assert.NotNil(t, member["member_id"])
assert.NotEmpty(t, member["member_id"])
businessMemberID = member["member_id"].(string)
t.Logf("Generated member_id: %s", businessMemberID)
})
// Test GetMemberByMemberID
t.Run("GetMemberByMemberID", func(t *testing.T) {
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.NotNil(t, member)
assert.Equal(t, businessMemberID, member["member_id"])
assert.Equal(t, teamID, member["team_id"])
assert.Equal(t, memberUser, member["user_id"])
})
// Test GetMemberDetailByMemberID
t.Run("GetMemberDetailByMemberID", func(t *testing.T) {
member, err := testProvider.GetMemberDetailByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.NotNil(t, member)
assert.Equal(t, businessMemberID, member["member_id"])
assert.Equal(t, teamID, member["team_id"])
// Should contain detailed fields
assert.Contains(t, member, "created_at")
assert.Contains(t, member, "updated_at")
})
// Test UpdateMemberByMemberID
t.Run("UpdateMemberByMemberID", func(t *testing.T) {
updateData := maps.MapStrAny{
"role_id": "admin",
"notes": "Updated via member_id",
}
err := testProvider.UpdateMemberByMemberID(ctx, businessMemberID, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.Equal(t, "admin", member["role_id"])
})
// Test UpdateMemberRoleByMemberID
t.Run("UpdateMemberRoleByMemberID", func(t *testing.T) {
err := testProvider.UpdateMemberRoleByMemberID(ctx, businessMemberID, "moderator")
assert.NoError(t, err)
// Verify role was updated
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.Equal(t, "moderator", member["role_id"])
})
// Test UpdateMemberStatusByMemberID
t.Run("UpdateMemberStatusByMemberID", func(t *testing.T) {
err := testProvider.UpdateMemberStatusByMemberID(ctx, businessMemberID, "suspended")
assert.NoError(t, err)
// Verify status was updated
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.Equal(t, "suspended", member["status"])
// Change back to active
err = testProvider.UpdateMemberStatusByMemberID(ctx, businessMemberID, "active")
assert.NoError(t, err)
})
// Test UpdateMemberLastActivityByMemberID
t.Run("UpdateMemberLastActivityByMemberID", func(t *testing.T) {
err := testProvider.UpdateMemberLastActivityByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
// Verify last_active_at was updated and login_count incremented
member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
assert.NotNil(t, member["last_active_at"])
// login_count should be at least 1
loginCount := member["login_count"]
if loginCount != nil {
switch v := loginCount.(type) {
case int:
assert.True(t, v >= 1, "login_count should be at least 1")
case int64:
assert.True(t, v >= 1, "login_count should be at least 1")
case int32:
assert.True(t, v >= 1, "login_count should be at least 1")
default:
t.Logf("Unexpected login_count type: %T, value: %v", loginCount, loginCount)
assert.True(t, false, "login_count should be a numeric type")
}
} else {
assert.True(t, false, "login_count should not be nil")
}
})
// Test RemoveMemberByMemberID (at the end)
t.Run("RemoveMemberByMemberID", func(t *testing.T) {
err := testProvider.RemoveMemberByMemberID(ctx, businessMemberID)
assert.NoError(t, err)
// Verify member was removed
_, err = testProvider.GetMemberByMemberID(ctx, businessMemberID)
assert.Error(t, err)
assert.Contains(t, err.Error(), "member not found")
})
}
func TestMemberExistsByRobotEmail(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test users
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "Robot Email Test Team " + testUUID,
"display_name": "Robot Email Test " + testUUID,
"description": "A test team for robot email testing",
"owner_id": ownerUser,
"status": "active",
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
testRobotEmail := "testrobot" + testUUID + "@robot.example.com"
// Create robot member with robot_email
t.Run("CreateRobotMemberWithRobotEmail", func(t *testing.T) {
robotData := maps.MapStrAny{
"display_name": "TestBot" + testUUID,
"robot_email": testRobotEmail,
"role_id": "bot",
}
businessMemberID, err := testProvider.CreateRobotMember(ctx, teamID, robotData)
assert.NoError(t, err)
assert.NotEmpty(t, businessMemberID)
})
// Test MemberExistsByRobotEmail
t.Run("MemberExistsByRobotEmail_Exists", func(t *testing.T) {
exists, err := testProvider.MemberExistsByRobotEmail(ctx, testRobotEmail)
assert.NoError(t, err)
assert.True(t, exists)
})
// Test with non-existent robot email
t.Run("MemberExistsByRobotEmail_NotExists", func(t *testing.T) {
exists, err := testProvider.MemberExistsByRobotEmail(ctx, "nonexistent@robot.example.com")
assert.NoError(t, err)
assert.False(t, exists)
})
}
func TestUpdateRobotMember(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test user (team owner)
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
regularUser := createTestUser(ctx, t, "user"+testUUID)
// Create test team
teamMap := maps.MapStrAny{
"name": "Update Robot Test Team " + testUUID,
"display_name": "Update Robot Test " + testUUID,
"description": "A test team for robot update testing",
"owner_id": ownerUser,
"status": "active",
}
teamID, err := testProvider.CreateTeam(ctx, teamMap)
assert.NoError(t, err)
var robotMemberID string
var regularMemberID string
// Create a robot member
t.Run("Setup_CreateRobotMember", func(t *testing.T) {
robotData := maps.MapStrAny{
"display_name": "TestBot" + testUUID,
"bio": "Original bio",
"role_id": "bot",
"autonomous_mode": false,
"robot_status": "idle",
"system_prompt": "Original prompt",
"language_model": "gpt-3.5-turbo",
"cost_limit": 50.00,
"robot_email": "testbot" + testUUID + "@robot.example.com",
}
memberID, err := testProvider.CreateRobotMember(ctx, teamID, robotData)
assert.NoError(t, err)
assert.NotEmpty(t, memberID)
robotMemberID = memberID
})
// Create a regular user member for testing
t.Run("Setup_CreateRegularMember", func(t *testing.T) {
memberData := maps.MapStrAny{
"team_id": teamID,
"user_id": regularUser,
"member_type": "user",
"role_id": "user",
"status": "active",
}
memberID, err := testProvider.CreateMember(ctx, memberData)
assert.NoError(t, err)
assert.NotEmpty(t, memberID)
regularMemberID = memberID
})
// Test successful update of robot member
t.Run("UpdateRobotMember_Success", func(t *testing.T) {
updateData := maps.MapStrAny{
"display_name": "UpdatedBot" + testUUID,
"bio": "Updated bio",
"autonomous_mode": true,
"robot_status": "working",
"system_prompt": "Updated prompt",
"language_model": "gpt-4",
"cost_limit": 100.00,
}
err := testProvider.UpdateRobotMember(ctx, robotMemberID, updateData)
assert.NoError(t, err)
// Verify updates
member, err := testProvider.GetMemberDetailByMemberID(ctx, robotMemberID)
assert.NoError(t, err)
assert.Equal(t, "UpdatedBot"+testUUID, member["display_name"])
assert.Equal(t, "Updated bio", member["bio"])
assert.Equal(t, "working", member["robot_status"])
assert.Equal(t, "Updated prompt", member["system_prompt"])
assert.Equal(t, "gpt-4", member["language_model"])
})
// Test updating robot_email
t.Run("UpdateRobotMember_UpdateEmail", func(t *testing.T) {
newEmail := "updated-testbot" + testUUID + "@robot.example.com"
updateData := maps.MapStrAny{
"robot_email": newEmail,
}
err := testProvider.UpdateRobotMember(ctx, robotMemberID, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMemberDetailByMemberID(ctx, robotMemberID)
assert.NoError(t, err)
assert.Equal(t, newEmail, member["robot_email"])
})
// Test updating robot configuration fields
t.Run("UpdateRobotMember_UpdateConfiguration", func(t *testing.T) {
updateData := maps.MapStrAny{
"authorized_senders": []string{
"admin@example.com",
"manager@example.com",
},
"email_filter_rules": []string{
".*@example\\.com$",
".*@test\\.com$",
},
"robot_config": map[string]interface{}{
"max_tokens": 2000,
"temperature": 0.7,
},
"agents": []string{
"agent1",
"agent2",
},
"mcp_servers": []string{
"mcp://server1",
"mcp://server2",
},
}
err := testProvider.UpdateRobotMember(ctx, robotMemberID, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMemberDetailByMemberID(ctx, robotMemberID)
assert.NoError(t, err)
assert.NotNil(t, member["authorized_senders"])
assert.NotNil(t, member["email_filter_rules"])
assert.NotNil(t, member["robot_config"])
})
// Test error when trying to update non-robot member
t.Run("UpdateRobotMember_NotRobotMember", func(t *testing.T) {
updateData := maps.MapStrAny{
"display_name": "Should Fail",
}
err := testProvider.UpdateRobotMember(ctx, regularMemberID, updateData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not a robot member")
})
// Test error when member_id doesn't exist
t.Run("UpdateRobotMember_NotFound", func(t *testing.T) {
updateData := maps.MapStrAny{
"display_name": "Should Fail",
}
err := testProvider.UpdateRobotMember(ctx, "non-existent-member-id", updateData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "failed to get member")
})
// Test robot_email uniqueness validation during update
t.Run("UpdateRobotMember_EmailUniqueness", func(t *testing.T) {
// Create another robot with a different email
anotherRobotData := maps.MapStrAny{
"display_name": "AnotherBot" + testUUID,
"role_id": "bot",
"robot_email": "anotherbot" + testUUID + "@robot.example.com",
}
_, err := testProvider.CreateRobotMember(ctx, teamID, anotherRobotData)
assert.NoError(t, err)
// Try to update the first robot's email to match the second robot's email
updateData := maps.MapStrAny{
"robot_email": "anotherbot" + testUUID + "@robot.example.com",
}
err = testProvider.UpdateRobotMember(ctx, robotMemberID, updateData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "already exists")
})
// Test updating with same email (should succeed - no actual change)
t.Run("UpdateRobotMember_SameEmail", func(t *testing.T) {
// Get current email
member, err := testProvider.GetMemberDetailByMemberID(ctx, robotMemberID)
assert.NoError(t, err)
currentEmail := member["robot_email"]
// Update with same email
updateData := maps.MapStrAny{
"robot_email": currentEmail,
}
err = testProvider.UpdateRobotMember(ctx, robotMemberID, updateData)
assert.NoError(t, err)
})
// Test update with empty data (should not error)
t.Run("UpdateRobotMember_EmptyData", func(t *testing.T) {
err := testProvider.UpdateRobotMember(ctx, robotMemberID, maps.MapStrAny{})
assert.NoError(t, err) // Should not error, just do nothing
})
// Test updating status
t.Run("UpdateRobotMember_UpdateStatus", func(t *testing.T) {
updateData := maps.MapStrAny{
"status": "inactive",
}
err := testProvider.UpdateRobotMember(ctx, robotMemberID, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMemberByMemberID(ctx, robotMemberID)
assert.NoError(t, err)
assert.Equal(t, "inactive", member["status"])
// Restore to active
err = testProvider.UpdateRobotMember(ctx, robotMemberID, maps.MapStrAny{"status": "active"})
assert.NoError(t, err)
})
}
func TestRobotEmailUniqueness(t *testing.T) {
prepare(t)
defer clean()
ctx := context.Background()
// Use UUID to ensure unique identifiers
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
// Create test user (team owner)
ownerUser := createTestUser(ctx, t, "owner"+testUUID)
// Create two test teams
team1Map := maps.MapStrAny{
"name": "Robot Email Test Team 1 " + testUUID,
"display_name": "Robot Email Test 1 " + testUUID,
"description": "First test team for robot email testing",
"owner_id": ownerUser,
"status": "active",
}
team1ID, err := testProvider.CreateTeam(ctx, team1Map)
assert.NoError(t, err)
team2Map := maps.MapStrAny{
"name": "Robot Email Test Team 2 " + testUUID,
"display_name": "Robot Email Test 2 " + testUUID,
"description": "Second test team for robot email testing",
"owner_id": ownerUser,
"status": "active",
}
team2ID, err := testProvider.CreateTeam(ctx, team2Map)
assert.NoError(t, err)
testEmail := "unique-robot" + testUUID + "@robot.example.com"
// Test creating first robot with robot_email
t.Run("CreateFirstRobotWithEmail", func(t *testing.T) {
robotData := maps.MapStrAny{
"display_name": "Robot1" + testUUID,
"role_id": "bot",
"robot_email": testEmail,
"authorized_senders": []string{
"admin@example.com",
},
"email_filter_rules": []string{
".*@example\\.com$",
},
}
memberID, err := testProvider.CreateRobotMember(ctx, team1ID, robotData)
assert.NoError(t, err)
assert.NotEmpty(t, memberID)
// Verify robot_email was set
member, err := testProvider.GetMemberDetailByMemberID(ctx, memberID)
assert.NoError(t, err)
assert.Equal(t, testEmail, member["robot_email"])
})
// Test creating second robot with same robot_email should fail (global uniqueness)
t.Run("CreateSecondRobotWithSameEmail_ShouldFail", func(t *testing.T) {
robotData := maps.MapStrAny{
"display_name": "Robot2" + testUUID,
"role_id": "bot",
"robot_email": testEmail, // Same email as first robot
}
_, err := testProvider.CreateRobotMember(ctx, team2ID, robotData)
assert.Error(t, err)
// The error should indicate uniqueness constraint violation
// Note: The exact error message may vary depending on the database driver
})
// Test creating robot with different robot_email should succeed
t.Run("CreateRobotWithDifferentEmail_ShouldSucceed", func(t *testing.T) {
differentEmail := "another-robot" + testUUID + "@robot.example.com"
robotData := maps.MapStrAny{
"display_name": "Robot3" + testUUID,
"role_id": "bot",
"robot_email": differentEmail,
}
memberID, err := testProvider.CreateRobotMember(ctx, team2ID, robotData)
assert.NoError(t, err)
assert.NotEmpty(t, memberID)
// Verify robot_email was set
member, err := testProvider.GetMemberDetailByMemberID(ctx, memberID)
assert.NoError(t, err)
assert.Equal(t, differentEmail, member["robot_email"])
})
// Test updating robot_email
t.Run("UpdateRobotEmail", func(t *testing.T) {
// Create a new robot
newEmail := "updatable-robot" + testUUID + "@robot.example.com"
robotData := maps.MapStrAny{
"display_name": "Robot4" + testUUID,
"role_id": "bot",
"robot_email": newEmail,
}
memberID, err := testProvider.CreateRobotMember(ctx, team1ID, robotData)
assert.NoError(t, err)
// Update robot_email
updatedEmail := "updated-robot" + testUUID + "@robot.example.com"
updateData := maps.MapStrAny{
"robot_email": updatedEmail,
}
err = testProvider.UpdateMemberByMemberID(ctx, memberID, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMemberDetailByMemberID(ctx, memberID)
assert.NoError(t, err)
assert.Equal(t, updatedEmail, member["robot_email"])
})
// Test updating authorized_senders and email_filter_rules
t.Run("UpdateRobotEmailConfiguration", func(t *testing.T) {
// Create a new robot
robotData := maps.MapStrAny{
"display_name": "Robot5" + testUUID,
"role_id": "bot",
"robot_email": "config-robot" + testUUID + "@robot.example.com",
"authorized_senders": []string{
"initial@example.com",
},
"email_filter_rules": []string{
".*@initial\\.com$",
},
}
memberID, err := testProvider.CreateRobotMember(ctx, team1ID, robotData)
assert.NoError(t, err)
// Update email configuration
updateData := maps.MapStrAny{
"authorized_senders": []string{
"admin@example.com",
"manager@example.com",
"owner@example.com",
},
"email_filter_rules": []string{
".*@example\\.com$",
".*@test\\.com$",
".*@company\\.com$",
},
}
err = testProvider.UpdateMemberByMemberID(ctx, memberID, updateData)
assert.NoError(t, err)
// Verify update
member, err := testProvider.GetMemberDetailByMemberID(ctx, memberID)
assert.NoError(t, err)
assert.NotNil(t, member["authorized_senders"])
assert.NotNil(t, member["email_filter_rules"])
})
}
// Helper function createTestUser is defined in team_test.go