identity: service facade api.sx — login/verify/revoke/logout (10 tests, Phase 1 complete)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s

identity:start() spawns one coordinator owning the token table + session
registry and exposes the whole-domain ops. The coordinator is the owner
sessions notify on idle timeout, so an expired session deregisters itself
— timeout-driven, never swept. verify/2 answers identity only ({active,
Subject, Client, Scope}); permission is delegated to acl. 39/39.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-07 00:00:05 +00:00
parent 938e90455d
commit 064bbf18b3
6 changed files with 166 additions and 6 deletions

111
lib/identity/tests/api.sx Normal file
View File

@@ -0,0 +1,111 @@
;; 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))