Files
kanbante/tests/test_api.py
T
Spark f1dd921e63 test(palier-1): 16 tests — journal, métriques, détail, âge, WIP (52 verts)
FakeGitea étendu (get_issue, commentaires); le test de réconciliation
suit le nouveau retour de reconcile_board_state; l'assertion de
version suit APP_VERSION.
2026-10-11 10:05:51 -04:00

201 lines
6.3 KiB
Python

"""HTTP API behaviour with a fake Gitea (audit C2/C3/I5/M6/M7)."""
from app import models
from tests.conftest import csrf_from
def test_health_is_public_liveness(client):
from app.config import APP_VERSION
r = client.get("/api/health")
assert r.status_code == 200
assert r.json()["status"] == "ok"
assert r.json()["version"] == APP_VERSION
def test_readiness_reflects_gitea(client, fake_gitea):
assert client.get("/api/readiness").status_code == 200
fake_gitea.ping_result = False
r = client.get("/api/readiness")
assert r.status_code == 503
assert r.json()["checks"]["gitea"] is False
assert r.json()["checks"]["database"] is True
def test_dashboard_renders_three_categories(client):
r = client.get("/")
assert r.status_code == 200
assert "demo" in r.text and "vieux" in r.text and "archive" in r.text
assert "Archivés" in r.text and "Inactifs" in r.text
def test_board_hides_pull_requests_and_reconciles(client):
models.move_issue("Projets/demo", 3, "todo") # closed in Gitea
r = client.get("/board/Projets/demo")
assert r.status_code == 200
assert "Tâche un" in r.text
assert "PR déguisée" not in r.text # M6: PRs are not issues
state = models.get_board_state("Projets/demo")
assert [c["issue_number"] for c in state["done"]] == [3] # reconciled
def test_focus_lists_in_progress_across_projects(client):
models.move_issue("Projets/demo", 5, "in_progress")
r = client.get("/focus")
assert r.status_code == 200
assert "Tâche focus" in r.text
def test_gitea_outage_is_a_clean_502_not_500(client, fake_gitea):
fake_gitea.raise_on_search = True
r = client.get("/")
assert r.status_code == 502
assert "joindre Gitea" in r.text
r = client.get("/api/projects")
assert r.status_code == 502
assert r.json()["ok"] is False
# ── CSRF + moves + write-back ──────────────────────────────
def test_move_requires_csrf_token(client):
r = client.post(
"/api/move",
data={"repo_full_name": "Projets/demo", "issue_number": 1, "to_column": "done"},
)
assert r.status_code == 403
def test_move_to_done_closes_issue_in_gitea(client, fake_gitea):
token = csrf_from(client)
r = client.post(
"/api/move",
data={
"repo_full_name": "Projets/demo",
"issue_number": 1,
"to_column": "done",
"from_column": "backlog",
"to_order": "1",
"csrf_token": token,
},
)
assert r.status_code == 200
assert r.json()["gitea_sync"] == "closed"
assert fake_gitea.state_changes == [("Projets", "demo", 1, "closed")]
state = models.get_board_state("Projets/demo")
assert [c["issue_number"] for c in state["done"]] == [1]
def test_move_out_of_done_reopens_issue(client, fake_gitea):
token = csrf_from(client)
client.post(
"/api/move",
data={
"repo_full_name": "Projets/demo", "issue_number": 1,
"to_column": "done", "from_column": "backlog", "csrf_token": token,
},
)
r = client.post(
"/api/move",
data={
"repo_full_name": "Projets/demo", "issue_number": 1,
"to_column": "todo", "from_column": "done", "csrf_token": token,
},
)
assert r.json()["gitea_sync"] == "reopened"
assert fake_gitea.state_changes[-1] == ("Projets", "demo", 1, "open")
def test_move_writeback_failure_is_reported_not_fatal(client, fake_gitea):
fake_gitea.fail_state_change = True
token = csrf_from(client)
r = client.post(
"/api/move",
data={
"repo_full_name": "Projets/demo", "issue_number": 1,
"to_column": "done", "from_column": "backlog", "csrf_token": token,
},
)
assert r.status_code == 200
assert r.json()["ok"] is True
assert r.json()["gitea_sync"] == "failed"
def test_move_invalid_column_is_400(client):
token = csrf_from(client)
r = client.post(
"/api/move",
data={
"repo_full_name": "Projets/demo", "issue_number": 1,
"to_column": "nimporte_quoi", "csrf_token": token,
},
)
assert r.status_code == 400
assert r.json()["ok"] is False
def test_move_persists_card_order(client):
token = csrf_from(client)
r = client.post(
"/api/move",
data={
"repo_full_name": "Projets/demo", "issue_number": 5,
"to_column": "backlog", "from_column": "backlog",
"to_order": "5,1", "csrf_token": token,
},
)
assert r.status_code == 200
state = models.get_board_state("Projets/demo")
assert [c["issue_number"] for c in state["backlog"]][:2] == [5, 1]
# ── Notes + creation ───────────────────────────────────────
def test_notes_roundtrip_via_api(client):
token = csrf_from(client)
r = client.post(
"/api/notes",
data={"repo_full_name": "Projets/demo", "note": "à retenir", "csrf_token": token},
)
assert r.status_code == 200
assert models.get_notes("Projets/demo") == "à retenir"
def test_notes_size_limit_is_413(client, monkeypatch):
monkeypatch.setenv("KANBANTE_MAX_NOTE_CHARS", "10")
token = csrf_from(client)
r = client.post(
"/api/notes",
data={
"repo_full_name": "Projets/demo",
"note": "x" * 50,
"csrf_token": token,
},
)
assert r.status_code == 413
def test_create_issue_goes_to_gitea_and_lands_on_board(client, fake_gitea):
token = csrf_from(client)
r = client.post(
"/api/issues",
data={
"repo_full_name": "Projets/demo", "title": "Bug à corriger",
"column": "backlog", "csrf_token": token,
},
)
assert r.status_code == 201
number = r.json()["issue"]["number"]
assert fake_gitea.created == [("Projets", "demo", "Bug à corriger")]
state = models.get_board_state("Projets/demo")
assert number in [c["issue_number"] for c in state["backlog"]]
def test_create_issue_validates_title(client):
token = csrf_from(client)
r = client.post(
"/api/issues",
data={"repo_full_name": "Projets/demo", "title": " ", "csrf_token": token},
)
assert r.status_code == 400