155 lines
5.1 KiB
Python
155 lines
5.1 KiB
Python
"""Capture éclair (feuille de route #12) : API à jeton, page web, ntfy."""
|
|
|
|
import pytest
|
|
|
|
from app import models
|
|
from app.capture import parse_capture_text, parse_ntfy_message
|
|
from tests.conftest import csrf_from
|
|
|
|
TOKEN = "jeton-de-capture-test"
|
|
|
|
|
|
@pytest.fixture
|
|
def capture_env(monkeypatch):
|
|
monkeypatch.setenv("KANBANTE_CAPTURE_TOKEN", TOKEN)
|
|
monkeypatch.setenv("KANBANTE_CAPTURE_REPO", "Projets/demo")
|
|
|
|
|
|
# ── Parsing ───────────────────────────────────────────────────────
|
|
|
|
|
|
def test_parse_capture_text_first_line_is_title():
|
|
title, body = parse_capture_text("Idée géniale\n\nDétails\nsur deux lignes")
|
|
assert title == "Idée géniale"
|
|
assert body == "Détails\nsur deux lignes"
|
|
|
|
|
|
def test_parse_capture_text_empty():
|
|
assert parse_capture_text(" \n ") == ("", "")
|
|
|
|
|
|
def test_parse_ntfy_message_variants():
|
|
assert parse_ntfy_message(
|
|
{"event": "message", "title": "Titre", "message": "Corps"}
|
|
) == ("Titre", "Corps")
|
|
assert parse_ntfy_message(
|
|
{"event": "message", "message": "Ligne un\n\nLigne deux"}
|
|
) == ("Ligne un", "Ligne deux")
|
|
assert parse_ntfy_message({"event": "open"}) is None
|
|
assert parse_ntfy_message(
|
|
{"event": "message", "message": "auto", "tags": ["kanbante"]}
|
|
) is None # never ingest our own posts
|
|
|
|
|
|
# ── API ───────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_capture_api_disabled_without_token(client, fake_gitea):
|
|
r = client.post("/api/capture", json={"text": "Idée"})
|
|
assert r.status_code == 503
|
|
|
|
|
|
def test_capture_api_rejects_wrong_token(client, fake_gitea, capture_env):
|
|
r = client.post(
|
|
"/api/capture",
|
|
json={"text": "Idée"},
|
|
headers={"X-Capture-Token": "mauvais"},
|
|
)
|
|
assert r.status_code == 401
|
|
assert fake_gitea.created == []
|
|
|
|
|
|
def test_capture_api_creates_issue_in_default_repo(client, fake_gitea, capture_env):
|
|
r = client.post(
|
|
"/api/capture",
|
|
json={"text": "Idée éclair\n\nLe détail qui compte"},
|
|
headers={"X-Capture-Token": TOKEN},
|
|
)
|
|
assert r.status_code == 201
|
|
data = r.json()
|
|
assert data["ok"] is True
|
|
number = data["issue"]["number"]
|
|
assert fake_gitea.created == [("Projets", "demo", "Idée éclair")]
|
|
state = models.get_board_state("Projets/demo")
|
|
assert number in [c["issue_number"] for c in state["backlog"]]
|
|
events = [
|
|
e for e in models.get_events("Projets/demo")
|
|
if e["issue_number"] == number
|
|
]
|
|
assert any(
|
|
e["event_type"] == "created" and e["source"] == "capture" for e in events
|
|
)
|
|
|
|
|
|
def test_capture_api_text_plain_and_bearer(client, fake_gitea, capture_env):
|
|
r = client.post(
|
|
"/api/capture",
|
|
content="Titre en texte brut\nCorps",
|
|
headers={
|
|
"Authorization": f"Bearer {TOKEN}",
|
|
"Content-Type": "text/plain",
|
|
},
|
|
)
|
|
assert r.status_code == 201
|
|
assert fake_gitea.created == [("Projets", "demo", "Titre en texte brut")]
|
|
|
|
|
|
def test_capture_api_explicit_repo_and_column(client, fake_gitea, capture_env):
|
|
r = client.post(
|
|
"/api/capture",
|
|
json={"title": "Direct en todo", "repo": "Projets/demo", "column": "todo"},
|
|
headers={"X-Capture-Token": TOKEN},
|
|
)
|
|
assert r.status_code == 201
|
|
number = r.json()["issue"]["number"]
|
|
state = models.get_board_state("Projets/demo")
|
|
assert number in [c["issue_number"] for c in state["todo"]]
|
|
|
|
|
|
def test_capture_api_validates_input(client, fake_gitea, capture_env):
|
|
r = client.post(
|
|
"/api/capture", json={"text": " "},
|
|
headers={"X-Capture-Token": TOKEN},
|
|
)
|
|
assert r.status_code == 400
|
|
r = client.post(
|
|
"/api/capture",
|
|
json={"title": "X", "repo": "pas-de-slash"},
|
|
headers={"X-Capture-Token": TOKEN},
|
|
)
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_capture_api_no_repo_anywhere(client, fake_gitea, monkeypatch):
|
|
monkeypatch.setenv("KANBANTE_CAPTURE_TOKEN", TOKEN)
|
|
monkeypatch.delenv("KANBANTE_CAPTURE_REPO", raising=False)
|
|
r = client.post(
|
|
"/api/capture", json={"title": "Orpheline"},
|
|
headers={"X-Capture-Token": TOKEN},
|
|
)
|
|
assert r.status_code == 400
|
|
|
|
|
|
# ── Page web ──────────────────────────────────────────────────────
|
|
|
|
|
|
def test_capture_page_renders(client, capture_env):
|
|
r = client.get("/capture")
|
|
assert r.status_code == 200
|
|
assert "Capture éclair" in r.text
|
|
assert "Projets/demo" in r.text
|
|
|
|
|
|
def test_capture_web_form_creates_issue(client, fake_gitea, capture_env):
|
|
token = csrf_from(client)
|
|
r = client.post(
|
|
"/capture",
|
|
data={
|
|
"repo_full_name": "Projets/demo", "title": "Via le formulaire",
|
|
"body": "Détails", "column": "backlog", "csrf_token": token,
|
|
},
|
|
)
|
|
assert r.status_code == 200
|
|
assert "Capturé" in r.text
|
|
assert fake_gitea.created == [("Projets", "demo", "Via le formulaire")]
|