Files
cleveragents-core/features/steps/database_integration_steps.py
T
2025-11-24 20:04:18 -05:00

563 lines
18 KiB
Python

"""Step definitions for database integration tests."""
import tempfile
from datetime import datetime
from pathlib import Path
from behave import given, then, when
from features.mocks.mock_ai_provider import MockAIProvider
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
@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)