;; lib/acl/tests/direct.sx — Phase 1: direct grants + deny-overrides.

(define acl-dt-pass 0)
(define acl-dt-fail 0)
(define acl-dt-failures (list))

(define
  acl-dt-check!
  (fn
    (name got expected)
    (if
      (= got expected)
      (set! acl-dt-pass (+ acl-dt-pass 1))
      (do
        (set! acl-dt-fail (+ acl-dt-fail 1))
        (append!
          acl-dt-failures
          (str name "\n    expected: " expected "\n    got:      " got))))))

;; A small fixture used by most cases: alice can read page1, is denied edit on
;; page1, and a service may federate peer1.
(define
  acl-dt-fixture
  (fn
    ()
    (acl-build-db
      (list
        (acl-actor (quote alice) (quote user))
        (acl-actor (quote svc1) (quote service))
        (acl-resource-fact (quote page1) (quote page))
        (acl-resource-fact (quote peer1) (quote peer))
        (acl-grant (quote alice) (quote read) (quote page1))
        (acl-grant (quote alice) (quote edit) (quote page1))
        (acl-deny (quote alice) (quote edit) (quote page1))
        (acl-grant (quote svc1) (quote federate) (quote peer1))))))

(define
  acl-dt-run-all!
  (fn
    ()
    (let
      ((db (acl-dt-fixture)))
      (do
        (acl-dt-check!
          "direct grant permits"
          (acl-permit? db (quote alice) (quote read) (quote page1))
          true)
        (acl-dt-check!
          "service grant permits federate"
          (acl-permit? db (quote svc1) (quote federate) (quote peer1))
          true)
        (acl-dt-check!
          "missing action denied"
          (acl-permit? db (quote alice) (quote comment) (quote page1))
          false)
        (acl-dt-check!
          "missing resource denied"
          (acl-permit? db (quote alice) (quote read) (quote page2))
          false)
        (acl-dt-check!
          "missing subject denied"
          (acl-permit? db (quote bob) (quote read) (quote page1))
          false)
        (acl-dt-check!
          "wrong subject for service grant denied"
          (acl-permit? db (quote alice) (quote federate) (quote peer1))
          false)
        (acl-dt-check!
          "grant plus deny -> deny wins"
          (acl-permit? db (quote alice) (quote edit) (quote page1))
          false)
        (acl-dt-check!
          "deny alone still denies"
          (acl-permit?
            (acl-build-db
              (list (acl-deny (quote alice) (quote read) (quote page1))))
            (quote alice)
            (quote read)
            (quote page1))
          false)
        (acl-dt-check!
          "deny on edit does not block read"
          (acl-permit? db (quote alice) (quote read) (quote page1))
          true)
        (acl-dt-check!
          "empty db denies"
          (acl-permit?
            (acl-build-db (list))
            (quote alice)
            (quote read)
            (quote page1))
          false)
        (let
          ((db2 (acl-build-db (list (acl-grant (quote a) (quote read) (quote r)) (acl-grant (quote b) (quote read) (quote r)) (acl-deny (quote b) (quote read) (quote r))))))
          (do
            (acl-dt-check!
              "subject a allowed"
              (acl-permit? db2 (quote a) (quote read) (quote r))
              true)
            (acl-dt-check!
              "subject b denied by override"
              (acl-permit? db2 (quote b) (quote read) (quote r))
              false)))
        (let
          ((db3 (acl-build-db (list (acl-actor (quote editors) (quote role)) (acl-grant (quote editors) (quote edit) (quote post1))))))
          (acl-dt-check!
            "role subject direct grant"
            (acl-permit? db3 (quote editors) (quote edit) (quote post1))
            true))
        (do
          (acl/load!
            (list
              (acl-grant (quote carol) (quote moderate) (quote thread1))))
          (acl-dt-check!
            "api permit via current db"
            (acl/permit? (quote carol) (quote moderate) (quote thread1))
            true)
          (acl-dt-check!
            "api deny via current db"
            (acl/permit? (quote carol) (quote read) (quote thread1))
            false))
        (do
          (acl/load! (list))
          (acl-dt-check!
            "api reload clears prior grants"
            (acl/permit? (quote carol) (quote moderate) (quote thread1))
            false))
        (acl-dt-check!
          "schema grant arity valid"
          (acl-fact-valid? (acl-grant (quote x) (quote read) (quote y)))
          true)
        (acl-dt-check!
          "schema bad arity invalid"
          (acl-fact-valid? (list (quote grant) (quote x)))
          false)
        (acl-dt-check!
          "schema unknown predicate invalid"
          (acl-fact-valid? (list (quote frobnicate) (quote x)))
          false)
        (acl-dt-check!
          "schema subject kind known"
          (acl-subject-kind? (quote service))
          true)
        (acl-dt-check!
          "schema resource kind unknown"
          (acl-resource-kind? (quote galaxy))
          false)
        (acl-dt-check!
          "schema known action"
          (acl-known-action? (quote moderate))
          true)
        (acl-dt-check!
          "grant constructor shape"
          (acl-grant (quote u) (quote read) (quote p))
          (list (quote grant) (quote u) (quote read) (quote p)))
        (acl-dt-check!
          "actor constructor shape"
          (acl-actor (quote u) (quote user))
          (list (quote actor) (quote u) (quote user)))))))

(define
  acl-direct-tests-run!
  (fn
    ()
    (do
      (set! acl-dt-pass 0)
      (set! acl-dt-fail 0)
      (set! acl-dt-failures (list))
      (acl-dt-run-all!)
      {:failures acl-dt-failures :total (+ acl-dt-pass acl-dt-fail) :passed acl-dt-pass :failed acl-dt-fail})))
