Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 2 additions & 45 deletions tests/integration/test_deal_flow_e2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
- Approval gate integration
"""

import importlib
import sys

import ad_seller.flows.deal_request_flow as deal_request_flow_module
from ad_seller.models.buyer_identity import BuyerContext, BuyerIdentity
from ad_seller.models.core import DealType, PricingModel
from ad_seller.models.flow_state import (
Expand All @@ -18,46 +16,6 @@

from .conftest import InMemoryStorage, make_settings


def _get_deal_request_flow_class():
"""Import DealRequestFlow directly from its module, bypassing flows/__init__.py
which triggers discovery_inquiry_flow (broken with current crewai version)."""
mod_name = "ad_seller.flows.deal_request_flow"
if mod_name in sys.modules:
return sys.modules[mod_name].DealRequestFlow

# Ensure the parent package 'ad_seller.flows' exists in sys.modules
# as a stub so that find_spec / relative imports work, without
# executing the __init__.py that pulls in the broken module.
parent_name = "ad_seller.flows"
original_parent = sys.modules.get(parent_name)
installed_stub = False
if original_parent is None:
import types

import ad_seller # noqa: F401

stub = types.ModuleType(parent_name)
stub.__path__ = [str(importlib.resources.files("ad_seller").joinpath("flows"))]
stub.__package__ = parent_name
sys.modules[parent_name] = stub
installed_stub = True

spec = importlib.util.find_spec(mod_name)
if spec is None:
raise ImportError(f"Cannot find {mod_name}")
mod = importlib.util.module_from_spec(spec)
sys.modules[mod_name] = mod
spec.loader.exec_module(mod)

# Remove the stub so other tests that import ad_seller.flows get the real
# module (with ProductSetupFlow etc.) instead of a bare stub.
if installed_stub:
del sys.modules[parent_name]

return mod.DealRequestFlow


# ============================================================================
# Pricing Engine
# ============================================================================
Expand Down Expand Up @@ -139,7 +97,7 @@ class TestDealRequestFlowE2E:

def _make_state(self, request_text, buyer_context=None, seller_org="INTEG"):
"""Create a DealRequestState manually and return (module, state)."""
mod = sys.modules["ad_seller.flows.deal_request_flow"]
mod = deal_request_flow_module
state = mod.DealRequestState(
flow_id="test-flow-001",
flow_type="deal_request",
Expand All @@ -152,7 +110,6 @@ def _make_state(self, request_text, buyer_context=None, seller_org="INTEG"):

async def _run_steps(self, request_text, buyer_context=None, seller_org="INTEG"):
"""Run the flow steps manually in sequence on a DealRequestState."""
_get_deal_request_flow_class() # ensure module is loaded
mod, state = self._make_state(request_text, buyer_context, seller_org)

settings = make_settings(seller_organization_id=seller_org)
Expand Down
5 changes: 2 additions & 3 deletions tests/integration/test_packages_audience_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@
import sys
from types import ModuleType

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch) before importing main, mirroring test_quote_endpoints.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_agentic_audience_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

# Stub broken flow modules before importing main, mirroring sibling tests.
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_approval_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch) before any import of ad_seller.flows triggers __init__.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_audience_plan_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

# Stub broken flow modules before importing other ad_seller bits.
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_auth_header_binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version mismatch)
# before any import of ad_seller.flows triggers __init__.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_avails_contract_adoption.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch) before any import of ad_seller.flows triggers __init__.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_avails_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch) before any import of ad_seller.flows triggers __init__.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_avails_spec_dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch) before any import of ad_seller.flows triggers __init__.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_capability_audience_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch) before importing main, mirroring the pattern in
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
# test_quote_endpoints.py.
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_change_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@

# Stub broken flow modules
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_csv_catalog_coherence.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch). Same pattern used in test_issue34_catalog_fixes.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/test_deal_booking_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version mismatch)
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_deal_booking_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch). Mirrors the pattern in tests/unit/test_deal_booking_endpoints.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
12 changes: 5 additions & 7 deletions tests/unit/test_deal_performance_gam.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,11 @@ async def test_rest_performance_endpoint_returns_real_gam_numbers(self):
import sys
from types import ModuleType

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI
# version mismatch) before ad_seller.flows import — same pattern as
# test_avails_endpoint.py.
for _mod_name in (
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
):
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure — issue #60 part 2, unresolved) before
# ad_seller.flows import — same pattern as test_avails_endpoint.py.
# discovery_inquiry_flow no longer needs stubbing (issue #60 part 1).
for _mod_name in ("ad_seller.flows.execution_activation_flow",):
if _mod_name not in sys.modules:
_stub = ModuleType(_mod_name)
_cls = _mod_name.rsplit(".", 1)[-1].replace("_", " ").title().replace(" ", "")
Expand Down
5 changes: 2 additions & 3 deletions tests/unit/test_default_catalog_enrichment.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@
import pytest
from fastapi import HTTPException

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version
# mismatch). Same pattern used across the unit test suite.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
122 changes: 122 additions & 0 deletions tests/unit/test_discovery_inquiry_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Author: Green Mountain Systems AI Inc.
# Donated to IAB Tech Lab

"""Real coverage for DiscoveryInquiryFlow and POST /discovery (issue #60 part 1).

Every test file that touched ad_seller.flows used to stub this module out
before import, citing a "pre-existing @listen() bug with CrewAI version
mismatch" that dated to when crewai was pinned at >=0.86.0. crewai is
>=1.14.4 now, the flow runs cleanly end to end, and DiscoveryInquiryFlow
backs a live endpoint (POST /discovery, products.py:201) -- so the stub
wasn't just hiding dead code, it was hiding a served API path. See issue
#60 for the full investigation.

These tests run the real flow, not a stub.
"""

import httpx
import pytest
from httpx import ASGITransport

from ad_seller.flows.discovery_inquiry_flow import DiscoveryInquiryFlow
from ad_seller.interfaces.api.main import _get_optional_api_key_record, app
from ad_seller.models.buyer_identity import BuyerContext, BuyerIdentity
from ad_seller.models.core import DealType, PricingModel
from ad_seller.models.flow_state import ProductDefinition


def _make_product(product_id="ctv-premium-sports", inventory_type="ctv"):
return ProductDefinition(
product_id=product_id,
name="Premium CTV - Sports",
inventory_type=inventory_type,
supported_deal_types=[DealType.PREFERRED_DEAL],
supported_pricing_models=[PricingModel.CPM],
base_cpm=35.0,
floor_cpm=28.0,
minimum_impressions=100000,
)


def _products():
return {"ctv-premium-sports": _make_product()}


class TestDiscoveryInquiryFlowRouting:
"""The flow's own routing logic, run for real -- no stub in the way."""

@pytest.mark.parametrize(
"query, expected_type",
[
("show me what you have", "catalog"),
("how much does this cost?", "pricing"),
("what CPM for CTV?", "pricing"),
("what inventory is available?", "availability"),
("how many impressions do you have?", "availability"),
("what audience targeting do you support?", "targeting"),
],
)
def test_query_routes_to_expected_response_type(self, query, expected_type):
flow = DiscoveryInquiryFlow()
response = flow.query(query=query, buyer_context=None, products=_products())
assert flow.state.response_type == expected_type
assert response is not None

def test_public_buyer_gets_price_range_not_exact_price(self):
flow = DiscoveryInquiryFlow()
response = flow.query(
query="how much does CTV cost?", buyer_context=None, products=_products()
)
assert flow.state.response_type == "pricing"
assert response is not None

def test_authenticated_buyer_context_is_accepted(self):
ctx = BuyerContext(
identity=BuyerIdentity(agency_id="agency-1", agency_name="Test Agency"),
is_authenticated=True,
)
flow = DiscoveryInquiryFlow()
response = flow.query(query="what's available?", buyer_context=ctx, products=_products())
assert flow.state.response_type == "availability"
assert response is not None

def test_empty_catalog_does_not_crash(self):
flow = DiscoveryInquiryFlow()
response = flow.query(query="show me your catalog", buyer_context=None, products={})
assert response is not None


class TestDiscoveryEndpointRealFlow:
"""POST /discovery through the real FastAPI app -- the real
DiscoveryInquiryFlow runs, nothing mocked."""

@pytest.fixture
def client(self):
app.dependency_overrides[_get_optional_api_key_record] = lambda: None
transport = ASGITransport(app=app)
c = httpx.AsyncClient(transport=transport, base_url="http://test")
yield c
app.dependency_overrides.clear()

async def test_discovery_query_returns_200(self, client):
async with client as c:
resp = await c.post("/discovery", json={"query": "what inventory do you have?"})
assert resp.status_code == 200, resp.text

async def test_discovery_pricing_query_returns_200(self, client):
async with client as c:
resp = await c.post("/discovery", json={"query": "how much does CTV cost?"})
assert resp.status_code == 200, resp.text

async def test_discovery_query_with_buyer_tier_returns_200(self, client):
async with client as c:
resp = await c.post(
"/discovery",
json={"query": "what's available?", "buyer_tier": "agency", "agency_id": "ag-1"},
)
assert resp.status_code == 200, resp.text

async def test_discovery_missing_query_is_422(self, client):
async with client as c:
resp = await c.post("/discovery", json={})
assert resp.status_code == 422
5 changes: 2 additions & 3 deletions tests/unit/test_endpoint_no_flow_kickoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@

import pytest

# Stub broken flow modules (pre-existing @listen() bugs with CrewAI version mismatch).
# Same pattern used in test_deal_booking_endpoints.py.
# Stub execution_activation_flow (cancel-scope leak on ad-server
# connection failure, unresolved -- issue #60 part 2).
_broken_flows = [
"ad_seller.flows.discovery_inquiry_flow",
"ad_seller.flows.execution_activation_flow",
]
for _mod_name in _broken_flows:
Expand Down
Loading
Loading