diff --git a/scripts/feedback_api.py b/scripts/feedback_api.py index 6a96f8c..7462eb8 100644 --- a/scripts/feedback_api.py +++ b/scripts/feedback_api.py @@ -82,3 +82,46 @@ def collect_listings(db_path: Path | None = None, n: int = 5) -> list[dict]: ).fetchall() conn.close() return [{"title": r["title"], "company": r["company"], "url": r["url"]} for r in rows] + + +def build_issue_body(form: dict, context: dict, attachments: dict) -> str: + """Assemble the Forgejo issue markdown body from form data, context, and attachments.""" + _TYPE_LABELS = {"bug": "🐛 Bug", "feature": "✨ Feature Request", "other": "💬 Other"} + lines: list[str] = [ + f"## {_TYPE_LABELS.get(form.get('type', 'other'), '💬 Other')}", + "", + form.get("description", ""), + "", + ] + + if form.get("type") == "bug" and form.get("repro"): + lines += ["### Reproduction Steps", "", form["repro"], ""] + + if context: + lines += ["### Context", ""] + for k, v in context.items(): + lines.append(f"- **{k}:** {v}") + lines.append("") + + if attachments.get("logs"): + lines += [ + "
", + "App Logs (last 100 lines)", + "", + "```", + attachments["logs"], + "```", + "
", + "", + ] + + if attachments.get("listings"): + lines += ["### Recent Listings", ""] + for j in attachments["listings"]: + lines.append(f"- [{j['title']} @ {j['company']}]({j['url']})") + lines.append("") + + if attachments.get("submitter"): + lines += ["---", f"*Submitted by: {attachments['submitter']}*"] + + return "\n".join(lines) diff --git a/tests/test_feedback_api.py b/tests/test_feedback_api.py index 263ba38..03de328 100644 --- a/tests/test_feedback_api.py +++ b/tests/test_feedback_api.py @@ -119,3 +119,62 @@ def test_collect_listings_respects_n(tmp_path): "salary": "", "description": "", "date_found": "2026-03-01", }) assert len(collect_listings(db_path=db, n=3)) == 3 + + +# ── build_issue_body ────────────────────────────────────────────────────────── + +def test_build_issue_body_contains_description(): + from scripts.feedback_api import build_issue_body + form = {"type": "bug", "title": "Test", "description": "it broke", "repro": ""} + ctx = {"page": "Home", "version": "v1.0", "tier": "free", + "llm_backend": "ollama", "os": "Linux", "timestamp": "2026-03-03T00:00:00Z"} + body = build_issue_body(form, ctx, {}) + assert "it broke" in body + assert "Home" in body + assert "v1.0" in body + + +def test_build_issue_body_bug_includes_repro(): + from scripts.feedback_api import build_issue_body + form = {"type": "bug", "title": "X", "description": "desc", "repro": "step 1\nstep 2"} + body = build_issue_body(form, {}, {}) + assert "step 1" in body + assert "Reproduction" in body + + +def test_build_issue_body_no_repro_for_feature(): + from scripts.feedback_api import build_issue_body + form = {"type": "feature", "title": "X", "description": "add dark mode", "repro": "ignored"} + body = build_issue_body(form, {}, {}) + assert "Reproduction" not in body + + +def test_build_issue_body_logs_in_collapsible(): + from scripts.feedback_api import build_issue_body + form = {"type": "other", "title": "X", "description": "Y", "repro": ""} + body = build_issue_body(form, {}, {"logs": "log line 1\nlog line 2"}) + assert "
" in body + assert "log line 1" in body + + +def test_build_issue_body_omits_logs_when_not_provided(): + from scripts.feedback_api import build_issue_body + form = {"type": "bug", "title": "X", "description": "Y", "repro": ""} + body = build_issue_body(form, {}, {}) + assert "
" not in body + + +def test_build_issue_body_submitter_attribution(): + from scripts.feedback_api import build_issue_body + form = {"type": "bug", "title": "X", "description": "Y", "repro": ""} + body = build_issue_body(form, {}, {"submitter": "Jane Doe "}) + assert "Jane Doe" in body + + +def test_build_issue_body_listings_shown(): + from scripts.feedback_api import build_issue_body + form = {"type": "bug", "title": "X", "description": "Y", "repro": ""} + listings = [{"title": "CSM", "company": "Acme", "url": "https://example.com/1"}] + body = build_issue_body(form, {}, {"listings": listings}) + assert "CSM" in body + assert "Acme" in body