test(proxy_behavior): pin /key/list default-visibility matrix (8 scenarios)

Slice 9 of the management-endpoints behavior-pinning effort. For /key/list
the response IS the matrix: each of the 8 seeded actors calls the endpoint
with default filters and the test asserts set-equality between the returned
visible-token set (filtered to seeded tokens only, so unrelated rows can't
flap the assertion) and a pinned expected actor-set.

Pinned default visibility:

  * PROXY_ADMIN sees all 8 actors' keys.
  * Every other actor sees only their own key — including ORG_ADMIN
    (which had broader expectations going in but currently behaves
    same-as-internal-user for /key/list defaults) and TEAM_ADMIN (no
    team-aggregation without include_team_keys=true).

Future changes that broaden or narrow any single actor's default
visibility will turn this matrix red — exactly the regression signal we
want. Parameter-driven views (include_team_keys, filters) are deferred to
Slice 13 / PR2 follow-up.

Plan: https://www.notion.so/36643b8acdab8128a581ced0f6a4744d
This commit is contained in:
Yuneng Jiang 2026-05-19 21:45:29 -07:00
parent f5bd477292
commit 1afe43810b
No known key found for this signature in database

View file

@ -0,0 +1,84 @@
"""Slice 9 — actor visibility matrix for ``GET /key/list``.
For ``/key/list`` the *response itself* is the matrix: each actor calls the
endpoint with default filters and we assert which seeded actor keys end up in
the returned set. The set-equality assertion is filtered to seeded tokens
only, so unrelated rows in the DB (other tests, leftover dev data) can't flap
the matrix.
8 scenarios — one per actor. Expected visibility is pinned against the
current handler so future changes to the filter logic surface red.
"""
from typing import FrozenSet
import pytest
from .actors import Actor
pytestmark = pytest.mark.asyncio(loop_scope="session")
# Maps each actor → the set of seeded actors whose keys it is permitted to see
# under default ``/key/list`` (no filter params, page=1, size=10).
_VISIBILITY = {
Actor.PROXY_ADMIN: frozenset(Actor),
Actor.ORG_ADMIN: frozenset({Actor.ORG_ADMIN}),
Actor.TEAM_ADMIN: frozenset({Actor.TEAM_ADMIN}),
Actor.INTERNAL_USER: frozenset({Actor.INTERNAL_USER}),
Actor.OWNER: frozenset({Actor.OWNER}),
Actor.UNRELATED_SAME_ORG: frozenset({Actor.UNRELATED_SAME_ORG}),
Actor.CROSS_ORG_USER: frozenset({Actor.CROSS_ORG_USER}),
Actor.SERVICE_ACCOUNT: frozenset({Actor.SERVICE_ACCOUNT}),
}
@pytest.mark.parametrize(
"actor,expected_visible",
list(_VISIBILITY.items()),
ids=[a.value for a in _VISIBILITY],
)
async def test_key_list_visibility(
actor: Actor,
expected_visible: FrozenSet[Actor],
proxy_client,
world,
):
caller = world.keys[actor]
seeded_hashes = {a: world.keys[a].hashed for a in Actor}
hashed_to_actor = {h: a for a, h in seeded_hashes.items()}
# Use size=100 to ensure we see all 8 seeded keys for proxy_admin.
resp = await proxy_client.get(
"/key/list?size=100",
headers={"Authorization": f"Bearer {caller.cleartext}"},
)
assert (
resp.status_code == 200
), f"{actor.value} GET /key/list → {resp.status_code}: {resp.text}"
body = resp.json()
returned = body.get("keys", [])
# /key/list can return either token strings (default) or full objects.
# Default: list of dicts with ``token`` key. Reduce to hashes.
returned_hashes = set()
for entry in returned:
if isinstance(entry, dict):
tok = entry.get("token")
else:
tok = entry
if tok:
returned_hashes.add(tok)
visible_seeded = {
hashed_to_actor[h] for h in returned_hashes if h in hashed_to_actor
}
expected = set(expected_visible)
assert visible_seeded == expected, (
f"{actor.value} /key/list visibility differs:\n"
f" expected: {sorted(a.value for a in expected)}\n"
f" actual: {sorted(a.value for a in visible_seeded)}\n"
f" diff (missing): {sorted(a.value for a in (expected - visible_seeded))}\n"
f" diff (extra): {sorted(a.value for a in (visible_seeded - expected))}"
)