"""Références croisées des notes — Fusion #215 (ACC-1.3). Parseur d'URL profondes (formes figées de dracodev-commons ``contrats/liens-profonds.md``), stockage JSON par utilisateur et endpoints ``/api/refs`` (création par collage d'URL, liste, retrait). """ import pytest from backend.external_refs import ( add_ref, delete_ref, get_ref, list_refs, parse_deep_link, ) @pytest.fixture() def refs_env(tmp_path, monkeypatch): monkeypatch.setenv("OBSIGATE_EXTERNAL_REFS_DIR", str(tmp_path / "refs")) return tmp_path # ------------------------------------------------------------ parseur @pytest.mark.parametrize("url,attendu", [ ("https://tiktak.dracodev.net/?task=42", ("tiktak", "task", "42", "TikTak · tâche 42")), ("https://tiktak.dracodev.net/?list=7", ("tiktak", "list", "7", "TikTak · liste 7")), ("https://og.dracodev.net/#file=home-bruno:Projets%2FAlpha.md", ("obsigate", "note", "home-bruno:Projets/Alpha.md", "ObsiGate · Alpha.md")), ("https://flowdeck.dracodev.net/pages/15", ("flowdeck", "page", "15", "Flowdeck · page 15")), ("https://flowdeck.dracodev.net/collections/3", ("flowdeck", "project", "3", "Flowdeck · projet 3")), ("https://git.dracodev.net/Projets/fusion-trois-apps/issues/5", ("kanbante", "card", "Projets/fusion-trois-apps#5", "Kanbante · fusion-trois-apps #5")), ]) def test_parse_formes_reconnues(url, attendu): assert parse_deep_link(url) == attendu @pytest.mark.parametrize("url", [ "https://example.com/?task=1", "https://og.dracodev.net/#file=sansdeuxpoints", "https://og.dracodev.net/", "https://flowdeck.dracodev.net/autre/12", "https://git.dracodev.net/Projets/fusion-trois-apps", "pas une url", "", ]) def test_parse_formes_refusees(url): assert parse_deep_link(url) is None # ------------------------------------------------------------ stockage def test_stockage_cycle(refs_env): ref = add_ref("alice", "TestVault", "Note A.md", "tiktak", "task", "42", "https://tiktak.dracodev.net/?task=42", "TikTak · tâche 42") assert ref["owner_type"] == "obsigate.note" assert ref["owner_id"] == "TestVault:Note A.md" assert get_ref("alice", ref["id"])["target_id"] == "42" assert [r["id"] for r in list_refs("alice", "TestVault", "Note A.md")] == [ref["id"]] assert list_refs("alice", "TestVault", "Autre.md") == [] assert list_refs("bob", "TestVault", "Note A.md") == [] # par utilisateur removed = delete_ref("alice", ref["id"]) assert removed["id"] == ref["id"] assert delete_ref("alice", ref["id"]) is None assert list_refs("alice", "TestVault", "Note A.md") == [] # ------------------------------------------------------------ API def test_api_cycle_complet(client, refs_env): r = client.post("/api/refs", json={ "vault": "TestVault", "path": "Note A.md", "url": "https://flowdeck.dracodev.net/collections/3", }) assert r.status_code == 201, r.text ref = r.json() assert ref["target_app"] == "flowdeck" assert ref["target_type"] == "project" assert ref["label"] == "Flowdeck · projet 3" r = client.post("/api/refs", json={ "vault": "TestVault", "path": "Note A.md", "url": "https://tiktak.dracodev.net/?task=9", "label": "Ma tâche", }) assert r.status_code == 201, r.text assert r.json()["label"] == "Ma tâche" r = client.get("/api/refs", params={"vault": "TestVault", "path": "Note A.md"}) assert r.status_code == 200 assert [x["target_app"] for x in r.json()["refs"]] == ["flowdeck", "tiktak"] r = client.delete(f"/api/refs/{ref['id']}") assert r.status_code == 200 r = client.get("/api/refs", params={"vault": "TestVault", "path": "Note A.md"}) assert [x["target_app"] for x in r.json()["refs"]] == ["tiktak"] def test_api_url_inconnue_422(client, refs_env): r = client.post("/api/refs", json={ "vault": "TestVault", "path": "Note A.md", "url": "https://example.com/whatever", }) assert r.status_code == 422 def test_api_delete_introuvable_404(client, refs_env): r = client.delete("/api/refs/inexistant") assert r.status_code == 404 def test_api_vault_sans_acces_403(admin_client, refs_env): """Auth activée : un utilisateur limité à TestVault reçoit 403 ailleurs.""" r = admin_client.post("/api/auth/login", json={"username": "normaluser", "password": "normal123"}) assert r.status_code == 200, r.text headers = {"Authorization": f"Bearer {r.json()['access_token']}"} r = admin_client.get("/api/refs", headers=headers, params={"vault": "VaultFantome", "path": "x.md"}) assert r.status_code == 403 r = admin_client.get("/api/refs", headers=headers, params={"vault": "TestVault", "path": "x.md"}) assert r.status_code == 200