;; identity/tests/exchange.sx — token exchange (RFC 8693 §2.1): downscope a
;; valid access token into a new independent token for a downstream service.

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

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

(define idx-ev erlang-eval-ast)
(define idxnm (fn (v) (get v :name)))

(identity-load-oauth!)

;; Shared prelude: an access token A for alice with scope [read, write].
(define
  idx-token
  "O = identity_oauth:start(),\n   {consent_required, Rq} = identity_oauth:authorize(O, web, uri1, [read, write], alice, v),\n   {code, Cd} = identity_oauth:consent(O, Rq, allow),\n   {ok, A, _R} = identity_oauth:exchange(O, Cd, web, uri1, v)")

;; ── downscoping ──────────────────────────────────────────────────

(id-xchg-test
  "exchange downscopes to a subset"
  (idxnm
    (idx-ev
      (str
        idx-token
        ", {ok, X} = identity_oauth:token_exchange(O, A, [read]),\n           case identity_oauth:introspect(O, X) of\n             {active, _, _, [read]} -> downscoped;\n             {active, _, _, _} -> other;\n             {inactive} -> inactive\n           end")))
  "downscoped")

(id-xchg-test
  "the exchanged token keeps the subject"
  (idxnm
    (idx-ev
      (str
        idx-token
        ", {ok, X} = identity_oauth:token_exchange(O, A, [read]),\n           case identity_oauth:introspect(O, X) of\n             {active, Subject, _, _} -> Subject\n           end")))
  "alice")

(id-xchg-test
  "exchange to the same scope is allowed"
  (idxnm
    (idx-ev
      (str
        idx-token
        ", {ok, X} = identity_oauth:token_exchange(O, A, [read, write]),\n           case identity_oauth:introspect(O, X) of\n             {active, _, _, [read, write]} -> full;\n             {active, _, _, _} -> other;\n             {inactive} -> inactive\n           end")))
  "full")

;; ── scope cannot be widened ──────────────────────────────────────

(id-xchg-test
  "exchange cannot widen beyond the subject token's scope"
  (idxnm
    (idx-ev
      "O = identity_oauth:start(),\n       {consent_required, Rq} = identity_oauth:authorize(O, web, uri1, [read], alice, v),\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:token_exchange(O, A, [read, write]) of\n         {ok, _} -> widened;\n         {error, W} -> W\n       end"))
  "invalid_scope")

;; ── inactive subject token cannot be exchanged ───────────────────

(id-xchg-test
  "exchanging a revoked subject token is invalid_grant"
  (idxnm
    (idx-ev
      (str
        idx-token
        ", identity_oauth:revoke(O, A),\n           case identity_oauth:token_exchange(O, A, [read]) of\n             {ok, _} -> issued;\n             {error, W} -> W\n           end")))
  "invalid_grant")

;; ── independent lifecycles ───────────────────────────────────────

(id-xchg-test
  "revoking the subject token does not revoke the exchanged token"
  (idxnm
    (idx-ev
      (str
        idx-token
        ", {ok, X} = identity_oauth:token_exchange(O, A, [read]),\n           identity_oauth:revoke(O, A),\n           case identity_oauth:introspect(O, X) of\n             {active, _, _, _} -> still_active;\n             {inactive} -> inactive\n           end")))
  "still_active")

(id-xchg-test
  "revoking the exchanged token does not revoke the subject token"
  (idxnm
    (idx-ev
      (str
        idx-token
        ", {ok, X} = identity_oauth:token_exchange(O, A, [read]),\n           identity_oauth:revoke(O, X),\n           case identity_oauth:introspect(O, A) of\n             {active, _, _, _} -> still_active;\n             {inactive} -> inactive\n           end")))
  "still_active")

;; ── chained downscoping ──────────────────────────────────────────

(id-xchg-test
  "an exchanged token can itself be exchanged (chain)"
  (idxnm
    (idx-ev
      (str
        idx-token
        ", {ok, X1} = identity_oauth:token_exchange(O, A, [read, write]),\n           {ok, X2} = identity_oauth:token_exchange(O, X1, [read]),\n           case identity_oauth:introspect(O, X2) of\n             {active, _, _, [read]} -> chained;\n             {active, _, _, _} -> other;\n             {inactive} -> inactive\n           end")))
  "chained")

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