feat(reports): add quarterly and technique download routes [FASE-2.4]
Expose GET endpoints for quarterly-summary and technique reports with PDF, DOCX, and HTML formats.
This commit is contained in:
@@ -70,3 +70,38 @@ def generate_executive_report(
|
|||||||
media_type=_MEDIA_TYPES[format],
|
media_type=_MEDIA_TYPES[format],
|
||||||
filename=f"executive_summary.{format}",
|
filename=f"executive_summary.{format}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/quarterly-summary")
|
||||||
|
def generate_quarterly_report(
|
||||||
|
format: str = Query("pdf", pattern="^(pdf|docx|html)$"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
user: User = Depends(require_any_role("red_lead", "blue_lead", "viewer")),
|
||||||
|
):
|
||||||
|
"""Generate a quarterly security summary report."""
|
||||||
|
filepath = report_generation_service.generate_quarterly_summary(
|
||||||
|
db, output_format=format,
|
||||||
|
)
|
||||||
|
return FileResponse(
|
||||||
|
filepath,
|
||||||
|
media_type=_MEDIA_TYPES[format],
|
||||||
|
filename=f"quarterly_summary.{format}",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/technique/{technique_id}")
|
||||||
|
def generate_technique_report(
|
||||||
|
technique_id: UUID,
|
||||||
|
format: str = Query("pdf", pattern="^(pdf|docx|html)$"),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
user: User = Depends(get_current_user),
|
||||||
|
):
|
||||||
|
"""Generate a detailed report for one MITRE technique."""
|
||||||
|
filepath = report_generation_service.generate_technique_detail_report(
|
||||||
|
db, str(technique_id), output_format=format,
|
||||||
|
)
|
||||||
|
return FileResponse(
|
||||||
|
filepath,
|
||||||
|
media_type=_MEDIA_TYPES[format],
|
||||||
|
filename=f"technique_{technique_id}.{format}",
|
||||||
|
)
|
||||||
|
|||||||
42
backend/tests/test_professional_reports_router.py
Normal file
42
backend/tests/test_professional_reports_router.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
"""Professional reports router tests (FASE-2.4)."""
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from app.models.campaign import Campaign
|
||||||
|
|
||||||
|
|
||||||
|
@patch("app.services.report_generation_service.generate_purple_campaign_report")
|
||||||
|
def test_purple_campaign_pdf_download(mock_gen, client, auth_headers, db):
|
||||||
|
mock_gen.return_value = __file__ # existing file for FileResponse
|
||||||
|
|
||||||
|
campaign = Campaign(name="Export Camp", status="active")
|
||||||
|
db.add(campaign)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
r = client.get(
|
||||||
|
f"/api/v1/reports/generate/purple-campaign/{campaign.id}",
|
||||||
|
params={"format": "pdf"},
|
||||||
|
headers=auth_headers,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert r.headers["content-type"] == "application/pdf"
|
||||||
|
|
||||||
|
|
||||||
|
@patch("app.services.report_generation_service.generate_coverage_report")
|
||||||
|
def test_coverage_summary_html(mock_gen, client, auth_headers):
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
fd, path = tempfile.mkstemp(suffix=".html")
|
||||||
|
os.write(fd, b"<html><body>ok</body></html>")
|
||||||
|
os.close(fd)
|
||||||
|
mock_gen.return_value = path
|
||||||
|
|
||||||
|
r = client.get(
|
||||||
|
"/api/v1/reports/generate/coverage-summary",
|
||||||
|
params={"format": "html"},
|
||||||
|
headers=auth_headers,
|
||||||
|
)
|
||||||
|
assert r.status_code == 200
|
||||||
|
assert "text/html" in r.headers["content-type"]
|
||||||
|
os.unlink(path)
|
||||||
Reference in New Issue
Block a user