Download tests/test_keystore.py from wzender/mathly: direct link, hf CLI and curl.
- Browser
- Download file 9.06 kB
-
https://huggingface.co/spaces/wzender/mathly/resolve/main/tests/test_keystore.py
- Command line
-
hf download hf://spaces/wzender/mathly/tests/test_keystore.py
-
curl -L -o test_keystore.py https://huggingface.co/spaces/wzender/mathly/resolve/main/tests/test_keystore.py
9.06 kB
| """The API keys a developer replaces from the panel (app/keystore.py): kept encrypted, never shown back, put to work at once.""" | |
| import pytest | |
| from cryptography.fernet import Fernet | |
| from fastapi.testclient import TestClient | |
| from test_lab import FakeAuth | |
| from test_scoring import emissions, scorer_for | |
| import app.main | |
| from app.keystore import KeyStore | |
| from app.main import create_app | |
| from app.tts import SawtakTTS | |
| from app.tutor import Tutor | |
| MASTER = Fernet.generate_key().decode() | |
| NEW = "sk-ant-new-key-0123456789wxyz" | |
| class KeyAuth(FakeAuth): | |
| secret = "sb_secret_x" | |
| def __init__(self, table=True): | |
| super().__init__() | |
| self.rows, self.table, self.events = {}, table, [] | |
| def secret_rows(self): | |
| return list(self.rows.values()) if self.table else None | |
| def save_secret(self, row): | |
| self.rows[row["name"]] = {**row, "updated_at": "2026-10-18T10:00:00+00:00"} | |
| def delete_secret(self, name): | |
| self.rows.pop(name, None) | |
| def log_event(self, kind, user, label="", details=None): | |
| self.events.append((kind, label, details)) | |
| class AdminAuth(KeyAuth): | |
| developers = frozenset() # t@b.c is a teacher (the panel), not a developer | |
| def bearer(token): | |
| return {"Authorization": f"Bearer {token}"} | |
| def keys_env(monkeypatch): | |
| monkeypatch.setenv("SECRETS_MASTER_KEY", MASTER) | |
| monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-from-the-environment-1111") | |
| monkeypatch.setattr(app.main, "check_key", lambda name, value, anthropic_key="": None) | |
| def client(auth, tmp_path, tutor=None): | |
| return TestClient(create_app(scorer_for(emissions(["ħ", "aː", "l"])), SawtakTTS("old-sawtak-key", cache_dir=tmp_path), | |
| auth=auth, tutor=tutor or Tutor(object()))) | |
| def test_a_key_is_kept_encrypted_and_never_shown_back(keys_env, tmp_path): | |
| auth = KeyAuth() | |
| with client(auth, tmp_path) as c: | |
| before = c.get("/api/admin/keys", headers=bearer("teacher")).json() | |
| assert {k["name"]: k["source"] for k in before["keys"]}["ANTHROPIC_API_KEY"] == "env" | |
| assert "1111" in repr(before) and "from-the-environment" not in repr(before) # its last four only | |
| res = c.put("/api/admin/keys/ANTHROPIC_API_KEY", json={"value": f" {NEW} "}, headers=bearer("teacher")) | |
| assert res.status_code == 200, res.text | |
| assert NEW not in res.text and res.headers["cache-control"] == "no-store" | |
| row = next(k for k in res.json()["keys"] if k["name"] == "ANTHROPIC_API_KEY") | |
| assert (row["source"], row["last4"], row["updated_by"]) == ("db", "wxyz", "T@b.c") | |
| stored = auth.rows["ANTHROPIC_API_KEY"] | |
| assert NEW not in stored["ciphertext"] | |
| assert c.app.state.tutor.client.api_key == NEW # in use without a restart | |
| assert auth.events and NEW not in repr(auth.events) | |
| # a restart reads it back from the table | |
| assert KeyStore(auth, MASTER).get("ANTHROPIC_API_KEY") == "sk-ant-from-the-environment-1111" # not loaded yet | |
| keys = KeyStore(auth, MASTER) | |
| keys.load() | |
| assert keys.get("ANTHROPIC_API_KEY") == NEW | |
| def test_the_sawtak_key_is_swapped_in_place(keys_env, tmp_path): | |
| with client(KeyAuth(), tmp_path) as c: | |
| tts = c.app.state.tts | |
| assert c.put("/api/admin/keys/SAWTAK_API_KEY", json={"value": "sawtak-new-9876"}, | |
| headers=bearer("teacher")).status_code == 200 | |
| assert c.app.state.tts is tts and tts.api_key == "sawtak-new-9876" | |
| def test_clearing_goes_back_to_the_environment_key(keys_env, tmp_path): | |
| auth = KeyAuth() | |
| with client(auth, tmp_path) as c: | |
| c.put("/api/admin/keys/ANTHROPIC_API_KEY", json={"value": NEW}, headers=bearer("teacher")) | |
| res = c.delete("/api/admin/keys/ANTHROPIC_API_KEY", headers=bearer("teacher")) | |
| assert res.status_code == 200 and "ANTHROPIC_API_KEY" not in auth.rows | |
| assert c.app.state.tutor.client.api_key == "sk-ant-from-the-environment-1111" | |
| def test_only_developers_reach_the_keys(keys_env, tmp_path, monkeypatch): | |
| with client(KeyAuth(), tmp_path) as c: # a student | |
| assert c.get("/api/admin/keys", headers=bearer("student")).status_code == 403 | |
| assert c.put("/api/admin/keys/ANTHROPIC_API_KEY", json={"value": NEW}, headers=bearer("student")).status_code == 403 | |
| with client(AdminAuth(), tmp_path) as c: # the management panel is not enough | |
| assert c.get("/api/admin/keys", headers=bearer("teacher")).status_code == 403 | |
| monkeypatch.setattr(app.main, "load_auth", lambda: None) # an open deployment, without sign-in | |
| with TestClient(create_app(scorer_for(emissions(["ħ"])), SawtakTTS("k", cache_dir=tmp_path), | |
| tutor=Tutor(object()))) as c: | |
| assert c.get("/api/admin/keys").status_code == 403 | |
| def test_only_the_two_keys_are_settable(keys_env, tmp_path): | |
| with client(KeyAuth(), tmp_path) as c: | |
| for name in ("SUPABASE_SECRET_KEY", "SECRETS_MASTER_KEY"): | |
| assert c.put(f"/api/admin/keys/{name}", json={"value": NEW}, headers=bearer("teacher")).status_code == 404 | |
| assert c.put("/api/admin/keys/ANTHROPIC_API_KEY", json={"value": "two words"}, headers=bearer("teacher")).status_code == 400 | |
| def test_a_refused_key_is_not_kept(keys_env, tmp_path, monkeypatch): | |
| from app.keystore import KeyRefused | |
| def refuse(name, value, anthropic_key=""): | |
| raise KeyRefused("Anthropic דחה את המפתח.") | |
| monkeypatch.setattr(app.main, "check_key", refuse) | |
| auth = KeyAuth() | |
| with client(auth, tmp_path) as c: | |
| res = c.put("/api/admin/keys/ANTHROPIC_API_KEY", json={"value": NEW}, headers=bearer("teacher")) | |
| assert res.status_code == 400 and NEW not in res.text and not auth.rows | |
| def test_without_the_master_key_or_the_table_nothing_is_saved(keys_env, tmp_path, monkeypatch): | |
| with client(KeyAuth(table=False), tmp_path) as c: | |
| data = c.get("/api/admin/keys", headers=bearer("teacher")).json() | |
| assert not data["writable"] and "app_secrets" in data["setup"] | |
| assert c.put("/api/admin/keys/ANTHROPIC_API_KEY", json={"value": NEW}, headers=bearer("teacher")).status_code == 503 | |
| monkeypatch.delenv("SECRETS_MASTER_KEY") | |
| with client(KeyAuth(), tmp_path) as c: | |
| data = c.get("/api/admin/keys", headers=bearer("teacher")).json() | |
| assert not data["writable"] and "SECRETS_MASTER_KEY" in data["why"] | |
| def test_a_row_does_not_decrypt_under_another_name_or_master(tmp_path): | |
| auth = KeyAuth() | |
| keys = KeyStore(auth, MASTER) | |
| keys.set("ANTHROPIC_API_KEY", NEW, "t@b.c") | |
| auth.rows["SAWTAK_API_KEY"] = {**auth.rows["ANTHROPIC_API_KEY"], "name": "SAWTAK_API_KEY"} # copied across | |
| again = KeyStore(auth, MASTER) | |
| again.load() | |
| assert again.get("ANTHROPIC_API_KEY") == NEW and again.get("SAWTAK_API_KEY") != NEW | |
| other = KeyStore(auth, Fernet.generate_key().decode()) | |
| other.load() | |
| assert other.get("ANTHROPIC_API_KEY") != NEW | |
| def test_the_tutor_model_is_shown_and_switched_at_once(keys_env, tmp_path, monkeypatch): | |
| seen = [] | |
| monkeypatch.setattr(app.main, "check_key", lambda name, value, anthropic_key="": seen.append((name, anthropic_key))) | |
| monkeypatch.delenv("TUTOR_MODEL", raising=False) | |
| auth = KeyAuth() | |
| with client(auth, tmp_path, tutor=Tutor(object(), "claude-haiku-5-5")) as c: | |
| row = next(k for k in c.get("/api/admin/keys", headers=bearer("teacher")).json()["keys"] if k["name"] == "TUTOR_MODEL") | |
| assert row["source"] == "none" and row["value"] == "" and row["choices"][0]["id"] == "claude-haiku-5-5" | |
| res = c.put("/api/admin/keys/TUTOR_MODEL", json={"value": "claude-sonnet-5-5"}, headers=bearer("teacher")) | |
| assert res.status_code == 200, res.text | |
| row = next(k for k in res.json()["keys"] if k["name"] == "TUTOR_MODEL") | |
| assert (row["source"], row["value"], row["last4"]) == ("db", "claude-sonnet-5-5", "") # no secret: shown in full | |
| assert c.app.state.tutor.model == "claude-sonnet-5-5" | |
| assert seen == [("TUTOR_MODEL", "sk-ant-from-the-environment-1111")] # checked with the key in use | |
| c.delete("/api/admin/keys/TUTOR_MODEL", headers=bearer("teacher")) | |
| assert c.app.state.tutor.model == "claude-haiku-5-5" # back to the code's default | |
| def test_a_model_id_must_look_like_one(): | |
| from app.keystore import KeyRefused, check | |
| for bad in ("gpt-5", "claude haiku", "claude-"): | |
| with pytest.raises(KeyRefused): | |
| check("TUTOR_MODEL", bad, "sk-ant-x") | |
| def test_an_old_table_asks_for_its_migration(keys_env, tmp_path): | |
| from app.auth import AuthUnavailable | |
| class OldTable(KeyAuth): | |
| def save_secret(self, row): | |
| raise AuthUnavailable('supabase answered 400: {"code":"23514","message":"violates check constraint"}') | |
| with client(OldTable(), tmp_path) as c: | |
| res = c.put("/api/admin/keys/TUTOR_MODEL", json={"value": "claude-sonnet-5-5"}, headers=bearer("teacher")) | |
| assert res.status_code == 409 and "app_secrets_name_check" in res.json()["detail"] | |