Skip to content

api/*.py consumers still bind MagicMock code_intelligence stubs at import time (precommit_analyzer, redis_optimizer, anti_pattern_detector) #13181

Description

@mrveiss

Summary

Discovered while fixing #13111 (PR #13180). #13111 fixed the test-file side of the code_intelligence MagicMock-stub poisoning. The consumer side is still poisoned for three modules: several api/*.py route modules bind mocked symbols from the top-level conftest's code_intelligence.* stubs into their own module globals at import time, and unlike merge_conflict_resolver (fixed in #13180) they were left alone because the blast radius could not be measured without a full-suite run.

Affected

Consumer Stubbed import (module level) Observable effect under test
autobot-backend/api/analytics_precommit.py:37-40 BUILTIN_CHECKS, CheckDefinition, CheckResult, PrecommitAnalyzer from code_intelligence.precommit_analyzer BUILTIN_CHECKS = {cid: _engine_check_to_schema(c) for cid, c in _ENGINE_BUILTIN_CHECKS.items()} evaluates to {} — iterating a MagicMock yields nothing, so the module's entire check catalog is silently empty in every test
autobot-backend/api/code_intelligence.py:79 OptimizationSeverity, RedisOptimizer from code_intelligence.redis_optimizer severity-weight dicts and RedisOptimizer(...) calls operate on mocks
autobot-backend/api/code_intelligence.py:70, api/codebase_analytics/analyzers.py:32 AntiPatternDetector & co. from code_intelligence.anti_pattern_detector detector is a mock; optional_import "succeeds" against the stub

The subdirectory fix in #13180 (code_intelligence/conftest.py real-loads) cannot reach these: pytest collects api/ before code_intelligence/, so those globals are already bound by the time that conftest runs. The fix has to be a top-level _real_load_and_bind(...) in autobot-backend/conftest.py, exactly as done there for merge_conflict_resolver.

Why it was not bundled into #13180

All three are import-light (statically verified: stdlib + autobot_shared.logging_manager + utils.line_index, no chromadb/torch/redis/fastapi), so a top-level real-load is mechanically the same one-liner. What is unknown is whether any currently-passing api/ test depends on the mocked binding. api/analytics_precommit_test.py does not exist, and no test file imports api.code_intelligence, so the likely delta is small — but it needs a full-suite run to confirm, which #13180 could not do.

Secondary finding: pre-existing vacuous assertions

code_intelligence/llm_code_generator_test.py::TestModuleImports::test_import_from_code_intelligence and code_intelligence/llm_pattern_analyzer_test.py::TestModuleImports::test_all_exports_importable do from code_intelligence import X and assert X is not None. code_intelligence itself is a package stub whose catch-all __getattr__ returns a MagicMock, so these assertions pass unconditionally and would pass even if the symbol did not exist. llm_code_generator_test.py::test_all_exports_accessible is literally assert True.

This predates #13111 and was deliberately left unchanged there (fixing it needs the real code_intelligence/__init__.py, which is the heavy import chain the stub system exists to avoid). Either bind the real submodules' public symbols onto the stub namespace, or delete these three assertions as non-tests.

Verification steps

  1. Move precommit_analyzer, redis_optimizer, anti_pattern_detector from the top-level stub lists to _real_load_and_bind(...) calls (keeping the code_intelligence/conftest.py entries harmless — _real_load_and_bind no-ops on an already-real module from the same file, per bug(tests): 2 pre-existing failures in test_claim_classifier — duplicate module identity breaks isinstance + MagicMock awaited #12839).
  2. Full backend-suite run; confirm no net-new failures in api/.
  3. Confirm api.analytics_precommit.BUILTIN_CHECKS is non-empty under test.

Related: #13111, PR #13180, #9856, #12362, #12421, #12437, #12438, #12686

Activity

  1. modified the milestones: Backlog, v0.12.0 on Sep 12, 2026
  2. mrveiss commented on Sep 23, 2026

    @mrveiss
    OwnerAuthor

    A fourth instance tonight, and it arrived from the opposite direction — which is the part that generalises this issue beyond the three consumers in the table.

    New instance (#17342). #14881 collapsed four hand-copied enums, and the natural collapse target looked like the engine that owns the vocabulary:

    # api/schemas_analytics.py
    from code_intelligence.code_review_engine import ReviewCategory, ReviewSeverity
    from code_intelligence.precommit_analyzer import CheckCategory, CheckSeverity
    

    autobot-backend/conftest.py:810 stubs the whole code_intelligence package, so those four names are MagicMock attributes under the harness. A pydantic field annotated with a MagicMock has no schema, and the import dies:

    pydantic.errors.PydanticSchemaGenerationError: Unable to generate pydantic-core schema
    for <MagicMock id='139795823827936'>
    

    42 tests failed, every one citing the same mock id — api.metrics, api.rum, api.usage, api.knowledge_vectorization and the rest, all of which merely reach schemas_analytics transitively. One leaked object, 42 red tests, and nothing wrong with the production code.

    Why this is not just a fifth row in the table. The existing entries are pre-existing consumers that were left poisoned. This one was created by a refactor that was correct by every rule we enforce: it removed duplication, it imported rather than redeclared, and the enum guard added in the same PR passes. Nothing in the repo told the author that code_intelligence is a boundary production code must not import across. The stub list is in a conftest; the import is in an API module; the two never meet until CI.

    So the rule this issue should carry, stated once:

    A module stubbed wholesale in a conftest is a dependency boundary. Production code may not import a symbol across it. The stub cannot simply be removed — code_intelligence/__init__.py carries Python-3.10-only syntax, which is why it is stubbed at all.

    The fix that works, for the next person who hits it. Move the shared vocabulary to a leaf nothing stubs, rather than trying to un-stub the engine. For #17342 that is a new autobot_shared/code_review_enums.py with the four enums and three importers. Verified: grep -rn "from code_intelligence" autobot_shared/ is empty so there is no cycle, and autobot_shared.status_enums already resolves to real enums on this exact import path today. Not status_enums.py itself — it is 559 lines against the 600 MAX_LINES ceiling.

    Same class, different stub — worth linking here rather than filing twice. #17343 is this defect with testkit/auth_middleware_stub.py:194 replacing auth_middleware wholesale: an unauthenticated-access test returned 501 instead of 401 because the stub's check_admin_permission takes no Request and refuses nothing. The naive assertion (!= 200) passes, so that one produces a green security test that never touched an auth gate.

    That is the direction worth flagging loudest. This issue's four instances all fail loudly. #17343 shows the same root cause can pass silently, and a harness stub that makes a test pass for the wrong reason is strictly more dangerous than one that makes 42 fail for the right one. conftest.py:786-802 already records a third variant in its own comments — stubbing orchestration.causal_error_recovery gave TestCausalValidator a mock whose .valid was truthy, so 2 tests failed outright and 3 silently asserted nothing.

    Three of the four known variants therefore include a silent-pass mode. Any fix here should be judged on whether it catches those, not only the import-time explosions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions