Initial commit: CI Agent competitive-intelligence monitoring app
FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
"""Dashboard analytics: aggregate counts scoped correctly to the requesting
|
||||
user (never leaking another user's data), zero-filled for enum members with
|
||||
no data, and recent signals ordered newest-first."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models.alert import Alert
|
||||
from app.models.company import Company
|
||||
from app.models.detected_change import DetectedChange
|
||||
from app.models.enums import (
|
||||
ChangeType,
|
||||
MonitoringRunStatus,
|
||||
MonitoringRunTrigger,
|
||||
SeverityLevel,
|
||||
SourceType,
|
||||
)
|
||||
from app.models.monitoring_run import MonitoringRun
|
||||
from app.models.snapshot import Snapshot
|
||||
from app.models.source import Source
|
||||
from app.services.analytics_service import get_dashboard_analytics
|
||||
|
||||
|
||||
async def _make_company_with_activity(db_session, user_id: uuid.UUID) -> Company:
|
||||
company = Company(
|
||||
id=uuid.uuid4(), user_id=user_id, name="Acme Corp", slug=f"acme-{uuid.uuid4().hex[:6]}"
|
||||
)
|
||||
db_session.add(company)
|
||||
await db_session.flush()
|
||||
|
||||
source = Source(
|
||||
company_id=company.id,
|
||||
source_type=SourceType.WEBSITE,
|
||||
name="Site",
|
||||
base_url="https://acme.example",
|
||||
)
|
||||
db_session.add(source)
|
||||
await db_session.flush()
|
||||
|
||||
run = MonitoringRun(
|
||||
company_id=company.id,
|
||||
trigger_type=MonitoringRunTrigger.MANUAL,
|
||||
status=MonitoringRunStatus.SUCCESSFUL,
|
||||
)
|
||||
db_session.add(run)
|
||||
await db_session.flush()
|
||||
|
||||
snapshot = Snapshot(
|
||||
company_id=company.id,
|
||||
source_id=source.id,
|
||||
snapshot_type=source.source_type.value,
|
||||
hash="h1",
|
||||
structured_summary={},
|
||||
text_summary="",
|
||||
monitoring_run_id=run.id,
|
||||
)
|
||||
db_session.add(snapshot)
|
||||
await db_session.flush()
|
||||
|
||||
change = DetectedChange(
|
||||
company_id=company.id,
|
||||
source_id=source.id,
|
||||
monitoring_run_id=run.id,
|
||||
current_snapshot_id=snapshot.id,
|
||||
change_type=ChangeType.LEADERSHIP_CHANGE,
|
||||
raw_diff={},
|
||||
significance_score=0.6,
|
||||
confidence_score=0.8,
|
||||
severity=SeverityLevel.HIGH,
|
||||
summary="New CEO announced",
|
||||
)
|
||||
db_session.add(change)
|
||||
await db_session.flush()
|
||||
|
||||
alert = Alert(
|
||||
company_id=company.id,
|
||||
detected_change_id=change.id,
|
||||
user_id=user_id,
|
||||
title="Leadership change",
|
||||
summary="New CEO announced",
|
||||
why_it_matters="Signals a strategy shift",
|
||||
severity=SeverityLevel.HIGH,
|
||||
confidence=0.8,
|
||||
)
|
||||
db_session.add(alert)
|
||||
await db_session.commit()
|
||||
return company
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_analytics_aggregates_counts_for_the_requesting_user(db_session):
|
||||
user_id = uuid.uuid4()
|
||||
await _make_company_with_activity(db_session, user_id)
|
||||
|
||||
analytics = await get_dashboard_analytics(db_session, user_id)
|
||||
|
||||
assert analytics.changes_by_type["leadership_change"] == 1
|
||||
assert analytics.changes_by_type["price_change"] == 0
|
||||
assert analytics.alerts_by_severity["high"] == 1
|
||||
assert analytics.alerts_by_severity["critical"] == 0
|
||||
assert analytics.sources_by_status["active"] == 1
|
||||
assert len(analytics.recent_signals) == 1
|
||||
assert analytics.recent_signals[0].company_name == "Acme Corp"
|
||||
assert analytics.recent_signals[0].change_type == "leadership_change"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_analytics_does_not_leak_another_users_data(db_session):
|
||||
user_id = uuid.uuid4()
|
||||
other_user_id = uuid.uuid4()
|
||||
await _make_company_with_activity(db_session, other_user_id)
|
||||
|
||||
analytics = await get_dashboard_analytics(db_session, user_id)
|
||||
|
||||
assert all(count == 0 for count in analytics.changes_by_type.values())
|
||||
assert all(count == 0 for count in analytics.alerts_by_severity.values())
|
||||
assert analytics.recent_signals == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_analytics_recent_signals_ordered_newest_first(db_session):
|
||||
user_id = uuid.uuid4()
|
||||
company = await _make_company_with_activity(db_session, user_id)
|
||||
|
||||
source = Source(
|
||||
company_id=company.id,
|
||||
source_type=SourceType.WEBSITE,
|
||||
name="Site 2",
|
||||
base_url="https://acme.example/2",
|
||||
)
|
||||
db_session.add(source)
|
||||
await db_session.flush()
|
||||
run = MonitoringRun(company_id=company.id, trigger_type=MonitoringRunTrigger.MANUAL)
|
||||
db_session.add(run)
|
||||
await db_session.flush()
|
||||
snapshot = Snapshot(
|
||||
company_id=company.id,
|
||||
source_id=source.id,
|
||||
snapshot_type=source.source_type.value,
|
||||
hash="h2",
|
||||
structured_summary={},
|
||||
text_summary="",
|
||||
monitoring_run_id=run.id,
|
||||
)
|
||||
db_session.add(snapshot)
|
||||
await db_session.flush()
|
||||
newer_change = DetectedChange(
|
||||
company_id=company.id,
|
||||
source_id=source.id,
|
||||
monitoring_run_id=run.id,
|
||||
current_snapshot_id=snapshot.id,
|
||||
change_type=ChangeType.PRICE_CHANGE,
|
||||
raw_diff={},
|
||||
significance_score=0.5,
|
||||
confidence_score=0.7,
|
||||
severity=SeverityLevel.MEDIUM,
|
||||
summary="Price increased",
|
||||
created_at=datetime.now(UTC),
|
||||
)
|
||||
db_session.add(newer_change)
|
||||
await db_session.commit()
|
||||
|
||||
analytics = await get_dashboard_analytics(db_session, user_id)
|
||||
|
||||
assert analytics.recent_signals[0].change_type == "price_change"
|
||||
Reference in New Issue
Block a user