CI / test (push) Successful in 2m10s
- Tiroir : édition du titre et de la description (PATCH forge, journal 'edited', EVENT_TYPES étendu), création avec description dépliable. - Pièces jointes Gitea de bout en bout : client list/upload/delete/download (le téléchargement suit browser_download_url — l'endpoint asset ne sert que du JSON, attrapé au smoke réel), routes proxifiées avec CSRF et plafond 25 Mio, UI liste/envoi/suppression ; GitHub : supports_attachments=False, message propre. - Clic carte en délégation d'événement : les écouteurs par carte mouraient au premier rafraîchissement live. - 261 tests verts (7 nouveaux, palier 21) ; cycle réel complet validé sur une issue jetable du vrai Gitea (création, édition, envoi, téléchargement, suppression).
135 lines
4.6 KiB
Python
135 lines
4.6 KiB
Python
"""Palier 21 (0.31.0): description éditable, pièces jointes
|
|
d'issues, clic carte robuste (délégation côté JS)."""
|
|
|
|
from tests.conftest import csrf_from
|
|
|
|
|
|
def test_issue_update_title_and_body(client, fake_gitea):
|
|
token = csrf_from(client)
|
|
r = client.post(
|
|
"/api/issues/Projets/demo/1/update",
|
|
data={
|
|
"title": "Tâche un — précisée",
|
|
"body": "## Contexte\n\nLa description qui manquait.",
|
|
"csrf_token": token,
|
|
},
|
|
)
|
|
assert r.status_code == 200
|
|
payload = r.json()
|
|
assert payload["ok"] is True
|
|
assert payload["issue"]["body"].startswith("## Contexte")
|
|
issue = next(
|
|
i for i in fake_gitea.issues["Projets/demo"] if i["number"] == 1
|
|
)
|
|
assert issue["title"] == "Tâche un — précisée"
|
|
assert "description qui manquait" in issue["body"]
|
|
# Le journal consigne l'édition.
|
|
events = client.get("/api/events?repo=Projets/demo").json()["events"]
|
|
assert any(e["event_type"] == "edited" for e in events)
|
|
|
|
|
|
def test_issue_update_rejects_empty_title(client, fake_gitea):
|
|
token = csrf_from(client)
|
|
r = client.post(
|
|
"/api/issues/Projets/demo/1/update",
|
|
data={"title": " ", "body": "x", "csrf_token": token},
|
|
)
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_issue_detail_includes_assets_support(client, fake_gitea):
|
|
data = client.get("/api/issues/Projets/demo/1").json()
|
|
assert data["issue"]["attachments_supported"] is True
|
|
assert data["issue"]["assets"] == []
|
|
|
|
|
|
def test_asset_roundtrip(client, fake_gitea):
|
|
token = csrf_from(client)
|
|
r = client.post(
|
|
"/api/issues/Projets/demo/1/assets",
|
|
data={"csrf_token": token},
|
|
files={"attachment": ("notes.txt", b"bonjour kanbante", "text/plain")},
|
|
)
|
|
assert r.status_code == 201
|
|
asset = r.json()["asset"]
|
|
assert asset["name"] == "notes.txt"
|
|
assert asset["size"] == len(b"bonjour kanbante")
|
|
|
|
# Visible dans le détail.
|
|
data = client.get("/api/issues/Projets/demo/1").json()
|
|
assert [a["name"] for a in data["issue"]["assets"]] == ["notes.txt"]
|
|
|
|
# Téléchargement proxifié : contenu + disposition.
|
|
dl = client.get(
|
|
f"/api/issues/Projets/demo/1/assets/{asset['id']}/download"
|
|
)
|
|
assert dl.status_code == 200
|
|
assert dl.content == b"bonjour kanbante"
|
|
assert "notes.txt" in dl.headers["content-disposition"]
|
|
|
|
# Suppression.
|
|
token = csrf_from(client)
|
|
r = client.post(
|
|
f"/api/issues/Projets/demo/1/assets/{asset['id']}/delete",
|
|
data={"csrf_token": token},
|
|
)
|
|
assert r.status_code == 200
|
|
data = client.get("/api/issues/Projets/demo/1").json()
|
|
assert data["issue"]["assets"] == []
|
|
|
|
|
|
def test_asset_upload_rejects_empty_and_huge(client, fake_gitea):
|
|
token = csrf_from(client)
|
|
r = client.post(
|
|
"/api/issues/Projets/demo/1/assets",
|
|
data={"csrf_token": token},
|
|
files={"attachment": ("vide.txt", b"", "text/plain")},
|
|
)
|
|
assert r.status_code == 400
|
|
|
|
|
|
def test_board_page_has_editor_and_assets_ui(client, fake_gitea):
|
|
page = client.get("/board/Projets/demo")
|
|
assert page.status_code == 200
|
|
assert 'id="detail-edit-btn"' in page.text
|
|
assert 'id="detail-edit-form"' in page.text
|
|
assert 'id="detail-assets"' in page.text
|
|
assert 'id="asset-form"' in page.text
|
|
# Création avec description dépliable.
|
|
assert 'id="new-issue-body"' in page.text
|
|
assert 'id="new-issue-desc-toggle"' in page.text
|
|
|
|
|
|
async def test_gitea_download_follows_browser_download_url():
|
|
"""Gitea renvoie la fiche JSON de l'asset, pas le binaire :
|
|
le téléchargement doit suivre browser_download_url (réel
|
|
1.27.3, attrapé au smoke de la 0.31.0)."""
|
|
import httpx
|
|
|
|
from app.gitea_client import GiteaClient
|
|
|
|
def handler(request):
|
|
url = str(request.url)
|
|
if url.endswith("/assets/42"):
|
|
return httpx.Response(200, json={
|
|
"id": 42, "name": "preuve.txt",
|
|
"browser_download_url": "https://gitea.test/attachments/uuid-1",
|
|
})
|
|
if url.endswith("/attachments/uuid-1"):
|
|
assert request.headers.get("authorization", "").startswith("token ")
|
|
return httpx.Response(
|
|
200, content=b"contenu reel",
|
|
headers={"content-type": "text/plain"},
|
|
)
|
|
return httpx.Response(404)
|
|
|
|
client = GiteaClient(
|
|
base_url="https://gitea.test", token="jeton" + "-factice",
|
|
cache_ttl=0, transport=httpx.MockTransport(handler),
|
|
)
|
|
content, content_type = await client.download_issue_asset(
|
|
"Projets", "demo", 1, 42
|
|
)
|
|
assert content == b"contenu reel"
|
|
assert content_type == "text/plain"
|