mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-27 07:54:19 +00:00
feat(test): Phase 1.5 coverage expansion - COV-001 to COV-013
Coverage baseline raised from 13.65% to 26% (+12.35%) Gate raised from 12% to 18% Tasks completed: - COV-001: Command system unit tests (100% coverage) - COV-002: API service unit tests batch 1 (user/apikey/model/provider) - COV-003: Provider model manager unit tests - COV-004: Pipeline remaining stage tests (aggregator/cntfilter/longtext/msgtrun) - COV-005: Storage and utils coverage pass - COV-006: Gate ratchet 12%→15% - COV-007: Gate ratchet 15%→18% - COV-008: API service batch 2 (bot/pipeline/webhook/space/maintenance/mcp) - COV-009: Blocked - API controller circular import issue documented - COV-010: Plugin runtime unit tests (+0.08%) - COV-011: RAG and vector unit tests (+0.68%) - COV-012: Core boot and migration unit tests - COV-013: Provider requester logic unit tests (+0.62%) Key additions: - tests/utils/import_isolation.py: sys.modules isolation for circular imports - Provider requester mock tests: proved HTTP-dependent code can be tested locally - Vector filter utilities: 100% coverage on pure functions - API services: fake persistence pattern for unit testing Blocked issue COV-009 documented in langbot-test-plan/1.5/issues/ Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ def isolated_sys_modules(
|
||||
- Modules in both mocks and clear will be mocked (not cleared)
|
||||
- Original state is restored even if exception occurs
|
||||
- Modules not in sys.modules before context are removed after
|
||||
- Package attributes (e.g., my_pkg.submodule) are also saved/restored
|
||||
"""
|
||||
clear = clear or []
|
||||
touched = set(mocks.keys()) | set(clear)
|
||||
@@ -72,6 +73,14 @@ def isolated_sys_modules(
|
||||
if name in sys.modules:
|
||||
saved[name] = sys.modules[name]
|
||||
|
||||
# Save original package attributes that will be updated
|
||||
saved_attrs: dict[str, tuple[str, object]] = {}
|
||||
for mock_name, (pkg_name, attr_name) in _PACKAGE_ATTRIBUTE_UPDATES.items():
|
||||
if mock_name in mocks and pkg_name in sys.modules:
|
||||
pkg = sys.modules[pkg_name]
|
||||
if hasattr(pkg, attr_name):
|
||||
saved_attrs[mock_name] = (pkg_name, getattr(pkg, attr_name))
|
||||
|
||||
try:
|
||||
# Clear modules first (force re-import)
|
||||
for name in clear:
|
||||
@@ -82,6 +91,13 @@ def isolated_sys_modules(
|
||||
for name, module in mocks.items():
|
||||
sys.modules[name] = module
|
||||
|
||||
# Update package attributes to point to mocks
|
||||
# This is critical because `from package import submodule` gets the attribute,
|
||||
# not sys.modules directly
|
||||
for mock_name, (pkg_name, attr_name) in _PACKAGE_ATTRIBUTE_UPDATES.items():
|
||||
if mock_name in mocks and pkg_name in sys.modules:
|
||||
setattr(sys.modules[pkg_name], attr_name, mocks[mock_name])
|
||||
|
||||
yield
|
||||
|
||||
finally:
|
||||
@@ -93,6 +109,11 @@ def isolated_sys_modules(
|
||||
# Wasn't in sys.modules originally, remove it
|
||||
sys.modules.pop(name, None)
|
||||
|
||||
# Restore package attributes
|
||||
for mock_name, (pkg_name, original_value) in saved_attrs.items():
|
||||
if pkg_name in sys.modules:
|
||||
setattr(sys.modules[pkg_name], _PACKAGE_ATTRIBUTE_UPDATES[mock_name][1], original_value)
|
||||
|
||||
|
||||
def make_pipeline_handler_import_mocks() -> dict[str, MagicMock]:
|
||||
"""
|
||||
@@ -141,6 +162,16 @@ def make_pipeline_handler_import_mocks() -> dict[str, MagicMock]:
|
||||
}
|
||||
|
||||
|
||||
# Package attributes that need to be updated alongside sys.modules mocking.
|
||||
# When Python imports a submodule (e.g., langbot.pkg.provider.runner), it
|
||||
# automatically sets an attribute on the parent package. The import statement
|
||||
# `from ....provider import runner` gets this attribute, not sys.modules directly.
|
||||
# This dict maps mock module names to the parent packages that need attribute updates.
|
||||
_PACKAGE_ATTRIBUTE_UPDATES: dict[str, tuple[str, str]] = {
|
||||
'langbot.pkg.provider.runner': ('langbot.pkg.provider', 'runner'),
|
||||
}
|
||||
|
||||
|
||||
def get_handler_modules_to_clear(handler_name: str) -> list[str]:
|
||||
"""
|
||||
Get list of handler-related modules to clear before import.
|
||||
|
||||
Reference in New Issue
Block a user