Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b8c9c4ac6a | |||
| 73867d3990 |
@@ -53,7 +53,7 @@ def create_link(
|
|||||||
audit_service.log_action(
|
audit_service.log_action(
|
||||||
db,
|
db,
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
action="jira_link_created",
|
action="JIRA_LINK_CREATED",
|
||||||
entity_type="jira_link",
|
entity_type="jira_link",
|
||||||
entity_id=str(link.id),
|
entity_id=str(link.id),
|
||||||
details={
|
details={
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
"""Jira router API tests (FASE-1.3)."""
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from app.models.audit import AuditLog
|
||||||
|
from app.models.jira_link import JiraLink, JiraLinkEntityType
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_link_rejects_invalid_issue_key(client, admin_user, auth_headers):
|
||||||
|
response = client.post(
|
||||||
|
"/api/v1/jira/links",
|
||||||
|
json={
|
||||||
|
"entity_type": "test",
|
||||||
|
"entity_id": str(uuid.uuid4()),
|
||||||
|
"jira_issue_key": "invalid-key",
|
||||||
|
},
|
||||||
|
headers=auth_headers,
|
||||||
|
)
|
||||||
|
# Pydantic validation (422) or global validation handler (400)
|
||||||
|
assert response.status_code in (400, 422)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_link_returns_201(client, admin_user, auth_headers, db):
|
||||||
|
entity_id = uuid.uuid4()
|
||||||
|
response = client.post(
|
||||||
|
"/api/v1/jira/links",
|
||||||
|
json={
|
||||||
|
"entity_type": "test",
|
||||||
|
"entity_id": str(entity_id),
|
||||||
|
"jira_issue_key": "SEC-1234",
|
||||||
|
},
|
||||||
|
headers=auth_headers,
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
data = response.json()
|
||||||
|
assert data["jira_issue_key"] == "SEC-1234"
|
||||||
|
assert data["entity_id"] == str(entity_id)
|
||||||
|
|
||||||
|
audit = (
|
||||||
|
db.query(AuditLog)
|
||||||
|
.filter(AuditLog.action == "JIRA_LINK_CREATED")
|
||||||
|
.order_by(AuditLog.timestamp.desc())
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
assert audit is not None
|
||||||
|
assert audit.details.get("jira_issue_key") == "SEC-1234"
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_links_filters_by_entity(client, admin_user, auth_headers, db):
|
||||||
|
entity_id = uuid.uuid4()
|
||||||
|
link = JiraLink(
|
||||||
|
entity_type=JiraLinkEntityType.test,
|
||||||
|
entity_id=entity_id,
|
||||||
|
jira_issue_key="SEC-555",
|
||||||
|
created_by=admin_user.id,
|
||||||
|
)
|
||||||
|
db.add(link)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
response = client.get(
|
||||||
|
"/api/v1/jira/links",
|
||||||
|
params={"entity_type": "test", "entity_id": str(entity_id)},
|
||||||
|
headers=auth_headers,
|
||||||
|
)
|
||||||
|
assert response.status_code == 200
|
||||||
|
rows = response.json()
|
||||||
|
assert len(rows) == 1
|
||||||
|
assert rows[0]["jira_issue_key"] == "SEC-555"
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""Jira service unit tests (FASE-1.2)."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from app.domain.exceptions import InvalidOperationError
|
||||||
|
from app.models.jira_link import JiraLink, JiraLinkEntityType
|
||||||
|
from app.services import jira_service
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_jira_client_raises_when_disabled(monkeypatch):
|
||||||
|
monkeypatch.setattr("app.services.jira_service.settings.JIRA_ENABLED", False)
|
||||||
|
jira_service._jira_client = None
|
||||||
|
with pytest.raises(InvalidOperationError, match="not enabled"):
|
||||||
|
jira_service.get_jira_client()
|
||||||
|
|
||||||
|
|
||||||
|
@patch("app.services.jira_service.get_jira_client")
|
||||||
|
def test_sync_aegis_to_jira_adds_comment(mock_get_client, db):
|
||||||
|
mock_jira = MagicMock()
|
||||||
|
mock_get_client.return_value = mock_jira
|
||||||
|
|
||||||
|
link = JiraLink(
|
||||||
|
entity_type=JiraLinkEntityType.test,
|
||||||
|
entity_id=uuid4(),
|
||||||
|
jira_issue_key="SEC-99",
|
||||||
|
)
|
||||||
|
jira_service.sync_aegis_to_jira(db, link, {"state": "validated", "result": "pass"})
|
||||||
|
|
||||||
|
mock_jira.issue_add_comment.assert_called_once()
|
||||||
|
args = mock_jira.issue_add_comment.call_args[0]
|
||||||
|
assert args[0] == "SEC-99"
|
||||||
|
assert "Aegis Sync Update" in args[1]
|
||||||
|
assert link.last_synced_at is not None
|
||||||
|
|
||||||
|
|
||||||
|
@patch("app.services.jira_service.get_jira_client")
|
||||||
|
def test_search_jira_issues_maps_fields(mock_get_client):
|
||||||
|
mock_jira = MagicMock()
|
||||||
|
mock_get_client.return_value = mock_jira
|
||||||
|
mock_jira.jql.return_value = {
|
||||||
|
"issues": [
|
||||||
|
{
|
||||||
|
"key": "TST-1",
|
||||||
|
"fields": {
|
||||||
|
"summary": "Test issue",
|
||||||
|
"status": {"name": "In Progress"},
|
||||||
|
"assignee": {"displayName": "Alice"},
|
||||||
|
"priority": {"name": "High"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
results = jira_service.search_jira_issues("project = TEST", max_results=5)
|
||||||
|
|
||||||
|
assert len(results) == 1
|
||||||
|
assert results[0]["issue_key"] == "TST-1"
|
||||||
|
assert results[0]["summary"] == "Test issue"
|
||||||
|
assert results[0]["status"] == "In Progress"
|
||||||
|
mock_jira.jql.assert_called_once()
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""Worklog service tests — integrity hash (FASE-1.5)."""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from app.services import worklog_service
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_worklog_sets_integrity_hash(db, admin_user):
|
||||||
|
entity_id = uuid4()
|
||||||
|
started = datetime(2026, 5, 18, 9, 0, 0)
|
||||||
|
wl = worklog_service.create_worklog(
|
||||||
|
db,
|
||||||
|
entity_type="test",
|
||||||
|
entity_id=entity_id,
|
||||||
|
user_id=admin_user.id,
|
||||||
|
activity_type="red_team",
|
||||||
|
started_at=started,
|
||||||
|
duration_seconds=3600,
|
||||||
|
description="Manual entry",
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(wl)
|
||||||
|
|
||||||
|
assert wl.integrity_hash
|
||||||
|
assert len(wl.integrity_hash) == 64
|
||||||
|
assert worklog_service.verify_worklog_integrity(wl) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_verify_worklog_integrity_detects_tampering(db, admin_user):
|
||||||
|
wl = worklog_service.create_worklog(
|
||||||
|
db,
|
||||||
|
entity_type="test",
|
||||||
|
entity_id=uuid4(),
|
||||||
|
user_id=admin_user.id,
|
||||||
|
activity_type="blue_validation",
|
||||||
|
started_at=datetime(2026, 5, 18, 10, 0, 0),
|
||||||
|
duration_seconds=1800,
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(wl)
|
||||||
|
|
||||||
|
wl.duration_seconds = 9999
|
||||||
|
assert worklog_service.verify_worklog_integrity(wl) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_worklogs_filters_by_entity(db, admin_user):
|
||||||
|
eid = uuid4()
|
||||||
|
worklog_service.create_worklog(
|
||||||
|
db,
|
||||||
|
entity_type="campaign",
|
||||||
|
entity_id=eid,
|
||||||
|
user_id=admin_user.id,
|
||||||
|
activity_type="reporting",
|
||||||
|
started_at=datetime(2026, 5, 18, 11, 0, 0),
|
||||||
|
duration_seconds=600,
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
rows = worklog_service.list_worklogs(
|
||||||
|
db, entity_type="campaign", entity_id=eid,
|
||||||
|
)
|
||||||
|
assert len(rows) == 1
|
||||||
|
assert rows[0].entity_id == eid
|
||||||
Reference in New Issue
Block a user