Private
Public Access
0
0
Files
manual_slop/tests/test_project_context_20260627.py
T
ed 9e07fac1db refactor(consumers): replace 'models.<moved_class>' with direct imports
Per post_module_taxonomy_de_cruft_20260627 Phase 2 (FR7 continued).
The previous migration commit (8f11340b) handled the
'from src.models import X' pattern (85 sites). This commit handles
the 'models.<moved_class>' attribute access pattern (44 sites in 20
files), which the __getattr__ shim previously supported.

The migration was performed by the one-time script
scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_models_attr.py
which:
 1. For each 'models.<moved_class>' reference, replaces it with the
    bare class name (e.g., 'models.MCPConfiguration' -> 'MCPConfiguration')
 2. Adds the import 'from src.<destination> import <moved_class>' at
    the top of the file (deduplicated if the import already exists)
 3. Skips moved classes that the file already imports directly

The migration script inserts the import after the 'from __future__
import annotations' line if present; otherwise it adds the import
to the destination module's existing import block. Two files
required manual fixes because the script's regex didn't handle them:
 - src/rag_engine.py: uses 'from src import models' (not 'from
                            src.models import X'); the class is accessed
                            via 'models.RAGConfig'. Replaced with a
                            direct 'from src.mcp_client import RAGConfig'
                            import and removed the 'from src import models'.
 - tests/test_project_context_20260627.py: uses the parens-style
                            multi-line 'from src.models import (X, Y, Z)'.
                            Replaced with the parens-style direct import.

After this commit:
 - 'models.MCPConfiguration', 'models.FileItem', 'models.Ticket', etc.
   no longer work in src/ and tests/ (the AttributeError raises
   because models.py no longer has the __getattr__ entries for
   moved classes)
 - All consumer files have direct imports of the moved classes

Total: 44 'models.<moved_class>' references rewritten across 20 files.
2026-06-26 14:06:03 -04:00

158 lines
6.3 KiB
Python

"""Phase 2 regression-guard tests for cruft_elimination_20260627.
Per SPEC_CORRECTION_phase_2.md acceptance criteria:
- VC8 (corrected): flat_config returns typed ProjectContext
- VC8 (corrected): All 6 sub-dataclasses exist
- VC8 (corrected): Consumers unchanged (Option A) - existing tests pass
- VC8 (corrected): Dict-compat works (ctx.get() / ctx[])
- VC8 (corrected): output_dir REQUIRED field works (zero default is OK)
"""
from __future__ import annotations
import pytest
from src.project_manager import flat_config
from src.project import (
ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles,
ProjectScreenshots, ProjectDiscussion, EMPTY_PROJECT_CONTEXT,
)
def test_all_six_sub_dataclasses_importable() -> None:
"""All 6 sub-dataclasses are importable from src.models."""
assert isinstance(ProjectMeta, type)
assert isinstance(ProjectOutput, type)
assert isinstance(ProjectFiles, type)
assert isinstance(ProjectScreenshots, type)
assert isinstance(ProjectDiscussion, type)
assert isinstance(ProjectContext, type)
assert isinstance(EMPTY_PROJECT_CONTEXT, ProjectContext)
def test_flat_config_returns_project_context() -> None:
"""VC8 (corrected): flat_config returns ProjectContext (typed)."""
ctx = flat_config({})
assert isinstance(ctx, ProjectContext)
assert isinstance(ctx.project, ProjectMeta)
assert isinstance(ctx.output, ProjectOutput)
assert isinstance(ctx.files, ProjectFiles)
assert isinstance(ctx.screenshots, ProjectScreenshots)
assert isinstance(ctx.discussion, ProjectDiscussion)
def test_flat_config_empty_dict_yields_zero_defaults() -> None:
"""Empty dict input -> all sub-dataclass fields are zero-initialized."""
ctx = flat_config({})
assert ctx.project.name == ""
assert ctx.project.summary_only is False
assert ctx.project.execution_mode == "standard"
assert ctx.output.namespace == "project"
assert ctx.output.output_dir == "" # REQUIRED field, empty by default
assert ctx.files.base_dir == ""
assert ctx.files.paths == ()
assert ctx.screenshots.base_dir == "."
assert ctx.screenshots.paths == ()
assert ctx.discussion.roles == ()
assert ctx.discussion.history == ()
def test_flat_config_full_dict_input() -> None:
"""Full dict input -> correct dataclass fields populated."""
proj = {
"project": {"name": "test", "summary_only": True, "execution_mode": "fast"},
"output": {"namespace": "ns1", "output_dir": "/tmp/out"},
"files": {"base_dir": "/src", "paths": ["a.py", "b.py"]},
"screenshots":{"base_dir": "/scr", "paths": ["s1.png"]},
"discussion":{
"active": "main",
"roles": ["User", "AI"],
"discussions": {"main": {"history": ["msg1", "msg2"]}},
},
}
ctx = flat_config(proj, disc_name="main")
assert ctx.project.name == "test"
assert ctx.project.summary_only is True
assert ctx.project.execution_mode == "fast"
assert ctx.output.namespace == "ns1"
assert ctx.output.output_dir == "/tmp/out"
assert ctx.files.base_dir == "/src"
assert ctx.files.paths == ("a.py", "b.py")
assert ctx.screenshots.base_dir == "/scr"
assert ctx.screenshots.paths == ("s1.png",)
assert ctx.discussion.roles == ("User", "AI")
assert ctx.discussion.history == ("msg1", "msg2")
def test_flat_config_dict_compat_getitem() -> None:
"""ctx[\"key\"] returns the same shape as the legacy dict."""
proj = {"output": {"output_dir": "/out", "namespace": "ns1"}}
ctx = flat_config(proj)
assert ctx["output"] == {"namespace": "ns1", "output_dir": "/out"}
assert ctx["files"] == {"base_dir": "", "paths": []}
assert ctx["project"] == {"name": "", "summary_only": False, "execution_mode": "standard"}
def test_flat_config_dict_compat_get() -> None:
"""ctx.get(\"key\", default) returns dict value or default."""
ctx = flat_config({})
assert ctx.get("output") == {"namespace": "project", "output_dir": ""}
assert ctx.get("missing_key") is None
assert ctx.get("missing_key", "fallback") == "fallback"
def test_flat_config_to_dict_round_trip() -> None:
"""to_dict() returns the same shape as the legacy flat_config() dict."""
proj = {
"project": {"name": "p", "summary_only": False, "execution_mode": "std"},
"output": {"namespace": "ns", "output_dir": "/o"},
"files": {"base_dir": "/s", "paths": ["x"]},
"screenshots":{"base_dir": "/sc", "paths": ["i"]},
"context_presets": {"preset_a": {"name": "preset_a"}},
"discussion": {
"roles": ["User"],
"active": "main",
"discussions": {"main": {"history": ["h1"]}},
},
}
ctx = flat_config(proj, disc_name="main")
d = ctx.to_dict()
assert d["project"] == {"name": "p", "summary_only": False, "execution_mode": "std"}
assert d["output"] == {"namespace": "ns", "output_dir": "/o"}
assert d["files"] == {"base_dir": "/s", "paths": ["x"]}
assert d["screenshots"] == {"base_dir": "/sc", "paths": ["i"]}
assert d["context_presets"] == {"preset_a": {"name": "preset_a"}}
assert d["discussion"] == {"roles": ["User"], "history": ["h1"]}
def test_empty_project_context_sentinel() -> None:
"""EMPTY_PROJECT_CONTEXT is a zero-init ProjectContext."""
assert isinstance(EMPTY_PROJECT_CONTEXT, ProjectContext)
assert EMPTY_PROJECT_CONTEXT.project.name == ""
assert EMPTY_PROJECT_CONTEXT.output.output_dir == ""
assert EMPTY_PROJECT_CONTEXT.files.paths == ()
assert EMPTY_PROJECT_CONTEXT.screenshots.base_dir == "."
assert EMPTY_PROJECT_CONTEXT.discussion.roles == ()
def test_output_dir_required_field_zero_default() -> None:
"""output_dir is a REQUIRED field per spec; zero default (empty str) is
acceptable. aggregate.run would fail with a clear error when output_dir
is empty (existing behavior, not a regression)."""
ctx = flat_config({})
assert ctx.output.output_dir == ""
# Verify it's still a valid string field (not None)
assert isinstance(ctx.output.output_dir, str)
def test_flat_config_consumers_unchanged() -> None:
"""VC8 Option A: existing consumer code patterns continue to work via
dict-compat methods (ctx.get(\"key\"), ctx[\"key\"])."""
ctx = flat_config({})
# Mimic consumer patterns from src/aggregate.py:484-525:
assert ctx.get("output", {}).get("namespace", "project") == "project"
assert ctx.get("files", {}).get("paths", []) == [] # list, not tuple (legacy compat)
assert ctx.get("discussion", {}).get("history", []) == [] # list, not tuple (legacy compat)
# Mimic src/app_controller.py:4026 pattern: flat[\"files\"] returns dict
flat_files = ctx["files"]
assert isinstance(flat_files, dict)
assert "base_dir" in flat_files
assert "paths" in flat_files