- pier: TestCatalog, TestDispatch, TestGuardRules, TestSessions (Redis
across two instances and the memory store), TestFanOut; the store clock
and stateless-server seams in export_test.go
- surf: TestStreamingRoute (throttle, CORS profile and access-log pattern on
a stream route) and TestPierMount in both builds; the streaming smoke
restores the mount hook it replaces
- wristband TestMCPScopes; cabana TestAccountContributions (Postgres part:
transaction and unchanged My account payloads), TestRecordActionPrompt,
TestScopeLimitForMethods; TestRecordActionPromptRun pins the empty Value
of an action without a prompt
- vitest: 403 and failed-load cases for My account contributions, the
cancelled prompt
- gate: every name required (tagged runs included), account.go unchanged
since 51d088a, admin stage live
81 lines
3.0 KiB
Go
81 lines
3.0 KiB
Go
package wristband
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
// TestMCPScopes (D-21, the framework side of the backend scope): the MCP
|
|
// flavour's authorize allow-list is exactly Options.ScopesSupported. The
|
|
// default server refuses backend; a server that lists it accepts "read
|
|
// backend" and advertises it in its metadata; on either, openid and a scope
|
|
// nobody lists stay refused.
|
|
func TestMCPScopes(t *testing.T) {
|
|
_, challenge := s256Pair(t)
|
|
authorize := func(t *testing.T, s *Server, scope string) map[string]string {
|
|
t.Helper()
|
|
rec := httptest.NewRecorder()
|
|
s.Authorize(rec, authorizeRequest(map[string]string{
|
|
"client_id": "cli-scopes", "redirect_uri": authorizeTestRedirect, "response_type": "code",
|
|
"code_challenge": challenge, "code_challenge_method": "S256", "scope": scope, "state": "s",
|
|
}))
|
|
if rec.Code != http.StatusFound {
|
|
t.Fatalf("%q: status = %d, want 302", scope, rec.Code)
|
|
}
|
|
return queryOf(t, rec.Header().Get("Location"))
|
|
}
|
|
|
|
plain, plainBackend := newAuthorizeTestServer()
|
|
insertAuthorizeTestClient(plainBackend, "cli-scopes", []string{authorizeTestRedirect}, nil)
|
|
|
|
listing := DefaultOptions()
|
|
listing.Issuer = "https://issuer.example.test"
|
|
listing.Resource = "https://resource.example.test/mcp"
|
|
listing.ScopesSupported = append(listing.ScopesSupported, "backend")
|
|
withBackend := NewServer(listing)
|
|
backend := newMemoryBackend()
|
|
withBackend.SetBackend(backend)
|
|
insertAuthorizeTestClient(backend, "cli-scopes", []string{authorizeTestRedirect}, nil)
|
|
|
|
t.Run("default server refuses backend", func(t *testing.T) {
|
|
if q := authorize(t, plain, "read backend"); q["error"] != "invalid_scope" {
|
|
t.Fatalf("default server = %v", q)
|
|
}
|
|
if q := authorize(t, plain, "read write"); q["error"] != "" {
|
|
t.Fatalf("default server refused read write: %v", q)
|
|
}
|
|
})
|
|
|
|
t.Run("listing server accepts and advertises backend", func(t *testing.T) {
|
|
if q := authorize(t, withBackend, "read backend"); q["error"] != "" {
|
|
t.Fatalf("read backend = %v", q)
|
|
}
|
|
rec := httptest.NewRecorder()
|
|
withBackend.Metadata(rec, httptest.NewRequest(http.MethodGet, "/.well-known/oauth-authorization-server", nil))
|
|
var doc struct {
|
|
Scopes []string `json:"scopes_supported"`
|
|
}
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &doc); err != nil || !slices.Contains(doc.Scopes, "backend") {
|
|
t.Fatalf("scopes_supported = %v (%v)", doc.Scopes, err)
|
|
}
|
|
rec = httptest.NewRecorder()
|
|
plain.Metadata(rec, httptest.NewRequest(http.MethodGet, "/.well-known/oauth-authorization-server", nil))
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &doc); err != nil || slices.Contains(doc.Scopes, "backend") {
|
|
t.Fatalf("default scopes_supported = %v (%v)", doc.Scopes, err)
|
|
}
|
|
})
|
|
|
|
t.Run("openid and unlisted scopes stay refused", func(t *testing.T) {
|
|
for _, s := range []*Server{plain, withBackend} {
|
|
for _, scope := range []string{"openid backend", "read openid", "read admin"} {
|
|
if q := authorize(t, s, scope); q["error"] != "invalid_scope" {
|
|
t.Fatalf("%q = %v", scope, q)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|