;; identity/tests/expiry.sx — access-token expiry on a logical clock
;; (RFC 6749 §4.2.2 expires_in). `advance` stands in for time passing;
;; introspect returns inactive once the clock reaches a token's expiry.
;; Refresh mints a fresh short-lived access token — the point of refresh.

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

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

(define ide-ev erlang-eval-ast)
(define idenm (fn (v) (get v :name)))

(identity-load-token!)

;; ── within TTL is active; past TTL is inactive ───────────────────

(id-expiry-test
  "a token within its TTL is active"
  (idenm
    (ide-ev
      "R = identity_tokens:start(),\n       {ok, T} = identity_tokens:issue(R, alice, web, read, 100),\n       identity_tokens:advance(R, 50),\n       case identity_tokens:introspect(R, T) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "active")

(id-expiry-test
  "a token at its TTL boundary is expired"
  (idenm
    (ide-ev
      "R = identity_tokens:start(),\n       {ok, T} = identity_tokens:issue(R, alice, web, read, 100),\n       identity_tokens:advance(R, 100),\n       case identity_tokens:introspect(R, T) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "inactive")

(id-expiry-test
  "a token just before its TTL is still active"
  (idenm
    (ide-ev
      "R = identity_tokens:start(),\n       {ok, T} = identity_tokens:issue(R, alice, web, read, 100),\n       identity_tokens:advance(R, 99),\n       case identity_tokens:introspect(R, T) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "active")

;; ── no TTL (infinity) never expires ──────────────────────────────

(id-expiry-test
  "a token issued without a TTL never expires"
  (idenm
    (ide-ev
      "R = identity_tokens:start(),\n       {ok, T} = identity_tokens:issue(R, alice, web, read),\n       identity_tokens:advance(R, 100000),\n       case identity_tokens:introspect(R, T) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "active")

;; ── refresh mints a fresh short-lived token ──────────────────────

(id-expiry-test
  "refresh renews access after the old token expired"
  (idenm
    (ide-ev
      "R = identity_tokens:start(),\n       {ok, A, Rt} = identity_tokens:issue_grant(R, alice, web, read, 100),\n       identity_tokens:advance(R, 100),\n       inactive = case identity_tokens:introspect(R, A) of\n         {active, _, _, _} -> active; {inactive} -> inactive end,\n       {ok, A2, _R2} = identity_tokens:refresh(R, Rt),\n       case identity_tokens:introspect(R, A2) of\n         {active, _, _, _} -> renewed;\n         {inactive} -> inactive\n       end"))
  "renewed")

(id-expiry-test
  "the renewed token also expires after its own TTL"
  (idenm
    (ide-ev
      "R = identity_tokens:start(),\n       {ok, _A, Rt} = identity_tokens:issue_grant(R, alice, web, read, 100),\n       identity_tokens:advance(R, 100),\n       {ok, A2, _R2} = identity_tokens:refresh(R, Rt),\n       identity_tokens:advance(R, 100),\n       case identity_tokens:introspect(R, A2) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "inactive")

;; ── the logical clock ────────────────────────────────────────────

(id-expiry-test
  "the clock starts at zero and advances"
  (ide-ev
    "R = identity_tokens:start(),\n     identity_tokens:advance(R, 7),\n     identity_tokens:advance(R, 35),\n     identity_tokens:now(R)")
  42)

;; ── expiry composes with revocation ──────────────────────────────

(id-expiry-test
  "an expired token is also inactive after revoke (no contradiction)"
  (idenm
    (ide-ev
      "R = identity_tokens:start(),\n       {ok, T} = identity_tokens:issue(R, alice, web, read, 100),\n       identity_tokens:advance(R, 200),\n       identity_tokens:revoke(R, T),\n       case identity_tokens:introspect(R, T) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "inactive")

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