From 943ecd6a6b7085207a1f8c6731f2994c0db30529 Mon Sep 17 00:00:00 2001 From: Spark Date: Sun, 11 Oct 2026 12:26:12 -0400 Subject: [PATCH] =?UTF-8?q?test(0.6.0):=20colonne=20QA,=20adoption=20d'?= =?UTF-8?q?=C3=A9tiquettes,=20wallboard=20(10=20tests,=2098=20au=20total)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_palier4.py | 146 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/test_palier4.py diff --git a/tests/test_palier4.py b/tests/test_palier4.py new file mode 100644 index 0000000..e98ed9a --- /dev/null +++ b/tests/test_palier4.py @@ -0,0 +1,146 @@ +"""Palier 4 (0.6.0): QA column, label reconciliation, wallboard.""" + +from fastapi.testclient import TestClient + +import app.main as main +from app import models +from tests.conftest import _issue, csrf_from + + +def _column_block(html: str, column: str) -> str: + blocks = html.split('
", 1)[0]: + return block + raise AssertionError(f"column {column} not found") + + +def _labelled(number: int, title: str, label_names: list[str]) -> dict: + issue = _issue(number, title) + issue["labels"] = [ + {"id": 900 + i, "name": name, "color": "8b5cf6"} + for i, name in enumerate(label_names) + ] + return issue + + +# ── QA column ───────────────────────────────────────────────────── + + +def test_board_has_six_columns_including_qa(client): + r = client.get("/board/Projets/demo") + assert r.status_code == 200 + assert "🧪 QA" in r.text + positions = [ + r.text.index(f'data-column="{col}"') + for col in ("backlog", "todo", "in_progress", "qa", "review", "done") + ] + assert positions == sorted(positions) # QA between in_progress and review + + +def test_move_to_qa_syncs_kanban_qa_label(client, fake_gitea): + token = csrf_from(client) + r = client.post( + "/api/move", + data={ + "repo_full_name": "Projets/demo", "issue_number": 1, + "to_column": "qa", "from_column": "backlog", + "to_order": "1", "from_order": "5", "csrf_token": token, + }, + ) + assert r.status_code == 200 + assert r.json()["label_sync"] == "ok" + assert ("add", 1, "kanban:qa") in fake_gitea.label_ops + state = models.get_board_state("Projets/demo") + assert 1 in [c["issue_number"] for c in state["qa"]] + + +def test_qa_cards_appear_in_focus(client, fake_gitea): + models.move_issue("Projets/demo", 5, "qa") + r = client.get("/focus") + assert r.status_code == 200 + assert "Tâche focus" in r.text + assert "🧪 QA" in r.text + + +# ── Label reconciliation (adoption) ─────────────────────────────── + + +def test_board_adopts_diverging_kanban_label(client, fake_gitea): + fake_gitea.issues["Projets/demo"].append( + _labelled(9, "Déplacée via Gitea", ["kanban:review"]) + ) + r = client.get("/board/Projets/demo") + assert r.status_code == 200 + assert "Déplacée via Gitea" in _column_block(r.text, "review") + state = models.get_board_state("Projets/demo") + assert 9 in [c["issue_number"] for c in state["review"]] + events = [ + e for e in models.get_events("Projets/demo") if e["issue_number"] == 9 + ] + assert any( + e["event_type"] == "moved" and e["to_column"] == "review" + and e["source"] == "gitea" + for e in events + ) + + +def test_board_ignores_conflicting_kanban_labels(client, fake_gitea): + fake_gitea.issues["Projets/demo"].append( + _labelled(9, "Étiquettes en conflit", ["kanban:todo", "kanban:review"]) + ) + r = client.get("/board/Projets/demo") + assert r.status_code == 200 + assert "Étiquettes en conflit" in _column_block(r.text, "backlog") + state = models.get_board_state("Projets/demo") + elsewhere = [ + c["issue_number"] + for col, cards in state.items() if col != "backlog" + for c in cards + ] + assert 9 not in elsewhere + + +def test_adoption_is_idempotent(client, fake_gitea): + fake_gitea.issues["Projets/demo"].append( + _labelled(9, "Stable", ["kanban:qa"]) + ) + client.get("/board/Projets/demo") + client.get("/board/Projets/demo") + events = [ + e for e in models.get_events("Projets/demo") + if e["issue_number"] == 9 and e["event_type"] == "moved" + ] + assert len(events) == 1 # adopted once, then label and state agree + + +# ── Wallboard ───────────────────────────────────────────────────── + + +def test_wall_renders_active_projects(client): + models.move_issue("Projets/demo", 5, "in_progress") + r = client.get("/wall") + assert r.status_code == 200 + assert "Projets/demo" in r.text + assert "Tâche focus" in r.text # the in-progress card is on the wall + assert 'class="wall-slide active"' in r.text + assert "🧪 QA" in r.text # the six column counters include QA + + +def test_wall_single_repo_mode(client): + r = client.get("/wall", params={"repo": "Projets/demo"}) + assert r.status_code == 200 + assert "Projets/demo" in r.text + + +def test_wall_unknown_repo_shows_empty_state(client): + r = client.get("/wall", params={"repo": "Inconnu/xyz"}) + assert r.status_code == 200 + assert "Aucun projet actif" in r.text + + +def test_wall_is_behind_auth(monkeypatch, fake_gitea): + monkeypatch.setenv("KANBANTE_AUTH_USER", "bruno") + monkeypatch.setenv("KANBANTE_AUTH_PASSWORD", "motdepasse") + with TestClient(main.app) as secured: + assert secured.get("/wall").status_code == 401