"""Step definitions for database integration tests.""" import tempfile from datetime import datetime from pathlib import Path from behave import given, then, when from cleveragents.config.settings import Settings from cleveragents.domain.models.core import ( Change, Context, ContextType, OperationType, Plan, PlanStatus, Project, ProjectSettings, ) from cleveragents.infrastructure.database.unit_of_work import UnitOfWork from features.mocks.mock_ai_provider import MockAIProvider @given("I have a clean test database") def step_clean_test_database(context): """Create a clean test database.""" # Use in-memory SQLite for tests context.database_url = "sqlite:///:memory:" context.unit_of_work = UnitOfWork(context.database_url) context.unit_of_work.init_database() @given("I have initialized the Unit of Work") def step_init_unit_of_work(context): """Initialize Unit of Work.""" if not hasattr(context, "unit_of_work"): context.unit_of_work = UnitOfWork(context.database_url) context.unit_of_work.init_database() @when('I create a project with name "{name}"') def step_create_project(context, name): """Create a project using repository.""" project = Project( name=name, path=Path("/tmp/test-project"), settings=ProjectSettings(), created_at=datetime.now(), updated_at=datetime.now(), ) with context.unit_of_work.transaction() as ctx: context.created_project = ctx.projects.create(project) @then("the project should be saved in the database") def step_verify_project_saved(context): """Verify project is saved.""" assert context.created_project is not None assert context.created_project.id is not None @then("I can retrieve the project using Unit of Work") def step_can_retrieve_project_with_uow(context): """Verify we can retrieve the project using Unit of Work.""" with context.unit_of_work.transaction() as ctx: retrieved = ctx.projects.get_by_name(context.created_project.name) assert retrieved is not None assert retrieved.name == context.created_project.name assert retrieved.id == context.created_project.id @then("the project should have the correct properties") def step_verify_project_properties(context): """Verify project properties.""" with context.unit_of_work.transaction() as ctx: project = ctx.projects.get_by_id(context.created_project.id) assert project.name == context.created_project.name assert project.path == context.created_project.path assert project.settings is not None @when("I start a Unit of Work transaction") def step_start_transaction(context): """Start a UoW transaction.""" context.transaction = context.unit_of_work.transaction() context.transaction_context = context.transaction.__enter__() @when('I create a project "{name}" in the transaction') def step_create_project_in_transaction(context, name): """Create project in transaction.""" project = Project( name=name, path=Path(f"/tmp/{name}"), settings=ProjectSettings(), created_at=datetime.now(), updated_at=datetime.now(), ) context.transaction_project = context.transaction_context.projects.create(project) @when('I create a plan "{name}" in the transaction') def step_create_plan_in_transaction(context, name): """Create plan in transaction.""" plan = Plan( project_id=context.transaction_project.id, name=name, prompt="Test plan", status=PlanStatus.PENDING, current=True, created_at=datetime.now(), updated_at=datetime.now(), ) context.transaction_plan = context.transaction_context.plans.create(plan) @when("I commit the transaction") def step_commit_transaction(context): """Commit the transaction.""" context.transaction.__exit__(None, None, None) @then("both the project and plan should exist in the database") def step_verify_both_exist(context): """Verify both project and plan exist.""" with context.unit_of_work.transaction() as ctx: project = ctx.projects.get_by_id(context.transaction_project.id) assert project is not None plan = ctx.plans.get_by_id(context.transaction_plan.id) assert plan is not None @when("an error occurs during the transaction") def step_error_in_transaction(context): """Simulate an error in transaction.""" try: raise Exception("Simulated error") except Exception as e: context.transaction_error = e @then("the transaction should rollback") def step_verify_rollback(context): """Verify transaction rolled back.""" context.transaction.__exit__( type(context.transaction_error), context.transaction_error, None ) @then("no data should be saved to the database") def step_verify_no_data_saved(context): """Verify no data was saved.""" # Create a new transaction to check the database state # Use a fresh connection to ensure we see the current state from cleveragents.infrastructure.database.unit_of_work import UnitOfWork # Create a new UoW with the same database fresh_uow = UnitOfWork(context.unit_of_work.database_url) fresh_uow.init_database() # Ensure database is initialized with fresh_uow.transaction() as ctx: # Check that the project doesn't exist project = ctx.projects.get_by_name("test-project") # The rollback should have prevented saving # If the project exists, the rollback didn't work assert project is None, "Project was saved despite rollback" @given('I have a project "{name}" in the database') def step_create_project_in_db(context, name): """Create a project in database.""" project = Project( name=name, path=Path(f"/tmp/{name}"), settings=ProjectSettings(), created_at=datetime.now(), updated_at=datetime.now(), ) with context.unit_of_work.transaction() as ctx: context.test_project = ctx.projects.create(project) @when('I create a plan "{name}" for the project') def step_create_plan_for_project(context, name): """Create a plan for the project.""" plan = Plan( project_id=context.test_project.id, name=name, prompt=f"Plan {name}", status=PlanStatus.PENDING, current=False, created_at=datetime.now(), updated_at=datetime.now(), ) with context.unit_of_work.transaction() as ctx: context.first_plan = ctx.plans.create(plan) @when('I create another plan "{name}" for the project') def step_create_another_plan(context, name): """Create another plan.""" plan = Plan( project_id=context.test_project.id, name=name, prompt=f"Plan {name}", status=PlanStatus.PENDING, current=False, created_at=datetime.now(), updated_at=datetime.now(), ) with context.unit_of_work.transaction() as ctx: context.second_plan = ctx.plans.create(plan) @then("I should be able to list all plans for the project") def step_list_plans(context): """List all plans for project.""" with context.unit_of_work.transaction() as ctx: plans = ctx.plans.get_all_for_project(context.test_project.id) assert len(plans) == 2 plan_names = [p.name for p in plans] assert "feature-1" in plan_names assert "feature-2" in plan_names @then('I should be able to set "{name}" as the current plan') def step_set_current_plan(context, name): """Set a plan as current.""" with context.unit_of_work.transaction() as ctx: ctx.plans.set_current(context.test_project.id, context.second_plan.id) @then('the current plan should be "{name}"') def step_verify_current_plan(context, name): """Verify current plan.""" with context.unit_of_work.transaction() as ctx: current = ctx.plans.get_current_for_project(context.test_project.id) assert current is not None assert current.name == name @given("I have a project with a current plan") def step_project_with_current_plan(context): """Create project with current plan.""" project = Project( name="test-project", path=Path("/tmp/test-project"), settings=ProjectSettings(), created_at=datetime.now(), updated_at=datetime.now(), ) with context.unit_of_work.transaction() as ctx: context.test_project = ctx.projects.create(project) plan = Plan( project_id=context.test_project.id, name="current-plan", prompt="Test", status=PlanStatus.PENDING, current=True, created_at=datetime.now(), updated_at=datetime.now(), ) context.current_plan = ctx.plans.create(plan) @when('I add a file "{filename}" to the context') def step_add_file_to_context(context, filename): """Add file to context.""" file_context = Context( plan_id=context.current_plan.id, type=ContextType.FILE, path=filename, content=f"# Content of {filename}", file_hash="abc123", size=100, added_at=datetime.now(), ) with context.unit_of_work.transaction() as ctx: context.first_context = ctx.contexts.add(file_context) @when('I add another file "{filename}" to the context') def step_add_another_file(context, filename): """Add another file to context.""" file_context = Context( plan_id=context.current_plan.id, type=ContextType.FILE, path=filename, content=f"# Content of {filename}", file_hash="def456", size=150, added_at=datetime.now(), ) with context.unit_of_work.transaction() as ctx: context.second_context = ctx.contexts.add(file_context) @then("I should be able to list all context files") def step_list_context_files(context): """List context files.""" with context.unit_of_work.transaction() as ctx: contexts = ctx.contexts.get_for_plan(context.current_plan.id) assert len(contexts) == 2 paths = [c.path for c in contexts] assert "test.py" in paths assert "main.py" in paths @then('I should be able to remove "{filename}" from context') def step_remove_from_context(context, filename): """Remove file from context.""" with context.unit_of_work.transaction() as ctx: ctx.contexts.remove(context.first_context.id) @then('only "{filename}" should remain in context') def step_verify_remaining_context(context, filename): """Verify remaining context.""" with context.unit_of_work.transaction() as ctx: contexts = ctx.contexts.get_for_plan(context.current_plan.id) assert len(contexts) == 1 assert contexts[0].path == filename @given("I have a project with a built plan") def step_project_with_built_plan(context): """Create project with built plan.""" step_project_with_current_plan(context) with context.unit_of_work.transaction() as ctx: context.current_plan.status = PlanStatus.BUILT ctx.plans.update(context.current_plan) @when('I add a change to create "{filename}"') def step_add_create_change(context, filename): """Add a create change.""" change = Change( plan_id=context.current_plan.id, file_path=filename, operation=OperationType.CREATE, new_content="# New file content", applied=False, ) with context.unit_of_work.transaction() as ctx: context.first_change = ctx.changes.add(change) @when('I add a change to modify "{filename}"') def step_add_modify_change(context, filename): """Add a modify change.""" change = Change( plan_id=context.current_plan.id, file_path=filename, operation=OperationType.MODIFY, original_content="# Old content", new_content="# Modified content", applied=False, ) with context.unit_of_work.transaction() as ctx: context.second_change = ctx.changes.add(change) @when("I mark the first change as applied") def step_mark_change_applied(context): """Mark change as applied.""" with context.unit_of_work.transaction() as ctx: ctx.changes.mark_applied(context.first_change.id) @then("the applied change should have an applied timestamp") def step_verify_applied_timestamp(context): """Verify applied timestamp.""" with context.unit_of_work.transaction() as ctx: changes = ctx.changes.get_for_plan(context.current_plan.id) applied_change = next(c for c in changes if c.id == context.first_change.id) assert applied_change.applied is True assert applied_change.applied_at is not None @then("the second change should still be pending") def step_verify_pending_change(context): """Verify pending change.""" with context.unit_of_work.transaction() as ctx: changes = ctx.changes.get_for_plan(context.current_plan.id) pending_change = next(c for c in changes if c.id == context.second_change.id) assert pending_change.applied is False assert pending_change.applied_at is None @when("I use ProjectService to initialize a project") def step_use_project_service(context): """Use ProjectService.""" from cleveragents.application.services.project_service import ProjectService settings = Settings() service = ProjectService(settings, context.unit_of_work) with tempfile.TemporaryDirectory() as tmpdir: context.service_project = service.initialize_project( "service-test", Path(tmpdir) ) @then("the service should use Unit of Work") def step_verify_service_uses_uow(context): """Verify service uses UoW.""" # The service initialized successfully, which means it used UoW assert context.service_project is not None @then("the database infrastructure should be initialized") def step_verify_database_infrastructure_initialized(context): """Verify database infrastructure is initialized.""" # The database was initialized by the service using Unit of Work # We can verify this by checking that we can perform database operations assert context.service_project is not None # Verify we can query the database without errors with context.unit_of_work.transaction() as ctx: # Should be able to query the project # Use the actual project name that was created expected_name = ( context.service_project.name if hasattr(context, "service_project") and context.service_project else "new-project" ) project = ctx.projects.get_by_name(expected_name) assert project is not None, ( f"Project '{expected_name}' should exist in database" ) assert project.name == expected_name @then("the project should be persisted correctly") def step_verify_project_persisted(context): """Verify project is persisted.""" assert context.service_project.name == "service-test" assert context.service_project.settings is not None @given("I have an initialized project for database testing") def step_initialized_project_db(context): """Create initialized project for database tests.""" from cleveragents.application.services.project_service import ProjectService settings = Settings() service = ProjectService(settings, context.unit_of_work) with tempfile.TemporaryDirectory() as tmpdir: context.test_project = service.initialize_project("test-project", Path(tmpdir)) @when("I use PlanService to create a plan") def step_use_plan_service(context): """Use PlanService.""" from cleveragents.application.services.plan_service import PlanService settings = Settings() mock_provider = MockAIProvider() service = PlanService(settings, context.unit_of_work, ai_provider=mock_provider) context.service_plan = service.create_plan( context.test_project, "Create a new feature", "feature-plan" ) @then("the plan should be saved via repository") def step_verify_plan_saved_via_repo(context): """Verify plan saved via repository.""" assert context.service_plan is not None assert context.service_plan.id is not None @then("the plan should be set as current") def step_verify_plan_is_current(context): """Verify plan is current.""" assert context.service_plan.current is True @then("I should be able to retrieve it") def step_retrieve_plan(context): """Retrieve the plan.""" from cleveragents.application.services.plan_service import PlanService settings = Settings() mock_provider = MockAIProvider() service = PlanService(settings, context.unit_of_work, ai_provider=mock_provider) current = service.get_current_plan(context.test_project) assert current is not None assert current.name == "feature-plan" @given("I have a project with a plan") def step_project_with_plan(context): """Create project with plan.""" step_initialized_project_db(context) step_use_plan_service(context) @when("I use ContextService to add files") def step_use_context_service(context): """Use ContextService.""" from cleveragents.application.services.context_service import ContextService settings = Settings() service = ContextService(settings, context.unit_of_work) # Create a temporary file to add with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: f.write("# Test file content\n") context.test_file = Path(f.name) context.added_files = service.add_to_context( context.test_project, context.test_file, recursive=False ) @then("the context should be saved via repository") def step_verify_context_saved(context): """Verify context saved.""" assert len(context.added_files) > 0 @then("the files should be associated with the plan") def step_verify_files_associated(context): """Verify files associated with plan.""" from cleveragents.application.services.context_service import ContextService settings = Settings() service = ContextService(settings, context.unit_of_work) contexts = service.list_context(context.test_project) assert len(contexts) > 0 @then("I should be able to query the context") def step_query_context(context): """Query the context.""" from cleveragents.application.services.context_service import ContextService settings = Settings() service = ContextService(settings, context.unit_of_work) size = service.get_context_size(context.test_project) assert size > 0 # Cleanup if hasattr(context, "test_file"): context.test_file.unlink(missing_ok=True)