Skip to content

Stabilize high priority usage tracking tests without LangChain#7785

Open
tianmind-studio wants to merge 2 commits into
BasedHardware:mainfrom
tianmind-studio:codex/high-priority-usage-test-stability
Open

Stabilize high priority usage tracking tests without LangChain#7785
tianmind-studio wants to merge 2 commits into
BasedHardware:mainfrom
tianmind-studio:codex/high-priority-usage-test-stability

Conversation

@tianmind-studio

@tianmind-studio tianmind-studio commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Stub the minimal langchain_core and pytz surfaces needed by test_high_priority_usage_tracking.py so the source-level usage tracking tests run in lightweight backend test environments.
  • Provide the current database.users.get_user_language_preference and utils.llm.clients.get_llm test stubs expected by imported modules.
  • Guard optional dependency stubs so installed LangChain/pytz modules are not overwritten, and make the get_llm test stub fail loudly on unknown feature names.
  • Read inspected source files as UTF-8 so Windows non-UTF-8 locales do not fail source checks.

Windows reproduction

Before this change on this Windows test environment:

  • python -m pytest tests\unit\test_high_priority_usage_tracking.py -q -> collection error
  • root cause: ModuleNotFoundError: No module named 'langchain_core' while importing utils.llm.usage_tracker

Testing

  • python -m pytest tests\unit\test_high_priority_usage_tracking.py -q -> 21 passed
  • python -m black --line-length 120 --skip-string-normalization tests\unit\test_high_priority_usage_tracking.py --check
  • python -m py_compile tests\unit\test_high_priority_usage_tracking.py
  • git diff --check

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes test collection failures on Windows (and other environments without LangChain installed) by stubbing langchain_core submodules and pytz before importing utils.llm.usage_tracker, and adds the missing get_user_language_preference and get_llm stubs needed by the imported source modules. A UTF-8 encoding argument is also added to _read_source to prevent failures on non-UTF-8 Windows locales.

  • LangChain + pytz stubs: Minimal stub classes (BaseCallbackHandler, LLMResult, PydanticOutputParser, ChatPromptTemplate) and a pytz.UTC/pytz.timezone stub are inserted into sys.modules before the real usage_tracker import, making the 19 tests runnable without installing LangChain.
  • get_llm stub (_get_llm_stub): Maps known feature names to the appropriate mock LLM, with a silent mock_llm_mini fallback for unrecognised names.
  • _read_source fix: Passes encoding=\"utf-8\" to Path.read_text() so source-level string checks don't fail on Windows systems with a non-UTF-8 default locale.

Confidence Score: 3/5

Safe to merge in environments where LangChain is not installed, but the stub setup can silently replace real LangChain classes in the same pytest session when the package is present.

The stub attribute assignments (lines 72–75) are unconditional — they run whether _stub_module returned a freshly-created empty module or the real installed module. In a full CI environment with LangChain installed, these lines overwrite BaseCallbackHandler, LLMResult, PydanticOutputParser, and ChatPromptTemplate on the live module objects, which can silently corrupt any other test that imports those symbols afterward. The rest of the changes (UTF-8 fix, missing mock attributes) are straightforward and correct.

backend/tests/unit/test_high_priority_usage_tracking.py — the LangChain stub setup block (lines 34–75) needs a guard to skip attribute assignments when the real package is already loaded.

Important Files Changed

Filename Overview
backend/tests/unit/test_high_priority_usage_tracking.py Adds LangChain/pytz stubs and missing database/LLM client stubs so tests run without the heavy dependencies; the stub attribute assignments can corrupt real LangChain module objects if the package is installed in the same pytest session.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["pytest collects test file"] --> B{"langchain_core\nin sys.modules?"}
    B -- "No (target env)" --> C["_stub_module creates\nfresh ModuleType objects"]
    C --> D["Set stub classes on\nnewly-created modules\n(safe)"]
    B -- "Yes (LangChain installed)" --> E["_stub_module returns\nreal module objects"]
    E --> F["Stub classes assigned\nonto real modules\n⚠️ overwrites real classes"]
    D --> G["usage_tracker imports\nBaseCallbackHandler, LLMResult\nfrom stub modules"]
    F --> G
    G --> H["Test body runs:\ncontext-capture functions\nreplace mock_llm_mini.invoke"]
    H --> I["Production function called\n(suggest_goal, get_goal_advice, ...)"]
    I --> J["track_usage context manager\nsets _usage_context ContextVar"]
    J --> K["LLM mock invoke called;\ntest asserts captured context"]
Loading

Reviews (1): Last reviewed commit: "Stabilize high priority usage tracking t..." | Re-trigger Greptile

Comment on lines +34 to +75
langchain_core_mod = _stub_module("langchain_core")
if not hasattr(langchain_core_mod, '__path__'):
langchain_core_mod.__path__ = []

callbacks_mod = _stub_module("langchain_core.callbacks")
outputs_mod = _stub_module("langchain_core.outputs")
output_parsers_mod = _stub_module("langchain_core.output_parsers")
prompts_mod = _stub_module("langchain_core.prompts")


class BaseCallbackHandler:
pass


class LLMResult:
pass


class PydanticOutputParser:
def __init__(self, pydantic_object):
self.pydantic_object = pydantic_object

def get_format_instructions(self):
return ""

def parse(self, text):
return self.pydantic_object(**json.loads(text))


class ChatPromptTemplate:
@classmethod
def from_messages(cls, _messages):
return cls()

def __or__(self, other):
return other


callbacks_mod.BaseCallbackHandler = BaseCallbackHandler
outputs_mod.LLMResult = LLMResult
output_parsers_mod.PydanticOutputParser = PydanticOutputParser
prompts_mod.ChatPromptTemplate = ChatPromptTemplate

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stub attributes overwrite real LangChain classes when the package is installed

_stub_module returns the real langchain_core.callbacks module when langchain_core is already in sys.modules (i.e., the package is installed). The subsequent attribute assignments on lines 72–75 then replace the real BaseCallbackHandler, LLMResult, PydanticOutputParser, and ChatPromptTemplate with stub implementations on the live module objects. Any other test that later does from langchain_core.callbacks import BaseCallbackHandler in the same pytest session would receive the stub class, silently producing wrong behavior or unexpected test failures.

A minimal guard avoids this: capture whether the stub was freshly created before assigning attributes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f483ea6b2: optional LangChain/pytz modules now go through _optional_stub_module, which imports real modules when available and only injects stub attributes when the module was newly created as a stub. Added a regression test proving already-loaded modules are preserved. Local validation: 21 passed.

Comment on lines +178 to +188
def _get_llm_stub(name, **_kwargs):
if name in {'goals', 'knowledge_graph', 'daily_summary_simple'}:
return mock_llm_mini
if name in {'goals_advice', 'notifications'}:
return mock_llm_medium
if name == 'daily_summary':
return mock_llm_medium_experiment
return mock_llm_mini


clients_mod.get_llm = _get_llm_stub

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent fallback in _get_llm_stub masks uncovered get_llm call sites

The final return mock_llm_mini fallback means any new get_llm(name) call whose name is not enumerated in the stub (e.g., after a future feature is added) silently gets mock_llm_mini instead of failing loudly. The context-capture tests then pass even though the wrong mock is in use, so tracking regressions for new features would go undetected. Raising ValueError for unknown names (or at minimum warnings.warn) would make the mismatch visible immediately.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in f483ea6b2: _get_llm_stub now raises ValueError for unknown feature names instead of silently falling back to mock_llm_mini, and there is a regression test for that failure path. Local validation: 21 passed.

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.

1 participant