53111dbb95
* auto-claude: subtask-1-1 - Remove validation_strategy backward compatibility shim - Delete apps/backend/validation_strategy.py shim file that re-exported from spec.validation_strategy - Update docstring in spec/validation_strategy.py to show correct import path Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-1-2 - Remove service_orchestrator shim - Deleted apps/backend/service_orchestrator.py backward compatibility shim file - Updated docstring in services/orchestrator.py to use correct import path (from services.orchestrator import instead of from service_orchestrator import) - Verified no external imports of the shim remain in the codebase - Import from services.orchestrator works correctly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-1 - Remove Chunk/ChunkStatus aliases from implementation_plan Removed backwards compatibility aliases as part of cleaning up outdated compatibility shims: - Removed ChunkStatus = SubtaskStatus from enums.py - Removed Chunk = Subtask from subtask.py - Removed Chunk/ChunkStatus exports from __init__.py - Updated all test files to use canonical names (Subtask, SubtaskStatus) This completes subtasks 2-1, 2-2, and 2-3 together since the test files depend on all three changes being made atomically. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-2-4 - Remove deprecated use_orchestrator_review field from GitHub runner models * auto-claude: subtask-3-1 - Update apps/frontend/src/main/index.ts to use platform imports * auto-claude: subtask-3-2 - Update python-detector.ts to use platform imports - Import isWindows from ./platform module - Replace all process.platform === 'win32' checks with isWindows() calls - Remove redundant local isWindows variable declarations * auto-claude: subtask-3-3 - Update apps/frontend/src/main/claude-cli-utils.ts to use platform imports * auto-claude: subtask-3-4 - Update apps/frontend/src/main/config-paths.ts to use platform imports * auto-claude: subtask-3-5 - Update apps/frontend/src/main/memory-service.ts to use platform imports * auto-claude: subtask-4-1 - Update claude-code-handlers.ts to use platform imports Replace all direct process.platform checks with centralized platform abstraction functions from ../platform module: - isWindows() for Windows platform checks - isMacOS() for macOS/Darwin platform checks - isLinux() for Linux platform checks This removes 6 instances of process.platform === '...' comparisons and 1 local isWindows variable assignment, replacing them with the platform abstraction layer for better cross-platform consistency. * auto-claude: subtask-4-2 - Update apps/frontend/src/main/ipc-handlers/mcp-handlers.ts to use platform imports * auto-claude: subtask-4-3 - Update apps/frontend/src/main/ipc-handlers/memory-handlers.ts to use platform imports - Replace process.platform checks with platform module functions - Add getOllamaExecutablePaths(), getOllamaInstallCommand(), and getWhichCommand() to platform/paths.ts - Export new functions from platform/index.ts - Migrate checkOllamaInstalled() to use platform module for path resolution - Migrate getOllamaInstallCommand() to delegate to platform module - Update debug log to use getCurrentOS() instead of process.platform Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-4-4 - Update apps/frontend/src/main/ipc-handlers/termina * auto-claude: subtask-4-5 - Update apps/frontend/src/main/ipc-handlers/github/ - Replace direct process.platform check with getWhichCommand() from platform abstraction - Import getWhichCommand from ../../platform for cross-platform which/where command * auto-claude: subtask-4-6 - Update apps/frontend/src/main/ipc-handlers/github/utils/subprocess-runner.ts to use platform imports * auto-claude: subtask-5-1 - Update apps/frontend/src/main/agent/agent-process.ts Replace direct process.platform checks with platform abstraction: - Import isWindows from ../platform module - Replace `process.platform !== 'win32'` with `!isWindows()` - Replace `process.platform === 'win32'` with `isWindows()` This ensures consistent platform detection using the centralized platform abstraction layer. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * auto-claude: subtask-5-2 - Update apps/frontend/src/main/agent/agent-queue.ts * auto-claude: subtask-5-3 - Update apps/frontend/src/main/terminal/pty-daemon.ts to use platform imports * auto-claude: subtask-5-4 - Update pty-daemon-client.ts to use platform imports Replace direct process.platform === 'win32' check with isWindows() from the platform abstraction layer for consistent cross-platform handling of socket paths. * auto-claude: subtask-5-5 - Update apps/frontend/src/main/insights/config.ts to use platform imports - Import isWindows() from '../platform' - Replace process.platform === 'win32' checks with isWindows() - Maintains case-insensitive path comparison on Windows * auto-claude: subtask-5-6 - Update apps/frontend/src/main/changelog/version-suggester.ts to use platform imports * auto-claude: subtask-5-7 - Update apps/frontend/src/main/changelog/generator. * fix: Remove unused import and fix test import paths - Remove unused `isWindows` import from memory-handlers.ts - Fix test_service_orchestrator.py to import from services.orchestrator instead of the removed service_orchestrator shim - Fix case sensitivity in path ("Apps" -> "apps") Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Fix test import paths for case sensitivity and removed shims - Fix path case sensitivity: "Apps" -> "apps" in 21 test files - Update test_validation_strategy.py to import from spec.validation_strategy Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
603 lines
20 KiB
Python
603 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for Implementation Plan Management
|
|
========================================
|
|
|
|
Tests the implementation_plan.py module functionality including:
|
|
- Data structures (Subtask, Phase, ImplementationPlan)
|
|
- Status transitions
|
|
- Progress tracking
|
|
- Dependency resolution
|
|
- Plan serialization
|
|
"""
|
|
|
|
import json
|
|
import pytest
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
from implementation_plan import (
|
|
ImplementationPlan,
|
|
Phase,
|
|
Subtask,
|
|
Verification,
|
|
WorkflowType,
|
|
PhaseType,
|
|
SubtaskStatus,
|
|
VerificationType,
|
|
create_feature_plan,
|
|
create_investigation_plan,
|
|
create_refactor_plan,
|
|
)
|
|
|
|
|
|
class TestSubtask:
|
|
"""Tests for Subtask data structure."""
|
|
|
|
def test_create_simple_chunk(self):
|
|
"""Creates a simple chunk with defaults."""
|
|
chunk = Subtask(
|
|
id="chunk-1",
|
|
description="Implement user model",
|
|
)
|
|
|
|
assert chunk.id == "chunk-1"
|
|
assert chunk.description == "Implement user model"
|
|
assert chunk.status == SubtaskStatus.PENDING
|
|
assert chunk.service is None
|
|
assert chunk.files_to_modify == []
|
|
assert chunk.files_to_create == []
|
|
|
|
def test_create_full_chunk(self):
|
|
"""Creates a chunk with all fields."""
|
|
chunk = Subtask(
|
|
id="chunk-2",
|
|
description="Add API endpoint",
|
|
status=SubtaskStatus.IN_PROGRESS,
|
|
service="backend",
|
|
files_to_modify=["app/routes.py"],
|
|
files_to_create=["app/models/user.py"],
|
|
patterns_from=["app/models/profile.py"],
|
|
)
|
|
|
|
assert chunk.service == "backend"
|
|
assert "app/routes.py" in chunk.files_to_modify
|
|
assert "app/models/user.py" in chunk.files_to_create
|
|
|
|
def test_chunk_start(self):
|
|
"""Subtask can be started."""
|
|
chunk = Subtask(id="test", description="Test")
|
|
|
|
chunk.start(session_id=1)
|
|
|
|
assert chunk.status == SubtaskStatus.IN_PROGRESS
|
|
assert chunk.started_at is not None
|
|
assert chunk.session_id == 1
|
|
|
|
def test_chunk_complete(self):
|
|
"""Subtask can be completed."""
|
|
chunk = Subtask(id="test", description="Test")
|
|
chunk.start(session_id=1)
|
|
|
|
chunk.complete(output="Done successfully")
|
|
|
|
assert chunk.status == SubtaskStatus.COMPLETED
|
|
assert chunk.completed_at is not None
|
|
assert chunk.actual_output == "Done successfully"
|
|
|
|
def test_chunk_fail(self):
|
|
"""Subtask can be marked as failed."""
|
|
chunk = Subtask(id="test", description="Test")
|
|
chunk.start(session_id=1)
|
|
|
|
chunk.fail(reason="Test error")
|
|
|
|
assert chunk.status == SubtaskStatus.FAILED
|
|
assert "FAILED: Test error" in chunk.actual_output
|
|
|
|
def test_chunk_to_dict(self):
|
|
"""Subtask serializes to dict correctly."""
|
|
chunk = Subtask(
|
|
id="chunk-1",
|
|
description="Test chunk",
|
|
service="backend",
|
|
files_to_modify=["file.py"],
|
|
)
|
|
|
|
data = chunk.to_dict()
|
|
|
|
assert data["id"] == "chunk-1"
|
|
assert data["description"] == "Test chunk"
|
|
assert data["status"] == "pending"
|
|
assert data["service"] == "backend"
|
|
assert "file.py" in data["files_to_modify"]
|
|
|
|
def test_chunk_from_dict(self):
|
|
"""Subtask deserializes from dict correctly."""
|
|
data = {
|
|
"id": "chunk-1",
|
|
"description": "Test chunk",
|
|
"status": "completed",
|
|
"service": "frontend",
|
|
}
|
|
|
|
chunk = Subtask.from_dict(data)
|
|
|
|
assert chunk.id == "chunk-1"
|
|
assert chunk.status == SubtaskStatus.COMPLETED
|
|
assert chunk.service == "frontend"
|
|
|
|
|
|
class TestVerification:
|
|
"""Tests for Verification data structure."""
|
|
|
|
def test_command_verification(self):
|
|
"""Creates command-type verification."""
|
|
verification = Verification(
|
|
type=VerificationType.COMMAND,
|
|
run="pytest tests/",
|
|
)
|
|
|
|
assert verification.type == VerificationType.COMMAND
|
|
assert verification.run == "pytest tests/"
|
|
|
|
def test_api_verification(self):
|
|
"""Creates API-type verification."""
|
|
verification = Verification(
|
|
type=VerificationType.API,
|
|
url="/api/users",
|
|
method="POST",
|
|
expect_status=201,
|
|
)
|
|
|
|
assert verification.type == VerificationType.API
|
|
assert verification.method == "POST"
|
|
assert verification.expect_status == 201
|
|
|
|
def test_verification_to_dict(self):
|
|
"""Verification serializes to dict."""
|
|
verification = Verification(
|
|
type=VerificationType.BROWSER,
|
|
scenario="User can upload avatar",
|
|
)
|
|
|
|
data = verification.to_dict()
|
|
|
|
assert data["type"] == "browser"
|
|
assert data["scenario"] == "User can upload avatar"
|
|
|
|
def test_verification_from_dict(self):
|
|
"""Verification deserializes from dict."""
|
|
data = {
|
|
"type": "command",
|
|
"run": "npm test",
|
|
}
|
|
|
|
verification = Verification.from_dict(data)
|
|
|
|
assert verification.type == VerificationType.COMMAND
|
|
assert verification.run == "npm test"
|
|
|
|
|
|
class TestPhase:
|
|
"""Tests for Phase data structure."""
|
|
|
|
def test_create_phase(self):
|
|
"""Creates a phase with chunks."""
|
|
chunk1 = Subtask(id="c1", description="Chunk 1")
|
|
chunk2 = Subtask(id="c2", description="Chunk 2")
|
|
|
|
phase = Phase(
|
|
phase=1,
|
|
name="Setup",
|
|
type=PhaseType.SETUP,
|
|
subtasks=[chunk1, chunk2],
|
|
)
|
|
|
|
assert phase.phase == 1
|
|
assert phase.name == "Setup"
|
|
assert len(phase.subtasks) == 2
|
|
|
|
def test_phase_is_complete(self):
|
|
"""Phase completion checks all chunks."""
|
|
chunk1 = Subtask(id="c1", description="Chunk 1", status=SubtaskStatus.COMPLETED)
|
|
chunk2 = Subtask(id="c2", description="Chunk 2", status=SubtaskStatus.COMPLETED)
|
|
phase = Phase(phase=1, name="Test", subtasks=[chunk1, chunk2])
|
|
|
|
assert phase.is_complete() is True
|
|
|
|
def test_phase_not_complete_with_pending(self):
|
|
"""Phase not complete with pending chunks."""
|
|
chunk1 = Subtask(id="c1", description="Chunk 1", status=SubtaskStatus.COMPLETED)
|
|
chunk2 = Subtask(id="c2", description="Chunk 2", status=SubtaskStatus.PENDING)
|
|
phase = Phase(phase=1, name="Test", subtasks=[chunk1, chunk2])
|
|
|
|
assert phase.is_complete() is False
|
|
|
|
def test_phase_get_pending_chunks(self):
|
|
"""Gets pending chunks from phase."""
|
|
chunk1 = Subtask(id="c1", description="Chunk 1", status=SubtaskStatus.COMPLETED)
|
|
chunk2 = Subtask(id="c2", description="Chunk 2", status=SubtaskStatus.PENDING)
|
|
chunk3 = Subtask(id="c3", description="Chunk 3", status=SubtaskStatus.PENDING)
|
|
phase = Phase(phase=1, name="Test", subtasks=[chunk1, chunk2, chunk3])
|
|
|
|
pending = phase.get_pending_chunks()
|
|
|
|
assert len(pending) == 2
|
|
assert all(c.status == SubtaskStatus.PENDING for c in pending)
|
|
|
|
def test_phase_get_progress(self):
|
|
"""Gets progress counts from phase."""
|
|
chunk1 = Subtask(id="c1", description="Chunk 1", status=SubtaskStatus.COMPLETED)
|
|
chunk2 = Subtask(id="c2", description="Chunk 2", status=SubtaskStatus.COMPLETED)
|
|
chunk3 = Subtask(id="c3", description="Chunk 3", status=SubtaskStatus.PENDING)
|
|
phase = Phase(phase=1, name="Test", subtasks=[chunk1, chunk2, chunk3])
|
|
|
|
completed, total = phase.get_progress()
|
|
|
|
assert completed == 2
|
|
assert total == 3
|
|
|
|
def test_phase_to_dict(self):
|
|
"""Phase serializes to dict."""
|
|
chunk = Subtask(id="c1", description="Test")
|
|
phase = Phase(
|
|
phase=1,
|
|
name="Setup",
|
|
type=PhaseType.SETUP,
|
|
subtasks=[chunk],
|
|
depends_on=[],
|
|
)
|
|
|
|
data = phase.to_dict()
|
|
|
|
assert data["phase"] == 1
|
|
assert data["name"] == "Setup"
|
|
assert data["type"] == "setup"
|
|
assert len(data["chunks"]) == 1
|
|
|
|
def test_phase_from_dict(self):
|
|
"""Phase deserializes from dict."""
|
|
data = {
|
|
"phase": 2,
|
|
"name": "Implementation",
|
|
"type": "implementation",
|
|
"chunks": [{"id": "c1", "description": "Test"}],
|
|
"depends_on": [1],
|
|
}
|
|
|
|
phase = Phase.from_dict(data)
|
|
|
|
assert phase.phase == 2
|
|
assert phase.type == PhaseType.IMPLEMENTATION
|
|
assert len(phase.subtasks) == 1
|
|
assert 1 in phase.depends_on
|
|
|
|
|
|
class TestImplementationPlan:
|
|
"""Tests for ImplementationPlan data structure."""
|
|
|
|
def test_create_plan(self):
|
|
"""Creates an implementation plan."""
|
|
plan = ImplementationPlan(
|
|
feature="User Authentication",
|
|
workflow_type=WorkflowType.FEATURE,
|
|
services_involved=["backend", "frontend"],
|
|
)
|
|
|
|
assert plan.feature == "User Authentication"
|
|
assert plan.workflow_type == WorkflowType.FEATURE
|
|
assert "backend" in plan.services_involved
|
|
|
|
def test_plan_get_available_phases(self, sample_implementation_plan: dict):
|
|
"""Gets phases with satisfied dependencies."""
|
|
plan = ImplementationPlan.from_dict(sample_implementation_plan)
|
|
|
|
# Mark phase 1 as complete
|
|
for chunk in plan.phases[0].subtasks:
|
|
chunk.status = SubtaskStatus.COMPLETED
|
|
|
|
available = plan.get_available_phases()
|
|
|
|
# Phase 2 and 3 depend on phase 1, so they should be available
|
|
phase_nums = [p.phase for p in available]
|
|
assert 2 in phase_nums
|
|
assert 3 in phase_nums
|
|
|
|
def test_plan_get_next_subtask(self, sample_implementation_plan: dict):
|
|
"""Gets next subtask to work on."""
|
|
plan = ImplementationPlan.from_dict(sample_implementation_plan)
|
|
|
|
result = plan.get_next_subtask()
|
|
|
|
assert result is not None
|
|
phase, subtask = result
|
|
# Should be first pending subtask in phase 1
|
|
assert phase.phase == 1
|
|
assert subtask.status == SubtaskStatus.PENDING
|
|
|
|
def test_plan_get_progress(self, sample_implementation_plan: dict):
|
|
"""Gets overall progress."""
|
|
plan = ImplementationPlan.from_dict(sample_implementation_plan)
|
|
|
|
# Complete some subtasks
|
|
plan.phases[0].subtasks[0].status = SubtaskStatus.COMPLETED
|
|
|
|
progress = plan.get_progress()
|
|
|
|
assert progress["total_phases"] == 3
|
|
assert progress["total_subtasks"] == 4 # Based on fixture
|
|
assert progress["completed_subtasks"] == 1
|
|
assert progress["percent_complete"] == 25.0 # 1/4 = 25%
|
|
assert progress["is_complete"] is False
|
|
|
|
def test_plan_save_and_load(self, temp_dir: Path, sample_implementation_plan: dict):
|
|
"""Plan saves and loads correctly."""
|
|
plan = ImplementationPlan.from_dict(sample_implementation_plan)
|
|
plan_path = temp_dir / "plan.json"
|
|
|
|
plan.save(plan_path)
|
|
loaded = ImplementationPlan.load(plan_path)
|
|
|
|
assert loaded.feature == plan.feature
|
|
assert len(loaded.phases) == len(plan.phases)
|
|
assert loaded.updated_at is not None
|
|
|
|
def test_plan_to_dict(self, sample_implementation_plan: dict):
|
|
"""Plan serializes to dict."""
|
|
plan = ImplementationPlan.from_dict(sample_implementation_plan)
|
|
|
|
data = plan.to_dict()
|
|
|
|
assert data["feature"] == "User Avatar Upload"
|
|
assert data["workflow_type"] == "feature"
|
|
assert len(data["phases"]) == 3
|
|
|
|
def test_plan_from_dict(self, sample_implementation_plan: dict):
|
|
"""Plan deserializes from dict."""
|
|
plan = ImplementationPlan.from_dict(sample_implementation_plan)
|
|
|
|
assert plan.feature == "User Avatar Upload"
|
|
assert plan.workflow_type == WorkflowType.FEATURE
|
|
assert len(plan.services_involved) == 3
|
|
|
|
def test_plan_status_summary(self, sample_implementation_plan: dict):
|
|
"""Plan generates status summary."""
|
|
plan = ImplementationPlan.from_dict(sample_implementation_plan)
|
|
|
|
summary = plan.get_status_summary()
|
|
|
|
assert "User Avatar Upload" in summary
|
|
assert "feature" in summary
|
|
assert "0%" in summary or "chunks" in summary
|
|
|
|
|
|
class TestCreateFeaturePlan:
|
|
"""Tests for create_feature_plan helper."""
|
|
|
|
def test_creates_basic_plan(self):
|
|
"""Creates a feature plan with phases."""
|
|
phases_config = [
|
|
{
|
|
"name": "Backend",
|
|
"chunks": [
|
|
{"id": "api", "description": "Add API endpoint"},
|
|
],
|
|
},
|
|
{
|
|
"name": "Frontend",
|
|
"depends_on": [1],
|
|
"chunks": [
|
|
{"id": "ui", "description": "Add UI component"},
|
|
],
|
|
},
|
|
]
|
|
|
|
plan = create_feature_plan(
|
|
feature="User Profile",
|
|
services=["backend", "frontend"],
|
|
phases_config=phases_config,
|
|
)
|
|
|
|
assert plan.feature == "User Profile"
|
|
assert plan.workflow_type == WorkflowType.FEATURE
|
|
assert len(plan.phases) == 2
|
|
assert plan.phases[1].depends_on == [1]
|
|
|
|
def test_sets_parallel_safe(self):
|
|
"""Respects parallel_safe flag."""
|
|
phases_config = [
|
|
{
|
|
"name": "Parallel Phase",
|
|
"parallel_safe": True,
|
|
"chunks": [
|
|
{"id": "c1", "description": "Chunk 1"},
|
|
{"id": "c2", "description": "Chunk 2"},
|
|
],
|
|
},
|
|
]
|
|
|
|
plan = create_feature_plan(
|
|
feature="Test",
|
|
services=["backend"],
|
|
phases_config=phases_config,
|
|
)
|
|
|
|
assert plan.phases[0].parallel_safe is True
|
|
|
|
|
|
class TestCreateInvestigationPlan:
|
|
"""Tests for create_investigation_plan helper."""
|
|
|
|
def test_creates_investigation_plan(self):
|
|
"""Creates an investigation plan for debugging."""
|
|
plan = create_investigation_plan(
|
|
bug_description="Login fails for users with special characters",
|
|
services=["backend", "frontend"],
|
|
)
|
|
|
|
assert "Fix:" in plan.feature
|
|
assert plan.workflow_type == WorkflowType.INVESTIGATION
|
|
assert len(plan.phases) == 3 # Reproduce, Investigate, Fix
|
|
|
|
def test_has_blocked_fix_chunks(self):
|
|
"""Fix phase starts blocked."""
|
|
plan = create_investigation_plan(
|
|
bug_description="Test bug",
|
|
services=["backend"],
|
|
)
|
|
|
|
# Fix phase should have blocked chunks
|
|
fix_phase = plan.phases[2] # Phase 3 - Fix
|
|
assert any(c.status == SubtaskStatus.BLOCKED for c in fix_phase.subtasks)
|
|
|
|
|
|
class TestCreateRefactorPlan:
|
|
"""Tests for create_refactor_plan helper."""
|
|
|
|
def test_creates_refactor_plan(self):
|
|
"""Creates a refactor plan with stages."""
|
|
stages = [
|
|
{
|
|
"name": "Add New System",
|
|
"chunks": [
|
|
{"id": "new-api", "description": "Add new API"},
|
|
],
|
|
},
|
|
{
|
|
"name": "Migrate Consumers",
|
|
"chunks": [
|
|
{"id": "migrate", "description": "Update consumers"},
|
|
],
|
|
},
|
|
{
|
|
"name": "Remove Old System",
|
|
"chunks": [
|
|
{"id": "remove", "description": "Remove old code"},
|
|
],
|
|
},
|
|
]
|
|
|
|
plan = create_refactor_plan(
|
|
refactor_description="Replace auth system",
|
|
services=["backend"],
|
|
stages=stages,
|
|
)
|
|
|
|
assert plan.workflow_type == WorkflowType.REFACTOR
|
|
assert len(plan.phases) == 3
|
|
# Each phase should depend on the previous
|
|
assert plan.phases[1].depends_on == [1]
|
|
assert plan.phases[2].depends_on == [2]
|
|
|
|
|
|
class TestDependencyResolution:
|
|
"""Tests for phase dependency resolution."""
|
|
|
|
def test_no_available_phases_when_deps_not_met(self):
|
|
"""No phases available when dependencies aren't met."""
|
|
plan = ImplementationPlan(
|
|
feature="Test",
|
|
phases=[
|
|
Phase(phase=1, name="Setup", subtasks=[
|
|
Subtask(id="c1", description="Setup", status=SubtaskStatus.PENDING)
|
|
]),
|
|
Phase(phase=2, name="Build", depends_on=[1], subtasks=[
|
|
Subtask(id="c2", description="Build")
|
|
]),
|
|
],
|
|
)
|
|
|
|
available = plan.get_available_phases()
|
|
|
|
# Only phase 1 should be available (no dependencies)
|
|
assert len(available) == 1
|
|
assert available[0].phase == 1
|
|
|
|
def test_multiple_phases_available_parallel(self):
|
|
"""Multiple phases can be available in parallel."""
|
|
plan = ImplementationPlan(
|
|
feature="Test",
|
|
phases=[
|
|
Phase(phase=1, name="Setup", subtasks=[
|
|
Subtask(id="c1", description="Setup", status=SubtaskStatus.COMPLETED)
|
|
]),
|
|
Phase(phase=2, name="Backend", depends_on=[1], subtasks=[
|
|
Subtask(id="c2", description="Backend")
|
|
]),
|
|
Phase(phase=3, name="Frontend", depends_on=[1], subtasks=[
|
|
Subtask(id="c3", description="Frontend")
|
|
]),
|
|
],
|
|
)
|
|
|
|
available = plan.get_available_phases()
|
|
|
|
# Phases 2 and 3 should both be available (both depend only on phase 1)
|
|
assert len(available) == 2
|
|
phase_nums = [p.phase for p in available]
|
|
assert 2 in phase_nums
|
|
assert 3 in phase_nums
|
|
|
|
def test_phase_blocked_by_multiple_deps(self):
|
|
"""Phase blocked when any dependency not met."""
|
|
plan = ImplementationPlan(
|
|
feature="Test",
|
|
phases=[
|
|
Phase(phase=1, name="Phase1", subtasks=[
|
|
Subtask(id="c1", description="C1", status=SubtaskStatus.COMPLETED)
|
|
]),
|
|
Phase(phase=2, name="Phase2", subtasks=[
|
|
Subtask(id="c2", description="C2", status=SubtaskStatus.PENDING)
|
|
]),
|
|
Phase(phase=3, name="Phase3", depends_on=[1, 2], subtasks=[
|
|
Subtask(id="c3", description="C3")
|
|
]),
|
|
],
|
|
)
|
|
|
|
available = plan.get_available_phases()
|
|
|
|
# Phase 3 requires both 1 and 2, but 2 isn't complete
|
|
phase_nums = [p.phase for p in available]
|
|
assert 3 not in phase_nums
|
|
|
|
|
|
class TestSubtaskCritique:
|
|
"""Tests for self-critique functionality on subtasks."""
|
|
|
|
def test_chunk_stores_critique_result(self):
|
|
"""Subtask can store critique results."""
|
|
chunk = Subtask(id="test", description="Test")
|
|
|
|
chunk.critique_result = {
|
|
"passed": True,
|
|
"issues": [],
|
|
"suggestions": ["Consider adding error handling"],
|
|
}
|
|
|
|
assert chunk.critique_result["passed"] is True
|
|
|
|
def test_critique_serializes(self):
|
|
"""Critique result serializes correctly."""
|
|
chunk = Subtask(id="test", description="Test")
|
|
chunk.critique_result = {"passed": False, "issues": ["Missing tests"]}
|
|
|
|
data = chunk.to_dict()
|
|
|
|
assert "critique_result" in data
|
|
assert data["critique_result"]["passed"] is False
|
|
|
|
def test_critique_deserializes(self):
|
|
"""Critique result deserializes correctly."""
|
|
data = {
|
|
"id": "test",
|
|
"description": "Test",
|
|
"critique_result": {"passed": True, "score": 8},
|
|
}
|
|
|
|
chunk = Subtask.from_dict(data)
|
|
|
|
assert chunk.critique_result is not None
|
|
assert chunk.critique_result["score"] == 8
|