Files
CIAgent/apps/api/tests/integration/test_acme_fixture_demo.py
saksham 1a4c80958f 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).
2026-08-05 10:48:20 -04:00

118 lines
4.4 KiB
Python

"""Regression test for the collection + change-detection pipeline using the
Acme Mobility Systems v1/v2 fixture HTML under tests/fixtures/acme_mobility/,
read straight off disk and run through the real pipeline end-to-end - so a
change to those fixtures, or a regression in the pipeline, breaks a test
here rather than going unnoticed. No live network: respx mocks every HTTP
call for a fictitious acme-mobility.example host."""
from __future__ import annotations
import uuid
from pathlib import Path
from unittest.mock import patch
import httpx
import pytest
import respx
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from app.models.company import Company
from app.models.enums import ChangeType, SourceType
from app.models.monitor_configuration import MonitorConfiguration
from app.repositories.source_repository import SnapshotRepository, SourceRepository
from app.services import collection_service
from app.services.change_detection_service import detect_change_for_source
_FIXTURE_ROOT = Path(__file__).resolve().parent.parent / "fixtures" / "acme_mobility"
_HOST = "https://acme-mobility.example"
_PAGES = ["about", "products", "careers", "press", "pricing"]
def _page_html(version: str, page: str) -> str:
return (_FIXTURE_ROOT / version / f"{page}.html").read_text(encoding="utf-8")
@pytest.fixture(autouse=True)
def _no_real_dns():
with patch("socket.getaddrinfo", return_value=[(2, 1, 6, "", ("93.184.216.34", 0))]):
yield
async def _make_company(db_session) -> Company:
company = Company(
id=uuid.uuid4(),
user_id=uuid.uuid4(),
name="Acme Mobility Systems (Demo)",
slug=f"acme-mobility-demo-{uuid.uuid4().hex[:6]}",
official_website=f"{_HOST}/about",
monitoring_focus="leadership changes, pricing, hiring, expansion",
)
db_session.add(company)
db_session.add(MonitorConfiguration(company_id=company.id))
await db_session.commit()
result = await db_session.execute(
select(Company)
.where(Company.id == company.id)
.options(
selectinload(Company.aliases),
selectinload(Company.competitors),
selectinload(Company.enrichment),
)
)
return result.scalar_one()
def _mock_version(version: str):
respx.get(f"{_HOST}/robots.txt").mock(return_value=httpx.Response(404))
for page in _PAGES:
respx.get(f"{_HOST}/{page}").mock(
return_value=httpx.Response(200, html=_page_html(version, page))
)
@pytest.mark.asyncio
async def test_acme_v1_to_v2_produces_the_expected_change_types(db_session, settings):
company = await _make_company(db_session)
repo = SourceRepository(db_session)
sources = {}
for page in _PAGES:
sources[page] = await repo.create(
company_id=company.id,
source_type=SourceType.CUSTOM_URL,
name=f"Acme Mobility - {page.title()}",
base_url=f"{_HOST}/{page}",
)
await db_session.commit()
# Baseline run (v1): every source collects successfully, no prior
# snapshot to diff against yet.
with respx.mock:
_mock_version("v1")
for page in _PAGES:
result = await collection_service.collect_source(
db_session, settings, sources[page], company
)
assert result.status.value == "active", f"{page} baseline collection failed"
# Second run (v2): about/careers/press/pricing changed, products did not.
with respx.mock:
_mock_version("v2")
for page in _PAGES:
await collection_service.collect_source(db_session, settings, sources[page], company)
snapshot_repo = SnapshotRepository(db_session)
changes: dict[str, ChangeType | None] = {}
for page in _PAGES:
current = await snapshot_repo.latest_for_source(sources[page].id)
change = await detect_change_for_source(
db_session, sources[page], company, current, uuid.uuid4()
)
changes[page] = change.change_type if change else None
assert changes["about"] == ChangeType.LEADERSHIP_CHANGE
assert changes["pricing"] == ChangeType.PRICE_CHANGE
assert changes["products"] is None # identical content -> hash short-circuit, no change
assert changes["careers"] is not None # new job listing -> detected as a real change
assert changes["press"] is not None # new press entry -> detected as a real change