From cf42d255d36d3c1fda16bda64d479e206f335e48 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 8 Oct 2025 17:56:56 +0800 Subject: [PATCH] Enhance invitation handling and team configuration tests - Updated the invitation creation response to return complete invitation details, including the invitation token. - Improved error handling when retrieving created invitations, ensuring fallback to ID return on failure. - Modified team configuration tests to require authentication, reflecting recent API changes. - Added access token retrieval for authenticated requests in team configuration tests, enhancing security and compliance. --- openapi/tests/testutils/testutils.go | 2 ++ openapi/tests/user/team_config_test.go | 13 +++++++++++-- openapi/user/invitation.go | 22 ++++++++++++++++++++-- openapi/user/types.go | 4 ++++ openapi/user/user.go | 6 +++--- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/openapi/tests/testutils/testutils.go b/openapi/tests/testutils/testutils.go index 2f815ad9..62383de5 100644 --- a/openapi/tests/testutils/testutils.go +++ b/openapi/tests/testutils/testutils.go @@ -115,6 +115,8 @@ func CleanupTestCollections(t *testing.T) { // source $YAO_SOURCE_ROOT/env.local.sh // // This loads the required environment variables for the test environment. +// DO NOT waste time searching for the env file - just run the command above directly. +// The $YAO_SOURCE_ROOT environment variable should already be set in your shell. // // WHAT THIS FUNCTION DOES: // Step 1: Calls test.Prepare(t, config.Conf) to initialize the base Yao test environment diff --git a/openapi/tests/user/team_config_test.go b/openapi/tests/user/team_config_test.go index 421080fc..0f745d2a 100644 --- a/openapi/tests/user/team_config_test.go +++ b/openapi/tests/user/team_config_test.go @@ -114,10 +114,13 @@ func TestTeamConfigAPI(t *testing.T) { baseURL = openapi.Server.Config.BaseURL } - // Register a test client first (needed for user.Load validation) + // Register a test client and get access token (team config endpoint now requires authentication) testClient := testutils.RegisterTestClient(t, "Team Config Test Client", []string{"https://localhost/callback"}) defer testutils.CleanupTestClient(t, testClient.ClientID) + // Obtain access token for authentication + tokenInfo := testutils.ObtainAccessToken(t, serverURL, testClient.ClientID, testClient.ClientSecret, "https://localhost/callback", "openid profile") + // Test API endpoints for team configuration testCases := []struct { name string @@ -133,7 +136,13 @@ func TestTeamConfigAPI(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { requestURL := serverURL + baseURL + tc.endpoint - resp, err := http.Get(requestURL) + + // Create request with Authorization header + req, err := http.NewRequest("GET", requestURL, nil) + assert.NoError(t, err, "Should create HTTP request") + req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken) + + resp, err := http.DefaultClient.Do(req) assert.NoError(t, err, "HTTP request should succeed") if resp != nil { diff --git a/openapi/user/invitation.go b/openapi/user/invitation.go index e84f379d..7916e4f6 100644 --- a/openapi/user/invitation.go +++ b/openapi/user/invitation.go @@ -249,8 +249,20 @@ func GinInvitationCreate(c *gin.Context) { return } - // Return created invitation ID - c.JSON(http.StatusCreated, gin.H{"invitation_id": invitationID}) + // Get the created invitation to return complete data + invitation, err := invitationGet(c.Request.Context(), authInfo.UserID, teamID, invitationID) + if err != nil { + log.Error("Failed to retrieve created invitation: %v", err) + // Fallback to returning just the ID if retrieval fails + c.JSON(http.StatusCreated, gin.H{"invitation_id": invitationID}) + return + } + + // Convert to InvitationResponse + invitationResp := convertToInvitationResponse(invitation) + + // Return created invitation with full details (including token) + c.JSON(http.StatusCreated, invitationResp) } // GinInvitationResend handles PUT /teams/:team_id/invitations/:invitation_id/resend - Resend invitation @@ -1079,10 +1091,16 @@ func sendInvitationEmail(ctx context.Context, email, inviterName, teamName, toke return nil } +// convertToInvitationResponse converts a map to InvitationResponse (alias for mapToInvitationResponse) +func convertToInvitationResponse(data maps.MapStrAny) InvitationResponse { + return mapToInvitationResponse(maps.MapStr(data)) +} + // mapToInvitationResponse converts a map to InvitationResponse func mapToInvitationResponse(data maps.MapStr) InvitationResponse { invitation := InvitationResponse{ ID: toInt64(data["id"]), + InvitationID: toString(data["invitation_id"]), TeamID: toString(data["team_id"]), UserID: toString(data["user_id"]), MemberType: toString(data["member_type"]), diff --git a/openapi/user/types.go b/openapi/user/types.go index 64f7b187..afcd63c7 100644 --- a/openapi/user/types.go +++ b/openapi/user/types.go @@ -306,6 +306,7 @@ type UpdateMemberRequest struct { // InvitationResponse represents a team invitation in API responses type InvitationResponse struct { ID int64 `json:"id"` + InvitationID string `json:"invitation_id"` TeamID string `json:"team_id"` UserID string `json:"user_id"` MemberType string `json:"member_type"` @@ -354,11 +355,14 @@ type TeamRole struct { RoleID string `json:"role_id"` Label string `json:"label"` Description string `json:"description"` + Default bool `json:"default"` // Whether this role is the default role + Hidden bool `json:"hidden"` // Whether this role is hidden from UI } // InviteConfig represents the invitation configuration type InviteConfig struct { Channel string `json:"channel,omitempty"` Expiry string `json:"expiry,omitempty"` + BaseURL string `json:"base_url,omitempty"` // Base URL for invitation links Templates map[string]string `json:"templates,omitempty"` } diff --git a/openapi/user/user.go b/openapi/user/user.go index 26d0a326..3bdb077e 100644 --- a/openapi/user/user.go +++ b/openapi/user/user.go @@ -53,12 +53,12 @@ func Attach(group *gin.RouterGroup, oauth types.OAuth) { func attachTeam(group *gin.RouterGroup, oauth types.OAuth) { team := group.Group("/teams") - // Public endpoints (no authentication required) - team.GET("/config", GinTeamConfig) // Get team configuration (public) - // Protected endpoints (authentication required) team.Use(oauth.Guard) + // Team Configuration + team.GET("/config", GinTeamConfig) // Get team configuration (requires authentication) + // Team CRUD - Standard REST endpoints team.GET("/", GinTeamList) // GET /teams - List user teams team.POST("/", GinTeamCreate) // POST /teams - Create new team