Skip to content

fix(tests): unbreak suite collection and restore the ruff gates - #83

Closed
grandcamel wants to merge 1 commit into
signalwire:mainfrom
grandcamel:fix/ci-gates
Closed

fix(tests): unbreak suite collection and restore the ruff gates#83
grandcamel wants to merge 1 commit into
signalwire:mainfrom
grandcamel:fix/ci-gates

Conversation

@grandcamel

Copy link
Copy Markdown
Contributor

Summary

Merge this one first — it unbreaks the test suite and the ruff CI gates for every other open PR.

The suite has been silently ~90-tests-red since the pyupgrade sweep

tests/unit/search/test_search_service.py replaces fastapi/numpy/nltk/… in sys.modules with bare stubs at module scope, imports search_service, then restores everything. Since 94e0386 (chore(lint): enable ruff pyupgrade) rewrote Optional[X] to X | None, that import dies half-way: search_service.py has no from __future__ import annotations, so the method annotation credentials: HTTPBasicCredentials | None is evaluated eagerly at class-definition time — and with the stub's HTTPBasicCredentials = None that's None | NoneTypeError.

The crash happens during collection, before the restore block runs, so the stubs stay installed for the entire pytest session. Every later test that touches FastAPI (ai_chat, web, auth_handler, web_mixin, security/webhook) inherits a fastapi module whose FastAPI is None. On this checkout that's 95 failed + 9 errors; with this two-line fix the same environment reports 10 failed / 5139 passed (the 10 are unrelated environment issues: 9 × test_examples stdout-log pollution, 1 × sentence-transformers/torch unavailable on Intel macOS). Runtime also drops ~10 min → ~2.5 min.

Fix: from __future__ import annotations in search_service.py, making its annotations lazy — the module now imports cleanly under the test's stubs, the restore block runs, and no state leaks. No runtime behavior change. The existing test file is itself the regression test: without the future-import its collection crashes.

Restore the ruff gates

run-ci.sh's LINT/FMT gates currently fail on main (recent commits landed without them):

  • ai_chat/__init__.py — RUF022, __all__ not sorted
  • search/document_processor.py — SIM102, nested if collapsed into the elif condition (no behavior change)
  • ruff format over ai_chat/gateway.py and core/function_result.py (formatting only)

After this PR: ruff check signalwire and ruff format --check signalwire both pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV

Since the pyupgrade sweep (94e0386), test_search_service.py's module
import crashes during collection: search_service.py has no future
annotations import, so `credentials: HTTPBasicCredentials | None`
evaluates eagerly, and under the test's sys.modules stubs that is
None | None -> TypeError. The crash lands before the test file's
restore block, so the fastapi/numpy/nltk stubs stay installed for the
whole pytest session and every later FastAPI-touching test fails with
FastAPI=None (95 failed + 9 errors on this checkout; 10 environmental
failures after the fix). from __future__ import annotations makes the
module's annotations lazy again — no runtime change; the existing test
file is itself the regression test.

Also restores the LINT/FMT gates, which currently fail on main:
- ai_chat/__init__.py: sort __all__ (RUF022)
- search/document_processor.py: fold nested if into elif (SIM102)
- ruff format over ai_chat/gateway.py and core/function_result.py

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV
@anthmFS

anthmFS commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Superseded by #81, which landed the same fixes: the __all__ sort (RUF022) and the document_processor.py SIM102 collapse are already on main and character-identical to this diff, plus the same ruff reformatting.

One line here is genuinely new and not on main — from __future__ import annotations in search_service.py. Splitting that out into its own PR rather than losing it.

Thanks — the diagnosis was right, it just raced with #81.

@anthmFS anthmFS closed this Aug 13, 2026
anthmFS added a commit that referenced this pull request Aug 13, 2026
)

* docs(rest)+test(core): true live_transcribe call shape; drop stale ai_sidecar bypass

calling.md showed live_transcribe(call_id, action="start", lang="en"),
but the generated signature takes action as a keyword-only TypedDict
union ({"start": {...}} or the literal "stop") and has no lang/from_lang
/to_lang parameters — copying the documented call raises TypeError. The
examples now show the real shape, with the start parameters inside the
action object where the schema defines them.

The sidecar pattern test appended ai_sidecar to the raw document with a
comment that the verb was not in the live SWML schema yet. It landed in
4645d48, so the test now uses add_verb_to_section directly — the path
the comment promised, and stronger, since the verb goes through schema
validation instead of around it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV
(cherry picked from commit 77fc34c)

* fix(ai_chat): forward conversation_timeout on the start path

prepare() puts conversation_timeout into the start params, but the HTTP
dispatch rebuilt the create_conversation call with only id and config_url,
silently dropping it. A gateway configured with conversation_timeout=900
told the browser 900 (via effective_timeout) while the service kept its
own 3600 default — the page schedules its idle warning around a number
the service never enforces. The chat path was unaffected (raw_post
streams params verbatim), which kept the drift invisible for
conversations opened by a first message.

The regression test drives the real HTTP dispatch through the ASGI
harness — prepare() was already correct, so a prepare()-level assertion
would have passed with the bug in place. Verified red without the fix,
green with it; it also pins the auto-create chat path so the two paths
cannot drift apart again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV
(cherry picked from commit 5bf93c7)

* fix(FunctionResult): emit execute_swml transfer beside the SWML document

execute_swml(transfer=True) wrote the flag INSIDE the SWML document —
{"SWML": {..., "transfer": "true"}} — where it is not a SWML key, so
the document executed but the call never exited the agent. The platform
documents transfer as a sibling of the SWML key in the action object,
which is exactly the shape the live-proven connect() and
swml_transfer() helpers already emit. The action is now
{"SWML": <doc>, "transfer": "true"}; transfer=False still omits the
key, and the caller's dict is still never mutated.

The three tests that pinned the inside placement now pin the sibling
placement, one of them asserting shape-parity with connect()'s action
so the two paths cannot drift apart again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV
(cherry picked from commit 295c17e)

* fix(FunctionResult): tap direction is speak/listen/both, and always emitted

The tap() helper disagreed with the SWML schema (and the platform docs)
twice over. It accepted and emitted "hear", which is not a tap
direction — the verb's enum is speak/listen/both, exactly the set
record_call() already uses — so direction="hear" produced SWML the
platform rejects; it now raises the existing ValueError instead of
silently emitting a dead tap. And its omit-if-default logic assumed the
verb's default matches the helper's "both" when the verb actually
defaults to "speak", so tap(uri) — documented as tapping both
directions — produced a speak-only tap. direction is now always
emitted; codec/rtp_ptime omissions stay (their helper defaults match
the verb defaults).

Note for the port audit: tap.direction's Literal feeds
python_signatures.json in porting-sdk as enum<...>, so the oracle needs
a regen alongside this change.

docs/swaig_reference.md and docs/api_reference.md updated to the real
enum (api_reference had a third variant — inbound/outbound plus a G722
codec and sip: URIs that the helper never accepted).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgA3KeCEPMKMJVvroZY1wV
(cherry picked from commit 0851a94)

* fix: annotate the new gateway test; salvage the search_service import

Two small follow-ups to the four fixes cherry-picked above.

The `conversation_timeout` test arrived unannotated. mypy has `tests` in scope
deliberately ("a new untyped test fails the gate"), so it reds TYPECHECK on
main. Annotated; mypy is back to main's exact baseline.

`from __future__ import annotations` in search_service.py is the one line worth
keeping from #83, which was otherwise superseded by #81. Measured on its own: no
change to the mypy count, so it is hygiene rather than a fix.

---------

Co-authored-by: grandcamel <jasonkrue@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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