;; identity/tests/pkce.sx — PKCE S256 (RFC 7636 §4.2). The challenge is
;; sha256(verifier) (compared as raw digests — base64url is wire encoding,
;; omitted); the verifier is presented at exchange and re-hashed. An S256
;; challenge is carried as a tagged {s256, Hash}; bare challenges remain
;; `plain` (RFC 7636 §4.1), so both methods coexist. Requires the fixed
;; erlang binary substrate (binary =:= + crypto:hash on binary literals).

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

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

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

(identity-load-oauth!)

;; Shared S256 prelude: verifier V, challenge = sha256(V), consented code.
(define
  idp-s256
  "V = <<\"verifier-abc-123\">>,\n   Ch = crypto:hash(sha256, V),\n   O = identity_oauth:start(),\n   {consent_required, Rq} = identity_oauth:authorize(O, web, uri1, read, alice, {s256, Ch}),\n   {code, Cd} = identity_oauth:consent(O, Rq, allow)")

;; ── S256 happy path ──────────────────────────────────────────────

(id-pkce-test
  "S256: exchange with the matching verifier yields an active token"
  (idpnm
    (idp-ev
      (str
        idp-s256
        ", {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-pkce-test
  "S256: the token carries the authorized subject"
  (idpnm
    (idp-ev
      (str
        idp-s256
        ", {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")

;; ── S256 rejects a wrong verifier ────────────────────────────────

(id-pkce-test
  "S256: a wrong verifier is invalid_grant"
  (idpnm
    (idp-ev
      (str
        idp-s256
        ", case identity_oauth:exchange(O, Cd, web, uri1, <<\"wrong-verifier\">>) of\n             {ok, _, _} -> ok;\n             {error, W} -> W\n           end")))
  "invalid_grant")

(id-pkce-test
  "S256: the bare challenge hash is not accepted as the verifier"
  (idpnm
    (idp-ev
      (str
        idp-s256
        ", case identity_oauth:exchange(O, Cd, web, uri1, Ch) of\n             {ok, _, _} -> ok;\n             {error, W} -> W\n           end")))
  "invalid_grant")

;; ── plain PKCE still works (coexistence) ─────────────────────────

(id-pkce-test
  "plain: a bare challenge still matches its verifier"
  (idpnm
    (idp-ev
      "O = identity_oauth:start(),\n       {consent_required, Rq} = identity_oauth:authorize(O, web, uri1, read, alice, plainverif),\n       {code, Cd} = identity_oauth:consent(O, Rq, allow),\n       {ok, A, _R} = identity_oauth:exchange(O, Cd, web, uri1, plainverif),\n       case identity_oauth:introspect(O, A) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "active")

;; ── S256 threads through PAR ──────────────────────────────────────

(id-pkce-test
  "S256 works through a pushed authorization request"
  (idpnm
    (idp-ev
      "V = <<\"par-verifier\">>,\n       Ch = crypto:hash(sha256, V),\n       O = identity_oauth:start(),\n       {ok, Ru} = identity_oauth:push_authorization_request(O, web, uri1, read, alice, {s256, Ch}),\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")

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