mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 18:33:15 +00:00
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.
274 lines
9.2 KiB
Python
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"
|