214 lines
6.9 KiB
Python
214 lines
6.9 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_board_renders_closed_issue_in_done_column(client):
|
|
# Regression: reconcile moved the closed issue's row to "done", but
|
|
# the board merged open issues only, so the card never rendered and
|
|
# the "Terminé" column stayed empty.
|
|
models.move_issue("Projets/demo", 3, "todo") # closed in Gitea
|
|
r = client.get("/board/Projets/demo")
|
|
assert r.status_code == 200
|
|
assert "Ancienne tâche" in r.text
|
|
payload = client.get("/api/board/Projets/demo").json()
|
|
assert payload["columns"]["done"]["count"] == 1
|
|
assert "Ancienne tâche" in payload["columns"]["done"]["html"]
|
|
|
|
|
|
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
|