|
| 1 | +"""Funding and executive detectors must consume their shared filing batch together.""" |
| 2 | +from types import SimpleNamespace |
| 3 | +from uuid import uuid4 |
| 4 | + |
| 5 | +import pytest |
| 6 | + |
| 7 | +from apps.api.core.tenancy import current_workspace_var |
| 8 | +from apps.api.database import SessionLocal |
| 9 | +from apps.api.services.leadgen.enrichment.providers import sec_edgar |
| 10 | +from apps.api.services.leadgen.orm_models import LeadRow, SignalRow |
| 11 | +from apps.api.services.poller import engine |
| 12 | +from apps.api.services.poller.models import WatchSubscription |
| 13 | +from apps.api.services.signals.store import SignalStore |
| 14 | + |
| 15 | + |
| 16 | +@pytest.mark.parametrize("kind,types,expected", [ |
| 17 | + ("company", ["company_funded", "executive_hired"], {"company_funded", "executive_hired"}), |
| 18 | + ("funding", ["company_funded", "executive_hired"], {"company_funded", "executive_hired"}), |
| 19 | + ("funding", ["company_funded"], {"company_funded"}), |
| 20 | + ("company", ["executive_hired"], {"executive_hired"}), |
| 21 | +]) |
| 22 | +def test_sec_poll_preserves_enabled_event_types_and_replay(kind, types, expected, monkeypatch): |
| 23 | + workspace, watch_id = f"sec-batch-{uuid4()}", str(uuid4()) |
| 24 | + filing = SimpleNamespace(cik="0001234567", accession="acc101", filing_date="2026-01-01", |
| 25 | + fields={"funding_amount": 5000000}, |
| 26 | + related_persons=[{"name": "Synthetic Person", "title": "Chief Executive Officer"}]) |
| 27 | + calls = [] |
| 28 | + class Provider: |
| 29 | + async def list_form_d_since(self, target, since): |
| 30 | + calls.append(since) |
| 31 | + return [filing] if not since or filing.accession > since else [] |
| 32 | + monkeypatch.setattr(sec_edgar, "SecEdgarProvider", Provider) |
| 33 | + monkeypatch.setattr(engine, "_debit_source", lambda *args: True) |
| 34 | + with SessionLocal() as db: |
| 35 | + lead = LeadRow(workspace_id=workspace, company="Synthetic SEC Company") |
| 36 | + db.add(lead) |
| 37 | + db.flush() |
| 38 | + lead_id = lead.id |
| 39 | + db.add(WatchSubscription(id=watch_id, workspace_id=workspace, kind=kind, |
| 40 | + target=lead.company, lead_id=lead_id, interval="daily", enabled=True, |
| 41 | + signal_types=types, cursor={"bootstrapped": True, "sec_last_accession": "acc100"})) |
| 42 | + db.commit() |
| 43 | + store = SignalStore(workspace) |
| 44 | + token = current_workspace_var.set(workspace) |
| 45 | + try: |
| 46 | + for attempt in ("first", "replay"): |
| 47 | + with SessionLocal() as db: |
| 48 | + watch = db.get(WatchSubscription, watch_id) |
| 49 | + kinds = engine._source_set(watch) |
| 50 | + for source in kinds: |
| 51 | + engine._poll_one_source(store, watch_id, workspace, source, attempt, lead_id, False) |
| 52 | + with SessionLocal() as db: |
| 53 | + signals = db.query(SignalRow).filter_by(workspace_id=workspace).all() |
| 54 | + assert {signal.signal_type for signal in signals} == expected |
| 55 | + assert len(signals) == len(expected) |
| 56 | + assert {signal.lead_id for signal in signals} == {lead_id} |
| 57 | + assert db.get(WatchSubscription, watch_id).cursor["sec_last_accession"] == "acc101" |
| 58 | + assert calls == ["acc100", "acc101"] |
| 59 | + finally: |
| 60 | + with SessionLocal() as db: |
| 61 | + db.query(SignalRow).filter_by(workspace_id=workspace).delete() |
| 62 | + db.query(WatchSubscription).filter_by(id=watch_id).delete() |
| 63 | + db.query(LeadRow).filter_by(workspace_id=workspace).delete() |
| 64 | + db.commit() |
| 65 | + current_workspace_var.reset(token) |
0 commit comments