idea198-civicswarm-privacy-.../tests/test_service.py

92 lines
3.8 KiB
Python

from __future__ import annotations
from pathlib import Path
from civicswarm.api import create_app
from civicswarm.service import CivicSwarmService
def build_service(tmp_path: Path) -> CivicSwarmService:
return CivicSwarmService.from_path(tmp_path / "civicswarm.sqlite")
def seed_service(service: CivicSwarmService) -> int:
proposal = service.create_proposal(
city="Springfield",
title="School street safety",
body=(
"Add protected bike lanes near schools.\n"
"Install safer crossings for pedestrians.\n"
"Preserve bus access for students and elders."
),
geography="ward-3",
tags=["transport", "schools", "safety"],
)
service.register_resident("resident-a", geography="ward-3", interests=["transport", "safety"], lived_experience=["parent"], languages=["en"])
service.register_resident("resident-b", geography="ward-7", interests=["schools"], lived_experience=["bus rider"], languages=["es", "en"])
service.register_resident("resident-c", geography="ward-3", interests=["parks"], lived_experience=["elder"], languages=["en"])
service.add_comment(proposal["id"], "I support safer crossings for children and elders.", resident_key="resident-a", geography="ward-3")
service.add_comment(proposal["id"], "We need bus access and safer sidewalks.", resident_key="resident-b", geography="ward-7")
service.add_comment(proposal["id"], "I worry about delayed bus service.", resident_key="resident-c", geography="ward-3")
service.submit_preference(proposal["id"], "resident-a", 0.9)
service.submit_preference(proposal["id"], "resident-b", 0.4)
service.submit_preference(proposal["id"], "resident-c", 0.1)
return proposal["id"]
def test_service_routes_comments_and_exports_brief(tmp_path: Path):
service = build_service(tmp_path)
proposal_id = seed_service(service)
summary = service.summarize_comments(proposal_id)
assert summary
assert "en" in summary
routed = service.route_proposal(proposal_id, top_n=2)
assert routed["fragments"]
assert routed["fragments"][0]["matches"]
dashboard = service.build_dashboard(proposal_id)
assert dashboard["preference_aggregate"]["count"] == 3
assert dashboard["consensus_pockets"]
brief = service.export_brief(proposal_id)
assert brief["proposal"]["title"] == "School street safety"
assert brief["provenance"]
assert brief["ledger"]
def test_dashboard_surface_exposes_api(tmp_path: Path):
app = create_app(tmp_path / "api.sqlite")
client = __import__("fastapi.testclient", fromlist=["TestClient"]).TestClient(app)
proposal = client.post(
"/proposals",
json={
"city": "Springfield",
"title": "Library weekend hours",
"body": "Keep the library open later on weekends for students and families.",
"geography": "ward-1",
"tags": ["libraries", "education"],
},
).json()
client.post("/residents", json={"resident_key": "resident-x", "geography": "ward-1", "interests": ["libraries"], "lived_experience": ["student"], "languages": ["en"]})
client.post(f"/proposals/{proposal['id']}/comments", json={"text": "I support later hours.", "resident_key": "resident-x", "channel": "sms"})
brief = client.get(f"/proposals/{proposal['id']}/brief").json()
assert brief["proposal"]["title"] == "Library weekend hours"
assert brief["summary"]
def test_private_preference_aggregation_supports_noise(tmp_path: Path):
service = build_service(tmp_path)
proposal_id = seed_service(service)
clean = service.aggregate_preferences(proposal_id)
noisy = service.aggregate_preferences(proposal_id, epsilon=0.75, seed=42)
assert clean["count"] == 3
assert noisy["count"] >= 1
assert noisy["mean_score"] != clean["mean_score"] or noisy["count"] != clean["count"]