package ops
import (
"context"
"encoding/json"
"strings"
"testing"
)
type truthIn struct {
Repo string `json:"repo" validate:"required,max=64"`
}
// specFor builds a spec from one registry and returns it decoded.
func specFor(t *testing.T, r *Registry) map[string]any {
t.Helper()
spec, err := Spec(r, "quarry", "0.0.0-test")
if err != nil {
t.Fatal(err)
}
raw, err := json.Marshal(spec)
if err != nil {
t.Fatal(err)
}
var doc map[string]any
if err := json.Unmarshal(raw, &doc); err != nil {
t.Fatal(err)
}
return doc
}
// The spec declares the scheme that authenticates and no other.
//
// It declared a bearer token on 147 of 209 operations, and the router
// has never accepted one: Authorization: Bearer answers 401, and the
// 401 advertises Basic. A client generated from the document could not
// make a single call and had nothing in the document saying why
// (a907073c, G-5).
func TestTheSpecDeclaresBasicAndNoBearer(t *testing.T) {
r := NewRegistry()
if err := Register(r, Op[truthIn, struct{}]{Name: "truth.write", Kind: Command, Auth: Token, Caps: "i",
Route: "POST /api/v1/r/{repo}/truth", Doc: "test double",
Run: func(context.Context, truthIn) (struct{}, error) { return struct{}{}, nil }}); err != nil {
t.Fatal(err)
}
doc := specFor(t, r)
schemes := doc["components"].(map[string]any)["securitySchemes"].(map[string]any)
if _, ok := schemes["bearerToken"]; ok {
t.Fatal("FAIL: the spec still declares a bearer scheme the router rejects")
}
basic, ok := schemes["basicAuth"].(map[string]any)
if !ok || basic["scheme"] != "basic" {
t.Fatalf("FAIL: basicAuth not declared as http/basic: %v", schemes)
}
op := doc["paths"].(map[string]any)["/api/v1/r/{repo}/truth"].(map[string]any)["post"].(map[string]any)
sec := op["security"].([]any)[0].(map[string]any)
if _, ok := sec["basicAuth"]; !ok {
t.Fatalf("FAIL: the operation's security requirement is not basicAuth: %v", sec)
}
t.Log("ok: basicAuth declared, bearerToken gone, operations require basicAuth")
}
// An operation's capability letters and their scope travel with it, and
// its summary says so. The auth level is the router's floor, not the
// gate: mirror.list is "token" and refuses every ordinary account
// because it needs capability a (763c5c89).
func TestAnOperationPublishesTheCapabilityItsGateNeeds(t *testing.T) {
r := NewRegistry()
if err := Register(r, Op[truthIn, struct{}]{Name: "truth.admin", Kind: Query, Auth: Token, Caps: "a", Scope: ScopeFleet,
Route: "GET /api/v1/truth", Doc: "List everything.",
Run: func(context.Context, truthIn) (struct{}, error) { return struct{}{}, nil }}); err != nil {
t.Fatal(err)
}
if err := Register(r, Op[truthIn, struct{}]{Name: "truth.either", Kind: Command, Auth: Token, Caps: "kf",
Route: "POST /api/v1/r/{repo}/truth", Doc: "Write something",
Run: func(context.Context, truthIn) (struct{}, error) { return struct{}{}, nil }}); err != nil {
t.Fatal(err)
}
doc := specFor(t, r)
paths := doc["paths"].(map[string]any)
admin := paths["/api/v1/truth"].(map[string]any)["get"].(map[string]any)
if admin["x-quarry-caps"] != "a" || admin["x-quarry-auth"] != "token" {
t.Fatalf("FAIL: caps/level = %v / %v", admin["x-quarry-caps"], admin["x-quarry-auth"])
}
if s := admin["summary"].(string); !strings.Contains(s, "Needs capability a on the "+ScopeFleet.String()) {
t.Fatalf("FAIL: the summary does not say what the gate needs: %q", s)
}
either := paths["/api/v1/r/{repo}/truth"].(map[string]any)["post"].(map[string]any)
if s := either["summary"].(string); !strings.Contains(s, "capability k or f") {
t.Fatalf("FAIL: a two-letter gate is not read as either: %q", s)
}
t.Logf("ok: %q", admin["summary"])
}