Skip to content

Commit 76e9f84

Browse files
author
Review
committed
fix(ex_app): keep set_init_status mockable from sync tests
The previous fix piped every progress update through ``asyncio.run_coroutine_threadsafe(nc.set_init_status(...), loop)``. That works at runtime but breaks ``tests_unit/test_fetch_model_file.py``, which drives ``fetch_models_task`` from a regular sync test with a ``MagicMock`` ``nc``: there is no event loop and no real coroutine, so the loop-scheduling branch short-circuits and ``nc.set_init_status`` was never called at all. Make ``_ProgressReporter.__call__`` invoke ``nc.set_init_status`` unconditionally so mocks (and any future sync ``NextcloudApp`` shim) observe the call. Inspect the return value: if it is a coroutine, schedule it on the captured loop and wait for the result; if there is no loop available, ``coro.close()`` to avoid the "coroutine never awaited" warning; if it is not a coroutine at all (the mock case) just drop it. Also drop the empty ``error`` argument when calling ``set_init_status`` to keep the call signature identical to the pre-Phase-2 behaviour, so existing tests that assert ``call(100)`` rather than ``call(100, "")`` keep matching.
1 parent 69823ec commit 76e9f84

1 file changed

Lines changed: 14 additions & 4 deletions

File tree

nc_py_api/ex_app/integration_fastapi.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,19 +200,29 @@ class _ProgressReporter:
200200
"""Bridges sync model-fetch code to the async :py:meth:`NextcloudApp.set_init_status`.
201201
202202
``fetch_models_task`` runs in a FastAPI ``BackgroundTasks`` worker thread when the caller
203-
is sync, so we cannot ``await`` here. Each call schedules a coroutine on the captured main
204-
event loop and waits for the result to keep progress reporting linearizable; if no loop
205-
is available the call is silently dropped so model downloads still proceed.
203+
is sync, so we cannot ``await`` here. The reporter always calls ``nc.set_init_status`` so
204+
that mocks and sync test doubles observe the invocation, then schedules the resulting
205+
coroutine on the captured main event loop and waits for the result to keep progress
206+
reporting linearizable. If no loop is available the coroutine is closed to avoid a
207+
``coroutine '...' was never awaited`` warning, and the download proceeds anyway.
206208
"""
207209

208210
def __init__(self, nc: NextcloudApp, loop: asyncio.AbstractEventLoop | None):
209211
self._nc = nc
210212
self._loop = loop
211213

212214
def __call__(self, progress: int, error: str = "") -> None:
215+
# Call ``set_init_status`` with the same signature the bare sync API used (omitting
216+
# ``error`` when empty) so existing mocks keep matching ``call(100)`` rather than
217+
# ``call(100, "")``.
218+
result = self._nc.set_init_status(progress) if not error else self._nc.set_init_status(progress, error)
219+
if not asyncio.iscoroutine(result):
220+
# Mocked or sync wrapper - the call has been observed, nothing else to do.
221+
return
213222
if self._loop is None or self._loop.is_closed():
223+
result.close()
214224
return
215-
future = asyncio.run_coroutine_threadsafe(self._nc.set_init_status(progress, error), self._loop)
225+
future = asyncio.run_coroutine_threadsafe(result, self._loop)
216226
try:
217227
future.result(timeout=30)
218228
except Exception: # noqa pylint: disable=broad-exception-caught

0 commit comments

Comments
 (0)