package server import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/go-chi/chi/v5" "git.wntrmute.dev/mc/mcr/internal/auth" "git.wntrmute.dev/mc/mcr/internal/policy" ) type fakePolicyEvaluator struct { effect policy.Effect rule *policy.Rule } func (f *fakePolicyEvaluator) Evaluate(_ policy.PolicyInput) (policy.Effect, *policy.Rule) { return f.effect, f.rule } // newPolicyTestRouter creates a chi router with a repo-scoped route // protected by RequirePolicy. The handler returns 200 on success. func newPolicyTestRouter(evaluator PolicyEvaluator, action policy.Action, auditFn AuditFunc) *chi.Mux { r := chi.NewRouter() r.Route("/v2/{name}", func(sub chi.Router) { sub.Use(RequirePolicy(evaluator, action, auditFn)) sub.Get("/test", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) }) }) return r } func TestRequirePolicyAdminAllowed(t *testing.T) { evaluator := &fakePolicyEvaluator{effect: policy.Allow} router := newPolicyTestRouter(evaluator, policy.ActionPull, nil) claims := &auth.Claims{Subject: "admin-uuid", AccountType: "human", Roles: []string{"admin"}} req := httptest.NewRequest("GET", "/v2/myrepo/test", nil) req = req.WithContext(auth.ContextWithClaims(req.Context(), claims)) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("admin allowed: got %d, want 200", rr.Code) } } func TestRequirePolicyUserAllowed(t *testing.T) { evaluator := &fakePolicyEvaluator{effect: policy.Allow} router := newPolicyTestRouter(evaluator, policy.ActionPull, nil) claims := &auth.Claims{Subject: "user-uuid", AccountType: "human", Roles: []string{"user"}} req := httptest.NewRequest("GET", "/v2/myrepo/test", nil) req = req.WithContext(auth.ContextWithClaims(req.Context(), claims)) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("user allowed: got %d, want 200", rr.Code) } } func TestRequirePolicySystemDenied(t *testing.T) { evaluator := &fakePolicyEvaluator{effect: policy.Deny} router := newPolicyTestRouter(evaluator, policy.ActionPull, nil) claims := &auth.Claims{Subject: "system-uuid", AccountType: "system"} req := httptest.NewRequest("GET", "/v2/myrepo/test", nil) req = req.WithContext(auth.ContextWithClaims(req.Context(), claims)) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusForbidden { t.Fatalf("system denied: got %d, want 403", rr.Code) } var body ociErrorResponse if err := json.NewDecoder(rr.Body).Decode(&body); err != nil { t.Fatalf("decode error body: %v", err) } if len(body.Errors) != 1 || body.Errors[0].Code != "DENIED" { t.Fatalf("error code: got %+v, want DENIED", body.Errors) } } func TestRequirePolicySystemWithRuleAllowed(t *testing.T) { evaluator := &fakePolicyEvaluator{effect: policy.Allow} router := newPolicyTestRouter(evaluator, policy.ActionPull, nil) claims := &auth.Claims{Subject: "ci-uuid", AccountType: "system"} req := httptest.NewRequest("GET", "/v2/myrepo/test", nil) req = req.WithContext(auth.ContextWithClaims(req.Context(), claims)) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("system with rule: got %d, want 200", rr.Code) } } func TestRequirePolicyDenyRuleBlocks(t *testing.T) { evaluator := &fakePolicyEvaluator{ effect: policy.Deny, rule: &policy.Rule{ID: 99, Effect: policy.Deny, Description: "explicit deny"}, } router := newPolicyTestRouter(evaluator, policy.ActionDelete, nil) claims := &auth.Claims{Subject: "user-uuid", AccountType: "human", Roles: []string{"user"}} req := httptest.NewRequest("GET", "/v2/myrepo/test", nil) req = req.WithContext(auth.ContextWithClaims(req.Context(), claims)) rr := httptest.NewRecorder() router.ServeHTTP(rr, req) if rr.Code != http.StatusForbidden { t.Fatalf("deny rule blocks: got %d, want 403", rr.Code) } }