From 3523a7decc0b8e65fc401cbd4648d77f2be0ecb7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 19 Aug 2026 23:04:04 +0200 Subject: [PATCH] Fix Forgejo issue lifecycle identifiers --- WORK-RECORDS.md | 2 +- issue_core/api/ingest.py | 17 ++++++++++++++++- tests/test_api_ingest.py | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index fd31768..cf23991 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -49,7 +49,7 @@ | task | ISSUE-WP-0006-T04 | todo | — | workplans/ISSUE-WP-0006-forgejo-only-projection-boundary.md | | task | ISSUE-WP-0007-T00 | done | — | workplans/ISSUE-WP-0007-rapp-issue-core-railiance01-migration.md | | task | ISSUE-WP-0007-T01 | done | — | workplans/ISSUE-WP-0007-rapp-issue-core-railiance01-migration.md | -| task | ISSUE-WP-0007-T02 | todo | — | workplans/ISSUE-WP-0007-rapp-issue-core-railiance01-migration.md | +| task | ISSUE-WP-0007-T02 | progress | — | workplans/ISSUE-WP-0007-rapp-issue-core-railiance01-migration.md | | task | ISSUE-WP-0007-T03 | todo | — | workplans/ISSUE-WP-0007-rapp-issue-core-railiance01-migration.md | | task | ISSUE-WP-0007-T04 | todo | — | workplans/ISSUE-WP-0007-rapp-issue-core-railiance01-migration.md | | task | ISSUE-WP-0007-T05 | todo | — | workplans/ISSUE-WP-0007-rapp-issue-core-railiance01-migration.md | diff --git a/issue_core/api/ingest.py b/issue_core/api/ingest.py index d492894..bf670c7 100644 --- a/issue_core/api/ingest.py +++ b/issue_core/api/ingest.py @@ -113,6 +113,21 @@ def _mapping_backend_name(backend_type: str) -> str: return "sqlite" if backend_type == "local" else backend_type +def _public_issue_id(issue: Issue, backend_type: str) -> str: + """Return the identifier accepted by the backend's issue routes. + + Forgejo/Gitea exposes both a database-wide ``id`` and a repository-local + issue ``number``. Its issue CRUD routes address the latter, so returning + the database ID from ingestion makes the documented POST -> PATCH lifecycle + fail with 404. Local backends continue to expose their native string ID. + """ + if backend_type in {"gitea", "github"} and issue.number: + return str(issue.number) + if issue.id: + return issue.id + return str(issue.number) + + def _upsert_mapping_if_requested( payload: TaskIngestionRequest, *, @@ -184,7 +199,7 @@ async def ingest_task(payload: TaskIngestionRequest) -> TaskIngestionResponse: except Exception: pass - issue_id = created.id or str(created.number) + issue_id = _public_issue_id(created, backend_type) issue_url: Optional[str] = None if created.sync_metadata: issue_url = created.sync_metadata.get("url") or created.sync_metadata.get("html_url") diff --git a/tests/test_api_ingest.py b/tests/test_api_ingest.py index 417dff2..6667a08 100644 --- a/tests/test_api_ingest.py +++ b/tests/test_api_ingest.py @@ -6,6 +6,7 @@ import os import tempfile import uuid from pathlib import Path +from types import SimpleNamespace import pytest @@ -15,6 +16,7 @@ pytest.importorskip("httpx") from fastapi.testclient import TestClient from issue_core.api.app import create_app +from issue_core.api.ingest import _public_issue_id API_KEY = "test-key-not-a-real-secret-only-for-pytest" @@ -75,6 +77,20 @@ def test_healthz(client): assert response.json()["status"] == "ok" +@pytest.mark.unit +def test_public_issue_id_uses_repository_number_for_gitea(): + issue = SimpleNamespace(id="91234", number=176) + + assert _public_issue_id(issue, "gitea") == "176" + + +@pytest.mark.unit +def test_public_issue_id_preserves_native_local_id(): + issue = SimpleNamespace(id="2c29792e-1234-4567-890a-22c4fa664abc", number=12) + + assert _public_issue_id(issue, "local") == issue.id + + @pytest.mark.unit def test_ingest_rejects_missing_api_key(client, valid_payload): response = client.post("/issues/", json=valid_payload)