feat(0.31.0): description éditable, pièces jointes d'issues, clic carte fiable
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).
This commit is contained in:
Spark
2026-10-23 09:27:38 -04:00
parent 28322c12cf
commit d4cf8d1275
13 changed files with 689 additions and 12 deletions
+46
View File
@@ -187,6 +187,52 @@ class FakeGitea:
"404", request=request, response=_httpx.Response(404, request=request)
)
supports_attachments = True
async def update_issue(self, owner, repo, number, title=None, body=None):
issue = await self.get_issue(owner, repo, number)
if title is not None:
issue["title"] = title
if body is not None:
issue["body"] = body
return issue
# Pièces jointes en mémoire : {(repo, numéro): [assets]}
@property
def _assets(self):
return self.__dict__.setdefault("assets_store", {})
async def list_issue_assets(self, owner, repo, number):
return list(self._assets.get((f"{owner}/{repo}", number), []))
async def upload_issue_asset(
self, owner, repo, number, filename, content, content_type="application/octet-stream"
):
store = self._assets.setdefault((f"{owner}/{repo}", number), [])
asset = {
"id": 700 + len(store) + number,
"name": filename,
"size": len(content),
"download_count": 0,
"created_at": "2026-10-23T10:00:00Z",
"_content": content,
"_type": content_type,
}
store.append(asset)
return {k: v for k, v in asset.items() if not k.startswith("_")}
async def delete_issue_asset(self, owner, repo, number, asset_id):
store = self._assets.get((f"{owner}/{repo}", number), [])
self._assets[(f"{owner}/{repo}", number)] = [
a for a in store if a["id"] != asset_id
]
async def download_issue_asset(self, owner, repo, number, asset_id):
for asset in self._assets.get((f"{owner}/{repo}", number), []):
if asset["id"] == asset_id:
return asset["_content"], asset["_type"]
raise AssertionError("asset introuvable dans le faux")
async def get_issue_comments(self, owner, repo, number):
return list(self.comments.get((f"{owner}/{repo}", number), []))