Merge pull request #1244 from trheyi/main
Enhance robot member functionality with avatar support
This commit is contained in:
commit
ece929669d
6 changed files with 119 additions and 9 deletions
|
|
@ -1137,6 +1137,7 @@ func TestMemberCreateRobot(t *testing.T) {
|
|||
teamID,
|
||||
map[string]interface{}{
|
||||
"name": "AI Assistant Full",
|
||||
"avatar": fmt.Sprintf("https://example.com/avatars/ai-full-%s.png", testUUID),
|
||||
"email": fmt.Sprintf("ai-full-%s@test.com", testUUID),
|
||||
"robot_email": fmt.Sprintf("robot-full-%s@robot.test.com", testUUID),
|
||||
"authorized_senders": []string{"user1@test.com", "user2@test.com"},
|
||||
|
|
@ -1637,6 +1638,7 @@ func TestMemberUpdateRobot(t *testing.T) {
|
|||
func() (string, string) { return createTestRobot("2") },
|
||||
map[string]interface{}{
|
||||
"name": "Updated Robot Full",
|
||||
"avatar": fmt.Sprintf("https://example.com/avatars/full-%s.png", testUUID),
|
||||
"email": fmt.Sprintf("updated-display-%s@test.com", testUUID),
|
||||
"robot_email": fmt.Sprintf("updated-robot-%s@robot.test.com", testUUID),
|
||||
"bio": "Updated comprehensive description",
|
||||
|
|
@ -1673,6 +1675,7 @@ func TestMemberUpdateRobot(t *testing.T) {
|
|||
body, _ := io.ReadAll(getResp.Body)
|
||||
json.Unmarshal(body, &member)
|
||||
assert.Equal(t, "Updated Robot Full", member["display_name"])
|
||||
assert.Equal(t, fmt.Sprintf("https://example.com/avatars/full-%s.png", testUUID), member["avatar"])
|
||||
assert.Equal(t, "Updated system prompt", member["system_prompt"])
|
||||
assert.Equal(t, "gpt-4", member["language_model"])
|
||||
}
|
||||
|
|
@ -1902,6 +1905,73 @@ func TestMemberUpdateRobot(t *testing.T) {
|
|||
"should handle empty update (no-op)",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"update robot avatar",
|
||||
func() (string, string) { return createTestRobot("13") },
|
||||
map[string]interface{}{
|
||||
"name": "Robot with Avatar",
|
||||
"robot_email": fmt.Sprintf("robot-avatar-%s@robot.test.com", testUUID),
|
||||
"avatar": fmt.Sprintf("https://example.com/avatars/robot-%s.png", testUUID),
|
||||
},
|
||||
map[string]string{
|
||||
"Authorization": "Bearer " + tokenInfo.AccessToken,
|
||||
},
|
||||
200,
|
||||
"should update robot avatar successfully",
|
||||
func(t *testing.T, memberID string) {
|
||||
// Verify the avatar was updated
|
||||
getMemberURL := serverURL + baseURL + "/user/teams/" + teamID + "/members/" + memberID
|
||||
getReq, _ := http.NewRequest("GET", getMemberURL, nil)
|
||||
getReq.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
|
||||
client := &http.Client{}
|
||||
getResp, err := client.Do(getReq)
|
||||
assert.NoError(t, err)
|
||||
if getResp != nil {
|
||||
defer getResp.Body.Close()
|
||||
if getResp.StatusCode == 200 {
|
||||
var member map[string]interface{}
|
||||
body, _ := io.ReadAll(getResp.Body)
|
||||
json.Unmarshal(body, &member)
|
||||
assert.Equal(t, "Robot with Avatar", member["display_name"])
|
||||
assert.Equal(t, fmt.Sprintf("https://example.com/avatars/robot-%s.png", testUUID), member["avatar"], "Should have correct avatar URL")
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"update only robot avatar",
|
||||
func() (string, string) { return createTestRobot("14") },
|
||||
map[string]interface{}{
|
||||
"avatar": fmt.Sprintf("https://example.com/avatars/updated-%s.png", testUUID),
|
||||
},
|
||||
map[string]string{
|
||||
"Authorization": "Bearer " + tokenInfo.AccessToken,
|
||||
},
|
||||
200,
|
||||
"should update only avatar without affecting other fields",
|
||||
func(t *testing.T, memberID string) {
|
||||
// Verify only avatar was updated
|
||||
getMemberURL := serverURL + baseURL + "/user/teams/" + teamID + "/members/" + memberID
|
||||
getReq, _ := http.NewRequest("GET", getMemberURL, nil)
|
||||
getReq.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
|
||||
client := &http.Client{}
|
||||
getResp, err := client.Do(getReq)
|
||||
assert.NoError(t, err)
|
||||
if getResp != nil {
|
||||
defer getResp.Body.Close()
|
||||
if getResp.StatusCode == 200 {
|
||||
var member map[string]interface{}
|
||||
body, _ := io.ReadAll(getResp.Body)
|
||||
json.Unmarshal(body, &member)
|
||||
// Avatar should be updated
|
||||
assert.Equal(t, fmt.Sprintf("https://example.com/avatars/updated-%s.png", testUUID), member["avatar"], "Should have updated avatar URL")
|
||||
// Original fields should remain
|
||||
assert.Equal(t, "Test Robot 14", member["display_name"], "Name should remain unchanged")
|
||||
assert.Equal(t, "gpt-3.5-turbo", member["language_model"], "LLM should remain unchanged")
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ func TestGetTeamConfigPublic(t *testing.T) {
|
|||
assert.Equal(t, originalConfig.Role, publicConfig.Role, "Role should be preserved")
|
||||
assert.Equal(t, originalConfig.Roles, publicConfig.Roles, "Roles should be preserved")
|
||||
assert.Equal(t, originalConfig.Invite, publicConfig.Invite, "Invite config should be preserved")
|
||||
assert.Equal(t, originalConfig.Uploader, publicConfig.Uploader, "Uploader should be preserved (public field)")
|
||||
assert.Equal(t, originalConfig.AvatarAgent, publicConfig.AvatarAgent, "AvatarAgent should be preserved (public field)")
|
||||
|
||||
// Test robot configuration
|
||||
if originalConfig.Robot != nil {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,19 @@ func TestTeamConfigStructure(t *testing.T) {
|
|||
// Verify team config structure is valid
|
||||
assert.IsType(t, &user.TeamConfig{}, teamConfig, "Should return correct team config type")
|
||||
|
||||
// Test uploader field (public field)
|
||||
if teamConfig.Uploader != "" {
|
||||
t.Logf("Uploader configured: %s", teamConfig.Uploader)
|
||||
assert.NotEmpty(t, teamConfig.Uploader, "Uploader should not be empty if set")
|
||||
}
|
||||
|
||||
// Test avatar_agent field (public field, optional)
|
||||
if teamConfig.AvatarAgent != "" {
|
||||
t.Logf("Avatar agent configured: %s", teamConfig.AvatarAgent)
|
||||
} else {
|
||||
t.Log("Avatar agent not configured (optional field)")
|
||||
}
|
||||
|
||||
// Test roles configuration
|
||||
if teamConfig.Roles != nil {
|
||||
assert.IsType(t, []*user.TeamRole{}, teamConfig.Roles, "Roles should be slice of TeamRole pointers")
|
||||
|
|
@ -163,6 +176,19 @@ func TestTeamConfigAPI(t *testing.T) {
|
|||
// Verify team config structure
|
||||
assert.IsType(t, &user.TeamConfig{}, &teamConfig, "Should return correct team config type")
|
||||
|
||||
// Test uploader field (public field)
|
||||
if teamConfig.Uploader != "" {
|
||||
t.Logf("API returned uploader: %s", teamConfig.Uploader)
|
||||
assert.NotEmpty(t, teamConfig.Uploader, "Uploader should not be empty if set")
|
||||
}
|
||||
|
||||
// Test avatar_agent field (public field, optional)
|
||||
if teamConfig.AvatarAgent != "" {
|
||||
t.Logf("API returned avatar_agent: %s", teamConfig.AvatarAgent)
|
||||
} else {
|
||||
t.Log("API returned no avatar_agent (optional field)")
|
||||
}
|
||||
|
||||
// Test roles if present
|
||||
if teamConfig.Roles != nil {
|
||||
assert.IsType(t, []*user.TeamRole{}, teamConfig.Roles, "Roles should be slice of TeamRole pointers")
|
||||
|
|
|
|||
|
|
@ -344,10 +344,12 @@ func GetTeamConfigPublic(locale string) *TeamConfig {
|
|||
|
||||
// Create a deep copy of the config to avoid modifying the original
|
||||
publicConfig := &TeamConfig{
|
||||
Type: originalConfig.Type,
|
||||
Role: originalConfig.Role,
|
||||
Roles: originalConfig.Roles, // Shallow copy is OK for roles (read-only)
|
||||
Invite: originalConfig.Invite, // Shallow copy is OK for invite config (read-only)
|
||||
Type: originalConfig.Type,
|
||||
Role: originalConfig.Role,
|
||||
Roles: originalConfig.Roles, // Shallow copy is OK for roles (read-only)
|
||||
Invite: originalConfig.Invite, // Shallow copy is OK for invite config (read-only)
|
||||
Uploader: originalConfig.Uploader, // Public information
|
||||
AvatarAgent: originalConfig.AvatarAgent, // Public information
|
||||
}
|
||||
|
||||
// Handle robot config - create a copy without sensitive fields
|
||||
|
|
|
|||
|
|
@ -287,6 +287,9 @@ func GinMemberCreateRobot(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Add optional fields
|
||||
if req.Avatar != "" {
|
||||
baseData["avatar"] = req.Avatar
|
||||
}
|
||||
if req.Email != "" {
|
||||
baseData["email"] = req.Email // Optional: display-only email
|
||||
}
|
||||
|
|
@ -394,6 +397,9 @@ func GinMemberUpdateRobot(c *gin.Context) {
|
|||
if req.Name != "" {
|
||||
updateData["display_name"] = req.Name
|
||||
}
|
||||
if req.Avatar != "" {
|
||||
updateData["avatar"] = req.Avatar
|
||||
}
|
||||
if req.Email != "" {
|
||||
updateData["email"] = req.Email
|
||||
}
|
||||
|
|
|
|||
|
|
@ -445,6 +445,7 @@ type MemberDetailResponse struct {
|
|||
// CreateRobotMemberRequest represents the request to create a new robot member
|
||||
type CreateRobotMemberRequest struct {
|
||||
Name string `json:"name" binding:"required"` // Display name
|
||||
Avatar string `json:"avatar,omitempty"` // Avatar URL or file ID
|
||||
Email string `json:"email,omitempty"` // Email address (optional, for display only)
|
||||
RobotEmail string `json:"robot_email" binding:"required"` // Robot's globally unique email address (required)
|
||||
AuthorizedSenders []string `json:"authorized_senders,omitempty"` // Whitelist of emails authorized to send commands
|
||||
|
|
@ -463,6 +464,7 @@ type CreateRobotMemberRequest struct {
|
|||
// UpdateRobotMemberRequest represents the request to update a robot member
|
||||
type UpdateRobotMemberRequest struct {
|
||||
Name string `json:"name,omitempty"` // Display name
|
||||
Avatar string `json:"avatar,omitempty"` // Avatar URL or file ID
|
||||
Email string `json:"email,omitempty"` // Email address (optional, for display only)
|
||||
RobotEmail string `json:"robot_email,omitempty"` // Robot's globally unique email address
|
||||
AuthorizedSenders []string `json:"authorized_senders,omitempty"` // Whitelist of emails authorized to send commands
|
||||
|
|
@ -617,11 +619,13 @@ type RobotDefaults struct {
|
|||
|
||||
// TeamConfig represents the team configuration loaded from DSL files
|
||||
type TeamConfig struct {
|
||||
Roles []*TeamRole `json:"roles,omitempty"`
|
||||
Robot *RobotConfig `json:"robot,omitempty"`
|
||||
Invite *InviteConfig `json:"invite,omitempty"`
|
||||
Type string `json:"type,omitempty"` // Default subscription type for new teams
|
||||
Role string `json:"role,omitempty"` // Default user role for team creator
|
||||
Roles []*TeamRole `json:"roles,omitempty"`
|
||||
Robot *RobotConfig `json:"robot,omitempty"`
|
||||
Invite *InviteConfig `json:"invite,omitempty"`
|
||||
Type string `json:"type,omitempty"` // Default subscription type for new teams
|
||||
Role string `json:"role,omitempty"` // Default user role for team creator
|
||||
Uploader string `json:"uploader,omitempty"` // Uploader for avatar and attachments (default: __yao.attachment)
|
||||
AvatarAgent string `json:"avatar_agent,omitempty"` // Agent ID for avatar generation (optional)
|
||||
}
|
||||
|
||||
// TeamRole represents a team role configuration
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue