e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
143 lines
5.0 KiB
Python
143 lines
5.0 KiB
Python
"""Partner assignment / visibility for non-admin users."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastapi import HTTPException
|
|
import pytest
|
|
|
|
from deeptutor.multi_user import partner_access
|
|
from deeptutor.multi_user.grants import empty_grant, normalize_grant
|
|
|
|
|
|
class _FakeManager:
|
|
def __init__(self, partners: list[dict]) -> None:
|
|
self._partners = partners
|
|
|
|
def list_partners(self) -> list[dict]:
|
|
return self._partners
|
|
|
|
|
|
def _patch_manager(monkeypatch, partners: list[dict]) -> None:
|
|
import deeptutor.services.partners as pkg
|
|
|
|
monkeypatch.setattr(pkg, "get_partner_manager", lambda: _FakeManager(partners))
|
|
|
|
|
|
# ── Grant shape ───────────────────────────────────────────────
|
|
|
|
|
|
def test_empty_grant_has_partners_list():
|
|
assert empty_grant("u")["partners"] == []
|
|
|
|
|
|
def test_normalize_grant_round_trips_partners():
|
|
grant = normalize_grant(
|
|
"u_alice",
|
|
{"partners": [{"partner_id": "p1"}, {"id": "p2"}, "not-a-dict", {}]},
|
|
)
|
|
# Non-dict entries are dropped; dict entries (even empty) survive.
|
|
assert grant["partners"] == [{"partner_id": "p1"}, {"id": "p2"}, {}]
|
|
|
|
|
|
def test_normalize_grant_missing_partners_defaults_empty():
|
|
assert normalize_grant("u_alice", {"skills": []})["partners"] == []
|
|
|
|
|
|
# ── assigned_partner_ids ──────────────────────────────────────
|
|
|
|
|
|
def test_assigned_partner_ids_reads_grant(as_user, monkeypatch):
|
|
monkeypatch.setattr(
|
|
partner_access,
|
|
"load_grant",
|
|
lambda uid: {"partners": [{"partner_id": "p1"}, {"id": "p2"}, {"partner_id": " "}]},
|
|
)
|
|
with as_user("u_alice", role="user"):
|
|
assert partner_access.assigned_partner_ids() == {"p1", "p2"}
|
|
|
|
|
|
# ── assert_partner_allowed ────────────────────────────────────
|
|
|
|
|
|
def test_admin_may_use_any_partner(as_user, monkeypatch):
|
|
# Admin short-circuits before any grant lookup.
|
|
monkeypatch.setattr(
|
|
partner_access,
|
|
"load_grant",
|
|
lambda uid: (_ for _ in ()).throw(AssertionError("admin must not read grants")),
|
|
)
|
|
with as_user("u_admin", role="admin"):
|
|
partner_access.assert_partner_allowed("anything") # no raise
|
|
|
|
|
|
def test_non_admin_allowed_only_for_assigned(as_user, monkeypatch):
|
|
monkeypatch.setattr(
|
|
partner_access, "load_grant", lambda uid: {"partners": [{"partner_id": "p1"}]}
|
|
)
|
|
with as_user("u_alice", role="user"):
|
|
partner_access.assert_partner_allowed("p1") # assigned → ok
|
|
with pytest.raises(HTTPException) as exc:
|
|
partner_access.assert_partner_allowed("p2")
|
|
assert exc.value.status_code == 403
|
|
|
|
|
|
# ── visible_partner_cards ─────────────────────────────────────
|
|
|
|
|
|
def test_admin_sees_all_partners_identity_only(as_user, monkeypatch):
|
|
_patch_manager(
|
|
monkeypatch,
|
|
[
|
|
{"partner_id": "p1", "name": "P1", "emoji": "🤖", "channels": ["telegram"]},
|
|
{"partner_id": "p2", "name": "P2", "channels": [], "llm_selection": {"x": "y"}},
|
|
],
|
|
)
|
|
with as_user("u_admin", role="admin"):
|
|
cards = partner_access.visible_partner_cards()
|
|
assert {c["partner_id"] for c in cards} == {"p1", "p2"}
|
|
# Identity only — channel wiring / model selection must not leak to a card.
|
|
assert all("channels" not in c and "llm_selection" not in c for c in cards)
|
|
|
|
|
|
def test_non_admin_sees_only_assigned_partners(as_user, monkeypatch):
|
|
_patch_manager(
|
|
monkeypatch,
|
|
[{"partner_id": "p1", "name": "P1"}, {"partner_id": "p2", "name": "P2"}],
|
|
)
|
|
monkeypatch.setattr(
|
|
partner_access, "load_grant", lambda uid: {"partners": [{"partner_id": "p2"}]}
|
|
)
|
|
with as_user("u_alice", role="user"):
|
|
cards = partner_access.visible_partner_cards()
|
|
assert [c["partner_id"] for c in cards] == ["p2"]
|
|
|
|
|
|
def test_non_admin_with_no_grant_sees_nothing(as_user, monkeypatch):
|
|
_patch_manager(monkeypatch, [{"partner_id": "p1", "name": "P1"}])
|
|
monkeypatch.setattr(partner_access, "load_grant", lambda uid: empty_grant(uid))
|
|
with as_user("u_alice", role="user"):
|
|
assert partner_access.visible_partner_cards() == []
|
|
|
|
|
|
# ── assignable pool (admin side) ──────────────────────────────
|
|
|
|
|
|
def test_admin_partner_summary_is_identity_only(monkeypatch):
|
|
from deeptutor.multi_user import router
|
|
|
|
_patch_manager(
|
|
monkeypatch,
|
|
[
|
|
{
|
|
"partner_id": "p1",
|
|
"name": "Tutor",
|
|
"description": "math",
|
|
"emoji": "🤖",
|
|
"channels": ["telegram"],
|
|
"llm_selection": {"x": "y"},
|
|
}
|
|
],
|
|
)
|
|
summary = router._admin_partner_summary()
|
|
assert summary == [{"partner_id": "p1", "name": "Tutor", "description": "math", "emoji": "🤖"}]
|