;; identity/tests/cache.sx — delegated grant-verification cache. Proves ;; the cache is live (hits/misses) AND that revocation stays real: a ;; revoked token never reads valid out of the cache, because any revoke ;; bumps the generation and forces re-validation. (define id-cache-test-count 0) (define id-cache-test-pass 0) (define id-cache-test-fails (list)) (define id-cache-test (fn (name actual expected) (set! id-cache-test-count (+ id-cache-test-count 1)) (if (= actual expected) (set! id-cache-test-pass (+ id-cache-test-pass 1)) (append! id-cache-test-fails {:name name :expected expected :actual actual})))) (define idc-ev erlang-eval-ast) (define idcnm (fn (v) (get v :name))) (identity-load-token!) (identity-load-cache!) ;; ── delegation: cache forwards to the registry ─────────────────── (id-cache-test "introspect through the cache returns active" (idcnm (idc-ev "C = identity_grant_cache:start(),\n {ok, T} = identity_grant_cache:issue(C, alice, web, read),\n case identity_grant_cache:introspect(C, T) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end")) "active") ;; ── the cache is actually caching ──────────────────────────────── (id-cache-test "a repeated introspect is a cache hit" (idc-ev "C = identity_grant_cache:start(),\n {ok, T} = identity_grant_cache:issue(C, alice, web, read),\n identity_grant_cache:introspect(C, T),\n identity_grant_cache:introspect(C, T),\n case identity_grant_cache:stats(C) of {H, _} -> H end") 1) (id-cache-test "the first introspect of a token is a miss" (idc-ev "C = identity_grant_cache:start(),\n {ok, T} = identity_grant_cache:issue(C, alice, web, read),\n identity_grant_cache:introspect(C, T),\n identity_grant_cache:introspect(C, T),\n case identity_grant_cache:stats(C) of {_, M} -> M end") 1) ;; ── revocation stays real through the cache (the centrepiece) ───── (id-cache-test "a revoked token introspects inactive through the cache" (idcnm (idc-ev "C = identity_grant_cache:start(),\n {ok, T} = identity_grant_cache:issue(C, alice, web, read),\n identity_grant_cache:introspect(C, T),\n identity_grant_cache:revoke(C, T),\n case identity_grant_cache:introspect(C, T) of\n {active, _, _, _} -> still_valid;\n {inactive} -> inactive\n end")) "inactive") (id-cache-test "revoke invalidates the cache (post-revoke read re-validates)" (idc-ev "C = identity_grant_cache:start(),\n {ok, T} = identity_grant_cache:issue(C, alice, web, read),\n identity_grant_cache:introspect(C, T),\n identity_grant_cache:revoke(C, T),\n identity_grant_cache:introspect(C, T),\n case identity_grant_cache:stats(C) of {_, M} -> M end") 2) ;; ── cascade visibility through the cache ────────────────────────── (id-cache-test "cascade revocation is visible through the cache" (idcnm (idc-ev "C = identity_grant_cache:start(),\n {ok, A, R} = identity_grant_cache:issue_grant(C, alice, web, read),\n identity_grant_cache:introspect(C, A),\n identity_grant_cache:revoke(C, R),\n case identity_grant_cache:introspect(C, A) of\n {active, _, _, _} -> still_valid;\n {inactive} -> inactive\n end")) "inactive") ;; ── a sibling token re-validates correctly after a revoke ──────── (id-cache-test "revoking one token leaves an independent token valid" (idcnm (idc-ev "C = identity_grant_cache:start(),\n {ok, A} = identity_grant_cache:issue(C, alice, web, read),\n {ok, B} = identity_grant_cache:issue(C, bob, cli, write),\n identity_grant_cache:introspect(C, A),\n identity_grant_cache:introspect(C, B),\n identity_grant_cache:revoke(C, A),\n case identity_grant_cache:introspect(C, B) of\n {active, Subject, _, _} -> Subject;\n {inactive} -> inactive\n end")) "bob") ;; ── refresh flows through the cache and stays correct ──────────── (id-cache-test "a refreshed token introspects active through the cache" (idcnm (idc-ev "C = identity_grant_cache:start(),\n {ok, _A, R} = identity_grant_cache:issue_grant(C, alice, web, read),\n {ok, A2, _R2} = identity_grant_cache:refresh(C, R),\n case identity_grant_cache:introspect(C, A2) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end")) "active") ;; ── unknown token is inactive, and cached as such ──────────────── (id-cache-test "an unknown token introspects inactive through the cache" (idcnm (idc-ev "C = identity_grant_cache:start(),\n Bogus = make_ref(),\n case identity_grant_cache:introspect(C, Bogus) of\n {active, _, _, _} -> active;\n {inactive} -> inactive\n end")) "inactive") (define id-cache-test-summary (str "cache " id-cache-test-pass "/" id-cache-test-count))