mirror of
https://github.com/kharonsec/br-acc
synced 2026-04-25 17:15:02 +02:00
367 lines
12 KiB
Python
367 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import pytest
|
|
|
|
from bracc.config import settings
|
|
from bracc.models.entity import SourceAttribution
|
|
from bracc.models.pattern import PatternResult
|
|
|
|
if TYPE_CHECKING:
|
|
from httpx import AsyncClient
|
|
|
|
|
|
class _FakeNode(dict):
|
|
def __init__(self, element_id: str, labels: list[str], **props: object) -> None:
|
|
super().__init__(props)
|
|
self.element_id = element_id
|
|
self.labels = set(labels)
|
|
|
|
|
|
class _FakeEndpoint:
|
|
def __init__(self, element_id: str) -> None:
|
|
self.element_id = element_id
|
|
|
|
|
|
class _FakeRel(dict):
|
|
def __init__(
|
|
self,
|
|
element_id: str,
|
|
source_id: str,
|
|
target_id: str,
|
|
rel_type: str,
|
|
**props: object,
|
|
) -> None:
|
|
super().__init__(props)
|
|
self.element_id = element_id
|
|
self.start_node = _FakeEndpoint(source_id)
|
|
self.end_node = _FakeEndpoint(target_id)
|
|
self.type = rel_type
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_entity_lookup_disabled_in_public_mode(
|
|
client: AsyncClient,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "public_mode", True)
|
|
monkeypatch.setattr(settings, "public_allow_entity_lookup", False)
|
|
response = await client.get("/api/v1/entity/12345678901")
|
|
assert response.status_code == 403
|
|
assert "disabled in public mode" in response.json()["detail"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_person_lookup_disabled_in_public_mode(
|
|
client: AsyncClient,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "public_mode", True)
|
|
monkeypatch.setattr(settings, "public_allow_entity_lookup", True)
|
|
monkeypatch.setattr(settings, "public_allow_person", False)
|
|
response = await client.get("/api/v1/entity/12345678901")
|
|
assert response.status_code == 403
|
|
assert "Person lookup disabled" in response.json()["detail"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_search_hides_person_nodes_in_public_mode(
|
|
client: AsyncClient,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "public_mode", True)
|
|
monkeypatch.setattr(settings, "public_allow_person", False)
|
|
mocked_records = [
|
|
{
|
|
"node": {"name": "Pessoa Teste", "cpf": "12345678900"},
|
|
"node_labels": ["Person"],
|
|
"node_id": "p1",
|
|
"score": 3.1,
|
|
"document_id": "12345678900",
|
|
},
|
|
{
|
|
"node": {"razao_social": "Empresa Teste", "cnpj": "11.111.111/0001-11"},
|
|
"node_labels": ["Company"],
|
|
"node_id": "c1",
|
|
"score": 2.9,
|
|
"document_id": "11.111.111/0001-11",
|
|
},
|
|
]
|
|
with patch(
|
|
"bracc.routers.search.execute_query",
|
|
new_callable=AsyncMock,
|
|
return_value=mocked_records,
|
|
):
|
|
response = await client.get("/api/v1/search?q=teste")
|
|
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert payload["total"] == 1
|
|
assert payload["results"][0]["type"] == "company"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_public_meta_endpoint(client: AsyncClient) -> None:
|
|
with patch(
|
|
"bracc.routers.public.execute_query_single",
|
|
new_callable=AsyncMock,
|
|
return_value={
|
|
"total_nodes": 10,
|
|
"total_relationships": 20,
|
|
"company_count": 3,
|
|
"contract_count": 4,
|
|
"sanction_count": 5,
|
|
"finance_count": 6,
|
|
"bid_count": 7,
|
|
"cpi_count": 8,
|
|
},
|
|
):
|
|
response = await client.get("/api/v1/public/meta")
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert payload["product"] == "World Transparency Graph"
|
|
assert payload["mode"] == "public_safe"
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_public_patterns_company_endpoint(client: AsyncClient) -> None:
|
|
with patch("bracc.routers.public.settings.patterns_enabled", False):
|
|
response = await client.get("/api/v1/public/patterns/company/11111111000111")
|
|
assert response.status_code == 503
|
|
assert "temporarily unavailable" in response.json()["detail"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_public_patterns_company_endpoint_when_enabled(client: AsyncClient) -> None:
|
|
with (
|
|
patch("bracc.routers.public.settings.patterns_enabled", True),
|
|
patch(
|
|
"bracc.routers.public.execute_query_single",
|
|
new_callable=AsyncMock,
|
|
return_value={
|
|
"c": {"cnpj": "11.111.111/0001-11", "razao_social": "Empresa Teste"},
|
|
"entity_labels": ["Company"],
|
|
"entity_id": "c1",
|
|
},
|
|
),
|
|
patch(
|
|
"bracc.routers.public._PUBLIC_PROVIDER.run_pattern",
|
|
new_callable=AsyncMock,
|
|
return_value=[
|
|
PatternResult(
|
|
pattern_id="debtor_contracts",
|
|
pattern_name="Devedor com contratos públicos",
|
|
description="Coocorrência factual entre dívida ativa e contratos recorrentes",
|
|
data={
|
|
"cnpj": "11.111.111/0001-11",
|
|
"company_name": "Empresa Teste",
|
|
"risk_signal": 5.0,
|
|
"amount_total": 120000.0,
|
|
"window_start": "2024-01-01",
|
|
"window_end": "2024-12-31",
|
|
"evidence_refs": ["contract:1", "debt:2"],
|
|
"evidence_count": 2,
|
|
},
|
|
entity_ids=["c1"],
|
|
sources=[SourceAttribution(database="neo4j_public")],
|
|
exposure_tier="public_safe",
|
|
intelligence_tier="community",
|
|
)
|
|
],
|
|
),
|
|
):
|
|
response = await client.get("/api/v1/public/patterns/company/11111111000111")
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert payload["total"] == 1
|
|
assert payload["patterns"][0]["exposure_tier"] == "public_safe"
|
|
assert payload["patterns"][0]["data"]["evidence_refs"]
|
|
assert payload["patterns"][0]["data"]["risk_signal"] >= 1
|
|
assert "cpf" not in str(payload).lower()
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_public_graph_company_filters_person_nodes(client: AsyncClient) -> None:
|
|
with (
|
|
patch(
|
|
"bracc.routers.public.execute_query_single",
|
|
new_callable=AsyncMock,
|
|
return_value={
|
|
"c": {"cnpj": "11.111.111/0001-11", "razao_social": "Empresa Teste"},
|
|
"entity_labels": ["Company"],
|
|
"entity_id": "c1",
|
|
},
|
|
),
|
|
patch(
|
|
"bracc.routers.public.execute_query",
|
|
new_callable=AsyncMock,
|
|
return_value=[
|
|
{
|
|
"nodes": [
|
|
_FakeNode(
|
|
"c1",
|
|
["Company"],
|
|
razao_social="Empresa Teste",
|
|
cnpj="11.111.111/0001-11",
|
|
),
|
|
_FakeNode("p1", ["Person"], name="Pessoa Teste", cpf="12345678900"),
|
|
],
|
|
"relationships": [
|
|
_FakeRel("r1", "c1", "p1", "SOCIO_DE", confidence=1.0),
|
|
],
|
|
"center_id": "c1",
|
|
}
|
|
],
|
|
),
|
|
):
|
|
response = await client.get("/api/v1/public/graph/company/11111111000111")
|
|
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert len(payload["nodes"]) == 1
|
|
assert payload["nodes"][0]["type"] == "company"
|
|
assert len(payload["edges"]) == 0
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_baseline_disabled_in_public_mode(
|
|
client: AsyncClient,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "public_mode", True)
|
|
monkeypatch.setattr(settings, "public_allow_entity_lookup", False)
|
|
response = await client.get("/api/v1/baseline/test-id")
|
|
assert response.status_code == 403
|
|
assert "disabled in public mode" in response.json()["detail"]
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_stats_hides_person_count_in_public_mode(
|
|
client: AsyncClient,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "public_mode", True)
|
|
monkeypatch.setattr(settings, "public_allow_person", False)
|
|
# Clear stats cache to ensure fresh computation
|
|
import bracc.routers.meta as meta_mod
|
|
monkeypatch.setattr(meta_mod, "_stats_cache", None)
|
|
|
|
fake_record = {
|
|
"total_nodes": 100,
|
|
"total_relationships": 200,
|
|
"person_count": 999,
|
|
"company_count": 50,
|
|
"health_count": 10,
|
|
"finance_count": 5,
|
|
"contract_count": 20,
|
|
"sanction_count": 3,
|
|
"election_count": 7,
|
|
"amendment_count": 4,
|
|
"embargo_count": 2,
|
|
"education_count": 6,
|
|
"convenio_count": 8,
|
|
"laborstats_count": 9,
|
|
"offshore_entity_count": 1,
|
|
"offshore_officer_count": 2,
|
|
"global_pep_count": 3,
|
|
"cvm_proceeding_count": 4,
|
|
"expense_count": 11,
|
|
"pep_record_count": 12,
|
|
"expulsion_count": 13,
|
|
"leniency_count": 14,
|
|
"international_sanction_count": 15,
|
|
"gov_card_expense_count": 16,
|
|
"gov_travel_count": 17,
|
|
"bid_count": 18,
|
|
"fund_count": 19,
|
|
"dou_act_count": 20,
|
|
"tax_waiver_count": 21,
|
|
"municipal_finance_count": 22,
|
|
"declared_asset_count": 23,
|
|
"party_membership_count": 24,
|
|
"barred_ngo_count": 25,
|
|
"bcb_penalty_count": 26,
|
|
"labor_movement_count": 27,
|
|
"legal_case_count": 28,
|
|
"judicial_case_count": 29,
|
|
"source_document_count": 30,
|
|
"ingestion_run_count": 31,
|
|
"temporal_violation_count": 32,
|
|
"cpi_count": 33,
|
|
"inquiry_requirement_count": 34,
|
|
"inquiry_session_count": 35,
|
|
"municipal_bid_count": 36,
|
|
"municipal_contract_count": 37,
|
|
"municipal_gazette_act_count": 38,
|
|
}
|
|
with patch(
|
|
"bracc.routers.meta.execute_query_single",
|
|
new_callable=AsyncMock,
|
|
return_value=fake_record,
|
|
), patch(
|
|
"bracc.routers.meta.load_source_registry",
|
|
return_value=[],
|
|
), patch(
|
|
"bracc.routers.meta.source_registry_summary",
|
|
return_value={
|
|
"universe_v1_sources": 0,
|
|
"implemented_sources": 0,
|
|
"loaded_sources": 0,
|
|
"healthy_sources": 0,
|
|
"stale_sources": 0,
|
|
"blocked_external_sources": 0,
|
|
"quality_fail_sources": 0,
|
|
"discovered_uningested_sources": 0,
|
|
},
|
|
):
|
|
response = await client.get("/api/v1/meta/stats")
|
|
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert payload["person_count"] == 0
|
|
assert payload["company_count"] == 50 # non-person counts preserved
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_timeline_sanitizes_properties_in_public_mode(
|
|
client: AsyncClient,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "public_mode", True)
|
|
monkeypatch.setattr(settings, "public_allow_entity_lookup", True)
|
|
mock_records = [
|
|
{
|
|
"lbls": ["Contract"],
|
|
"props": {"type": "licitacao", "cpf": "12345678900", "value": 50000.0},
|
|
"event_date": "2024-01-15",
|
|
"id": "evt-1",
|
|
},
|
|
]
|
|
with patch(
|
|
"bracc.routers.entity.execute_query",
|
|
new_callable=AsyncMock,
|
|
return_value=mock_records,
|
|
):
|
|
response = await client.get("/api/v1/entity/test-id/timeline")
|
|
|
|
assert response.status_code == 200
|
|
payload = response.json()
|
|
assert len(payload["events"]) == 1
|
|
event_props = payload["events"][0]["properties"]
|
|
assert "cpf" not in event_props
|
|
assert event_props["value"] == 50000.0
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_investigations_disabled_in_public_mode(
|
|
client: AsyncClient,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "public_mode", True)
|
|
monkeypatch.setattr(settings, "public_allow_investigations", False)
|
|
response = await client.get("/api/v1/investigations/")
|
|
assert response.status_code == 403
|
|
assert "disabled in public mode" in response.json()["detail"]
|