;; identity/tests/api.sx — the service facade end-to-end: login issues a ;; session + token, verify proves identity, revoke and logout take effect ;; immediately. Exercises session + token + registry through one door. (define id-api-test-count 0) (define id-api-test-pass 0) (define id-api-test-fails (list)) (define id-api-test (fn (name actual expected) (set! id-api-test-count (+ id-api-test-count 1)) (if (= actual expected) (set! id-api-test-pass (+ id-api-test-pass 1)) (append! id-api-test-fails {:name name :expected expected :actual actual})))) (define ida-ev erlang-eval-ast) (define idanm (fn (v) (get v :name))) (identity-load-all!) ;; ── login + verify (happy path) ────────────────────────────────── (id-api-test "login then verify is active" (idanm (ida-ev "Svc = identity:start(),\n {ok, _Sid, Tok} = identity:login(Svc, alice, web, read),\n case identity:verify(Svc, Tok) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end")) "active") (id-api-test "verify returns the logged-in subject" (idanm (ida-ev "Svc = identity:start(),\n {ok, _Sid, Tok} = identity:login(Svc, alice, web, read),\n case identity:verify(Svc, Tok) of\n {active, Subject, _, _} -> Subject\n end")) "alice") (id-api-test "verify returns the granted scope" (idanm (ida-ev "Svc = identity:start(),\n {ok, _Sid, Tok} = identity:login(Svc, bob, cli, write),\n case identity:verify(Svc, Tok) of\n {active, _, _, Scope} -> Scope\n end")) "write") ;; ── revoke is real through the facade ──────────────────────────── (id-api-test "revoked token verifies inactive immediately" (idanm (ida-ev "Svc = identity:start(),\n {ok, _Sid, Tok} = identity:login(Svc, alice, web, read),\n identity:revoke(Svc, Tok),\n case identity:verify(Svc, Tok) of\n {active, _, _, _} -> still_valid;\n {inactive} -> inactive\n end")) "inactive") ;; ── session lifecycle through the facade ───────────────────────── (id-api-test "fresh session reports active" (idanm (ida-ev "Svc = identity:start(),\n {ok, Sid, _Tok} = identity:login(Svc, alice, web, read),\n identity:session_status(Svc, Sid)")) "active") (id-api-test "logout makes the session gone" (idanm (ida-ev "Svc = identity:start(),\n {ok, Sid, _Tok} = identity:login(Svc, alice, web, read),\n identity:logout(Svc, Sid),\n identity:session_status(Svc, Sid)")) "gone") (id-api-test "status of an unknown session is gone" (idanm (ida-ev "Svc = identity:start(),\n identity:session_status(Svc, 999)")) "gone") ;; ── independence: logins do not bleed into each other ──────────── (id-api-test "revoking one login leaves the other active" (idanm (ida-ev "Svc = identity:start(),\n {ok, _S1, T1} = identity:login(Svc, alice, web, read),\n {ok, _S2, T2} = identity:login(Svc, bob, cli, write),\n identity:revoke(Svc, T1),\n case identity:verify(Svc, T2) of\n {active, Subject, _, _} -> Subject;\n {inactive} -> inactive\n end")) "bob") (id-api-test "logging out one session leaves the other active" (idanm (ida-ev "Svc = identity:start(),\n {ok, S1, _T1} = identity:login(Svc, alice, web, read),\n {ok, S2, _T2} = identity:login(Svc, alice, cli, read),\n identity:logout(Svc, S1),\n identity:session_status(Svc, S2)")) "active") ;; ── coordinator deregisters on a session_expired notification ──── ;; A live idle session fires its own `after` timeout and notifies its ;; owner (the coordinator), which then deregisters it — timeout-driven, ;; never swept. The owner-internal path can't be observed by driving the ;; scheduler idle from the test's main process, so we assert the handler ;; directly: the mailbox is FIFO, so the expiry notification is processed ;; before the following status query. (id-api-test "session_expired notification deregisters the session" (idanm (ida-ev "Svc = identity:start(),\n {ok, Sid, _Tok} = identity:login(Svc, alice, web, read, 50),\n active = identity:session_status(Svc, Sid),\n Svc ! {session_expired, Sid},\n identity:session_status(Svc, Sid)")) "gone") (define id-api-test-summary (str "api " id-api-test-pass "/" id-api-test-count))