Skip to content

test: stop stubbing discovery_inquiry_flow, restore real coverage - #63

Merged
Sirajmx merged 1 commit into
IABTechLab:mainfrom
garvitkaushik-123:flows/unstub-discovery-inquiry
Sep 9, 2026
Merged

test: stop stubbing discovery_inquiry_flow, restore real coverage#63
Sirajmx merged 1 commit into
IABTechLab:mainfrom
garvitkaushik-123:flows/unstub-discovery-inquiry

Conversation

@garvitkaushik-123

Copy link
Copy Markdown
Contributor

Summary

Part 1 of #60 (green-lit by @atc964 on the issue).

The "pre-existing @listen() bugs with CrewAI version mismatch" stub that every ad_seller.flows-touching test file carried for discovery_inquiry_flow dates to when crewai was pinned at >=0.86.0 (commit 530df34, March 2026). It's >=1.14.4 now (resolves to 1.15.2) — a major version bump. Verified on the issue that the flow runs cleanly end-to-end on the current version, including the exact production call path (flow.query()'s sync self.kickoff() invoked from inside an async FastAPI handler).

This wasn't just stale test hygiene — DiscoveryInquiryFlow backs a live endpoint (POST /discovery, products.py:201), so the blanket stub meant that endpoint had zero real test coverage for as long as the stub existed (grep -rl "DiscoveryInquiryFlow" tests/ returned nothing before this PR).

execution_activation_flow stays stubbed — it has a separate, real bug (a cancel-scope leak in an MCP client cleanup path, not the @listen() issue the old comment claimed) that's tracked as #60 part 2 and will be fixed independently.

Changes

  • Removed the discovery_inquiry_flow entry from the _broken_flows stub list in all 33 test files that had it. execution_activation_flow entries are untouched.
  • Simplified test_deal_flow_e2e.py: its _get_deal_request_flow_class() helper existed solely to bypass flows/__init__.py via manual sys.modules surgery so importing DealRequestFlow wouldn't also trigger discovery_inquiry_flow. With the stub gone that's unnecessary — replaced with a plain module import (net -41 lines in that file alone).
  • Fixed the stale docstring in test_linear_tv.py making the same claim.
  • Added tests/unit/test_discovery_inquiry_flow.py: the flow's own routing logic across all four response types (catalog/pricing/availability/targeting) run for real, plus POST /discovery exercised through the actual FastAPI app with the real flow — not mocked — closing the exact coverage gap the issue found.

Test plan

Full suite: 1491 passed, 28 skipped (pre-existing, unrelated to this change), no regressions. ruff check / format --check clean.

Part of #60.

…sue IABTechLab#60 part 1)

The "pre-existing @listen() bugs with CrewAI version mismatch" stub
that every ad_seller.flows-touching test file carried for
discovery_inquiry_flow dates to when crewai was pinned at >=0.86.0
(commit 530df34, March 2026). crewai is >=1.14.4 now (resolves to
1.15.2) -- a major version bump, and the flow runs cleanly end to end
on it: verified DiscoveryInquiryFlow.kickoff_async() across all four
routing branches, both standalone and under pytest, and confirmed the
production call path (the sync flow.query() wrapper -- which calls
self.kickoff(), not kickoff_async() -- invoked from inside an async
FastAPI handler) also works correctly. atc964 confirmed on the issue
that the rationale was real at the time and just never got revisited
as crewai moved forward.

This wasn't just stale test hygiene: DiscoveryInquiryFlow backs a live
endpoint (POST /discovery, products.py:201), so the blanket stub meant
that endpoint had been running in production with zero real test
coverage for as long as the stub existed. jaanijuk caught that on the
issue -- grep for "DiscoveryInquiryFlow" in tests/ before this change
returns nothing.

execution_activation_flow stays stubbed. It has a separate, real bug
(a cancel-scope leak in an MCP client cleanup path on ad-server
connection failure, landing outside the code's own try/except) that's
being tracked and fixed independently as issue IABTechLab#60 part 2 -- it just
isn't the @listen() bug the old comment claimed either.

Changes:
- Removed the discovery_inquiry_flow entry from the _broken_flows stub
  list in all 33 test files that had it (execution_activation_flow
  entries are untouched).
- Simplified test_deal_flow_e2e.py: its _get_deal_request_flow_class()
  helper existed solely to bypass flows/__init__.py via manual
  sys.modules surgery so importing DealRequestFlow wouldn't also trigger
  discovery_inquiry_flow. With the stub gone that's unnecessary --
  replaced with a plain module import.
- Fixed the stale docstring in test_linear_tv.py making the same claim.
- Added tests/unit/test_discovery_inquiry_flow.py: the flow's own
  routing logic across all four response types run for real (no stub),
  plus POST /discovery exercised through the actual FastAPI app with
  the real flow (not mocked) -- closing the exact coverage gap this
  issue found.

Full suite: 1491 passed, 28 skipped (pre-existing, unrelated), no
regressions. ruff check / format clean.

Part of IABTechLab#60.

@Sirajmx Sirajmx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: The full suite matches the stated plan exactly - 1,491 passed, 28 skipped, with Ruff clean. There are no src/ changes, so there is no direct production-code behavioral impact.

Also ran end-to-end testing against a live server with no regressions across the route surface.

Looks good to merge.

@Sirajmx
Sirajmx merged commit 492d4e2 into IABTechLab:main Sep 9, 2026
5 checks passed
Sirajmx pushed a commit that referenced this pull request Sep 9, 2026
…ter/exit (issue #60 part 2) (#66)

OpenDirect21Client.connect()/disconnect() used to split
streamablehttp_client's and ClientSession's __aenter__/__aexit__ across
two separate method calls. That's fine as long as the connection
succeeds -- but the moment the attempt itself failed, __aenter__ never
returned, so __aexit__ was never called by anything. The transport was
abandoned to Python's async-generator GC finalizer, which runs in
whatever task the garbage collector happens to be executing in at
finalization time -- not necessarily the task that opened the
connection. anyio requires a cancel scope to be entered and exited by
the same task, so that mismatch crashed with "Attempted to exit cancel
scope in a different task than it was entered in", and the crash
propagated all the way up through kickoff_async(), taking down the
whole ExecutionActivationFlow.

Confirmed the root cause in isolation before touching anything: a bare
`streamablehttp_client(url).__aenter__()` / manual `__aexit__()` later
against an unreachable host reproduces the crash on its own, with
nothing else from this codebase involved. A plain, unbroken nested
`async with streamablehttp_client(...) as (r, w, _): async with
ClientSession(r, w) as session: ...` against the same unreachable host
fails cleanly with an ordinary ExceptionGroup -- no crash. The fix has
to make connect()/disconnect() behave like that nested block, not like
two independent calls.

connect() now starts a background task that owns the MCP session's
entire lifetime -- opening AND closing streamablehttp_client and
ClientSession within one unbroken `async with`, in that one task, from
start to finish. The task blocks on an asyncio.Event in between;
disconnect() sets the event and awaits the task to let it unwind
naturally, in the same task it opened in. This mirrors the pattern
deals_api_mcp_client.py already uses for exactly this reason, cited in
its own docstring: "Satisfies anyio's cancel-scope invariant by
running the full streamablehttp_client lifecycle inside a single
background asyncio Task."

Verified: the original ExecutionActivationFlow repro from #60 no
longer raises anything -- kickoff_async() now completes and the
connection failure is recorded as a plain state warning, exactly the
graceful-degrade behavior create_execution_order's own (now
reachable) try/except was always meant to provide. Verified both
execution_type paths (deal_id and io_order). Verified 5 repeated
connect/disconnect cycles against an unreachable host in a row.

Found along the way, not fixed here (separate bug, out of scope for
this crash): with the crash no longer masking it, the resulting
warning reads "'OpenDirect21Client' object has no attribute
'create_execution_order'" -- UnifiedClient.create_execution_order()
calls a method OpenDirect21Client never actually defines. Worth its
own issue.

Tests: new tests/unit/test_opendirect21_client.py -- connection
failure degrades cleanly with no raise (the regression), REST fallback
via _call_tool when MCP never connected, 5 repeated cycles, disconnect
without a prior connect is a no-op, and a mocked successful connection
still sets up the session/tools and disconnects both context managers
correctly (the happy path the rewrite must not break).

Full suite: 1484 passed, 28 skipped (pre-existing, unrelated), no
regressions. ruff check / format clean.

Closes #60 (part 2, alongside #63 for part 1).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants