- Added role_id field to the team model for specifying team owner roles. - Enhanced team creation logic to set default role_id based on team configuration if not provided. - Updated tests to reflect changes in team type and role handling, ensuring accurate assertions. - Improved documentation for team configuration to clarify default role settings for team creators.
575 lines
18 KiB
Go
575 lines
18 KiB
Go
package user_test
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/yaoapp/gou/model"
|
|
"github.com/yaoapp/kun/maps"
|
|
)
|
|
|
|
// TestTeamData represents test team data structure
|
|
type TestTeamData struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
Description string `json:"description"`
|
|
Website string `json:"website"`
|
|
OwnerID string `json:"owner_id"`
|
|
Status string `json:"status"`
|
|
Type string `json:"type"`
|
|
TypeID string `json:"type_id"`
|
|
Metadata map[string]interface{} `json:"metadata"`
|
|
}
|
|
|
|
// TestMemberData represents test member data structure
|
|
type TestMemberData struct {
|
|
TeamID string `json:"team_id"`
|
|
UserID string `json:"user_id"`
|
|
RoleID string `json:"role_id"`
|
|
Status string `json:"status"`
|
|
InvitedBy string `json:"invited_by"`
|
|
MemberType string `json:"member_type"`
|
|
}
|
|
|
|
func TestTeamBasicOperations(t *testing.T) {
|
|
prepare(t)
|
|
defer clean()
|
|
|
|
ctx := context.Background()
|
|
|
|
// Use UUID to ensure unique identifiers
|
|
testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8] // 8 char UUID
|
|
|
|
// First, create a test user to be the team owner
|
|
testUser := &TestUserData{
|
|
PreferredUsername: "teamowner" + testUUID,
|
|
Email: "teamowner" + testUUID + "@example.com",
|
|
Password: "TestPass123!",
|
|
Name: "Team Owner " + testUUID,
|
|
GivenName: "Team",
|
|
FamilyName: "Owner",
|
|
Status: "active",
|
|
RoleID: "admin",
|
|
TypeID: "regular",
|
|
EmailVerified: true,
|
|
Metadata: map[string]interface{}{"source": "test"},
|
|
}
|
|
|
|
userMap := maps.MapStrAny{
|
|
"preferred_username": testUser.PreferredUsername,
|
|
"email": testUser.Email,
|
|
"password": testUser.Password,
|
|
"name": testUser.Name,
|
|
"given_name": testUser.GivenName,
|
|
"family_name": testUser.FamilyName,
|
|
"status": testUser.Status,
|
|
"role_id": testUser.RoleID,
|
|
"type_id": testUser.TypeID,
|
|
"email_verified": testUser.EmailVerified,
|
|
"metadata": testUser.Metadata,
|
|
}
|
|
|
|
// Create the owner user
|
|
_, err := testProvider.CreateUser(ctx, userMap)
|
|
assert.NoError(t, err)
|
|
ownerUserID := userMap["user_id"].(string)
|
|
|
|
// Create test team data dynamically
|
|
testTeam := &TestTeamData{
|
|
Name: "Test Team " + testUUID,
|
|
DisplayName: "Test Display " + testUUID,
|
|
Description: "A test team for unit testing",
|
|
Website: "https://test" + testUUID + ".example.com",
|
|
OwnerID: ownerUserID,
|
|
Status: "active",
|
|
Type: "corporation",
|
|
TypeID: "free",
|
|
Metadata: map[string]interface{}{"test": true, "uuid": testUUID},
|
|
}
|
|
|
|
var testTeamID string // Store the auto-generated team_id
|
|
|
|
// Test CreateTeam
|
|
t.Run("CreateTeam", func(t *testing.T) {
|
|
teamMap := maps.MapStrAny{
|
|
"name": testTeam.Name,
|
|
"display_name": testTeam.DisplayName,
|
|
"description": testTeam.Description,
|
|
"website": testTeam.Website,
|
|
"owner_id": testTeam.OwnerID,
|
|
"status": testTeam.Status,
|
|
"type": testTeam.Type,
|
|
"type_id": testTeam.TypeID,
|
|
"metadata": testTeam.Metadata,
|
|
}
|
|
|
|
id, err := testProvider.CreateTeam(ctx, teamMap)
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, id)
|
|
|
|
// Verify team was created with auto-generated team_id
|
|
assert.Contains(t, teamMap, "team_id")
|
|
assert.NotEmpty(t, teamMap["team_id"])
|
|
|
|
// Store generated team_id for subsequent tests
|
|
testTeamID = teamMap["team_id"].(string)
|
|
})
|
|
|
|
// Test GetTeam
|
|
t.Run("GetTeam", func(t *testing.T) {
|
|
team, err := testProvider.GetTeam(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, team)
|
|
assert.Equal(t, testTeam.Name, team["name"])
|
|
assert.Equal(t, testTeam.DisplayName, team["display_name"])
|
|
assert.Equal(t, testTeam.OwnerID, team["owner_id"])
|
|
assert.Equal(t, testTeam.TypeID, team["type_id"])
|
|
})
|
|
|
|
// Test GetTeamDetail
|
|
t.Run("GetTeamDetail", func(t *testing.T) {
|
|
team, err := testProvider.GetTeamDetail(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, team)
|
|
assert.Equal(t, testTeam.Name, team["name"])
|
|
assert.Equal(t, testTeam.Website, team["website"])
|
|
assert.Equal(t, testTeam.Description, team["description"])
|
|
})
|
|
|
|
// Test TeamExists
|
|
t.Run("TeamExists", func(t *testing.T) {
|
|
exists, err := testProvider.TeamExists(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
assert.True(t, exists)
|
|
|
|
// Test with non-existent team
|
|
exists, err = testProvider.TeamExists(ctx, "non-existent-team-"+testUUID)
|
|
assert.NoError(t, err)
|
|
assert.False(t, exists)
|
|
})
|
|
|
|
// Test UpdateTeam
|
|
t.Run("UpdateTeam", func(t *testing.T) {
|
|
updateData := maps.MapStrAny{
|
|
"description": "Updated test team description",
|
|
"display_name": "Updated Display Name",
|
|
"metadata": map[string]interface{}{"updated": true},
|
|
}
|
|
|
|
err := testProvider.UpdateTeam(ctx, testTeamID, updateData)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify update
|
|
team, err := testProvider.GetTeam(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "Updated test team description", team["description"])
|
|
assert.Equal(t, "Updated Display Name", team["display_name"])
|
|
|
|
// Test updating sensitive fields (should be ignored)
|
|
sensitiveData := maps.MapStrAny{
|
|
"team_id": "new-team-id",
|
|
"created_at": "2023-01-01",
|
|
"verified_at": "2023-01-01",
|
|
}
|
|
|
|
err = testProvider.UpdateTeam(ctx, testTeamID, sensitiveData)
|
|
assert.NoError(t, err) // Should not error, just ignore sensitive fields
|
|
})
|
|
|
|
// Test UpdateTeamStatus
|
|
t.Run("UpdateTeamStatus", func(t *testing.T) {
|
|
err := testProvider.UpdateTeamStatus(ctx, testTeamID, "inactive")
|
|
assert.NoError(t, err)
|
|
|
|
// Verify status was updated
|
|
team, err := testProvider.GetTeam(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "inactive", team["status"])
|
|
|
|
// Change back to active for other tests
|
|
err = testProvider.UpdateTeamStatus(ctx, testTeamID, "active")
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
// Test VerifyTeam
|
|
t.Run("VerifyTeam", func(t *testing.T) {
|
|
err := testProvider.VerifyTeam(ctx, testTeamID, ownerUserID)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify team was marked as verified
|
|
team, err := testProvider.GetTeamDetail(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
// Database may return int64(1) instead of bool(true)
|
|
isVerified := team["is_verified"]
|
|
assert.True(t, isVerified == true || isVerified == int64(1) || isVerified == 1)
|
|
// verified_by might be nil due to sensitive field filtering, just check it's not empty if present
|
|
if verifiedBy := team["verified_by"]; verifiedBy != nil {
|
|
assert.Equal(t, ownerUserID, verifiedBy)
|
|
}
|
|
})
|
|
|
|
// Test UnverifyTeam
|
|
t.Run("UnverifyTeam", func(t *testing.T) {
|
|
err := testProvider.UnverifyTeam(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify team was marked as unverified
|
|
team, err := testProvider.GetTeamDetail(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
// Database may return int64(0) instead of bool(false)
|
|
isVerified := team["is_verified"]
|
|
assert.True(t, isVerified == false || isVerified == int64(0) || isVerified == 0)
|
|
assert.Nil(t, team["verified_by"])
|
|
})
|
|
|
|
// Test GetTeamsByOwner
|
|
t.Run("GetTeamsByOwner", func(t *testing.T) {
|
|
teams, err := testProvider.GetTeamsByOwner(ctx, ownerUserID)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, teams, 1)
|
|
assert.Equal(t, testTeamID, teams[0]["team_id"])
|
|
})
|
|
|
|
// Test GetTeamsByStatus
|
|
t.Run("GetTeamsByStatus", func(t *testing.T) {
|
|
teams, err := testProvider.GetTeamsByStatus(ctx, "active")
|
|
assert.NoError(t, err)
|
|
assert.True(t, len(teams) >= 1) // At least our test team
|
|
|
|
// Find our test team in the results
|
|
found := false
|
|
for _, team := range teams {
|
|
if team["team_id"] == testTeamID {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found, "Test team should be found in active teams")
|
|
})
|
|
|
|
// Test PaginateTeams
|
|
t.Run("PaginateTeams", func(t *testing.T) {
|
|
param := model.QueryParam{
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "status", Value: "active"},
|
|
},
|
|
}
|
|
|
|
result, err := testProvider.PaginateTeams(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")
|
|
})
|
|
|
|
// Test CountTeams
|
|
t.Run("CountTeams", func(t *testing.T) {
|
|
param := model.QueryParam{
|
|
Wheres: []model.QueryWhere{
|
|
{Column: "status", Value: "active"},
|
|
},
|
|
}
|
|
|
|
count, err := testProvider.CountTeams(ctx, param)
|
|
assert.NoError(t, err)
|
|
assert.True(t, count >= 1) // At least our test team
|
|
})
|
|
|
|
// Test DeleteTeam (at the end)
|
|
t.Run("DeleteTeam", func(t *testing.T) {
|
|
err := testProvider.DeleteTeam(ctx, testTeamID)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify team was deleted
|
|
_, err = testProvider.GetTeam(ctx, testTeamID)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "team not found")
|
|
})
|
|
}
|
|
|
|
func TestTeamMemberOperations(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
|
|
testTeam := &TestTeamData{
|
|
Name: "Member Test Team " + testUUID,
|
|
DisplayName: "Member Test " + testUUID,
|
|
Description: "A test team for member testing",
|
|
OwnerID: ownerUser,
|
|
Status: "active",
|
|
Type: "corporation",
|
|
TypeID: "business",
|
|
Metadata: map[string]interface{}{"test": true},
|
|
}
|
|
|
|
teamMap := maps.MapStrAny{
|
|
"name": testTeam.Name,
|
|
"display_name": testTeam.DisplayName,
|
|
"description": testTeam.Description,
|
|
"owner_id": testTeam.OwnerID,
|
|
"status": testTeam.Status,
|
|
"type": testTeam.Type,
|
|
"type_id": testTeam.TypeID,
|
|
"metadata": testTeam.Metadata,
|
|
}
|
|
|
|
teamID, err := testProvider.CreateTeam(ctx, teamMap)
|
|
assert.NoError(t, err)
|
|
|
|
var memberID int64
|
|
|
|
// Test AddMember (invitation-based)
|
|
t.Run("AddMember", func(t *testing.T) {
|
|
id, err := testProvider.AddMember(ctx, teamID, memberUser, "user", ownerUser)
|
|
assert.NoError(t, err)
|
|
assert.Greater(t, id, int64(0))
|
|
memberID = 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 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, "pending", member["status"]) // Initially pending
|
|
})
|
|
|
|
// Test GetMemberByID
|
|
t.Run("GetMemberByID", func(t *testing.T) {
|
|
member, err := testProvider.GetMemberByID(ctx, memberID)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, member)
|
|
assert.Equal(t, teamID, member["team_id"])
|
|
assert.Equal(t, memberUser, member["user_id"])
|
|
})
|
|
|
|
// Test AcceptInvitation
|
|
t.Run("AcceptInvitation", func(t *testing.T) {
|
|
// First get the invitation token and invitation_id
|
|
member, err := testProvider.GetMemberDetail(ctx, teamID, memberUser)
|
|
assert.NoError(t, err)
|
|
invitationToken := member["invitation_token"].(string)
|
|
assert.NotEmpty(t, invitationToken)
|
|
invitationID := member["invitation_id"].(string)
|
|
assert.NotEmpty(t, invitationID)
|
|
|
|
// Accept the invitation
|
|
err = testProvider.AcceptInvitation(ctx, invitationID, invitationToken, "")
|
|
assert.NoError(t, err)
|
|
|
|
// Verify member status changed to active
|
|
member, err = testProvider.GetMember(ctx, teamID, memberUser)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "active", member["status"])
|
|
})
|
|
|
|
// Test UpdateMemberRole
|
|
t.Run("UpdateMemberRole", func(t *testing.T) {
|
|
err := testProvider.UpdateMemberRole(ctx, teamID, memberUser, "admin")
|
|
assert.NoError(t, err)
|
|
|
|
// Verify role was updated
|
|
member, err := testProvider.GetMember(ctx, teamID, memberUser)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "admin", member["role_id"])
|
|
})
|
|
|
|
// Test UpdateMemberStatus
|
|
t.Run("UpdateMemberStatus", func(t *testing.T) {
|
|
err := testProvider.UpdateMemberStatus(ctx, teamID, memberUser, "inactive")
|
|
assert.NoError(t, err)
|
|
|
|
// Verify status was updated
|
|
member, err := testProvider.GetMember(ctx, teamID, memberUser)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "inactive", 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
|
|
member, err := testProvider.GetMember(ctx, teamID, memberUser)
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, member["last_active_at"])
|
|
})
|
|
|
|
// Test GetTeamMembers
|
|
t.Run("GetTeamMembers", func(t *testing.T) {
|
|
members, err := testProvider.GetTeamMembers(ctx, teamID)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, members, 1) // Only our test member
|
|
assert.Equal(t, memberUser, members[0]["user_id"])
|
|
})
|
|
|
|
// Test GetUserTeams
|
|
t.Run("GetUserTeams", func(t *testing.T) {
|
|
teams, err := testProvider.GetUserTeams(ctx, memberUser)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, teams, 1) // Only our test team
|
|
assert.Equal(t, teamID, teams[0]["team_id"])
|
|
})
|
|
|
|
// Test GetTeamMembersByStatus
|
|
t.Run("GetTeamMembersByStatus", func(t *testing.T) {
|
|
members, err := testProvider.GetTeamMembersByStatus(ctx, teamID, "active")
|
|
assert.NoError(t, err)
|
|
assert.Len(t, members, 1) // Our active member
|
|
assert.Equal(t, memberUser, members[0]["user_id"])
|
|
})
|
|
|
|
// Test CreateRobotMember
|
|
t.Run("CreateRobotMember", func(t *testing.T) {
|
|
robotData := maps.MapStrAny{
|
|
"robot_name": "TestBot" + testUUID,
|
|
"robot_description": "A test robot for unit testing",
|
|
"role_id": "bot",
|
|
"is_active_robot": true,
|
|
"robot_status": "idle",
|
|
}
|
|
|
|
robotID, err := testProvider.CreateRobotMember(ctx, teamID, robotData)
|
|
assert.NoError(t, err)
|
|
assert.Greater(t, robotID, int64(0))
|
|
})
|
|
|
|
// Test GetTeamRobotMembers
|
|
t.Run("GetTeamRobotMembers", func(t *testing.T) {
|
|
robots, err := testProvider.GetTeamRobotMembers(ctx, teamID)
|
|
assert.NoError(t, err)
|
|
assert.Len(t, robots, 1) // Our test robot
|
|
assert.Equal(t, "robot", robots[0]["member_type"])
|
|
assert.Equal(t, "TestBot"+testUUID, robots[0]["robot_name"])
|
|
})
|
|
|
|
// 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 TestTeamErrorHandling(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
|
|
|
|
t.Run("GetTeam_NotFound", func(t *testing.T) {
|
|
_, err := testProvider.GetTeam(ctx, nonExistentTeamID)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "team not found")
|
|
})
|
|
|
|
t.Run("GetTeamDetail_NotFound", func(t *testing.T) {
|
|
_, err := testProvider.GetTeamDetail(ctx, nonExistentTeamID)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "team not found")
|
|
})
|
|
|
|
t.Run("UpdateTeam_NotFound", func(t *testing.T) {
|
|
updateData := maps.MapStrAny{"name": "Test"}
|
|
err := testProvider.UpdateTeam(ctx, nonExistentTeamID, updateData)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "team not found")
|
|
})
|
|
|
|
t.Run("DeleteTeam_NotFound", func(t *testing.T) {
|
|
err := testProvider.DeleteTeam(ctx, nonExistentTeamID)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "team not found")
|
|
})
|
|
|
|
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("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("CreateTeam_MissingRequiredFields", func(t *testing.T) {
|
|
// Missing name
|
|
teamData := maps.MapStrAny{
|
|
"owner_id": "test-owner",
|
|
}
|
|
_, err := testProvider.CreateTeam(ctx, teamData)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "name is required")
|
|
|
|
// Missing owner_id
|
|
teamData = maps.MapStrAny{
|
|
"name": "Test Team",
|
|
}
|
|
_, err = testProvider.CreateTeam(ctx, teamData)
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "owner_id is required")
|
|
})
|
|
}
|
|
|
|
// Helper function to create a test user and return the user_id
|
|
func createTestUser(ctx context.Context, t *testing.T, suffix string) string {
|
|
userMap := maps.MapStrAny{
|
|
"preferred_username": "testuser" + suffix,
|
|
"email": "testuser" + suffix + "@example.com",
|
|
"password": "TestPass123!",
|
|
"name": "Test User " + suffix,
|
|
"given_name": "Test",
|
|
"family_name": "User",
|
|
"status": "active",
|
|
"role_id": "user",
|
|
"type_id": "regular",
|
|
"email_verified": true,
|
|
"metadata": map[string]interface{}{"source": "test"},
|
|
}
|
|
|
|
_, err := testProvider.CreateUser(ctx, userMap)
|
|
assert.NoError(t, err)
|
|
return userMap["user_id"].(string)
|
|
}
|