Remove deprecated test files and refactor OAuth response handling
- Deleted obsolete test files for various OpenAPI components, including config_test.go, dsl_test.go, hello_test.go, oauth_test.go, oauth_token_test.go, and openapi_test.go, to streamline the codebase. - Refactored OAuth response handling by integrating response methods from the response package, ensuring consistent error and success responses across OAuth endpoints. - Enhanced error handling and response structure for improved clarity and maintainability, aligning with best practices for API responses.
This commit is contained in:
parent
40519e8429
commit
8d35e824ae
10 changed files with 524 additions and 338 deletions
|
|
@ -1,9 +1,11 @@
|
|||
package kb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/gou/graphrag/types"
|
||||
"github.com/yaoapp/yao/kb"
|
||||
)
|
||||
|
||||
|
|
@ -11,8 +13,44 @@ import (
|
|||
|
||||
// CreateCollection creates a new collection
|
||||
func CreateCollection(c *gin.Context) {
|
||||
// TODO: Implement create collection logic
|
||||
c.JSON(http.StatusCreated, gin.H{"message": "Collection created"})
|
||||
var req CreateCollectionRequest
|
||||
|
||||
// Parse and bind JSON request
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request format: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Validate request parameters
|
||||
if err := validateCreateCollectionRequest(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if kb.Instance is available
|
||||
if kb.Instance == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Knowledge base not initialized"})
|
||||
return
|
||||
}
|
||||
|
||||
// Create CollectionConfig
|
||||
collectionConfig := types.CollectionConfig{
|
||||
ID: req.ID,
|
||||
Metadata: req.Metadata,
|
||||
Config: req.Config,
|
||||
}
|
||||
|
||||
// Call the actual CreateCollection method
|
||||
collectionID, err := kb.Instance.CreateCollection(c.Request.Context(), collectionConfig)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create collection: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Collection created successfully",
|
||||
"collection_id": collectionID,
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveCollection removes an existing collection
|
||||
|
|
@ -36,3 +74,28 @@ func GetCollections(c *gin.Context) {
|
|||
}
|
||||
c.JSON(http.StatusOK, collections)
|
||||
}
|
||||
|
||||
// CreateCollectionRequest represents the request structure for creating a collection
|
||||
type CreateCollectionRequest struct {
|
||||
ID string `json:"id" binding:"required"`
|
||||
Metadata map[string]interface{} `json:"metadata"`
|
||||
Config *types.CreateCollectionOptions `json:"config" binding:"required"`
|
||||
}
|
||||
|
||||
// validateCreateCollectionRequest validates the incoming request for creating a collection
|
||||
func validateCreateCollectionRequest(req *CreateCollectionRequest) error {
|
||||
if req.ID == "" {
|
||||
return fmt.Errorf("id is required")
|
||||
}
|
||||
|
||||
if req.Config == nil {
|
||||
return fmt.Errorf("config is required")
|
||||
}
|
||||
|
||||
// Validate CreateCollectionOptions
|
||||
if err := req.Config.Validate(); err != nil {
|
||||
return fmt.Errorf("invalid config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
153
openapi/oauth.go
153
openapi/oauth.go
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/yao/openapi/oauth"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"github.com/yaoapp/yao/openapi/response"
|
||||
)
|
||||
|
||||
// OAuth handlers
|
||||
|
|
@ -73,7 +74,7 @@ func (openapi *OpenAPI) oauthAuthorize(c *gin.Context) {
|
|||
// Parse and validate authorization request
|
||||
authReq, parseErr := openapi.parseAuthorizationRequest(c)
|
||||
if parseErr != nil {
|
||||
openapi.respondWithAuthorizationError(c, authReq.RedirectURI, parseErr, authReq.State)
|
||||
response.RespondWithAuthorizationError(c, authReq.RedirectURI, parseErr, authReq.State)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -81,18 +82,18 @@ func (openapi *OpenAPI) oauthAuthorize(c *gin.Context) {
|
|||
authResp, err := openapi.OAuth.Authorize(c, authReq)
|
||||
if err != nil {
|
||||
// OAuth service returned an error
|
||||
openapi.respondWithAuthorizationError(c, authReq.RedirectURI, ErrServerError, authReq.State)
|
||||
response.RespondWithAuthorizationError(c, authReq.RedirectURI, response.ErrServerError, authReq.State)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if authorization response contains an error
|
||||
if authResp.Error != "" {
|
||||
// Convert OAuth service error to ErrorResponse
|
||||
oauthError := &ErrorResponse{
|
||||
oauthError := &response.ErrorResponse{
|
||||
Code: authResp.Error,
|
||||
ErrorDescription: authResp.ErrorDescription,
|
||||
}
|
||||
openapi.respondWithAuthorizationError(c, authReq.RedirectURI, oauthError, authReq.State)
|
||||
response.RespondWithAuthorizationError(c, authReq.RedirectURI, oauthError, authReq.State)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +115,7 @@ func (openapi *OpenAPI) oauthAuthorize(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Fallback: return JSON response if no redirect URI (should not happen with valid requests)
|
||||
openapi.respondWithSuccess(c, StatusOK, authResp)
|
||||
response.RespondWithSuccess(c, response.StatusOK, authResp)
|
||||
}
|
||||
|
||||
// oauthToken handles token requests - RFC 6749 Section 3.2
|
||||
|
|
@ -123,7 +124,7 @@ func (openapi *OpenAPI) oauthToken(c *gin.Context) {
|
|||
|
||||
// Validate grant type
|
||||
if grantType == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +142,7 @@ func (openapi *OpenAPI) oauthToken(c *gin.Context) {
|
|||
openapi.handleTokenExchangeGrant(c)
|
||||
|
||||
default:
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrUnsupportedGrantType)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrUnsupportedGrantType)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -150,20 +151,20 @@ func (openapi *OpenAPI) handleStandardTokenGrant(c *gin.Context, grantType strin
|
|||
// Extract client credentials from Basic Auth header or form parameters
|
||||
clientID, clientSecret := openapi.extractClientCredentials(c)
|
||||
if clientID == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidClient)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate client credentials using OAuth service
|
||||
oauthService, ok := openapi.OAuth.(*oauth.Service)
|
||||
if !ok {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidClient)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
|
||||
return
|
||||
}
|
||||
|
||||
clientInfo, err := oauthService.GetClientProvider().GetClientByCredentials(c, clientID, clientSecret)
|
||||
if err != nil {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidClient)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -179,13 +180,13 @@ func (openapi *OpenAPI) handleStandardTokenGrant(c *gin.Context, grantType strin
|
|||
|
||||
// Basic validation for authorization code grant
|
||||
if code == "" || redirectURI == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that client supports authorization code grant
|
||||
if !openapi.clientSupportsGrantType(clientInfo, types.GrantTypeAuthorizationCode) {
|
||||
openapi.respondWithSecureError(c, StatusUnauthorized, ErrUnauthorizedClient)
|
||||
response.RespondWithSecureError(c, response.StatusUnauthorized, response.ErrUnauthorizedClient)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -194,13 +195,13 @@ func (openapi *OpenAPI) handleStandardTokenGrant(c *gin.Context, grantType strin
|
|||
|
||||
// Basic validation for device code grant
|
||||
if code == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that client supports device code grant
|
||||
if !openapi.clientSupportsGrantType(clientInfo, types.GrantTypeDeviceCode) {
|
||||
openapi.respondWithSecureError(c, StatusUnauthorized, ErrUnauthorizedClient)
|
||||
response.RespondWithSecureError(c, response.StatusUnauthorized, response.ErrUnauthorizedClient)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +211,7 @@ func (openapi *OpenAPI) handleStandardTokenGrant(c *gin.Context, grantType strin
|
|||
|
||||
// Validate that client supports client credentials grant
|
||||
if !openapi.clientSupportsGrantType(clientInfo, types.GrantTypeClientCredentials) {
|
||||
openapi.respondWithSecureError(c, StatusUnauthorized, ErrUnauthorizedClient)
|
||||
response.RespondWithSecureError(c, response.StatusUnauthorized, response.ErrUnauthorizedClient)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -219,16 +220,16 @@ func (openapi *OpenAPI) handleStandardTokenGrant(c *gin.Context, grantType strin
|
|||
token, err := openapi.OAuth.Token(c, grantType, code, clientID, codeVerifier)
|
||||
if err != nil {
|
||||
// Convert OAuth service error to token error response with security headers
|
||||
if oauthErr, ok := err.(*ErrorResponse); ok {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, oauthErr)
|
||||
if oauthErr, ok := err.(*response.ErrorResponse); ok {
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, oauthErr)
|
||||
} else {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidGrant)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidGrant)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Return successful token response with OAuth security headers (RFC 6749 Section 5.1: MUST set Cache-Control: no-store)
|
||||
openapi.respondWithSecureSuccess(c, StatusOK, token)
|
||||
response.RespondWithSecureSuccess(c, response.StatusOK, token)
|
||||
}
|
||||
|
||||
// handleRefreshTokenGrant handles refresh token requests - RFC 6749 Section 6
|
||||
|
|
@ -236,26 +237,26 @@ func (openapi *OpenAPI) handleRefreshTokenGrant(c *gin.Context) {
|
|||
// Extract client credentials from Basic Auth header or form parameters
|
||||
clientID, clientSecret := openapi.extractClientCredentials(c)
|
||||
if clientID == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidClient)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate client credentials using OAuth service
|
||||
oauthService, ok := openapi.OAuth.(*oauth.Service)
|
||||
if !ok {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidClient)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
|
||||
return
|
||||
}
|
||||
|
||||
clientInfo, err := oauthService.GetClientProvider().GetClientByCredentials(c, clientID, clientSecret)
|
||||
if err != nil {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidClient)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClient)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that client supports refresh token grant
|
||||
if !openapi.clientSupportsGrantType(clientInfo, types.GrantTypeRefreshToken) {
|
||||
openapi.respondWithSecureError(c, StatusUnauthorized, ErrUnauthorizedClient)
|
||||
response.RespondWithSecureError(c, response.StatusUnauthorized, response.ErrUnauthorizedClient)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -264,7 +265,7 @@ func (openapi *OpenAPI) handleRefreshTokenGrant(c *gin.Context) {
|
|||
|
||||
// Basic validation
|
||||
if refreshToken == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -277,16 +278,16 @@ func (openapi *OpenAPI) handleRefreshTokenGrant(c *gin.Context) {
|
|||
}
|
||||
if err != nil {
|
||||
// Convert OAuth service error to token error response with security headers
|
||||
if oauthErr, ok := err.(*ErrorResponse); ok {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, oauthErr)
|
||||
if oauthErr, ok := err.(*response.ErrorResponse); ok {
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, oauthErr)
|
||||
} else {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidGrant)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidGrant)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Return successful refresh token response with security headers
|
||||
openapi.respondWithSecureSuccess(c, StatusOK, refreshResponse)
|
||||
response.RespondWithSecureSuccess(c, response.StatusOK, refreshResponse)
|
||||
}
|
||||
|
||||
// handleTokenExchangeGrant handles token exchange requests - RFC 8693
|
||||
|
|
@ -298,7 +299,7 @@ func (openapi *OpenAPI) handleTokenExchangeGrant(c *gin.Context) {
|
|||
|
||||
// Basic validation
|
||||
if subjectToken == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -306,16 +307,16 @@ func (openapi *OpenAPI) handleTokenExchangeGrant(c *gin.Context) {
|
|||
exchangeResponse, err := openapi.OAuth.TokenExchange(c, subjectToken, subjectTokenType, audience, scope)
|
||||
if err != nil {
|
||||
// Convert OAuth service error to token error response with security headers
|
||||
if oauthErr, ok := err.(*ErrorResponse); ok {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, oauthErr)
|
||||
if oauthErr, ok := err.(*response.ErrorResponse); ok {
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, oauthErr)
|
||||
} else {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidGrant)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidGrant)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Return successful token exchange response with security headers
|
||||
openapi.respondWithSecureSuccess(c, StatusOK, exchangeResponse)
|
||||
response.RespondWithSecureSuccess(c, response.StatusOK, exchangeResponse)
|
||||
}
|
||||
|
||||
// oauthRevoke handles token revocation - RFC 7009
|
||||
|
|
@ -324,7 +325,7 @@ func (openapi *OpenAPI) oauthRevoke(c *gin.Context) {
|
|||
tokenTypeHint := c.PostForm("token_type_hint") // Optional hint about token type
|
||||
|
||||
if token == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -333,14 +334,14 @@ func (openapi *OpenAPI) oauthRevoke(c *gin.Context) {
|
|||
if err != nil {
|
||||
// OAuth spec requires returning 200 even for invalid tokens to prevent information leakage
|
||||
// Only return error for server errors
|
||||
if oauthErr, ok := err.(*ErrorResponse); ok && oauthErr.Code == ErrServerError.Code {
|
||||
openapi.respondWithError(c, StatusInternalServerError, ErrServerError)
|
||||
if oauthErr, ok := err.(*response.ErrorResponse); ok && oauthErr.Code == response.ErrServerError.Code {
|
||||
response.RespondWithError(c, response.StatusInternalServerError, response.ErrServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// RFC 7009: Return 200 OK for successful revocation (or invalid tokens)
|
||||
c.Status(StatusOK)
|
||||
c.Status(response.StatusOK)
|
||||
}
|
||||
|
||||
// oauthIntrospect handles token introspection - RFC 7662
|
||||
|
|
@ -348,7 +349,7 @@ func (openapi *OpenAPI) oauthIntrospect(c *gin.Context) {
|
|||
token := c.PostForm("token")
|
||||
|
||||
if token == "" {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -356,15 +357,15 @@ func (openapi *OpenAPI) oauthIntrospect(c *gin.Context) {
|
|||
introspectionResult, err := openapi.OAuth.Introspect(c, token)
|
||||
if err != nil {
|
||||
// Return inactive token response on error (RFC 7662) with security headers
|
||||
response := &TokenIntrospectionResponse{
|
||||
tokenResponse := &response.TokenIntrospectionResponse{
|
||||
Active: false,
|
||||
}
|
||||
openapi.respondWithSecureSuccess(c, StatusOK, response)
|
||||
response.RespondWithSecureSuccess(c, response.StatusOK, tokenResponse)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert OAuth service response to API response format
|
||||
response := &TokenIntrospectionResponse{
|
||||
tokenResponse := &response.TokenIntrospectionResponse{
|
||||
Active: introspectionResult.Active,
|
||||
Scope: introspectionResult.Scope,
|
||||
ClientID: introspectionResult.ClientID,
|
||||
|
|
@ -376,19 +377,19 @@ func (openapi *OpenAPI) oauthIntrospect(c *gin.Context) {
|
|||
Audience: introspectionResult.Audience,
|
||||
}
|
||||
|
||||
openapi.respondWithSecureSuccess(c, StatusOK, response)
|
||||
response.RespondWithSecureSuccess(c, response.StatusOK, tokenResponse)
|
||||
}
|
||||
|
||||
// oauthJWKS returns JSON Web Key Set - RFC 7517
|
||||
func (openapi *OpenAPI) oauthJWKS(c *gin.Context) {
|
||||
jwks, err := openapi.OAuth.JWKS(c)
|
||||
if err != nil {
|
||||
openapi.respondWithError(c, StatusInternalServerError, ErrServerError)
|
||||
response.RespondWithError(c, response.StatusInternalServerError, response.ErrServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// RFC 7517 compliance: Return JWKS directly with security headers
|
||||
openapi.respondWithSecureSuccess(c, StatusOK, jwks)
|
||||
response.RespondWithSecureSuccess(c, response.StatusOK, jwks)
|
||||
}
|
||||
|
||||
// oauthUserInfo returns user information - OpenID Connect Core 1.0
|
||||
|
|
@ -396,39 +397,39 @@ func (openapi *OpenAPI) oauthUserInfo(c *gin.Context) {
|
|||
// Check for Bearer token in Authorization header
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" || len(authHeader) < 7 || authHeader[:7] != "Bearer " {
|
||||
openapi.respondWithError(c, StatusUnauthorized, ErrInvalidToken)
|
||||
response.RespondWithError(c, response.StatusUnauthorized, response.ErrInvalidToken)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement user info retrieval
|
||||
openapi.respondWithError(c, StatusNotImplemented, ErrServerError)
|
||||
response.RespondWithError(c, response.StatusNotImplemented, response.ErrServerError)
|
||||
}
|
||||
|
||||
// OAuth Extended Endpoints Implementation
|
||||
|
||||
// oauthRegister handles dynamic client registration - RFC 7591
|
||||
func (openapi *OpenAPI) oauthRegister(c *gin.Context) {
|
||||
var req DynamicClientRegistrationRequest
|
||||
var req response.DynamicClientRegistrationRequest
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidClientMetadata)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClientMetadata)
|
||||
return
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
if len(req.RedirectURIs) == 0 {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrMissingRedirectURI)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrMissingRedirectURI)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := openapi.OAuth.DynamicClientRegistration(c, &req)
|
||||
if err != nil {
|
||||
openapi.respondWithSecureError(c, StatusBadRequest, ErrInvalidClientMetadata)
|
||||
response.RespondWithSecureError(c, response.StatusBadRequest, response.ErrInvalidClientMetadata)
|
||||
return
|
||||
}
|
||||
|
||||
// Return the authorization response with security headers (RFC 7591 compliant, contains client credentials)
|
||||
openapi.respondWithSecureSuccess(c, StatusCreated, res)
|
||||
response.RespondWithSecureSuccess(c, response.StatusCreated, res)
|
||||
}
|
||||
|
||||
// extractClientCredentials extracts client ID and secret from Basic Auth header or form parameters
|
||||
|
|
@ -479,12 +480,12 @@ func (openapi *OpenAPI) oauthGetClient(c *gin.Context) {
|
|||
clientID := c.Param("client_id")
|
||||
|
||||
if clientID == "" {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement client retrieval logic
|
||||
openapi.respondWithError(c, StatusNotFound, ErrInvalidClient)
|
||||
response.RespondWithError(c, response.StatusNotFound, response.ErrInvalidClient)
|
||||
}
|
||||
|
||||
// oauthUpdateClient updates client configuration - RFC 7592
|
||||
|
|
@ -492,18 +493,18 @@ func (openapi *OpenAPI) oauthUpdateClient(c *gin.Context) {
|
|||
clientID := c.Param("client_id")
|
||||
|
||||
if clientID == "" {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req DynamicClientRegistrationRequest
|
||||
var req response.DynamicClientRegistrationRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrInvalidClientMetadata)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidClientMetadata)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement client update logic
|
||||
openapi.respondWithError(c, StatusNotImplemented, ErrServerError)
|
||||
response.RespondWithError(c, response.StatusNotImplemented, response.ErrServerError)
|
||||
}
|
||||
|
||||
// oauthDeleteClient deletes client configuration - RFC 7592
|
||||
|
|
@ -511,12 +512,12 @@ func (openapi *OpenAPI) oauthDeleteClient(c *gin.Context) {
|
|||
clientID := c.Param("client_id")
|
||||
|
||||
if clientID == "" {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement client deletion logic
|
||||
c.Status(StatusNoContent)
|
||||
c.Status(response.StatusNoContent)
|
||||
}
|
||||
|
||||
// oauthDeviceAuthorization handles device authorization - RFC 8628
|
||||
|
|
@ -524,12 +525,12 @@ func (openapi *OpenAPI) oauthDeviceAuthorization(c *gin.Context) {
|
|||
clientID := c.PostForm("client_id")
|
||||
|
||||
if clientID == "" {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement device authorization logic
|
||||
response := &DeviceAuthorizationResponse{
|
||||
deviceResponse := &response.DeviceAuthorizationResponse{
|
||||
DeviceCode: "generated-device-code",
|
||||
UserCode: "USER-CODE",
|
||||
VerificationURI: "https://example.com/device",
|
||||
|
|
@ -537,31 +538,31 @@ func (openapi *OpenAPI) oauthDeviceAuthorization(c *gin.Context) {
|
|||
Interval: 5, // 5 seconds
|
||||
}
|
||||
|
||||
openapi.respondWithSuccess(c, StatusOK, response)
|
||||
response.RespondWithSuccess(c, response.StatusOK, deviceResponse)
|
||||
}
|
||||
|
||||
// oauthPushedAuthorizationRequest handles PAR - RFC 9126
|
||||
func (openapi *OpenAPI) oauthPushedAuthorizationRequest(c *gin.Context) {
|
||||
var req PushedAuthorizationRequest
|
||||
var req response.PushedAuthorizationRequest
|
||||
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Basic validation
|
||||
if req.ClientID == "" {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement PAR logic
|
||||
response := &PushedAuthorizationResponse{
|
||||
parResponse := &response.PushedAuthorizationResponse{
|
||||
RequestURI: "urn:example:bwc4JK-ESC0w8acc191e-Y1LTC2",
|
||||
ExpiresIn: 60, // 60 seconds
|
||||
}
|
||||
|
||||
openapi.respondWithSuccess(c, StatusCreated, response)
|
||||
response.RespondWithSuccess(c, response.StatusCreated, parResponse)
|
||||
}
|
||||
|
||||
// oauthTokenExchange handles token exchange - RFC 8693
|
||||
|
|
@ -569,29 +570,29 @@ func (openapi *OpenAPI) oauthTokenExchange(c *gin.Context) {
|
|||
grantType := c.PostForm("grant_type")
|
||||
|
||||
if grantType != types.GrantTypeTokenExchange {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrUnsupportedGrantType)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrUnsupportedGrantType)
|
||||
return
|
||||
}
|
||||
|
||||
subjectToken := c.PostForm("subject_token")
|
||||
if subjectToken == "" {
|
||||
openapi.respondWithError(c, StatusBadRequest, ErrInvalidRequest)
|
||||
response.RespondWithError(c, response.StatusBadRequest, response.ErrInvalidRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement token exchange logic
|
||||
response := &TokenExchangeResponse{
|
||||
exchangeResponse := &response.TokenExchangeResponse{
|
||||
AccessToken: "exchanged-access-token",
|
||||
IssuedTokenType: "urn:ietf:params:oauth:token-type:access_token",
|
||||
TokenType: types.TokenTypeBearer,
|
||||
ExpiresIn: 3600, // 1 hour
|
||||
}
|
||||
|
||||
openapi.respondWithSuccess(c, StatusOK, response)
|
||||
response.RespondWithSuccess(c, response.StatusOK, exchangeResponse)
|
||||
}
|
||||
|
||||
// parseAuthorizationRequest parses and validates authorization request parameters
|
||||
func (openapi *OpenAPI) parseAuthorizationRequest(c *gin.Context) (*types.AuthorizationRequest, *ErrorResponse) {
|
||||
func (openapi *OpenAPI) parseAuthorizationRequest(c *gin.Context) (*types.AuthorizationRequest, *response.ErrorResponse) {
|
||||
// Parse authorization request parameters from both GET (query) and POST (form) methods
|
||||
authReq := &types.AuthorizationRequest{
|
||||
ClientID: openapi.getParam(c, "client_id"),
|
||||
|
|
@ -607,12 +608,12 @@ func (openapi *OpenAPI) parseAuthorizationRequest(c *gin.Context) (*types.Author
|
|||
|
||||
// Basic validation
|
||||
if authReq.ClientID == "" {
|
||||
return authReq, ErrInvalidRequest
|
||||
return authReq, response.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Validate response_type parameter - RFC 6749 Section 3.1.1
|
||||
if authReq.ResponseType == "" {
|
||||
return authReq, ErrInvalidRequest
|
||||
return authReq, response.ErrInvalidRequest
|
||||
}
|
||||
|
||||
// Check supported response types
|
||||
|
|
@ -621,9 +622,9 @@ func (openapi *OpenAPI) parseAuthorizationRequest(c *gin.Context) (*types.Author
|
|||
// Authorization code flow - supported
|
||||
case types.ResponseTypeToken:
|
||||
// Implicit flow - deprecated in OAuth 2.1, return error
|
||||
return authReq, ErrUnsupportedResponseType
|
||||
return authReq, response.ErrUnsupportedResponseType
|
||||
default:
|
||||
return authReq, ErrUnsupportedResponseType
|
||||
return authReq, response.ErrUnsupportedResponseType
|
||||
}
|
||||
|
||||
return authReq, nil
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package openapi
|
||||
package response
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
|
@ -147,7 +147,7 @@ const (
|
|||
|
||||
// setOAuthSecurityHeaders sets standard OAuth 2.0/2.1 security headers
|
||||
// These headers are required by OAuth 2.1 specification for enhanced security
|
||||
func (openapi *OpenAPI) setOAuthSecurityHeaders(c *gin.Context) {
|
||||
func SetOAuthSecurityHeaders(c *gin.Context) {
|
||||
c.Header("Cache-Control", "no-store")
|
||||
c.Header("Pragma", "no-cache")
|
||||
c.Header("X-Content-Type-Options", "nosniff")
|
||||
|
|
@ -156,30 +156,30 @@ func (openapi *OpenAPI) setOAuthSecurityHeaders(c *gin.Context) {
|
|||
}
|
||||
|
||||
// setJSONContentType sets JSON content type header for OAuth responses
|
||||
func (openapi *OpenAPI) setJSONContentType(c *gin.Context) {
|
||||
func SetJSONContentType(c *gin.Context) {
|
||||
c.Header("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
// respondWithSuccess sends a successful response (no wrapper, direct data)
|
||||
func (openapi *OpenAPI) respondWithSuccess(c *gin.Context, statusCode int, data interface{}) {
|
||||
openapi.setJSONContentType(c)
|
||||
// RespondWithSuccess sends a successful response (no wrapper, direct data)
|
||||
func RespondWithSuccess(c *gin.Context, statusCode int, data interface{}) {
|
||||
SetJSONContentType(c)
|
||||
c.JSON(statusCode, data)
|
||||
}
|
||||
|
||||
// respondWithError sends an error response (no wrapper, direct error)
|
||||
func (openapi *OpenAPI) respondWithError(c *gin.Context, statusCode int, err *ErrorResponse) {
|
||||
openapi.setJSONContentType(c)
|
||||
// RespondWithError sends an error response (no wrapper, direct error)
|
||||
func RespondWithError(c *gin.Context, statusCode int, err *ErrorResponse) {
|
||||
SetJSONContentType(c)
|
||||
|
||||
// Add WWW-Authenticate header for 401 responses
|
||||
if statusCode == StatusUnauthorized {
|
||||
openapi.addWWWAuthenticateHeader(c, err)
|
||||
AddWWWAuthenticateHeader(c, err)
|
||||
}
|
||||
|
||||
c.JSON(statusCode, err)
|
||||
}
|
||||
|
||||
// respondWithAuthorizationError sends an authorization endpoint error via redirect
|
||||
func (openapi *OpenAPI) respondWithAuthorizationError(c *gin.Context, redirectURI string, err *ErrorResponse, state string) {
|
||||
// RespondWithAuthorizationError sends an authorization endpoint error via redirect
|
||||
func RespondWithAuthorizationError(c *gin.Context, redirectURI string, err *ErrorResponse, state string) {
|
||||
// Build error redirect URL
|
||||
redirectURL := redirectURI
|
||||
if redirectURL != "" {
|
||||
|
|
@ -204,11 +204,11 @@ func (openapi *OpenAPI) respondWithAuthorizationError(c *gin.Context, redirectUR
|
|||
}
|
||||
|
||||
// Fallback to JSON error response if no redirect URI
|
||||
openapi.respondWithError(c, StatusBadRequest, err)
|
||||
RespondWithError(c, StatusBadRequest, err)
|
||||
}
|
||||
|
||||
// addWWWAuthenticateHeader adds appropriate WWW-Authenticate header
|
||||
func (openapi *OpenAPI) addWWWAuthenticateHeader(c *gin.Context, err *ErrorResponse) {
|
||||
// AddWWWAuthenticateHeader adds appropriate WWW-Authenticate header
|
||||
func AddWWWAuthenticateHeader(c *gin.Context, err *ErrorResponse) {
|
||||
challenge := &WWWAuthenticateChallenge{
|
||||
Scheme: types.WWWAuthenticateSchemeBearer,
|
||||
Realm: "OAuth",
|
||||
|
|
@ -238,21 +238,21 @@ func (openapi *OpenAPI) addWWWAuthenticateHeader(c *gin.Context, err *ErrorRespo
|
|||
c.Header("WWW-Authenticate", headerValue)
|
||||
}
|
||||
|
||||
// respondWithSecureSuccess sends a successful response with OAuth security headers (for sensitive endpoints)
|
||||
func (openapi *OpenAPI) respondWithSecureSuccess(c *gin.Context, statusCode int, data interface{}) {
|
||||
openapi.setOAuthSecurityHeaders(c)
|
||||
openapi.setJSONContentType(c)
|
||||
// RespondWithSecureSuccess sends a successful response with OAuth security headers (for sensitive endpoints)
|
||||
func RespondWithSecureSuccess(c *gin.Context, statusCode int, data interface{}) {
|
||||
SetOAuthSecurityHeaders(c)
|
||||
SetJSONContentType(c)
|
||||
c.JSON(statusCode, data)
|
||||
}
|
||||
|
||||
// respondWithSecureError sends an error response with OAuth security headers (for sensitive endpoints)
|
||||
func (openapi *OpenAPI) respondWithSecureError(c *gin.Context, statusCode int, err *ErrorResponse) {
|
||||
openapi.setOAuthSecurityHeaders(c)
|
||||
openapi.setJSONContentType(c)
|
||||
// RespondWithSecureError sends an error response with OAuth security headers (for sensitive endpoints)
|
||||
func RespondWithSecureError(c *gin.Context, statusCode int, err *ErrorResponse) {
|
||||
SetOAuthSecurityHeaders(c)
|
||||
SetJSONContentType(c)
|
||||
|
||||
// Add WWW-Authenticate header for 401 responses
|
||||
if statusCode == StatusUnauthorized {
|
||||
openapi.addWWWAuthenticateHeader(c, err)
|
||||
AddWWWAuthenticateHeader(c, err)
|
||||
}
|
||||
|
||||
c.JSON(statusCode, err)
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
package openapi
|
||||
package openapi_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/openapi"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
)
|
||||
|
||||
|
|
@ -37,7 +40,7 @@ func TestConfigUnmarshalJSON_TimeParsingCorrect(t *testing.T) {
|
|||
}
|
||||
}`
|
||||
|
||||
var config Config
|
||||
var config openapi.Config
|
||||
err := jsoniter.Unmarshal([]byte(jsonData), &config)
|
||||
assert.NoError(t, err, "JSON unmarshaling should succeed")
|
||||
|
||||
|
|
@ -111,11 +114,11 @@ func TestFormatDuration(t *testing.T) {
|
|||
|
||||
func TestConfigMarshalUnmarshalRoundTrip(t *testing.T) {
|
||||
// Create a config with duration fields
|
||||
originalConfig := &Config{
|
||||
originalConfig := &openapi.Config{
|
||||
BaseURL: "/v1",
|
||||
Store: "__yao.oauth.store",
|
||||
Cache: "__yao.oauth.cache",
|
||||
OAuth: &OAuth{
|
||||
OAuth: &openapi.OAuth{
|
||||
IssuerURL: "https://localhost:5099",
|
||||
Signing: types.SigningConfig{
|
||||
SigningCertPath: "/path/to/cert.pem",
|
||||
|
|
@ -145,7 +148,7 @@ func TestConfigMarshalUnmarshalRoundTrip(t *testing.T) {
|
|||
assert.NoError(t, err, "Marshal should succeed")
|
||||
|
||||
// Unmarshal back to config
|
||||
var unmarshaledConfig Config
|
||||
var unmarshaledConfig openapi.Config
|
||||
err = jsoniter.Unmarshal(jsonData, &unmarshaledConfig)
|
||||
assert.NoError(t, err, "Unmarshal should succeed")
|
||||
|
||||
|
|
@ -178,11 +181,11 @@ func TestConfigMarshalUnmarshalRoundTrip(t *testing.T) {
|
|||
|
||||
// TestConfigJSONOutputDemo demonstrates the human-readable JSON output format
|
||||
func TestConfigJSONOutputDemo(t *testing.T) {
|
||||
config := &Config{
|
||||
config := &openapi.Config{
|
||||
BaseURL: "/v1",
|
||||
Store: "__yao.oauth.store",
|
||||
Cache: "__yao.oauth.cache",
|
||||
OAuth: &OAuth{
|
||||
OAuth: &openapi.OAuth{
|
||||
IssuerURL: "https://localhost:5099",
|
||||
Signing: types.SigningConfig{
|
||||
SigningCertPath: "openapi/certs/signing-cert.pem",
|
||||
|
|
@ -350,3 +353,55 @@ func TestCertificatePathConversion(t *testing.T) {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
// parseDuration parses a time duration string (e.g., "24h", "1h", "10m") into time.Duration
|
||||
func parseDuration(durationStr string) (time.Duration, error) {
|
||||
if durationStr == "" || durationStr == "0" || durationStr == "0s" {
|
||||
return 0, nil
|
||||
}
|
||||
return time.ParseDuration(durationStr)
|
||||
}
|
||||
|
||||
// formatDuration converts time.Duration to human-readable string format
|
||||
func formatDuration(duration time.Duration) string {
|
||||
if duration == 0 {
|
||||
return "0s"
|
||||
}
|
||||
return duration.String()
|
||||
}
|
||||
|
||||
// convertRelativeToAbsolutePath converts relative certificate path to absolute path
|
||||
func convertRelativeToAbsolutePath(relativePath, rootPath string) string {
|
||||
if relativePath == "" {
|
||||
return ""
|
||||
}
|
||||
// If already absolute path, return as is
|
||||
if filepath.IsAbs(relativePath) {
|
||||
return relativePath
|
||||
}
|
||||
// Convert relative path to absolute: Root + "openapi" + "certs" + relativePath
|
||||
return filepath.Join(rootPath, "openapi", "certs", relativePath)
|
||||
}
|
||||
|
||||
// convertAbsoluteToRelativePath converts absolute certificate path to relative path
|
||||
func convertAbsoluteToRelativePath(absolutePath, rootPath string) string {
|
||||
if absolutePath == "" {
|
||||
return ""
|
||||
}
|
||||
// If not absolute path, return as is
|
||||
if !filepath.IsAbs(absolutePath) {
|
||||
return absolutePath
|
||||
}
|
||||
|
||||
// Remove Root + "openapi" + "certs" prefix
|
||||
certBasePath := filepath.Join(rootPath, "openapi", "certs")
|
||||
if strings.HasPrefix(absolutePath, certBasePath) {
|
||||
relativePath := strings.TrimPrefix(absolutePath, certBasePath)
|
||||
// Remove leading separator
|
||||
relativePath = strings.TrimPrefix(relativePath, string(filepath.Separator))
|
||||
return relativePath
|
||||
}
|
||||
|
||||
// If path doesn't match expected pattern, return as is
|
||||
return absolutePath
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package openapi
|
||||
package openapi_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -10,23 +10,25 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/dsl/types"
|
||||
"github.com/yaoapp/yao/openapi"
|
||||
"github.com/yaoapp/yao/openapi/tests/testutils"
|
||||
)
|
||||
|
||||
// TestDSLCreate tests the DSL creation endpoint
|
||||
func TestDSLCreate(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
// Register test client and get token
|
||||
client := RegisterTestClient(t, "DSL Create Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
client := testutils.RegisterTestClient(t, "DSL Create Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
// Generate unique test ID
|
||||
testID := fmt.Sprintf("test_model_%d", time.Now().UnixNano())
|
||||
|
|
@ -51,7 +53,7 @@ func TestDSLCreate(t *testing.T) {
|
|||
|
||||
for _, store := range stores {
|
||||
t.Run(fmt.Sprintf("CreateModel_%s", store), func(t *testing.T) {
|
||||
// Prepare request body
|
||||
// testutils.Prepare request body
|
||||
createData := map[string]interface{}{
|
||||
"id": testID + "_" + store,
|
||||
"source": modelSource,
|
||||
|
|
@ -89,17 +91,17 @@ func TestDSLCreate(t *testing.T) {
|
|||
|
||||
// TestDSLInspect tests the DSL inspection endpoint
|
||||
func TestDSLInspect(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
client := RegisterTestClient(t, "DSL Inspect Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
client := testutils.RegisterTestClient(t, "DSL Inspect Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
testID := fmt.Sprintf("test_inspect_%d", time.Now().UnixNano())
|
||||
modelSource := fmt.Sprintf(`{
|
||||
|
|
@ -162,17 +164,17 @@ func TestDSLInspect(t *testing.T) {
|
|||
|
||||
// TestDSLSource tests the DSL source retrieval endpoint
|
||||
func TestDSLSource(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
client := RegisterTestClient(t, "DSL Source Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
client := testutils.RegisterTestClient(t, "DSL Source Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
testID := fmt.Sprintf("test_source_%d", time.Now().UnixNano())
|
||||
modelSource := fmt.Sprintf(`{
|
||||
|
|
@ -230,17 +232,17 @@ func TestDSLSource(t *testing.T) {
|
|||
|
||||
// TestDSLList tests the DSL listing endpoint
|
||||
func TestDSLList(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
client := RegisterTestClient(t, "DSL List Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
client := testutils.RegisterTestClient(t, "DSL List Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
// Create multiple test models
|
||||
testTag := fmt.Sprintf("test_list_%d", time.Now().UnixNano())
|
||||
|
|
@ -317,17 +319,17 @@ func TestDSLList(t *testing.T) {
|
|||
|
||||
// TestDSLUpdate tests the DSL update endpoint
|
||||
func TestDSLUpdate(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
client := RegisterTestClient(t, "DSL Update Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
client := testutils.RegisterTestClient(t, "DSL Update Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
testID := fmt.Sprintf("test_update_%d", time.Now().UnixNano())
|
||||
|
||||
|
|
@ -424,17 +426,17 @@ func TestDSLUpdate(t *testing.T) {
|
|||
|
||||
// TestDSLExists tests the DSL existence check endpoint
|
||||
func TestDSLExists(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
client := RegisterTestClient(t, "DSL Exists Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
client := testutils.RegisterTestClient(t, "DSL Exists Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
testID := fmt.Sprintf("test_exists_%d", time.Now().UnixNano())
|
||||
nonExistentID := fmt.Sprintf("non_existent_%d", time.Now().UnixNano())
|
||||
|
|
@ -500,17 +502,17 @@ func TestDSLExists(t *testing.T) {
|
|||
|
||||
// TestDSLDelete tests the DSL deletion endpoint
|
||||
func TestDSLDelete(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
client := RegisterTestClient(t, "DSL Delete Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
client := testutils.RegisterTestClient(t, "DSL Delete Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
testID := fmt.Sprintf("test_delete_%d", time.Now().UnixNano())
|
||||
|
||||
|
|
@ -576,17 +578,17 @@ func TestDSLDelete(t *testing.T) {
|
|||
|
||||
// TestDSLValidate tests the DSL validation endpoint
|
||||
func TestDSLValidate(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
client := RegisterTestClient(t, "DSL Validate Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
client := testutils.RegisterTestClient(t, "DSL Validate Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -662,12 +664,12 @@ func TestDSLValidate(t *testing.T) {
|
|||
|
||||
// TestDSLUnauthorized tests that endpoints return 401 when not authenticated
|
||||
func TestDSLUnauthorized(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
endpoints := []struct {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package openapi
|
||||
package openapi_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
|
@ -6,17 +6,19 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/openapi"
|
||||
"github.com/yaoapp/yao/openapi/tests/testutils"
|
||||
"github.com/yaoapp/yao/share"
|
||||
)
|
||||
|
||||
func TestHelloWorldPublic(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
|
@ -79,21 +81,21 @@ func TestHelloWorldPublic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHelloWorldProtected(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
// Register a test client for authentication
|
||||
client := RegisterTestClient(t, "Hello World Protected Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
client := testutils.RegisterTestClient(t, "Hello World Protected Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
|
||||
// Obtain access token for authentication
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -164,13 +166,13 @@ func TestHelloWorldProtected(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHelloWorldProtectedUnauthorized(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package openapi
|
||||
package openapi_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -9,32 +9,34 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/openapi"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"github.com/yaoapp/yao/openapi/tests/testutils"
|
||||
)
|
||||
|
||||
func TestOAuthRegister(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Debug: Check if Server is properly initialized
|
||||
if Server == nil {
|
||||
t.Fatal("OpenAPI Server is nil")
|
||||
// Debug: Check if openapi.Server is properly initialized
|
||||
if openapi.Server == nil {
|
||||
t.Fatal("OpenAPI openapi.Server is nil")
|
||||
}
|
||||
|
||||
if Server.Config == nil {
|
||||
t.Fatal("OpenAPI Server.Config is nil")
|
||||
if openapi.Server.Config == nil {
|
||||
t.Fatal("OpenAPI openapi.Server.Config is nil")
|
||||
}
|
||||
|
||||
if Server.OAuth == nil {
|
||||
t.Fatal("OpenAPI Server.OAuth is nil")
|
||||
if openapi.Server.OAuth == nil {
|
||||
t.Fatal("OpenAPI openapi.Server.OAuth is nil")
|
||||
}
|
||||
|
||||
t.Logf("Server initialized with BaseURL: %s", Server.Config.BaseURL)
|
||||
t.Logf("openapi.Server initialized with BaseURL: %s", openapi.Server.Config.BaseURL)
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
endpoint := serverURL + baseURL + "/oauth/register"
|
||||
|
|
@ -108,24 +110,24 @@ func TestOAuthRegister(t *testing.T) {
|
|||
assert.Equal(t, req.RedirectURIs, response.DynamicClientRegistrationRequest.RedirectURIs)
|
||||
|
||||
// Verify that default values were applied when not specified in request
|
||||
assert.NotEmpty(t, response.DynamicClientRegistrationRequest.GrantTypes, "Server should apply default grant types")
|
||||
assert.NotEmpty(t, response.DynamicClientRegistrationRequest.ResponseTypes, "Server should apply default response types")
|
||||
assert.Equal(t, "web", response.DynamicClientRegistrationRequest.ApplicationType, "Server should apply default application type")
|
||||
assert.Equal(t, "client_secret_basic", response.DynamicClientRegistrationRequest.TokenEndpointAuthMethod, "Server should apply default auth method")
|
||||
assert.NotEmpty(t, response.DynamicClientRegistrationRequest.GrantTypes, "openapi.Server should apply default grant types")
|
||||
assert.NotEmpty(t, response.DynamicClientRegistrationRequest.ResponseTypes, "openapi.Server should apply default response types")
|
||||
assert.Equal(t, "web", response.DynamicClientRegistrationRequest.ApplicationType, "openapi.Server should apply default application type")
|
||||
assert.Equal(t, "client_secret_basic", response.DynamicClientRegistrationRequest.TokenEndpointAuthMethod, "openapi.Server should apply default auth method")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestOAuthAuthorize(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Register a test client for realistic testing
|
||||
testClient := RegisterTestClient(t, "OAuth Test Client", []string{"http://localhost/callback"})
|
||||
defer CleanupTestClient(t, testClient.ClientID)
|
||||
testClient := testutils.RegisterTestClient(t, "OAuth Test Client", []string{"http://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, testClient.ClientID)
|
||||
|
||||
// Prepare test data
|
||||
endpoint := serverURL + Server.Config.BaseURL + "/oauth/authorize"
|
||||
// testutils.Prepare test data
|
||||
endpoint := serverURL + openapi.Server.Config.BaseURL + "/oauth/authorize"
|
||||
t.Logf("Testing authorize endpoint: %s", endpoint)
|
||||
|
||||
t.Run("Valid Authorization Request", func(t *testing.T) {
|
||||
|
|
@ -324,28 +326,28 @@ func TestOAuthAuthorize(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOAuthJWKS(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Debug: Check if Server is properly initialized
|
||||
if Server == nil {
|
||||
t.Fatal("OpenAPI Server is nil")
|
||||
// Debug: Check if openapi.Server is properly initialized
|
||||
if openapi.Server == nil {
|
||||
t.Fatal("OpenAPI openapi.Server is nil")
|
||||
}
|
||||
|
||||
if Server.Config == nil {
|
||||
t.Fatal("OpenAPI Server.Config is nil")
|
||||
if openapi.Server.Config == nil {
|
||||
t.Fatal("OpenAPI openapi.Server.Config is nil")
|
||||
}
|
||||
|
||||
if Server.OAuth == nil {
|
||||
t.Fatal("OpenAPI Server.OAuth is nil")
|
||||
if openapi.Server.OAuth == nil {
|
||||
t.Fatal("OpenAPI openapi.Server.OAuth is nil")
|
||||
}
|
||||
|
||||
t.Logf("Server initialized with BaseURL: %s", Server.Config.BaseURL)
|
||||
t.Logf("openapi.Server initialized with BaseURL: %s", openapi.Server.Config.BaseURL)
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
endpoint := serverURL + baseURL + "/oauth/jwks"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package openapi
|
||||
package openapi_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -9,29 +9,32 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/openapi"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"github.com/yaoapp/yao/openapi/response"
|
||||
"github.com/yaoapp/yao/openapi/tests/testutils"
|
||||
)
|
||||
|
||||
func TestOAuthToken_AuthorizationCode(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
// Register a test client
|
||||
client := RegisterTestClient(t, "Token Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
client := testutils.RegisterTestClient(t, "Token Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
|
||||
// Obtain authorization code dynamically
|
||||
authInfo := ObtainAuthorizationCode(t, serverURL, client.ClientID, "https://localhost/callback", "openid profile")
|
||||
authInfo := testutils.ObtainAuthorizationCode(t, serverURL, client.ClientID, "https://localhost/callback", "openid profile")
|
||||
|
||||
// Test authorization code grant
|
||||
t.Run("Valid Authorization Code Grant", func(t *testing.T) {
|
||||
// Prepare token request with PKCE code verifier
|
||||
// testutils.Prepare token request with PKCE code verifier
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("code", authInfo.Code)
|
||||
|
|
@ -77,7 +80,7 @@ func TestOAuthToken_AuthorizationCode(t *testing.T) {
|
|||
t.Run("Invalid Authorization Code", func(t *testing.T) {
|
||||
// Test with invalid authorization code - should return error
|
||||
|
||||
// Prepare token request with invalid code
|
||||
// testutils.Prepare token request with invalid code
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("code", "invalid-code")
|
||||
|
|
@ -105,7 +108,7 @@ func TestOAuthToken_AuthorizationCode(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Missing Required Parameters", func(t *testing.T) {
|
||||
// Prepare token request missing redirect_uri
|
||||
// testutils.Prepare token request missing redirect_uri
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "authorization_code")
|
||||
data.Set("code", authInfo.Code)
|
||||
|
|
@ -130,21 +133,21 @@ func TestOAuthToken_AuthorizationCode(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOAuthToken_ClientCredentials(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
// Register a test client for client credentials
|
||||
client := RegisterTestClient(t, "Client Credentials Test", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
client := testutils.RegisterTestClient(t, "Client Credentials Test", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
|
||||
t.Run("Valid Client Credentials Grant", func(t *testing.T) {
|
||||
// Prepare token request
|
||||
// testutils.Prepare token request
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "client_credentials")
|
||||
data.Set("scope", "api:read api:write")
|
||||
|
|
@ -191,7 +194,7 @@ func TestOAuthToken_ClientCredentials(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Client Credentials Without Authentication", func(t *testing.T) {
|
||||
// Prepare token request
|
||||
// testutils.Prepare token request
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "client_credentials")
|
||||
|
||||
|
|
@ -213,21 +216,21 @@ func TestOAuthToken_ClientCredentials(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOAuthToken_RefreshToken(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
// Register a test client
|
||||
client := RegisterTestClient(t, "Refresh Token Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
client := testutils.RegisterTestClient(t, "Refresh Token Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
|
||||
// First, get an access token and refresh token using authorization code
|
||||
authInfo := ObtainAuthorizationCode(t, serverURL, client.ClientID, "https://localhost/callback", "openid profile")
|
||||
authInfo := testutils.ObtainAuthorizationCode(t, serverURL, client.ClientID, "https://localhost/callback", "openid profile")
|
||||
|
||||
// Get initial token with PKCE code verifier
|
||||
data := url.Values{}
|
||||
|
|
@ -256,7 +259,7 @@ func TestOAuthToken_RefreshToken(t *testing.T) {
|
|||
assert.NotEmpty(t, initialToken.RefreshToken)
|
||||
|
||||
t.Run("Valid Refresh Token Grant", func(t *testing.T) {
|
||||
// Prepare refresh token request
|
||||
// testutils.Prepare refresh token request
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", initialToken.RefreshToken)
|
||||
|
|
@ -308,7 +311,7 @@ func TestOAuthToken_RefreshToken(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Invalid Refresh Token", func(t *testing.T) {
|
||||
// Prepare refresh token request with invalid token
|
||||
// testutils.Prepare refresh token request with invalid token
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "refresh_token")
|
||||
data.Set("refresh_token", "invalid-refresh-token")
|
||||
|
|
@ -329,7 +332,7 @@ func TestOAuthToken_RefreshToken(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Missing Refresh Token", func(t *testing.T) {
|
||||
// Prepare refresh token request without refresh_token parameter
|
||||
// testutils.Prepare refresh token request without refresh_token parameter
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "refresh_token")
|
||||
// Missing refresh_token
|
||||
|
|
@ -351,20 +354,20 @@ func TestOAuthToken_RefreshToken(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOAuthToken_InvalidGrantType(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
client := RegisterTestClient(t, "Invalid Grant Test", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
client := testutils.RegisterTestClient(t, "Invalid Grant Test", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
|
||||
t.Run("Unsupported Grant Type", func(t *testing.T) {
|
||||
// Prepare token request with unsupported grant type
|
||||
// testutils.Prepare token request with unsupported grant type
|
||||
data := url.Values{}
|
||||
data.Set("grant_type", "unsupported_grant_type")
|
||||
|
||||
|
|
@ -389,7 +392,7 @@ func TestOAuthToken_InvalidGrantType(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Missing Grant Type", func(t *testing.T) {
|
||||
// Prepare token request without grant_type
|
||||
// testutils.Prepare token request without grant_type
|
||||
data := url.Values{}
|
||||
// Missing grant_type
|
||||
|
||||
|
|
@ -462,24 +465,24 @@ func base64Encode(data []byte) string {
|
|||
}
|
||||
|
||||
func TestOAuthRevoke(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
// Register a test client
|
||||
client := RegisterTestClient(t, "Revoke Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
client := testutils.RegisterTestClient(t, "Revoke Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
|
||||
// Obtain access token directly using the utility function
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
t.Run("Valid Access Token Revocation", func(t *testing.T) {
|
||||
// Prepare revocation request
|
||||
// testutils.Prepare revocation request
|
||||
data := url.Values{}
|
||||
data.Set("token", tokenInfo.AccessToken)
|
||||
data.Set("token_type_hint", "access_token")
|
||||
|
|
@ -503,7 +506,7 @@ func TestOAuthRevoke(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Valid Refresh Token Revocation", func(t *testing.T) {
|
||||
// Prepare revocation request for refresh token
|
||||
// testutils.Prepare revocation request for refresh token
|
||||
data := url.Values{}
|
||||
data.Set("token", tokenInfo.RefreshToken)
|
||||
data.Set("token_type_hint", "refresh_token")
|
||||
|
|
@ -527,7 +530,7 @@ func TestOAuthRevoke(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Invalid Token Revocation", func(t *testing.T) {
|
||||
// Prepare revocation request with invalid token
|
||||
// testutils.Prepare revocation request with invalid token
|
||||
data := url.Values{}
|
||||
data.Set("token", "invalid-token-12345")
|
||||
data.Set("token_type_hint", "access_token")
|
||||
|
|
@ -551,7 +554,7 @@ func TestOAuthRevoke(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Missing Token Parameter", func(t *testing.T) {
|
||||
// Prepare revocation request without token parameter
|
||||
// testutils.Prepare revocation request without token parameter
|
||||
data := url.Values{}
|
||||
// Missing token parameter
|
||||
|
||||
|
|
@ -573,24 +576,24 @@ func TestOAuthRevoke(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestOAuthIntrospect(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Get base URL from server config
|
||||
baseURL := ""
|
||||
if Server != nil && Server.Config != nil {
|
||||
baseURL = Server.Config.BaseURL
|
||||
if openapi.Server != nil && openapi.Server.Config != nil {
|
||||
baseURL = openapi.Server.Config.BaseURL
|
||||
}
|
||||
|
||||
// Register a test client
|
||||
client := RegisterTestClient(t, "Introspect Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
client := testutils.RegisterTestClient(t, "Introspect Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
|
||||
// Obtain access token directly using the utility function
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
|
||||
|
||||
t.Run("Valid Access Token Introspection", func(t *testing.T) {
|
||||
// Prepare introspection request
|
||||
// testutils.Prepare introspection request
|
||||
data := url.Values{}
|
||||
data.Set("token", tokenInfo.AccessToken)
|
||||
data.Set("token_type_hint", "access_token")
|
||||
|
|
@ -615,7 +618,7 @@ func TestOAuthIntrospect(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// Parse response directly (no wrapper)
|
||||
var introspectResp TokenIntrospectionResponse
|
||||
var introspectResp response.TokenIntrospectionResponse
|
||||
err = json.Unmarshal(body, &introspectResp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
|
@ -630,7 +633,7 @@ func TestOAuthIntrospect(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Invalid Token Introspection", func(t *testing.T) {
|
||||
// Prepare introspection request with invalid token
|
||||
// testutils.Prepare introspection request with invalid token
|
||||
data := url.Values{}
|
||||
data.Set("token", "invalid-token-12345")
|
||||
data.Set("token_type_hint", "access_token")
|
||||
|
|
@ -655,7 +658,7 @@ func TestOAuthIntrospect(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// Parse response directly (no wrapper)
|
||||
var introspectResp TokenIntrospectionResponse
|
||||
var introspectResp response.TokenIntrospectionResponse
|
||||
err = json.Unmarshal(body, &introspectResp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
|
@ -666,7 +669,7 @@ func TestOAuthIntrospect(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Missing Token Parameter", func(t *testing.T) {
|
||||
// Prepare introspection request without token parameter
|
||||
// testutils.Prepare introspection request without token parameter
|
||||
data := url.Values{}
|
||||
// Missing token parameter
|
||||
|
||||
|
|
@ -727,7 +730,7 @@ func TestOAuthIntrospect(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
// Parse response directly (no wrapper)
|
||||
var introspectResp TokenIntrospectionResponse
|
||||
var introspectResp response.TokenIntrospectionResponse
|
||||
err = json.Unmarshal(body, &introspectResp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
41
openapi/tests/openapi_test.go
Normal file
41
openapi/tests/openapi_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package openapi_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/openapi"
|
||||
"github.com/yaoapp/yao/openapi/tests/testutils"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
assert.NotNil(t, openapi.Server)
|
||||
assert.NotEmpty(t, serverURL)
|
||||
assert.Contains(t, serverURL, "http://127.0.0.1:")
|
||||
}
|
||||
|
||||
func TestObtainAccessToken(t *testing.T) {
|
||||
serverURL := testutils.Prepare(t)
|
||||
defer testutils.Clean()
|
||||
|
||||
// Register a test client
|
||||
client := testutils.RegisterTestClient(t, "Token Utility Test Client", []string{"https://localhost/callback"})
|
||||
defer testutils.CleanupTestClient(t, client.ClientID)
|
||||
|
||||
// Test the ObtainAccessToken utility function
|
||||
tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile email")
|
||||
|
||||
// Verify token information
|
||||
assert.NotEmpty(t, tokenInfo.AccessToken, "Access token should not be empty")
|
||||
assert.NotEmpty(t, tokenInfo.RefreshToken, "Refresh token should not be empty")
|
||||
assert.Equal(t, "Bearer", tokenInfo.TokenType, "Token type should be Bearer")
|
||||
assert.Greater(t, tokenInfo.ExpiresIn, 0, "ExpiresIn should be greater than 0")
|
||||
assert.Equal(t, client.ClientID, tokenInfo.ClientID, "Client ID should match")
|
||||
// Note: Scope might be empty in token response, which is valid
|
||||
|
||||
t.Logf("Successfully obtained token: AccessToken=%s, TokenType=%s, ExpiresIn=%d, Scope=%s",
|
||||
tokenInfo.AccessToken, tokenInfo.TokenType, tokenInfo.ExpiresIn, tokenInfo.Scope)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package openapi
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
|
@ -8,12 +8,13 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/yao/config"
|
||||
"github.com/yaoapp/yao/openapi"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"github.com/yaoapp/yao/test"
|
||||
)
|
||||
|
|
@ -21,6 +22,12 @@ import (
|
|||
// testServer holds the test HTTP server instance
|
||||
var testServer *http.Server
|
||||
|
||||
// testMutex protects global state access during concurrent test execution
|
||||
var testMutex sync.RWMutex
|
||||
|
||||
// activeTestCount tracks the number of active tests using the global server
|
||||
var activeTestCount int
|
||||
|
||||
// Prepare initializes the OpenAPI test environment and starts a mock HTTP server.
|
||||
//
|
||||
// AI ASSISTANT INSTRUCTIONS:
|
||||
|
|
@ -85,13 +92,22 @@ var testServer *http.Server
|
|||
// ERROR HANDLING:
|
||||
// If any step fails, the test will fail immediately with a descriptive error message.
|
||||
func Prepare(t *testing.T) string {
|
||||
// Use write lock to protect global state initialization
|
||||
testMutex.Lock()
|
||||
defer func() {
|
||||
activeTestCount++
|
||||
testMutex.Unlock()
|
||||
}()
|
||||
|
||||
// Step 1: Initialize base test environment with all Yao dependencies
|
||||
test.Prepare(t, config.Conf)
|
||||
|
||||
// Step 2: Initialize OpenAPI server and make it available globally
|
||||
_, err := Load(config.Conf)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load OpenAPI server: %v", err)
|
||||
// Step 2: Initialize OpenAPI server and make it available globally (only if not already initialized)
|
||||
if openapi.Server == nil {
|
||||
_, err := openapi.Load(config.Conf)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to load OpenAPI server: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Create Gin router and attach OpenAPI server
|
||||
|
|
@ -99,8 +115,8 @@ func Prepare(t *testing.T) string {
|
|||
router := gin.New()
|
||||
|
||||
// Attach the OpenAPI server to the router
|
||||
if Server != nil {
|
||||
Server.Attach(router)
|
||||
if openapi.Server != nil {
|
||||
openapi.Server.Attach(router)
|
||||
}
|
||||
|
||||
// Step 4: Start HTTP server on random available port
|
||||
|
|
@ -109,13 +125,13 @@ func Prepare(t *testing.T) string {
|
|||
t.Fatalf("Failed to create listener: %v", err)
|
||||
}
|
||||
|
||||
testServer = &http.Server{
|
||||
server := &http.Server{
|
||||
Handler: router,
|
||||
}
|
||||
|
||||
// Start server in background
|
||||
go func() {
|
||||
if err := testServer.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
if err := server.Serve(listener); err != nil && err != http.ErrServerClosed {
|
||||
t.Errorf("Failed to start test server: %v", err)
|
||||
}
|
||||
}()
|
||||
|
|
@ -123,6 +139,9 @@ func Prepare(t *testing.T) string {
|
|||
// Wait a moment for server to start
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Store server instance for this test (each test gets its own HTTP server)
|
||||
testServer = server
|
||||
|
||||
// Return server URL
|
||||
serverURL := fmt.Sprintf("http://%s", listener.Addr().String())
|
||||
return serverURL
|
||||
|
|
@ -154,7 +173,7 @@ func Prepare(t *testing.T) string {
|
|||
// - The order of cleanup steps is important: HTTP server first, then OpenAPI cleanup, then base cleanup
|
||||
// - Server shutdown has a 5-second timeout to prevent hanging tests
|
||||
func Clean() {
|
||||
// Step 1: Gracefully shutdown the HTTP test server
|
||||
// Step 1: Gracefully shutdown the HTTP test server for this test
|
||||
if testServer != nil {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -166,11 +185,21 @@ func Clean() {
|
|||
testServer = nil
|
||||
}
|
||||
|
||||
// Step 2: Reset OpenAPI server instance to prevent state leakage
|
||||
Server = nil
|
||||
// Step 2: Use lock to safely decrement active test count and clean global state if needed
|
||||
testMutex.Lock()
|
||||
activeTestCount--
|
||||
shouldCleanGlobalState := activeTestCount <= 0
|
||||
if shouldCleanGlobalState {
|
||||
// Reset global state only when no other tests are active
|
||||
openapi.Server = nil
|
||||
activeTestCount = 0 // Ensure it doesn't go negative
|
||||
}
|
||||
testMutex.Unlock()
|
||||
|
||||
// Step 3: Clean up base test environment and all dependencies
|
||||
test.Clean()
|
||||
// Step 3: Clean up base test environment
|
||||
if shouldCleanGlobalState {
|
||||
test.Clean()
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterTestClient registers a test OAuth client and returns the client information.
|
||||
|
|
@ -209,7 +238,11 @@ func Clean() {
|
|||
// ERROR HANDLING:
|
||||
// If client registration fails, the test will fail immediately with a descriptive error message.
|
||||
func RegisterTestClient(t *testing.T, clientName string, redirectURIs []string) *types.ClientInfo {
|
||||
if Server == nil || Server.OAuth == nil {
|
||||
testMutex.RLock()
|
||||
server := openapi.Server
|
||||
testMutex.RUnlock()
|
||||
|
||||
if server == nil || server.OAuth == nil {
|
||||
t.Fatal("OpenAPI server not initialized. Call Prepare(t) first.")
|
||||
}
|
||||
|
||||
|
|
@ -232,7 +265,7 @@ func RegisterTestClient(t *testing.T, clientName string, redirectURIs []string)
|
|||
|
||||
// Register the client using the OAuth service
|
||||
ctx := context.Background()
|
||||
response, err := Server.OAuth.DynamicClientRegistration(ctx, req)
|
||||
response, err := server.OAuth.DynamicClientRegistration(ctx, req)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register test client: %v", err)
|
||||
}
|
||||
|
|
@ -279,7 +312,11 @@ func RegisterTestClient(t *testing.T, clientName string, redirectURIs []string)
|
|||
// If client deletion fails, logs an error but does not fail the test.
|
||||
// This prevents cleanup failures from affecting test results.
|
||||
func CleanupTestClient(t *testing.T, clientID string) {
|
||||
if Server == nil || Server.OAuth == nil {
|
||||
testMutex.RLock()
|
||||
server := openapi.Server
|
||||
testMutex.RUnlock()
|
||||
|
||||
if server == nil || server.OAuth == nil {
|
||||
// Server might already be cleaned up, which is OK
|
||||
return
|
||||
}
|
||||
|
|
@ -290,7 +327,7 @@ func CleanupTestClient(t *testing.T, clientID string) {
|
|||
|
||||
// Delete the client using the OAuth service
|
||||
ctx := context.Background()
|
||||
err := Server.OAuth.DeleteClient(ctx, clientID)
|
||||
err := server.OAuth.DeleteClient(ctx, clientID)
|
||||
if err != nil {
|
||||
// Log error but don't fail the test - cleanup should be resilient
|
||||
t.Logf("Warning: Failed to cleanup test client %s: %v", clientID, err)
|
||||
|
|
@ -321,6 +358,7 @@ func CreateTestClientCredentials() (clientID, clientSecret string) {
|
|||
return "test-client-id", "test-client-secret"
|
||||
}
|
||||
|
||||
// AuthorizationInfo represents the information needed for OAuth authorization.
|
||||
// ObtainAuthorizationCode dynamically obtains an authorization code for testing OAuth token endpoints.
|
||||
//
|
||||
// AI ASSISTANT INSTRUCTIONS:
|
||||
|
|
@ -383,8 +421,13 @@ type AuthorizationInfo struct {
|
|||
CodeChallengeMethod string
|
||||
}
|
||||
|
||||
// ObtainAuthorizationCode obtains an authorization code for testing OAuth token endpoints.
|
||||
func ObtainAuthorizationCode(t *testing.T, serverURL, clientID, redirectURI, scope string) *AuthorizationInfo {
|
||||
if Server == nil || Server.OAuth == nil {
|
||||
testMutex.RLock()
|
||||
server := openapi.Server
|
||||
testMutex.RUnlock()
|
||||
|
||||
if server == nil || server.OAuth == nil {
|
||||
t.Fatal("OpenAPI server not initialized. Call Prepare(t) first.")
|
||||
}
|
||||
|
||||
|
|
@ -409,7 +452,7 @@ func ObtainAuthorizationCode(t *testing.T, serverURL, clientID, redirectURI, sco
|
|||
|
||||
// Call OAuth service to process authorization request
|
||||
ctx := context.Background()
|
||||
authResp, err := Server.OAuth.Authorize(ctx, authReq)
|
||||
authResp, err := server.OAuth.Authorize(ctx, authReq)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to obtain authorization code: %v", err)
|
||||
}
|
||||
|
|
@ -439,6 +482,7 @@ func ObtainAuthorizationCode(t *testing.T, serverURL, clientID, redirectURI, sco
|
|||
return authInfo
|
||||
}
|
||||
|
||||
// TokenInfo represents the information needed for OAuth token exchange.
|
||||
// ObtainAccessToken directly obtains an access token for testing OAuth endpoints that require authentication.
|
||||
//
|
||||
// AI ASSISTANT INSTRUCTIONS:
|
||||
|
|
@ -495,8 +539,13 @@ type TokenInfo struct {
|
|||
ClientID string
|
||||
}
|
||||
|
||||
// ObtainAccessToken obtains an access token for testing OAuth endpoints that require authentication.
|
||||
func ObtainAccessToken(t *testing.T, serverURL, clientID, clientSecret, redirectURI, scope string) *TokenInfo {
|
||||
if Server == nil || Server.OAuth == nil {
|
||||
testMutex.RLock()
|
||||
server := openapi.Server
|
||||
testMutex.RUnlock()
|
||||
|
||||
if server == nil || server.OAuth == nil {
|
||||
t.Fatal("OpenAPI server not initialized. Call Prepare(t) first.")
|
||||
}
|
||||
|
||||
|
|
@ -505,7 +554,7 @@ func ObtainAccessToken(t *testing.T, serverURL, clientID, clientSecret, redirect
|
|||
|
||||
// Step 2: Exchange authorization code for access token with PKCE code verifier
|
||||
ctx := context.Background()
|
||||
token, err := Server.OAuth.Token(ctx, "authorization_code", authInfo.Code, clientID, authInfo.CodeVerifier)
|
||||
token, err := server.OAuth.Token(ctx, "authorization_code", authInfo.Code, clientID, authInfo.CodeVerifier)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to exchange authorization code for token: %v", err)
|
||||
}
|
||||
|
|
@ -529,38 +578,6 @@ func ObtainAccessToken(t *testing.T, serverURL, clientID, clientSecret, redirect
|
|||
return tokenInfo
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
|
||||
assert.NotNil(t, Server)
|
||||
assert.NotEmpty(t, serverURL)
|
||||
assert.Contains(t, serverURL, "http://127.0.0.1:")
|
||||
}
|
||||
|
||||
func TestObtainAccessToken(t *testing.T) {
|
||||
serverURL := Prepare(t)
|
||||
defer Clean()
|
||||
|
||||
// Register a test client
|
||||
client := RegisterTestClient(t, "Token Utility Test Client", []string{"https://localhost/callback"})
|
||||
defer CleanupTestClient(t, client.ClientID)
|
||||
|
||||
// Test the ObtainAccessToken utility function
|
||||
tokenInfo := ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile email")
|
||||
|
||||
// Verify token information
|
||||
assert.NotEmpty(t, tokenInfo.AccessToken, "Access token should not be empty")
|
||||
assert.NotEmpty(t, tokenInfo.RefreshToken, "Refresh token should not be empty")
|
||||
assert.Equal(t, "Bearer", tokenInfo.TokenType, "Token type should be Bearer")
|
||||
assert.Greater(t, tokenInfo.ExpiresIn, 0, "ExpiresIn should be greater than 0")
|
||||
assert.Equal(t, client.ClientID, tokenInfo.ClientID, "Client ID should match")
|
||||
// Note: Scope might be empty in token response, which is valid
|
||||
|
||||
t.Logf("Successfully obtained token: AccessToken=%s, TokenType=%s, ExpiresIn=%d, Scope=%s",
|
||||
tokenInfo.AccessToken, tokenInfo.TokenType, tokenInfo.ExpiresIn, tokenInfo.Scope)
|
||||
}
|
||||
|
||||
// generateCodeVerifier generates a cryptographically random code verifier for PKCE
|
||||
func generateCodeVerifier() string {
|
||||
// PKCE code verifier should be 43-128 characters long
|
||||
Loading…
Add table
Reference in a new issue