;; identity/tests/delegation.sx — the identity -> acl boundary.
;; Authentication (identity) gates BEFORE authorization (acl): an inactive
;; token is unauthenticated (401) and acl is never consulted; only an
;; authenticated subject's request is delegated to acl for permit/deny.

(define id-deleg-test-count 0)
(define id-deleg-test-pass 0)
(define id-deleg-test-fails (list))

(define
  id-deleg-test
  (fn
    (name actual expected)
    (set! id-deleg-test-count (+ id-deleg-test-count 1))
    (if
      (= actual expected)
      (set! id-deleg-test-pass (+ id-deleg-test-pass 1))
      (append! id-deleg-test-fails {:name name :expected expected :actual actual}))))

(define idl-ev erlang-eval-ast)
(define idlnm (fn (v) (get v :name)))

(identity-load-delegation!)

;; Shared prelude: a token registry, a stub acl, and a token granting
;; [read, write] to alice, all bound.
(define
  idl-setup
  "R = identity_tokens:start(),\n   A = identity_delegation:stub_acl(),\n   {ok, T} = identity_tokens:issue(R, alice, web, [read, write])")

;; ── authenticated + acl permits ──────────────────────────────────

(id-deleg-test
  "an authenticated, permitted request returns the subject"
  (idlnm
    (idl-ev
      (str
        idl-setup
        ", case identity_delegation:check(R, A, T, read, doc1) of\n             {ok, S} -> S;\n             {error, W} -> W\n           end")))
  "alice")

;; ── authenticated + acl denies → 403 ─────────────────────────────

(id-deleg-test
  "an authenticated but unpermitted request is forbidden"
  (idlnm
    (idl-ev
      "R = identity_tokens:start(),\n       A = identity_delegation:stub_acl(),\n       {ok, T} = identity_tokens:issue(R, alice, web, [read]),\n       case identity_delegation:check(R, A, T, write, doc1) of\n         {ok, _} -> permitted;\n         {error, W} -> W\n       end"))
  "forbidden")

;; ── unauthenticated → 401, acl never consulted ───────────────────

(id-deleg-test
  "a revoked token is unauthenticated, not forbidden"
  (idlnm
    (idl-ev
      (str
        idl-setup
        ", identity_tokens:revoke(R, T),\n           case identity_delegation:check(R, A, T, read, doc1) of\n             {ok, _} -> permitted;\n             {error, W} -> W\n           end")))
  "unauthenticated")

(id-deleg-test
  "an unknown token is unauthenticated"
  (idlnm
    (idl-ev
      "R = identity_tokens:start(),\n       A = identity_delegation:stub_acl(),\n       Bogus = make_ref(),\n       case identity_delegation:check(R, A, Bogus, read, doc1) of\n         {ok, _} -> permitted;\n         {error, W} -> W\n       end"))
  "unauthenticated")

(id-deleg-test
  "an expired token is unauthenticated"
  (idlnm
    (idl-ev
      "R = identity_tokens:start(),\n       A = identity_delegation:stub_acl(),\n       {ok, T} = identity_tokens:issue(R, alice, web, [read], 100),\n       identity_tokens:advance(R, 100),\n       case identity_delegation:check(R, A, T, read, doc1) of\n         {ok, _} -> permitted;\n         {error, W} -> W\n       end"))
  "unauthenticated")

;; ── 401 takes precedence over 403 (identity gates first) ─────────

(id-deleg-test
  "a revoked token with no matching scope is still unauthenticated"
  (idlnm
    (idl-ev
      "R = identity_tokens:start(),\n       A = identity_delegation:stub_acl(),\n       {ok, T} = identity_tokens:issue(R, alice, web, [admin]),\n       identity_tokens:revoke(R, T),\n       case identity_delegation:check(R, A, T, read, doc1) of\n         {ok, _} -> permitted;\n         {error, W} -> W\n       end"))
  "unauthenticated")

;; ── acl is what decides for an authenticated subject ─────────────

(id-deleg-test
  "the same subject is permitted one action and denied another"
  (idl-ev
    "R = identity_tokens:start(),\n     A = identity_delegation:stub_acl(),\n     {ok, T} = identity_tokens:issue(R, alice, web, [read]),\n     Allowed = case identity_delegation:check(R, A, T, read, doc1) of\n       {ok, _} -> 1; {error, _} -> 0 end,\n     Denied = case identity_delegation:check(R, A, T, write, doc1) of\n       {ok, _} -> 1; {error, _} -> 0 end,\n     Allowed - Denied")
  1)

(id-deleg-test
  "identity does not widen permission beyond the token scope"
  (idlnm
    (idl-ev
      "R = identity_tokens:start(),\n       A = identity_delegation:stub_acl(),\n       {ok, T} = identity_tokens:issue(R, alice, web, [read, write]),\n       case identity_delegation:check(R, A, T, delete, doc1) of\n         {ok, _} -> permitted;\n         {error, W} -> W\n       end"))
  "forbidden")

(define
  id-deleg-test-summary
  (str "delegation " id-deleg-test-pass "/" id-deleg-test-count))
