feat: list and show quizzes over MCP for their owner

- quizzes_list_quizzes and quizzes_show_quiz bound to GET /api/v1/quizzes
  and /api/v1/quizzes/{slug} behind user.api_token, the per-user
  golem15.quizzes.manage throttle, a scope and the access_quizzes permission
- the permission is grantable through pact.HasFrontendPermissions
- owner JSON shapes with solutions in manage_json.go, never used by a
  student route
This commit is contained in:
Jakub Zych
2026-11-09 14:25:07 +01:00
parent 174be3bcbd
commit e615cc88db
10 changed files with 645 additions and 2 deletions

View File

@@ -69,6 +69,8 @@ func TestRoutesRegistered(t *testing.T) {
"POST /_quizzes/api/v1/attempts/{id}/answers",
"POST /_quizzes/api/v1/attempts/{id}/finish",
"POST /api/v1/quizzes/import",
"GET /api/v1/quizzes",
"GET /api/v1/quizzes/{slug}",
}
var got []string
for _, r := range router.Routes() {
@@ -84,6 +86,17 @@ func TestRoutesRegistered(t *testing.T) {
}
continue
}
if r.Pattern == "/api/v1/quizzes" || strings.HasPrefix(r.Pattern, "/api/v1/quizzes/") {
for _, mw := range []string{"user.api_token", "throttle:golem15.quizzes.manage", "user.permission:golem15.quizzes.access_quizzes"} {
if !slices.Contains(r.Middleware, mw) {
t.Errorf("%s %s is not behind %s: %v", r.Method, r.Pattern, mw, r.Middleware)
}
}
if !slices.Contains(r.Middleware, "user.scope:read") && !slices.Contains(r.Middleware, "user.scope:write") {
t.Errorf("%s %s has no token scope: %v", r.Method, r.Pattern, r.Middleware)
}
continue
}
if !slices.Contains(r.Middleware, "jwt.auth") {
t.Errorf("%s %s is not behind jwt.auth: %v", r.Method, r.Pattern, r.Middleware)
}