130 lines
4.8 KiB
Python
130 lines
4.8 KiB
Python
"""Admin identity registry stays local. Run from backend/: python tests/test_identity_registry.py"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from tests.harness import configure_test_engine
|
|
|
|
configure_test_engine()
|
|
|
|
os.environ["KANSHO_FAKE_PROVIDER"] = "1"
|
|
os.environ["KANSHO_FAKE_DETECT"] = "1"
|
|
|
|
from fastapi.testclient import TestClient
|
|
from identity_store import STATUS_LEGACY, list_confirmed_identities, list_registry
|
|
from main import app
|
|
from privacy_gateway import reset_debug
|
|
|
|
|
|
def expect(ok: bool, message: str) -> None:
|
|
if not ok:
|
|
raise SystemExit(f"FAIL: {message}")
|
|
print(f"OK {message}")
|
|
|
|
|
|
def main() -> None:
|
|
reset_debug()
|
|
import sqlite3
|
|
from db import _IDENTITY_MAPPING_COLUMNS, _ensure_columns
|
|
from identity_store import migrate_legacy_identity_rows as migrate_legacy
|
|
|
|
conn = sqlite3.connect(":memory:")
|
|
conn.row_factory = sqlite3.Row
|
|
conn.executescript(
|
|
"""
|
|
CREATE TABLE identity_mappings (
|
|
id TEXT PRIMARY KEY,
|
|
profile_id TEXT NOT NULL,
|
|
token TEXT NOT NULL,
|
|
local_label TEXT NOT NULL DEFAULT '',
|
|
created TEXT NOT NULL DEFAULT '2026-08-01 00:00:00'
|
|
);
|
|
INSERT INTO identity_mappings (id, profile_id, token, local_label)
|
|
VALUES ('m1', 'p1', 'PERSON:01', 'Altbau');
|
|
"""
|
|
)
|
|
_ensure_columns(conn, "identity_mappings", _IDENTITY_MAPPING_COLUMNS)
|
|
migrate_legacy(conn)
|
|
legacy = dict(conn.execute("SELECT * FROM identity_mappings WHERE id = 'm1'").fetchone())
|
|
expect(legacy["status"] == STATUS_LEGACY, "existing rows become legacy_review_required")
|
|
expect(legacy["origin"] == "legacy_auto", "existing rows keep legacy origin")
|
|
expect(legacy["canonical_label"] == "Altbau", "canonical_label is copied from local_label")
|
|
expect(bool(legacy["updated"]), "updated is populated after ALTER")
|
|
conn.close()
|
|
|
|
with TestClient(app) as client:
|
|
setup = client.post(
|
|
"/api/auth/setup",
|
|
json={"email": "lars@example.test", "name": "Lars", "password": "test-pass"},
|
|
)
|
|
headers = {"X-Auth-Token": setup.json()["token"]}
|
|
profile_id = setup.json()["profile_id"]
|
|
|
|
from db import get_db
|
|
import uuid
|
|
|
|
legacy_id = str(uuid.uuid4())
|
|
with get_db() as conn:
|
|
conn.execute(
|
|
"""
|
|
INSERT INTO identity_mappings
|
|
(id, profile_id, token, local_label, canonical_label, entity_type, status, origin)
|
|
VALUES (?, ?, ?, ?, ?, 'PERSON', ?, 'legacy_auto')
|
|
""",
|
|
(legacy_id, profile_id, "PERSON:70", "Altbau", "Altbau", STATUS_LEGACY),
|
|
)
|
|
expect(
|
|
not any(item["id"] == legacy_id for item in list_confirmed_identities(profile_id)),
|
|
"legacy row is not confirmed",
|
|
)
|
|
expect(any(item["id"] == legacy_id for item in list_registry(profile_id)), "legacy row remains reviewable")
|
|
|
|
patched = client.patch(
|
|
f"/api/admin/identities/{legacy_id}",
|
|
headers=headers,
|
|
json={"status": "confirmed", "entity_type": "PLACE", "canonical_label": "Altbau"},
|
|
)
|
|
expect(patched.status_code == 200, patched.text)
|
|
expect(patched.json()["status"] == "confirmed", "admin can confirm a legacy row")
|
|
expect(patched.json()["entity_type"] == "PLACE", "admin can correct the type")
|
|
|
|
created = client.post(
|
|
"/api/admin/identities",
|
|
headers=headers,
|
|
json={"canonical_label": "Nordwerk", "entity_type": "ORG", "aliases": ["Nordwerk AG"]},
|
|
)
|
|
expect(created.status_code == 200, created.text)
|
|
identity_id = created.json()["id"]
|
|
aliases = client.patch(
|
|
f"/api/admin/identities/{identity_id}",
|
|
headers=headers,
|
|
json={"aliases": ["Nordwerk AG", "Nordwerk"]},
|
|
)
|
|
expect(aliases.status_code == 200, aliases.text)
|
|
expect("Nordwerk AG" in (aliases.json().get("aliases") or []), "confirmed alias can be stored")
|
|
|
|
calls = {"n": 0}
|
|
|
|
def boom(*_args, **_kwargs):
|
|
calls["n"] += 1
|
|
raise AssertionError("no egress")
|
|
|
|
with patch("entity_detect.complete_chat", boom), patch("providers.complete_chat", boom):
|
|
gone = client.delete(f"/api/admin/identities/{identity_id}", headers=headers)
|
|
expect(gone.status_code == 200, "admin delete stays local")
|
|
expect(calls["n"] == 0, "registry actions do not call detect or generate")
|
|
expect(not any(item["id"] == identity_id for item in list_registry(profile_id)), "deleted row is gone")
|
|
|
|
print("All identity registry tests passed.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|