;; lib/acl/tests/inherit.sx — Phase 2: inheritance (groups, resource trees,
;; role expansion) with deny-overrides.

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

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

(define acl-it-p? (fn (db s a r) (acl-permit? db s a r)))

(define
  acl-it-run-all!
  (fn
    ()
    (do
      (let
        ((db (acl-build-db (list (acl-member-of (quote alice) (quote team)) (acl-grant (quote team) (quote read) (quote doc))))))
        (do
          (acl-it-check!
            "group grant reaches member"
            (acl-it-p? db (quote alice) (quote read) (quote doc))
            true)
          (acl-it-check!
            "group grant: non-member excluded"
            (acl-it-p? db (quote bob) (quote read) (quote doc))
            false)
          (acl-it-check!
            "group grant: wrong action"
            (acl-it-p? db (quote alice) (quote edit) (quote doc))
            false)))
      (let
        ((db (acl-build-db (list (acl-member-of (quote alice) (quote team)) (acl-member-of (quote team) (quote org)) (acl-member-of (quote org) (quote company)) (acl-grant (quote company) (quote read) (quote doc))))))
        (do
          (acl-it-check!
            "deep nested group grant reaches leaf member"
            (acl-it-p? db (quote alice) (quote read) (quote doc))
            true)
          (acl-it-check!
            "intermediate group also covered"
            (acl-it-p? db (quote team) (quote read) (quote doc))
            true)
          (acl-it-check!
            "mid group org covered"
            (acl-it-p? db (quote org) (quote read) (quote doc))
            true)))
      (let
        ((db (acl-build-db (list (acl-member-of (quote a) (quote b)) (acl-member-of (quote b) (quote a)) (acl-grant (quote b) (quote read) (quote r))))))
        (do
          (acl-it-check!
            "cyclic membership terminates and grants"
            (acl-it-p? db (quote a) (quote read) (quote r))
            true)
          (acl-it-check!
            "cyclic membership covers both"
            (acl-it-p? db (quote b) (quote read) (quote r))
            true)))
      (let
        ((db (acl-build-db (list (acl-child-of (quote sec) (quote chap)) (acl-child-of (quote chap) (quote book)) (acl-grant (quote u) (quote read) (quote book))))))
        (do
          (acl-it-check!
            "parent grant reaches direct child"
            (acl-it-p? db (quote u) (quote read) (quote chap))
            true)
          (acl-it-check!
            "parent grant reaches deep descendant"
            (acl-it-p? db (quote u) (quote read) (quote sec))
            true)
          (acl-it-check!
            "parent grant covers parent itself"
            (acl-it-p? db (quote u) (quote read) (quote book))
            true)
          (acl-it-check!
            "child grant does not climb to parent"
            (acl-it-p?
              (acl-build-db
                (list
                  (acl-child-of (quote sec) (quote book))
                  (acl-grant (quote u) (quote read) (quote sec))))
              (quote u)
              (quote read)
              (quote book))
            false)))
      (let
        ((db (acl-build-db (list (acl-member-of (quote alice) (quote team)) (acl-child-of (quote post1) (quote board)) (acl-grant (quote team) (quote comment) (quote board))))))
        (do
          (acl-it-check!
            "group + resource: member on child resource"
            (acl-it-p? db (quote alice) (quote comment) (quote post1))
            true)
          (acl-it-check!
            "group + resource: member on parent resource"
            (acl-it-p? db (quote alice) (quote comment) (quote board))
            true)))
      (let
        ((db (acl-build-db (list (acl-member-of (quote bob) (quote editor)) (acl-role-grant (quote editor) (quote edit) (quote page1)) (acl-role-grant (quote editor) (quote read) (quote page1))))))
        (do
          (acl-it-check!
            "role confers edit to member"
            (acl-it-p? db (quote bob) (quote edit) (quote page1))
            true)
          (acl-it-check!
            "role confers read to member"
            (acl-it-p? db (quote bob) (quote read) (quote page1))
            true)
          (acl-it-check!
            "role: capability not in tuple denied"
            (acl-it-p? db (quote bob) (quote moderate) (quote page1))
            false)
          (acl-it-check!
            "role: non-member excluded"
            (acl-it-p? db (quote eve) (quote edit) (quote page1))
            false)))
      (let
        ((db (acl-build-db (list (acl-member-of (quote bob) (quote editor)) (acl-child-of (quote draft) (quote page1)) (acl-role-grant (quote editor) (quote edit) (quote page1))))))
        (acl-it-check!
          "role grant flows to child resource"
          (acl-it-p? db (quote bob) (quote edit) (quote draft))
          true))
      (let
        ((db (acl-build-db (list (acl-member-of (quote alice) (quote team)) (acl-grant (quote team) (quote read) (quote doc)) (acl-deny (quote alice) (quote read) (quote doc))))))
        (acl-it-check!
          "explicit deny beats inherited group allow"
          (acl-it-p? db (quote alice) (quote read) (quote doc))
          false))
      (let
        ((db (acl-build-db (list (acl-member-of (quote alice) (quote team)) (acl-grant (quote alice) (quote read) (quote doc)) (acl-deny (quote team) (quote read) (quote doc))))))
        (do
          (acl-it-check!
            "group deny inherits and overrides direct grant"
            (acl-it-p? db (quote alice) (quote read) (quote doc))
            false)
          (acl-it-check!
            "group deny: another member also blocked"
            (acl-it-p? db (quote team) (quote read) (quote doc))
            false)))
      (let
        ((db (acl-build-db (list (acl-child-of (quote sec) (quote book)) (acl-grant (quote u) (quote read) (quote sec)) (acl-deny (quote u) (quote read) (quote book))))))
        (acl-it-check!
          "ancestor deny overrides descendant grant"
          (acl-it-p? db (quote u) (quote read) (quote sec))
          false))
      (let
        ((db (acl-build-db (list (acl-member-of (quote alice) (quote team)) (acl-grant (quote team) (quote read) (quote doc)) (acl-grant (quote team) (quote edit) (quote doc)) (acl-deny (quote alice) (quote edit) (quote doc))))))
        (do
          (acl-it-check!
            "deny on edit leaves inherited read intact"
            (acl-it-p? db (quote alice) (quote read) (quote doc))
            true)
          (acl-it-check!
            "deny on edit blocks edit"
            (acl-it-p? db (quote alice) (quote edit) (quote doc))
            false)))
      (let
        ((db (acl-build-db (list (acl-member-of (quote alice) (quote team)) (acl-deny (quote team) (quote read) (quote doc))))))
        (acl-it-check!
          "inherited deny, no grant: denied"
          (acl-it-p? db (quote alice) (quote read) (quote doc))
          false))
      (let
        ((db (acl-build-db (list (acl-child-of (quote a) (quote root)) (acl-child-of (quote b) (quote root)) (acl-grant (quote u) (quote read) (quote root)) (acl-deny (quote u) (quote read) (quote a))))))
        (do
          (acl-it-check!
            "deny on sibling a blocks a"
            (acl-it-p? db (quote u) (quote read) (quote a))
            false)
          (acl-it-check!
            "deny on sibling a leaves b permitted"
            (acl-it-p? db (quote u) (quote read) (quote b))
            true)
          (acl-it-check!
            "root itself still permitted"
            (acl-it-p? db (quote u) (quote read) (quote root))
            true)))
      (let
        ((db (acl-build-db (list (acl-grant (quote x) (quote read) (quote y))))))
        (acl-it-check!
          "direct grant under inheritance ruleset"
          (acl-it-p? db (quote x) (quote read) (quote y))
          true)))))

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