cd1f82aa79
Agent-Logs-Url: https://github.com/christianlouis/dmarq/sessions/59552cb2-df2b-43dd-a22c-bd699a458afb Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
145 lines
5.3 KiB
Python
145 lines
5.3 KiB
Python
"""
|
|
Integration tests for the domain detail API endpoints:
|
|
GET /api/v1/domains/{domain_id}/reports
|
|
GET /api/v1/domains/{domain_id}/sources
|
|
|
|
These tests ensure that the ReportStore data is correctly projected into
|
|
the Pydantic response models, including the policy-dict extraction and
|
|
the use of begin_timestamp/end_timestamp integers for date fields.
|
|
"""
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.services.report_store import ReportStore
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers / constants
|
|
# ---------------------------------------------------------------------------
|
|
|
|
DOMAIN = "example.com"
|
|
|
|
# A minimal parsed DMARC report with policy stored as a dict (the real-world
|
|
# shape produced by DMARCParser) and Unix timestamps alongside ISO strings.
|
|
REPORT_DICT_POLICY = {
|
|
"domain": DOMAIN,
|
|
"report_id": "rpt-dict-policy",
|
|
"org_name": "Google LLC",
|
|
"begin_date": "2020-08-15T00:00:00",
|
|
"end_date": "2020-08-15T23:59:59",
|
|
"begin_timestamp": 1597449600,
|
|
"end_timestamp": 1597535999,
|
|
"policy": {"p": "reject", "sp": "reject", "pct": "100"},
|
|
"records": [
|
|
{
|
|
"source_ip": "209.85.220.1",
|
|
"count": 10,
|
|
"disposition": "none",
|
|
"dkim_result": "pass",
|
|
"spf_result": "pass",
|
|
"header_from": DOMAIN,
|
|
}
|
|
],
|
|
"summary": {"total_count": 10, "passed_count": 10, "failed_count": 0},
|
|
}
|
|
|
|
# Same report but with policy already stored as a plain string.
|
|
REPORT_STR_POLICY = {
|
|
**REPORT_DICT_POLICY,
|
|
"report_id": "rpt-str-policy",
|
|
"policy": "quarantine",
|
|
}
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Fixtures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.fixture()
|
|
def seeded_client(client: TestClient):
|
|
"""Client with one report (dict-style policy) in the ReportStore."""
|
|
ReportStore.get_instance().add_report(REPORT_DICT_POLICY)
|
|
return client
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /api/v1/domains/{domain_id}/reports
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_get_domain_reports_returns_200(seeded_client: TestClient):
|
|
"""Endpoint returns HTTP 200 and correct structure for a known domain."""
|
|
response = seeded_client.get(f"/api/v1/domains/{DOMAIN}/reports")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "reports" in data
|
|
assert "compliance_timeline" in data
|
|
|
|
|
|
def test_get_domain_reports_policy_dict_extracted(seeded_client: TestClient):
|
|
"""When the stored policy is a dict, the 'p' value should be surfaced."""
|
|
response = seeded_client.get(f"/api/v1/domains/{DOMAIN}/reports")
|
|
assert response.status_code == 200
|
|
reports = response.json()["reports"]
|
|
assert len(reports) == 1
|
|
assert reports[0]["policy"] == "reject"
|
|
|
|
|
|
def test_get_domain_reports_policy_string_preserved(client: TestClient):
|
|
"""When the stored policy is already a string it should be kept as-is."""
|
|
ReportStore.get_instance().add_report(REPORT_STR_POLICY)
|
|
response = client.get(f"/api/v1/domains/{DOMAIN}/reports")
|
|
assert response.status_code == 200
|
|
reports = response.json()["reports"]
|
|
assert len(reports) == 1
|
|
assert reports[0]["policy"] == "quarantine"
|
|
|
|
|
|
def test_get_domain_reports_timestamps_are_integers(seeded_client: TestClient):
|
|
"""begin_date and end_date in the response must be integers (Unix timestamps)."""
|
|
response = seeded_client.get(f"/api/v1/domains/{DOMAIN}/reports")
|
|
assert response.status_code == 200
|
|
report = response.json()["reports"][0]
|
|
assert isinstance(report["begin_date"], int)
|
|
assert isinstance(report["end_date"], int)
|
|
assert report["begin_date"] == 1597449600
|
|
assert report["end_date"] == 1597535999
|
|
|
|
|
|
def test_get_domain_reports_unknown_domain_returns_404(client: TestClient):
|
|
"""Returns 404 when the requested domain has no reports."""
|
|
response = client.get("/api/v1/domains/no-such-domain.example.com/reports")
|
|
assert response.status_code == 404
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /api/v1/domains/{domain_id}/sources
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_get_domain_sources_returns_200(seeded_client: TestClient):
|
|
"""Endpoint returns HTTP 200 and a sources list for a known domain."""
|
|
response = seeded_client.get(f"/api/v1/domains/{DOMAIN}/sources")
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert "sources" in data
|
|
assert len(data["sources"]) == 1
|
|
source = data["sources"][0]
|
|
assert source["ip"] == "209.85.220.1"
|
|
assert source["spf"] == "pass"
|
|
assert source["dkim"] == "pass"
|
|
assert source["dmarc"] == "pass"
|
|
|
|
|
|
def test_get_domain_sources_days_param_accepted(seeded_client: TestClient):
|
|
"""The 'days' query parameter is accepted without raising a TypeError."""
|
|
response = seeded_client.get(f"/api/v1/domains/{DOMAIN}/sources?days=7")
|
|
assert response.status_code == 200
|
|
|
|
|
|
def test_get_domain_sources_unknown_domain_returns_404(client: TestClient):
|
|
"""Returns 404 when the requested domain has no reports."""
|
|
response = client.get("/api/v1/domains/no-such-domain.example.com/sources")
|
|
assert response.status_code == 404
|