;; identity/tests/par.sx — pushed authorization requests (PAR, RFC 9126):
;; lodge the authorization params up front under a single-use request_uri,
;; then redeem it into the normal consent flow. The binding (client,
;; redirect, PKCE) carried by the pushed request is enforced at exchange.

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

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

(define idp-ev erlang-eval-ast)
(define idpnm (fn (v) (get v :name)))

(identity-load-oauth!)

;; ── pushed request redeems into consent ──────────────────────────

(id-par-test
  "authorize_pushed on a fresh request_uri asks for consent"
  (idpnm
    (idp-ev
      "O = identity_oauth:start(),\n       {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n       case identity_oauth:authorize_pushed(O, Ru) of\n         {consent_required, _} -> consent_required;\n         {error, W} -> W\n       end"))
  "consent_required")

;; ── full PAR flow ────────────────────────────────────────────────

(id-par-test
  "the full PAR flow yields a working token"
  (idpnm
    (idp-ev
      "O = identity_oauth:start(),\n       {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n       {consent_required, Rq} = identity_oauth:authorize_pushed(O, Ru),\n       {code, Cd} = identity_oauth:consent(O, Rq, allow),\n       {ok, A, _R} = identity_oauth:exchange(O, Cd, web, uri1, v),\n       case identity_oauth:introspect(O, A) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "active")

(id-par-test
  "the PAR token carries the pushed subject"
  (idpnm
    (idp-ev
      "O = identity_oauth:start(),\n       {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n       {consent_required, Rq} = identity_oauth:authorize_pushed(O, Ru),\n       {code, Cd} = identity_oauth:consent(O, Rq, allow),\n       {ok, A, _R} = identity_oauth:exchange(O, Cd, web, uri1, v),\n       case identity_oauth:introspect(O, A) of\n         {active, Subject, _, _} -> Subject\n       end"))
  "alice")

;; ── request_uri is single-use ────────────────────────────────────

(id-par-test
  "a request_uri cannot be redeemed twice"
  (idpnm
    (idp-ev
      "O = identity_oauth:start(),\n       {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n       identity_oauth:authorize_pushed(O, Ru),\n       case identity_oauth:authorize_pushed(O, Ru) of\n         {consent_required, _} -> reused;\n         {error, W} -> W\n       end"))
  "invalid_request_uri")

(id-par-test
  "an unknown request_uri is rejected"
  (idpnm
    (idp-ev
      "O = identity_oauth:start(),\n       Bogus = make_ref(),\n       case identity_oauth:authorize_pushed(O, Bogus) of\n         {consent_required, _} -> ok;\n         {error, W} -> W\n       end"))
  "invalid_request_uri")

;; ── the pushed binding is still enforced at exchange ─────────────

(id-par-test
  "a PAR-issued code still enforces PKCE"
  (idpnm
    (idp-ev
      "O = identity_oauth:start(),\n       {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n       {consent_required, Rq} = identity_oauth:authorize_pushed(O, Ru),\n       {code, Cd} = identity_oauth:consent(O, Rq, allow),\n       case identity_oauth:exchange(O, Cd, web, uri1, wrongverif) of\n         {ok, _, _} -> ok;\n         {error, W} -> W\n       end"))
  "invalid_grant")

(id-par-test
  "a PAR-issued code still enforces client binding"
  (idpnm
    (idp-ev
      "O = identity_oauth:start(),\n       {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, v),\n       {consent_required, Rq} = identity_oauth:authorize_pushed(O, Ru),\n       {code, Cd} = identity_oauth:consent(O, Rq, allow),\n       case identity_oauth:exchange(O, Cd, attacker, uri1, v) of\n         {ok, _, _} -> ok;\n         {error, W} -> W\n       end"))
  "invalid_grant")

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