registry/internal/api/handlers/v0/auth/oidc_test.go
Radoslav Dimitrov 0fc4606be2
Stop pinning Go to patch versions, use stable in CI (#1137)
<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
We keep bumping `go 1.26.x` across the repo every time a new Go patch
drops
## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->
- **`go.mod` / `deploy/go.mod`**: `go 1.25.8` → `go 1.26` (minor only —
"this module needs Go 1.26")
- **All CI workflows**: `go-version-file: 'go.mod'` → `go-version:
'stable'` — CI always uses the latest stable Go, no more
  bump PRs
- **govulncheck**: removed hardcoded `go-version-input: '1.26.1'` →
`go-version-input: 'stable'`
## Breaking Changes
<!-- Will users need to update their code or configurations? -->

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [ ] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed

## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->

Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
2026-04-09 11:57:21 -04:00

107 lines
3.2 KiB
Go

package auth_test
import (
"context"
"fmt"
"testing"
"github.com/modelcontextprotocol/registry/internal/api/handlers/v0/auth"
"github.com/modelcontextprotocol/registry/internal/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// MockGenericOIDCValidator for testing
type MockGenericOIDCValidator struct {
validateFunc func(ctx context.Context, token string) (*auth.OIDCClaims, error)
}
func (m *MockGenericOIDCValidator) ValidateToken(ctx context.Context, token string) (*auth.OIDCClaims, error) {
if m.validateFunc != nil {
return m.validateFunc(ctx, token)
}
return nil, fmt.Errorf("not implemented")
}
func TestOIDCHandler_ExchangeToken(t *testing.T) {
tests := []struct {
name string
config *config.Config
mockValidator *MockGenericOIDCValidator
token string
expectedError bool
}{
{ //nolint:gosec // G101: test data, not real credentials
name: "successful token exchange with publish permissions",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://accounts.google.com",
OIDCClientID: "test-client-id",
OIDCExtraClaims: `[{"hd":"modelcontextprotocol.io"}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef", // 32 byte hex
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
ExtraClaims: map[string]any{
"email": "admin@modelcontextprotocol.io",
"email_verified": true,
"hd": "modelcontextprotocol.io",
"preferred_username": "admin",
},
}, nil
},
},
token: "valid-oidc-token",
expectedError: false,
},
{
name: "failed validation with invalid hosted domain",
config: &config.Config{
OIDCEnabled: true,
OIDCIssuer: "https://accounts.google.com",
OIDCClientID: "test-client-id",
OIDCExtraClaims: `[{"hd":"modelcontextprotocol.io"}]`,
OIDCPublishPerms: "*",
JWTPrivateKey: "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef",
},
mockValidator: &MockGenericOIDCValidator{
validateFunc: func(_ context.Context, _ string) (*auth.OIDCClaims, error) {
return &auth.OIDCClaims{
ExtraClaims: map[string]any{
"email": "user@example.com",
"email_verified": true,
"hd": "example.com", // Wrong domain
"preferred_username": "user",
},
}, nil
},
},
token: "invalid-domain-token",
expectedError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
handler := auth.NewOIDCHandler(tt.config)
if tt.mockValidator != nil {
handler.SetValidator(tt.mockValidator)
}
ctx := context.Background()
response, err := handler.ExchangeToken(ctx, tt.token)
if tt.expectedError {
assert.Error(t, err)
assert.Nil(t, response)
} else {
require.NoError(t, err)
require.NotNil(t, response)
assert.NotEmpty(t, response.RegistryToken)
assert.Greater(t, response.ExpiresAt, 0)
}
})
}
}