test(backend): Phase 1 - Improve testing infrastructure and add error path tests

## Changes

### pytest-asyncio Integration
- Add pytest-asyncio>=0.21.0 to requirements-dev.txt
- Update pytest.ini with asyncio_mode=auto and fixture loop scope
- Remove custom pytest_pyfunc_call handler from conftest.py
- Add @pytest.mark.asyncio to 21 async test functions

### Error Path Tests
- Create test_downloader_error_paths.py with 19 new tests covering:
  - DownloadStreamControl state management (6 tests)
  - Downloader configuration and initialization (4 tests)
  - DownloadProgress dataclass validation (1 test)
  - Custom exception handling (2 tests)
  - Authentication header generation (3 tests)
  - Session management (3 tests)

### Documentation
- Update backend-testing-improvement-plan.md with Phase 1 completion status

## Test Results
- All 458 service tests pass
- No regressions introduced

Relates to backend testing improvement plan Phase 1
This commit is contained in:
Will Miao
2026-02-11 10:29:21 +08:00
parent 6b1e3f06ed
commit 25e6d72c4f
6 changed files with 870 additions and 30 deletions

View File

@@ -105,35 +105,6 @@ def _isolate_settings_dir(tmp_path_factory, monkeypatch, request):
settings_manager_module.reset_settings_manager()
def pytest_pyfunc_call(pyfuncitem):
"""Allow bare async tests to run without pytest.mark.asyncio."""
test_function = pyfuncitem.function
if inspect.iscoroutinefunction(test_function):
func = pyfuncitem.obj
signature = inspect.signature(func)
accepted_kwargs: Dict[str, Any] = {}
for name, parameter in signature.parameters.items():
if parameter.kind is inspect.Parameter.VAR_POSITIONAL:
continue
if parameter.kind is inspect.Parameter.VAR_KEYWORD:
accepted_kwargs = dict(pyfuncitem.funcargs)
break
if name in pyfuncitem.funcargs:
accepted_kwargs[name] = pyfuncitem.funcargs[name]
original_policy = asyncio.get_event_loop_policy()
policy = pyfuncitem.funcargs.get("event_loop_policy")
if policy is not None and policy is not original_policy:
asyncio.set_event_loop_policy(policy)
try:
asyncio.run(func(**accepted_kwargs))
finally:
if policy is not None and policy is not original_policy:
asyncio.set_event_loop_policy(original_policy)
return True
return None
@dataclass
class MockHashIndex:
"""Minimal hash index stub mirroring the scanner contract."""