;; identity/tests/device.sx — device authorization grant (RFC 8628):
;; authorize → poll(pending) → approve/deny out-of-band → poll(token/denied).

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

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

(define idd-ev erlang-eval-ast)
(define iddnm (fn (v) (get v :name)))

(identity-load-device!)

;; ── polling before approval ──────────────────────────────────────

(id-device-test
  "polling a pending device code is authorization_pending"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       {ok, Dc, _Uc} = identity_device:authorize(D, tv, watch),\n       case identity_device:poll(D, Dc) of\n         {ok, _} -> got;\n         {error, W} -> W\n       end"))
  "authorization_pending")

;; ── approve → token ──────────────────────────────────────────────

(id-device-test
  "after approval, polling yields a working token"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       {ok, Dc, Uc} = identity_device:authorize(D, tv, watch),\n       identity_device:approve(D, Uc, alice),\n       {ok, T} = identity_device:poll(D, Dc),\n       case identity_device:introspect(D, T) of\n         {active, _, _, _} -> active;\n         {inactive} -> inactive\n       end"))
  "active")

(id-device-test
  "the device token carries the approving subject"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       {ok, Dc, Uc} = identity_device:authorize(D, tv, watch),\n       identity_device:approve(D, Uc, alice),\n       {ok, T} = identity_device:poll(D, Dc),\n       case identity_device:introspect(D, T) of\n         {active, Subject, _, _} -> Subject\n       end"))
  "alice")

(id-device-test
  "the device token carries the requested scope"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       {ok, Dc, Uc} = identity_device:authorize(D, tv, stream),\n       identity_device:approve(D, Uc, alice),\n       {ok, T} = identity_device:poll(D, Dc),\n       case identity_device:introspect(D, T) of\n         {active, _, _, Scope} -> Scope\n       end"))
  "stream")

;; ── deny ─────────────────────────────────────────────────────────

(id-device-test
  "after denial, polling is access_denied"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       {ok, Dc, Uc} = identity_device:authorize(D, tv, watch),\n       identity_device:deny(D, Uc),\n       case identity_device:poll(D, Dc) of\n         {ok, _} -> got;\n         {error, W} -> W\n       end"))
  "access_denied")

;; ── unknown codes ────────────────────────────────────────────────

(id-device-test
  "polling an unknown device code is invalid_grant"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       Bogus = make_ref(),\n       case identity_device:poll(D, Bogus) of\n         {ok, _} -> got;\n         {error, W} -> W\n       end"))
  "invalid_grant")

(id-device-test
  "approving an unknown user code is unknown_code"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       Bogus = make_ref(),\n       case identity_device:approve(D, Bogus, alice) of\n         ok -> ok;\n         {error, W} -> W\n       end"))
  "unknown_code")

;; ── single-use device code ───────────────────────────────────────

(id-device-test
  "the device code is single-use after issuing a token"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       {ok, Dc, Uc} = identity_device:authorize(D, tv, watch),\n       identity_device:approve(D, Uc, alice),\n       identity_device:poll(D, Dc),\n       case identity_device:poll(D, Dc) of\n         {ok, _} -> got;\n         {error, W} -> W\n       end"))
  "invalid_grant")

;; ── guarded transitions ──────────────────────────────────────────

(id-device-test
  "approving an already-denied request is rejected"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       {ok, _Dc, Uc} = identity_device:authorize(D, tv, watch),\n       identity_device:deny(D, Uc),\n       case identity_device:approve(D, Uc, alice) of\n         ok -> ok;\n         {error, W} -> W\n       end"))
  "denied")

;; ── independence ─────────────────────────────────────────────────

(id-device-test
  "two device requests are independent"
  (iddnm
    (idd-ev
      "D = identity_device:start(),\n       {ok, Dc1, Uc1} = identity_device:authorize(D, tv, watch),\n       {ok, Dc2, _Uc2} = identity_device:authorize(D, cli, deploy),\n       identity_device:approve(D, Uc1, alice),\n       case identity_device:poll(D, Dc2) of\n         {ok, _} -> got;\n         {error, W} -> W\n       end"))
  "authorization_pending")

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