8f11340b38
Per post_module_taxonomy_de_cruft_20260627 Phase 2 (FR7). Each
'from src.models import X' for a moved class is rewritten to
'from src.<destination> import X':
Ticket, Track, WorkerContext, TrackState, TrackMetadata,
ThinkingSegment, EMPTY_TRACK_STATE -> src.mma
ProjectContext, ProjectMeta, ProjectOutput, ProjectFiles,
ProjectScreenshots, ProjectDiscussion, EMPTY_PROJECT_CONTEXT -> src.project
FileItem, Preset, ContextPreset, ContextFileEntry,
NamedViewPreset -> src.project_files
Tool, ToolPreset -> src.tool_presets
BiasProfile -> src.tool_bias
TextEditorConfig, ExternalEditorConfig,
EMPTY_TEXT_EDITOR_CONFIG -> src.external_editor
Persona -> src.personas
WorkspaceProfile -> src.workspace_manager
MCPServerConfig, MCPConfiguration, VectorStoreConfig,
RAGConfig, load_mcp_config -> src.mcp_client
NOT touched (kept on src.models; Phase 3 or Phase 4 will move them):
GenerateRequest, ConfirmRequest, DEFAULT_TOOL_CATEGORIES, Metadata, PROVIDERS
Migration was performed by the one-time script
scripts/tier2/artifacts/post_module_taxonomy_de_cruft_20260627/migrate_imports.py
which uses a class-to-module map and re.sub() to rewrite each
'from src.models import X' line.
Total: 85 import lines rewritten across 71 files.
Note: this commit depends on the v2 SHIPPED work
(origin/tier2/module_taxonomy_refactor_20260627) being merged into
this branch NEXT. On master (without the v2 SHIPPED commits), the
destination modules do not exist and these imports would fail.
86 lines
3.6 KiB
Python
86 lines
3.6 KiB
Python
import unittest
|
|
from unittest.mock import patch
|
|
from src import conductor_tech_lead
|
|
from src.mma import Ticket
|
|
from src.result_types import Result
|
|
import pytest
|
|
|
|
class TestConductorTechLead(unittest.TestCase):
|
|
def test_generate_tickets_retry_failure(self) -> None:
|
|
with patch('src.ai_client.send') as mock_send:
|
|
mock_send.return_value = Result(data="invalid json")
|
|
# conductor_tech_lead.generate_tickets now raises RuntimeError on error after 3 attempts
|
|
with pytest.raises(RuntimeError):
|
|
conductor_tech_lead.generate_tickets("brief", "skeletons")
|
|
assert mock_send.call_count == 3
|
|
|
|
def test_generate_tickets_retry_success(self) -> None:
|
|
with patch('src.ai_client.send') as mock_send:
|
|
mock_send.side_effect = [Result(data="invalid json"), Result(data='[{"Task": "Test"}]')]
|
|
tickets = conductor_tech_lead.generate_tickets("brief", "skeletons")
|
|
assert tickets == [{"Task": "Test"}]
|
|
assert mock_send.call_count == 2
|
|
|
|
def test_generate_tickets_success(self) -> None:
|
|
with patch('src.ai_client.send') as mock_send:
|
|
mock_send.return_value = Result(data='[{"id": "T1", "description": "desc", "depends_on": []}]')
|
|
tickets = conductor_tech_lead.generate_tickets("brief", "skeletons")
|
|
self.assertEqual(len(tickets), 1)
|
|
self.assertEqual(tickets[0]['id'], "T1")
|
|
|
|
class TestTopologicalSort(unittest.TestCase):
|
|
def test_topological_sort_linear(self) -> None:
|
|
tickets = [
|
|
Ticket(id="t2", description="t2", depends_on=["t1"]),
|
|
Ticket(id="t1", description="t1", depends_on=[]),
|
|
]
|
|
sorted_tickets = conductor_tech_lead.topological_sort(tickets)
|
|
self.assertEqual(sorted_tickets[0].id, "t1")
|
|
self.assertEqual(sorted_tickets[1].id, "t2")
|
|
|
|
def test_topological_sort_complex(self) -> None:
|
|
tickets = [
|
|
Ticket(id="t3", description="t3", depends_on=["t1", "t2"]),
|
|
Ticket(id="t1", description="t1", depends_on=[]),
|
|
Ticket(id="t2", description="t2", depends_on=["t1"]),
|
|
]
|
|
sorted_tickets = conductor_tech_lead.topological_sort(tickets)
|
|
self.assertEqual(sorted_tickets[0].id, "t1")
|
|
self.assertEqual(sorted_tickets[1].id, "t2")
|
|
self.assertEqual(sorted_tickets[2].id, "t3")
|
|
|
|
def test_topological_sort_cycle(self) -> None:
|
|
tickets = [
|
|
Ticket(id="t1", description="t1", depends_on=["t2"]),
|
|
Ticket(id="t2", description="t2", depends_on=["t1"]),
|
|
]
|
|
with self.assertRaises(ValueError) as cm:
|
|
conductor_tech_lead.topological_sort(tickets)
|
|
# Match against our new standard ValueError message
|
|
self.assertIn("Dependency cycle detected", str(cm.exception))
|
|
|
|
def test_topological_sort_empty(self) -> None:
|
|
self.assertEqual(conductor_tech_lead.topological_sort([]), [])
|
|
|
|
def test_topological_sort_missing_dependency(self) -> None:
|
|
# If a ticket depends on something not in the list, we should handle it or let it fail.
|
|
# The TrackDAG silently ignores missing dependencies, causing cycle detection to trigger.
|
|
tickets = [
|
|
Ticket(id="t1", description="t1", depends_on=["missing"]),
|
|
]
|
|
# Currently this raises ValueError due to cycle detection on incomplete sort
|
|
with self.assertRaises(ValueError):
|
|
conductor_tech_lead.topological_sort(tickets)
|
|
|
|
def test_topological_sort_vlog(vlogger) -> None:
|
|
tickets = [
|
|
Ticket(id="t2", description="t2", depends_on=["t1"]),
|
|
Ticket(id="t1", description="t1", depends_on=[]),
|
|
]
|
|
vlogger.log_state("Input Order", ["t2", "t1"], ["t2", "t1"])
|
|
sorted_tickets = conductor_tech_lead.topological_sort(tickets)
|
|
result_ids = [t.id for t in sorted_tickets]
|
|
vlogger.log_state("Sorted Order", "N/A", result_ids)
|
|
assert result_ids == ["t1", "t2"]
|
|
vlogger.finalize("Topological Sort Verification", "PASS", "Linear dependencies correctly ordered.")
|