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:
Max 2025-07-24 15:53:29 +08:00
parent 40519e8429
commit 8d35e824ae
10 changed files with 524 additions and 338 deletions

View file

@ -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
}

View file

@ -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

View file

@ -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)

View file

@ -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
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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"

View file

@ -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)

View 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)
}

View file

@ -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