diff --git a/features/environment.py b/features/environment.py index 8984cfe3d..1f3663cab 100644 --- a/features/environment.py +++ b/features/environment.py @@ -62,9 +62,41 @@ def before_all(context): # MigrationRunner.init_or_upgrade so that fresh file-based SQLite # databases are created by copying the pre-migrated template (~1ms) # instead of running 25 Alembic migrations (~0.5-3s each). + # + # If running outside of nox (i.e. direct `behave` invocation), auto- + # create the template DB so the fast-path is always active. + _ensure_template_db() _install_template_db_patch() +def _ensure_template_db() -> None: + """Auto-create the template DB when CLEVERAGENTS_TEMPLATE_DB is not set. + + When running tests directly via ``behave`` (without nox), the env var + is missing. This function creates the template on the fly using + ``scripts/create_template_db.py`` so the fast-path is always active. + """ + if os.environ.get("CLEVERAGENTS_TEMPLATE_DB"): + return # Already set by nox or CI + + template_path = Path(__file__).parent.parent / "build" / ".template-migrated.db" + if template_path.is_file(): + # Template already exists from a prior run — reuse it. + os.environ["CLEVERAGENTS_TEMPLATE_DB"] = str(template_path.resolve()) + return + + try: + # Import the template creation script + scripts_dir = Path(__file__).parent.parent / "scripts" + sys.path.insert(0, str(scripts_dir)) + from create_template_db import create_template + + create_template(str(template_path)) + os.environ["CLEVERAGENTS_TEMPLATE_DB"] = str(template_path.resolve()) + except Exception: + pass # Fall back to normal Alembic migrations + + def _install_template_db_patch() -> None: """Monkey-patch MigrationRunner to copy a template DB for fresh SQLite files.""" template_path = os.environ.get("CLEVERAGENTS_TEMPLATE_DB") @@ -80,8 +112,10 @@ def _install_template_db_patch() -> None: _original_init_or_upgrade = MigrationRunner.init_or_upgrade - # Prefixes used by before_scenario when creating per-scenario temp DBs. - _SCENARIO_DB_PREFIXES = ("cleveragents_", "cleveragents_test_") + # Prefixes used by before_scenario and step files when creating temp DBs. + # "cleveragents_" / "cleveragents_test_" — before_scenario databases + # "test_" — databases created inside step files (services_coverage, etc.) + _SCENARIO_DB_PREFIXES = ("cleveragents_", "cleveragents_test_", "test_") def _fast_init_or_upgrade(self: Any, **kwargs: Any) -> None: """Copy the template DB instead of running Alembic migrations. @@ -168,14 +202,20 @@ def before_scenario(context, scenario): # Give each scenario a unique database file so scenarios cannot share # persisted state AND parallel subprocesses never collide on the same # SQLite file. Store the paths for cleanup in after_scenario. + # + # Features tagged @mock_only use fully mocked services and never touch + # the database, so skip the temp-file creation for them (~0.5ms each, + # but the real savings come from not triggering MigrationRunner later). context._scenario_db_paths = [] - for env_var, prefix in ( - ("CLEVERAGENTS_DATABASE_URL", "cleveragents_"), - ("CLEVERAGENTS_TEST_DATABASE_URL", "cleveragents_test_"), - ): - db_path = tempfile.mktemp(suffix=".db", prefix=prefix) - os.environ[env_var] = f"sqlite:///{db_path}" - context._scenario_db_paths.append(db_path) + _is_mock_only = "mock_only" in scenario.effective_tags + if not _is_mock_only: + for env_var, prefix in ( + ("CLEVERAGENTS_DATABASE_URL", "cleveragents_"), + ("CLEVERAGENTS_TEST_DATABASE_URL", "cleveragents_test_"), + ): + db_path = tempfile.mktemp(suffix=".db", prefix=prefix) + os.environ[env_var] = f"sqlite:///{db_path}" + context._scenario_db_paths.append(db_path) # Re-apply mock AI provider after container reset try: diff --git a/features/plan_commands_coverage.feature b/features/plan_commands_coverage.feature index 3ff317d98..f41865d1c 100644 --- a/features/plan_commands_coverage.feature +++ b/features/plan_commands_coverage.feature @@ -1,3 +1,4 @@ +@mock_only Feature: Plan Commands Coverage As a developer I want to test all plan command paths diff --git a/features/plan_service.feature b/features/plan_service.feature index abefc6ea7..2897d9437 100644 --- a/features/plan_service.feature +++ b/features/plan_service.feature @@ -23,21 +23,21 @@ Feature: Plan Service When I build the plan Then changes should be generated And the changes should include file operations - + Scenario: Build plan uses actor selection Given I have a plan service with stub provider registry And I have created a plan with "Generate example code" When I build the plan with actor "anthropic/claude-dev" Then changes should be generated And the stub provider registry should record provider "anthropic" and model "claude-dev" - + Scenario: Build plan fails when no provider configured Given I have a plan service without providers configured And I have created a plan with "Generate example code" When I try to build the plan Then a PlanError should be raised with message "No AI provider configured" And the PlanError details should include provider diagnostics - + Scenario: Apply generated changes Given I have a plan service @@ -81,154 +81,150 @@ Feature: Plan Service And the plan has a pending MOVE change to an absolute path When I apply the plan changes Then the absolute destination should exist and the source should be removed - + Scenario: Stream plan generation and persist usage Given I have a plan service with a streaming stub provider When I stream plan generation with prompt "Streamed instructions" Then the streaming events should include nodes "load_context, analyze_requirements, generate_plan, validate, __end__" And the streamed plan should persist token count 321 - + Scenario: Stream plan generation fails when provider never completes Given I have a plan service with an incomplete streaming provider When I try to stream plan generation with prompt "Incomplete stream" Then a PlanError should be raised with message "Provider streaming ended before completion" - + Scenario: Stream plan generation coerces dict payloads Given I have a plan service with a dict streaming provider When I stream plan generation with prompt "Dict payload" Then the streaming events should include nodes "generate_plan, __end__" And the streamed plan should persist token count 0 - + Scenario: Stream plan generation surfaces provider error strings Given I have a plan service with a string error streaming provider When I try to stream plan generation with prompt "String error" Then the streaming failure events should include an error with message "stub failure" And a PlanError should be raised with message "stub failure" - + Scenario: Stream plan generation rejects non-list change payloads Given I have a plan service with a non-list change streaming provider When I try to stream plan generation with prompt "Non list payload" Then a PlanError should be raised with message "Provider stream returned an invalid change payload" - + Scenario: Stream plan generation rejects invalid change entries Given I have a plan service with an invalid change entry streaming provider When I try to stream plan generation with prompt "Invalid change entry" Then a PlanError should be raised with message "Provider stream returned a non-change entry" - + Scenario: Mock provider resolution keeps nameless provider metadata - Given I have a plan service + Given I have a lightweight plan service for actor testing And mock provider mode is forced And the plan service uses a nameless AI provider And the plan service actor lookup returns actor "anthropic/claude-mock" with provider "anthropic" and model "claude-mock" When I resolve provider for actor "anthropic/claude-mock" Then the nameless provider resolution should return provider "anthropic" and model "claude-mock" - + Scenario: Actor resolution triggers lazy registry creation - Given I have a plan service + Given I have a lightweight plan service for actor testing And I stub the provider registry factory for lazy initialization And the plan service actor lookup returns actor "anthropic/claude-dev" with provider "anthropic" and model "claude-dev" When I resolve provider for actor "anthropic/claude-dev" Then the lazy registry should memoize provider "anthropic" and model "claude-dev" - + Scenario: LangSmith config omits missing metadata Given I have a temporary test directory for plan service And LangSmith integration is enabled for plan service When I prepare a LangSmith config without plan metadata for project "ephemeral-project" Then the LangSmith builder should receive only base metadata for project "ephemeral-project" And the prepared LangSmith config should include a generated thread id - + Scenario: Build fails when plan ID disappears mid-transaction Given I have a temporary test directory for plan service And I configure a stub plan service whose current plan loses its ID after the transaction When I try to build the plan with the stubbed service Then a PlanError should be raised with message "Plan does not have a valid ID" - + Scenario: Clearing memory without forgetting history keeps cached messages - Given I have a plan service + Given I have a lightweight plan service for actor testing And I stored a chat message in session "ephemeral-branch" When I clear the session "ephemeral-branch" memory without forgetting history Then the session "ephemeral-branch" memory should retain its stored messages Scenario: Actor resolution fails when actor service is missing - Given I have a plan service + Given I have a lightweight plan service for actor testing When I try to resolve actor "ghost/actor" Then a PlanError should be raised with message "Actor support is not configured" Scenario: Actor lookup validation errors surface as plan errors - Given I have a plan service + Given I have a lightweight plan service for actor testing And the plan service actor lookup raises ValidationError "invalid actor selection" When I try to resolve actor "invalid/actor" Then a PlanError should be raised with message "invalid actor selection" And the PlanError should include actor detail "invalid/actor" Scenario: Actor fallback fails when mock provisioning returns nothing - Given I have a plan service + Given I have a lightweight plan service for actor testing And the plan service actor lookup returns no actor and mock provisioning fails When I try to resolve actor "openai/gpt-4o" Then a PlanError should be raised with message "No actor configured" Scenario: Actor resolution returns explicit actors - Given I have a plan service + Given I have a lightweight plan service for actor testing And the plan service actor lookup returns actor "openai/gpt-4o" with provider "openai" and model "gpt-4o" When I try to resolve actor "openai/gpt-4o" Then the actor resolution should return actor "openai/gpt-4o" from source "explicit" Scenario: Mock actor provider resolution requires configured AI provider - Given I have a plan service + Given I have a lightweight plan service for actor testing And mock provider mode is forced And the plan service actor lookup returns actor "mock/provider" with provider "mock-provider" and model "mock-model" When I resolve provider for actor "mock/provider" Then a PlanError should be raised with message "No AI provider configured" Scenario: Mock actor provider resolution uses injected provider defaults - Given I have a plan service + Given I have a lightweight plan service for actor testing And mock provider mode is forced And the plan service AI provider is "injected-provider" with model "injected-model" And the plan service actor lookup returns actor "mock/provider" with provider "mock-provider" and model "mock-model" When I resolve provider for actor "mock/provider" Then the provider resolution should return provider "mock-provider" and model "mock-model" - + Scenario: Mock actor provider appears after initial check Given I have a Unit of Work instance for plan testing And the plan service uses a delayed mock provider And the plan service actor lookup returns actor "mock/provider" with provider "mock-provider" and model "mock-model" When I resolve provider for actor "mock/provider" Then the provider resolution should return provider "mock-provider" and model "mock-model" - + Scenario: Actor provider registry factory failure surfaces PlanError - Given I have a plan service + Given I have a lightweight plan service for actor testing And the plan service actor lookup returns actor "broken/provider" with provider "broken-provider" and model "broken-model" And the provider registry factory raises ValueError "actor registry missing" for actor providers When I resolve provider for actor "broken/provider" Then a PlanError should be raised with message "actor registry missing" - + Scenario: Actor provider registry errors surface as PlanError - Given I have a plan service + Given I have a lightweight plan service for actor testing And the plan service actor lookup returns actor "anthropic/haiku" with provider "anthropic" and model "haiku" And the provider registry raises ValueError "actor registry unavailable" for actor providers When I resolve provider for actor "anthropic/haiku" Then a PlanError should be raised with message "actor registry unavailable" - + Scenario: Actor provider registry resolves provider and model - Given I have a plan service + Given I have a lightweight plan service for actor testing And the plan service actor lookup returns actor "openai/gpt-4o" with provider "openai" and model "gpt-4o" And the provider registry returns provider "openai" with model "gpt-4o" When I resolve provider for actor "openai/gpt-4o" Then the provider resolution should return provider "openai" and model "gpt-4o" - + Scenario: Actor provider resolution uses lazy registry fallback - Given I have a plan service + Given I have a lightweight plan service for actor testing And the plan service actor lookup returns actor "openai/gpt-4o" with provider "openai" and model "gpt-4o" And I stub the provider registry factory for lazy initialization When I resolve provider for actor "openai/gpt-4o" Then the lazy registry should record actor provider "openai" and model "gpt-4o" - + Scenario: Streaming sanitization reports unrecoverable Python syntax Given I have a plan service with an unrecoverable streaming provider When I try to stream plan generation with prompt "Fatal stream" Then a PlanError should be raised containing "Validation failed: Syntax error" - - - - diff --git a/features/steps/plan_service_steps.py b/features/steps/plan_service_steps.py index 28e742236..fc3139eec 100644 --- a/features/steps/plan_service_steps.py +++ b/features/steps/plan_service_steps.py @@ -89,6 +89,24 @@ def step_create_plan_service(context: Context) -> None: ) +@given("I have a lightweight plan service for actor testing") +def step_create_lightweight_plan_service(context: Context) -> None: + """Create a lightweight PlanService using in-memory DB for actor resolution tests. + + This avoids the overhead of file-based DB creation, temp directories, and + project initialization — none of which are needed for testing actor/provider + resolution logic. + """ + if not hasattr(context, "unit_of_work"): + step_create_unit_of_work_plan(context) + if not hasattr(context, "temp_dir"): + context.temp_dir = Path(tempfile.mkdtemp(prefix="plan_actor_")) + settings = Settings() + context.plan_service = PlanService( + settings=settings, unit_of_work=context.unit_of_work, ai_provider=None + ) + + @given("I have a plan service with stub provider registry") def step_plan_service_with_stub_registry(context: Context) -> None: """Create a PlanService that uses a stub provider registry for overrides.""" diff --git a/features/steps/services_coverage_steps.py b/features/steps/services_coverage_steps.py index 492d1b270..50d95e6a8 100644 --- a/features/steps/services_coverage_steps.py +++ b/features/steps/services_coverage_steps.py @@ -2,6 +2,7 @@ import os import tempfile +import uuid from datetime import datetime from pathlib import Path @@ -17,79 +18,81 @@ from cleveragents.application.services.project_service import ProjectService from cleveragents.config.settings import Settings from cleveragents.core.exceptions import NotFoundError from cleveragents.domain.models.core import Project, ProjectSettings +from cleveragents.infrastructure.database.unit_of_work import UnitOfWork from features.mocks.mock_ai_provider import MockAIProvider +def _setup_context_service(context, *, with_plan: bool = True): + """Common setup for context service tests. + + Creates a temp dir, unique DB, ContextService, Project, and optionally + a Plan — consolidating the duplicated boilerplate across multiple Given + steps. + """ + context.temp_dir = tempfile.mkdtemp() + os.chdir(context.temp_dir) + Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) + settings = Settings() + + db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" + unit_of_work = UnitOfWork(f"sqlite:///{db_file}") + unit_of_work.init_database() + + context.context_service = ContextService(settings, unit_of_work) + + project_service = ProjectService(settings, unit_of_work) + context.test_project = project_service.initialize_project( + name="test-project", path=Path(context.temp_dir), force=True + ) + + if with_plan: + mock_provider = MockAIProvider() + plan_service = PlanService(settings, unit_of_work, ai_provider=mock_provider) + context.test_plan = plan_service.create_plan( + project=context.test_project, prompt="Test plan for context" + ) + + return settings, unit_of_work + + +def _setup_plan_service(context): + """Common setup for plan service tests. + + Creates a temp dir, unique DB, PlanService, and Project — consolidating + the duplicated boilerplate across multiple Given steps. + """ + context.temp_dir = tempfile.mkdtemp() + os.chdir(context.temp_dir) + Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) + settings = Settings() + + db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" + unit_of_work = UnitOfWork(f"sqlite:///{db_file}") + unit_of_work.init_database() + + mock_provider = MockAIProvider() + context.plan_service = PlanService( + settings, unit_of_work, ai_provider=mock_provider + ) + + project_service = ProjectService(settings, unit_of_work) + context.test_project = project_service.initialize_project( + name="test-project", path=Path(context.temp_dir), force=True + ) + + return settings, unit_of_work + + @given("a context service instance") def step_create_context_service(context): """Create a context service instance.""" - import uuid - - context.temp_dir = tempfile.mkdtemp() - os.chdir(context.temp_dir) - # Create .cleveragents directory - Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) - settings = Settings() - - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" - unit_of_work = UnitOfWork(f"sqlite:///{db_file}") - unit_of_work.init_database() - - context.context_service = ContextService(settings, unit_of_work) - - # Also create project and plan for context to work with - from cleveragents.application.services.plan_service import PlanService - from cleveragents.application.services.project_service import ProjectService - - project_service = ProjectService(settings, unit_of_work) - context.test_project = project_service.initialize_project( - name="test-project", path=Path(context.temp_dir), force=True - ) - - mock_provider = MockAIProvider() - plan_service = PlanService(settings, unit_of_work, ai_provider=mock_provider) - context.test_plan = plan_service.create_plan( - project=context.test_project, prompt="Test plan for context" - ) + _setup_context_service(context) @given("a context service instance with files") def step_create_context_service_with_files(context): """Create a context service with some files.""" - import uuid - - context.temp_dir = tempfile.mkdtemp() - os.chdir(context.temp_dir) - # Create .cleveragents directory - Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) - settings = Settings() - - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" - unit_of_work = UnitOfWork(f"sqlite:///{db_file}") - unit_of_work.init_database() - - context.context_service = ContextService(settings, unit_of_work) - - # Also create project and plan for context to work with - from cleveragents.application.services.plan_service import PlanService - from cleveragents.application.services.project_service import ProjectService - - project_service = ProjectService(settings, unit_of_work) - context.test_project = project_service.initialize_project( - name="test-project", path=Path(context.temp_dir), force=True - ) - - mock_provider = MockAIProvider() - plan_service = PlanService(settings, unit_of_work, ai_provider=mock_provider) - context.test_plan = plan_service.create_plan( - project=context.test_project, prompt="Test plan for context" - ) + _setup_context_service(context) # Add some test files for i in range(3): @@ -287,67 +290,14 @@ def step_verify_context_persisted(context): @given("a plan service instance") def step_create_plan_service(context): """Create a plan service instance.""" - import uuid - - context.temp_dir = tempfile.mkdtemp() - os.chdir(context.temp_dir) - # Create .cleveragents directory - Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) - settings = Settings() - - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" - unit_of_work = UnitOfWork(f"sqlite:///{db_file}") - unit_of_work.init_database() - - mock_provider = MockAIProvider() - context.plan_service = PlanService( - settings, unit_of_work, ai_provider=mock_provider - ) - - # Also create a project for the plan service to work with - from cleveragents.application.services.project_service import ProjectService - - project_service = ProjectService(settings, unit_of_work) - context.test_project = project_service.initialize_project( - name="test-project", path=Path(context.temp_dir), force=True - ) + _setup_plan_service(context) @given("a plan service instance with a plan") def step_create_plan_service_with_plan(context): """Create a plan service with a plan.""" - import uuid + _setup_plan_service(context) - context.temp_dir = tempfile.mkdtemp() - os.chdir(context.temp_dir) - # Create .cleveragents directory - Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) - settings = Settings() - - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" - unit_of_work = UnitOfWork(f"sqlite:///{db_file}") - unit_of_work.init_database() - - mock_provider = MockAIProvider() - context.plan_service = PlanService( - settings, unit_of_work, ai_provider=mock_provider - ) - - # Create a project and plan - from cleveragents.application.services.project_service import ProjectService - - project_service = ProjectService(settings, unit_of_work) - context.test_project = project_service.initialize_project( - name="test-project", path=Path(context.temp_dir), force=True - ) - - # Create a plan using the service context.test_plan = context.plan_service.create_plan( project=context.test_project, prompt="Test plan", name="test-plan" ) @@ -356,74 +306,19 @@ def step_create_plan_service_with_plan(context): @given("a plan service instance with a built plan") def step_create_plan_service_with_built_plan(context): """Create a plan service with a built plan.""" - import uuid + _setup_plan_service(context) - context.temp_dir = tempfile.mkdtemp() - os.chdir(context.temp_dir) - # Create .cleveragents directory - Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) - settings = Settings() - - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" - unit_of_work = UnitOfWork(f"sqlite:///{db_file}") - unit_of_work.init_database() - - mock_provider = MockAIProvider() - context.plan_service = PlanService( - settings, unit_of_work, ai_provider=mock_provider - ) - - # Create a project and plan - from cleveragents.application.services.project_service import ProjectService - - project_service = ProjectService(settings, unit_of_work) - context.test_project = project_service.initialize_project( - name="test-project", path=Path(context.temp_dir), force=True - ) - - # Create a plan and build it context.test_plan = context.plan_service.create_plan( project=context.test_project, prompt="Built plan", name="built-plan" ) - # Build the plan to generate changes context.changes = context.plan_service.build_plan(project=context.test_project) @given("a plan service instance with multiple plans") def step_create_plan_service_with_multiple_plans(context): """Create a plan service with multiple plans.""" - import uuid + _setup_plan_service(context) - context.temp_dir = tempfile.mkdtemp() - os.chdir(context.temp_dir) - # Create .cleveragents directory - Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) - settings = Settings() - - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" - unit_of_work = UnitOfWork(f"sqlite:///{db_file}") - unit_of_work.init_database() - - mock_provider = MockAIProvider() - context.plan_service = PlanService( - settings, unit_of_work, ai_provider=mock_provider - ) - - # Create a project - from cleveragents.application.services.project_service import ProjectService - - project_service = ProjectService(settings, unit_of_work) - context.test_project = project_service.initialize_project( - name="test-project", path=Path(context.temp_dir), force=True - ) - - # Create multiple plans context.plans = [] for i in range(3): plan = context.plan_service.create_plan( @@ -435,41 +330,12 @@ def step_create_plan_service_with_multiple_plans(context): @given("a plan service instance with an applied plan") def step_create_plan_service_with_applied_plan(context): """Create a plan service with an applied plan.""" - import uuid + _setup_plan_service(context) - context.temp_dir = tempfile.mkdtemp() - os.chdir(context.temp_dir) - # Create .cleveragents directory - Path(context.temp_dir, ".cleveragents").mkdir(exist_ok=True) - settings = Settings() - - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" - unit_of_work = UnitOfWork(f"sqlite:///{db_file}") - unit_of_work.init_database() - - mock_provider = MockAIProvider() - context.plan_service = PlanService( - settings, unit_of_work, ai_provider=mock_provider - ) - - # Create a project and plan - from cleveragents.application.services.project_service import ProjectService - - project_service = ProjectService(settings, unit_of_work) - context.test_project = project_service.initialize_project( - name="test-project", path=Path(context.temp_dir), force=True - ) - - # Create a plan, build it, and apply it context.test_plan = context.plan_service.create_plan( project=context.test_project, prompt="Applied plan", name="applied-plan" ) - # Build the plan context.changes = context.plan_service.build_plan(project=context.test_project) - # Apply the changes context.applied_count = context.plan_service.apply_changes( project=context.test_project ) @@ -603,44 +469,31 @@ def step_verify_changes_reverted(context): # Project Service steps -@given("a project service instance") -def step_create_project_service(context): - """Create a project service instance.""" - import uuid - +def _setup_project_service(context): + """Common setup for project service tests.""" context.temp_dir = tempfile.mkdtemp() os.chdir(context.temp_dir) settings = Settings() - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" unit_of_work = UnitOfWork(f"sqlite:///{db_file}") unit_of_work.init_database() context.project_service = ProjectService(settings, unit_of_work) + return settings, unit_of_work + + +@given("a project service instance") +def step_create_project_service(context): + """Create a project service instance.""" + _setup_project_service(context) @given("a project service instance with a project") def step_create_project_service_with_project(context): """Create a project service with an existing project.""" - import uuid + _setup_project_service(context) - context.temp_dir = tempfile.mkdtemp() - os.chdir(context.temp_dir) - settings = Settings() - - # Create unit of work with unique database - from cleveragents.infrastructure.database.unit_of_work import UnitOfWork - - db_file = Path(context.temp_dir) / f"test_{uuid.uuid4().hex}.db" - unit_of_work = UnitOfWork(f"sqlite:///{db_file}") - unit_of_work.init_database() - - context.project_service = ProjectService(settings, unit_of_work) - - # Initialize a project using the service context.project = context.project_service.initialize_project( name="test_project", path=Path(context.temp_dir) )