From 1afe43810b47e25af94c1ec19200a8be09e25035 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Tue, 19 May 2026 21:45:29 -0700 Subject: [PATCH] test(proxy_behavior): pin /key/list default-visibility matrix (8 scenarios) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../management/test_key_list.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/proxy_behavior/management/test_key_list.py diff --git a/tests/proxy_behavior/management/test_key_list.py b/tests/proxy_behavior/management/test_key_list.py new file mode 100644 index 00000000000..8cbb21b05fd --- /dev/null +++ b/tests/proxy_behavior/management/test_key_list.py @@ -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))}" + )