open-swe/tests/tools/test_jira_utils.py
Adam Moussa ae1f883b4c
refactor: move tests into tests/<domain>/ layout
Applies the plan's C5 step: git mv every test per the domain-reorg
move-map (movemap-m50.txt) into tests/{agent,analyzer,auth,dashboard,
github,middleware,models,reviewer,sandbox,slack,tools,webhooks}/, plus
the 13 fork-only placements from the scoping report §2c (Atlassian
webhook tests -> tests/webhooks/, test_atlassian_connect.py and
test_auth_error_leak.py -> tests/auth/, jira/confluence util tests ->
tests/tools/, test_repo_binding_isolation.py -> tests/sandbox/,
bot-identity/autofix tests -> tests/github/).

Path-only move: the only content edits are parents[1] -> parents[2]
fixes in test_e2b_integration.py and test_daytona_integration.py,
required because their __file__-relative ROOT path gained one more
directory level in the move.

Monkeypatch retargets for these files were already completed in C4;
none remained outstanding here.
2026-07-17 14:42:45 -04:00

274 lines
9.2 KiB
Python

"""Unit tests for the Jira REST utilities and ADF conversion."""
from __future__ import annotations
from typing import Any
import pytest
from agent.utils import adf, jira
# --- ADF conversion --------------------------------------------------------
def test_adf_to_markdown_handles_none_and_empty() -> None:
assert adf.adf_to_markdown(None) == ""
assert adf.adf_to_markdown({}) == ""
assert adf.adf_to_markdown("not a dict") == ""
def test_adf_to_markdown_paragraphs_marks_and_links() -> None:
doc = {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{"type": "text", "text": "Hello "},
{"type": "text", "text": "world", "marks": [{"type": "strong"}]},
],
},
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "a link",
"marks": [{"type": "link", "attrs": {"href": "https://x.com"}}],
}
],
},
],
}
md = adf.adf_to_markdown(doc)
assert "Hello **world**" in md
assert "[a link](https://x.com)" in md
def test_adf_to_markdown_bullet_and_code() -> None:
doc = {
"type": "doc",
"content": [
{
"type": "bulletList",
"content": [
{
"type": "listItem",
"content": [
{"type": "paragraph", "content": [{"type": "text", "text": "one"}]}
],
},
{
"type": "listItem",
"content": [
{"type": "paragraph", "content": [{"type": "text", "text": "two"}]}
],
},
],
},
{
"type": "codeBlock",
"attrs": {"language": "python"},
"content": [{"type": "text", "text": "print(1)"}],
},
],
}
md = adf.adf_to_markdown(doc)
assert "- one" in md
assert "- two" in md
assert "```python" in md
assert "print(1)" in md
def test_markdown_to_adf_structure() -> None:
doc = adf.markdown_to_adf("first block\n\nsecond block")
assert doc["type"] == "doc"
assert doc["version"] == 1
assert len(doc["content"]) == 2
assert doc["content"][0]["content"][0]["text"] == "first block"
def test_markdown_to_adf_empty_is_valid_doc() -> None:
doc = adf.markdown_to_adf("")
assert doc["type"] == "doc"
assert doc["content"] == [{"type": "paragraph", "content": []}]
def test_markdown_to_adf_multiline_block_uses_hardbreaks() -> None:
doc = adf.markdown_to_adf("line one\nline two")
para = doc["content"][0]["content"]
assert {"type": "hardBreak"} in para
# --- Jira REST utilities (mocked transport) --------------------------------
@pytest.fixture
def _jira_env(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(jira, "JIRA_BASE_URL", "https://seahaven.atlassian.net")
monkeypatch.setattr(jira, "JIRA_EMAIL", "bot@seahavenind.com")
monkeypatch.setattr(jira, "JIRA_API_TOKEN", "token")
def _mock_request(
monkeypatch: pytest.MonkeyPatch, response: dict[str, Any]
) -> list[dict[str, Any]]:
calls: list[dict[str, Any]] = []
async def fake_request(method: str, path: str, *, json=None, params=None):
calls.append({"method": method, "path": path, "json": json, "params": params})
return response
monkeypatch.setattr(jira, "_request", fake_request)
return calls
async def test_get_issue_normalizes_fields(
_jira_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
raw = {
"key": "PROJ-123",
"id": "10001",
"fields": {
"summary": "Fix the bug",
"description": {
"type": "doc",
"content": [
{"type": "paragraph", "content": [{"type": "text", "text": "details"}]}
],
},
"status": {"name": "In Progress"},
"assignee": {"displayName": "Ada", "emailAddress": "ada@x.com", "accountId": "acc1"},
"priority": {"name": "High"},
"labels": ["backend"],
"project": {"key": "PROJ", "name": "Project"},
"issuetype": {"name": "Bug"},
},
}
_mock_request(monkeypatch, raw)
result = await jira.get_issue("PROJ-123")
issue = result["issue"]
assert issue["key"] == "PROJ-123"
assert issue["title"] == "Fix the bug"
assert issue["description"] == "details"
assert issue["assignee"]["email"] == "ada@x.com"
assert issue["project_key"] == "PROJ"
assert issue["url"] == "https://seahaven.atlassian.net/browse/PROJ-123"
async def test_comment_on_issue_success(_jira_env: None, monkeypatch: pytest.MonkeyPatch) -> None:
calls = _mock_request(monkeypatch, {"id": "5001"})
ok = await jira.comment_on_issue("PROJ-1", "done, see PR")
assert ok is True
assert calls[0]["method"] == "POST"
assert calls[0]["path"] == "/issue/PROJ-1/comment"
# Body must be ADF, not raw markdown.
assert calls[0]["json"]["body"]["type"] == "doc"
async def test_comment_on_issue_error_returns_false(
_jira_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
_mock_request(monkeypatch, {"error": "boom"})
assert await jira.comment_on_issue("PROJ-1", "x") is False
async def test_get_issue_comments_normalizes(
_jira_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
_mock_request(
monkeypatch,
{
"comments": [
{
"id": "1",
"author": {"displayName": "Ada", "emailAddress": "ada@x.com", "accountId": "a"},
"body": {
"type": "doc",
"content": [
{"type": "paragraph", "content": [{"type": "text", "text": "hi"}]}
],
},
}
]
},
)
result = await jira.get_issue_comments("PROJ-1")
assert result["comments"][0]["body"] == "hi"
assert result["comments"][0]["author"]["email"] == "ada@x.com"
async def test_create_issue_builds_fields(_jira_env: None, monkeypatch: pytest.MonkeyPatch) -> None:
calls = _mock_request(monkeypatch, {"key": "PROJ-9", "id": "999"})
result = await jira.create_issue("PROJ", "New thing", description="body", priority="High")
assert result["success"] is True
assert result["issue"]["key"] == "PROJ-9"
sent = calls[0]["json"]["fields"]
assert sent["project"] == {"key": "PROJ"}
assert sent["summary"] == "New thing"
assert sent["description"]["type"] == "doc"
assert sent["priority"] == {"name": "High"}
async def test_update_issue_no_fields_errors(_jira_env: None) -> None:
result = await jira.update_issue("PROJ-1")
assert result["error"] == "No fields to update"
async def test_request_without_env_returns_error(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(jira, "JIRA_BASE_URL", "")
monkeypatch.setattr(jira, "JIRA_API_TOKEN", "")
result = await jira._request("GET", "/issue/PROJ-1")
assert "error" in result
# --- issue_key validation + path-segment encoding (INJ hardening) ----------
def test_is_valid_issue_key() -> None:
assert jira.is_valid_issue_key("PROJ-123")
assert jira.is_valid_issue_key("OS-1")
assert not jira.is_valid_issue_key("")
assert not jira.is_valid_issue_key("../../../../rest/api/2/permissions")
assert not jira.is_valid_issue_key("PROJ-1?expand=x")
assert not jira.is_valid_issue_key("PROJ-1/comment")
assert not jira.is_valid_issue_key("1-PROJ")
async def test_get_issue_percent_encodes_path(
_jira_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
# Even if a traversal key reaches the util, the path segment is encoded so it
# cannot climb out of /issue/ or inject a query.
calls = _mock_request(monkeypatch, {"key": "x", "fields": {}})
await jira.get_issue("../../../../rest/api/2/permissions")
assert calls[0]["path"] == "/issue/..%2F..%2F..%2F..%2Frest%2Fapi%2F2%2Fpermissions"
async def test_comment_on_issue_encodes_path(
_jira_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
calls = _mock_request(monkeypatch, {"id": "1"})
await jira.comment_on_issue("ABC-1?expand=evil", "hi")
assert calls[0]["path"] == "/issue/ABC-1%3Fexpand%3Devil/comment"
async def test_get_comment_fetches_single_comment(
_jira_env: None, monkeypatch: pytest.MonkeyPatch
) -> None:
calls = _mock_request(
monkeypatch,
{
"id": "10050",
"author": {"displayName": "Ada", "emailAddress": "ada@x.com", "accountId": "acc"},
"body": {
"type": "doc",
"content": [{"type": "paragraph", "content": [{"type": "text", "text": "hi"}]}],
},
},
)
result = await jira.get_comment("PROJ-1", "10050")
assert calls[0]["path"] == "/issue/PROJ-1/comment/10050"
assert result["comment"]["author"]["account_id"] == "acc"
assert result["comment"]["body"] == "hi"