Files
mcr/internal/server/policy_test.go
Kyle Isom d5580f01f2 Migrate module path from kyle/ to mc/ org
All import paths updated to git.wntrmute.dev/mc/. Bumps mcdsl to v1.2.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 02:05:59 -07:00

127 lines
3.9 KiB
Go

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)
}
}