563 lines
18 KiB
Python
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 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)
|