Files
cleveragents-core/features/steps/project_model_steps.py

362 lines
12 KiB
Python

"""Step definitions for Project domain model extension tests (B1.1)."""
from __future__ import annotations
from pathlib import Path
from behave import given, then, when
from behave.runner import Context
from pydantic import ValidationError
from cleveragents.domain.models.core.project import Project
from cleveragents.domain.models.core.resource import (
Resource,
ResourceType,
)
VALID_ULID = "01ARZ3NDEKTSV4RRFFQ69G5FAV"
VALID_ULID_2 = "01ARZ3NDEKTSV4RRFFQ69G5FAW"
VALID_ULID_3 = "01ARZ3NDEKTSV4RRFFQ69G5FAX"
def _make_resource(
name: str,
is_remote: bool = False,
resource_id: str = VALID_ULID_2,
) -> Resource:
"""Helper to create a test Resource."""
return Resource(
resource_id=resource_id,
name=name,
type=ResourceType.GIT_REPOSITORY,
location="/tmp/repo" if not is_remote else "https://github.com/test",
is_remote=is_remote,
)
# Project creation steps
@given('a Project with project_id "{pid}" name "{name}" namespace "{namespace}"')
def step_create_project_with_namespace(
context: Context, pid: str, name: str, namespace: str
) -> None:
"""Create a Project with explicit namespace."""
context.project = Project(
project_id=pid,
name=name,
namespace=namespace,
path=Path("/tmp/test"),
)
@given('a Project with project_id "{pid}" and name "{name}"')
def step_create_project_minimal(context: Context, pid: str, name: str) -> None:
"""Create a Project with minimal fields."""
context.project = Project(
project_id=pid,
name=name,
path=Path("/tmp/test"),
)
@given('a Project with project_id "{pid}" name "{name}" and description "{desc}"')
def step_create_project_with_description(
context: Context, pid: str, name: str, desc: str
) -> None:
"""Create a Project with description."""
context.project = Project(
project_id=pid,
name=name,
description=desc,
path=Path("/tmp/test"),
)
@given('a Project with project_id "{pid}" name "{name}" and tags "{tags_str}"')
def step_create_project_with_tags(
context: Context, pid: str, name: str, tags_str: str
) -> None:
"""Create a Project with tags."""
tags = [t.strip() for t in tags_str.split(",")]
context.project = Project(
project_id=pid,
name=name,
tags=tags,
path=Path("/tmp/test"),
)
@given('a Project with project_id "{pid}" name "{name}" and all remote resources')
def step_create_project_all_remote(context: Context, pid: str, name: str) -> None:
"""Create a Project where all resources are remote."""
resources = [
_make_resource("api-svc", is_remote=True, resource_id=VALID_ULID_2),
_make_resource("cloud-db", is_remote=True, resource_id=VALID_ULID_3),
]
context.project = Project(
project_id=pid,
name=name,
resources=resources,
path=Path("/tmp/test"),
)
@given('a Project with project_id "{pid}" name "{name}" and mixed resources')
def step_create_project_mixed_resources(context: Context, pid: str, name: str) -> None:
"""Create a Project with both local and remote resources."""
resources = [
_make_resource("local-repo", is_remote=False, resource_id=VALID_ULID_2),
_make_resource("api-svc", is_remote=True, resource_id=VALID_ULID_3),
]
context.project = Project(
project_id=pid,
name=name,
resources=resources,
path=Path("/tmp/test"),
)
@given('a Project with project_id "{pid}" name "{name}" and a resource named "{rname}"')
def step_create_project_with_resource(
context: Context, pid: str, name: str, rname: str
) -> None:
"""Create a Project with one resource."""
resource = _make_resource(rname)
context.project = Project(
project_id=pid,
name=name,
resources=[resource],
path=Path("/tmp/test"),
)
# Namespace validation steps
@when('I try to create a Project with namespace "{namespace}"')
def step_try_create_project_bad_namespace(context: Context, namespace: str) -> None:
"""Attempt to create a Project with an invalid namespace."""
context.error = None
try:
context.project = Project(
project_id=VALID_ULID,
name="test-project",
namespace=namespace,
path=Path("/tmp/test"),
)
except (ValidationError, ValueError) as e:
context.error = e
@then("a namespace validation error should be raised")
def step_check_namespace_error(context: Context) -> None:
"""Verify a namespace validation error was raised."""
assert context.error is not None, (
"Expected a namespace validation error but none was raised"
)
# Resource manipulation steps
@when('I add a resource named "{rname}" to the project')
def step_add_resource(context: Context, rname: str) -> None:
"""Add a resource to the project."""
resource = _make_resource(rname)
context.project = context.project.add_resource(resource)
@when('I remove the resource named "{rname}" from the project')
def step_remove_resource(context: Context, rname: str) -> None:
"""Remove a resource from the project."""
context.project = context.project.remove_resource(rname)
@when('I get the resource named "{rname}" from the project')
def step_get_resource(context: Context, rname: str) -> None:
"""Get a resource by name from the project."""
context.retrieved_resource = context.project.get_resource(rname)
# Project field assertions
@then('the project project_id should be "{expected}"')
def step_check_project_id(context: Context, expected: str) -> None:
"""Verify project_id."""
assert context.project.project_id == expected
# Note: "the project name should be" step is defined in service_steps.py
@then('the project namespace should be "{expected}"')
def step_check_project_namespace(context: Context, expected: str) -> None:
"""Verify project namespace."""
assert context.project.namespace == expected
@then("the project description should be none")
def step_check_project_description_none(context: Context) -> None:
"""Verify description is None."""
assert context.project.description is None
@then('the project description should be "{expected}"')
def step_check_project_description(context: Context, expected: str) -> None:
"""Verify description value."""
assert context.project.description == expected
@then("the project tags should be empty")
def step_check_project_tags_empty(context: Context) -> None:
"""Verify tags is empty list."""
assert context.project.tags == []
@then('the project tags should contain "{tag}"')
def step_check_project_tag(context: Context, tag: str) -> None:
"""Verify tags contains expected value."""
assert tag in context.project.tags
@then("the project resources should be empty")
def step_check_project_resources_empty(context: Context) -> None:
"""Verify resources is empty list."""
assert context.project.resources == []
@then("the project validation_config should be none")
def step_check_project_validation_config_none(context: Context) -> None:
"""Verify validation_config is None."""
assert context.project.validation_config is None
@then("the project context_config should not be none")
def step_check_project_context_config_not_none(context: Context) -> None:
"""Verify context_config is set."""
assert context.project.context_config is not None
@then("the project created_at should be set")
def step_check_project_created_at(context: Context) -> None:
"""Verify created_at is set."""
assert context.project.created_at is not None
@then("the project updated_at should be set")
def step_check_project_updated_at(context: Context) -> None:
"""Verify updated_at is set."""
assert context.project.updated_at is not None
@then("the project is_remote should be false")
def step_check_project_not_remote(context: Context) -> None:
"""Verify project is not remote."""
assert context.project.is_remote is False
@then("the project is_remote should be true")
def step_check_project_is_remote(context: Context) -> None:
"""Verify project is remote."""
assert context.project.is_remote is True
@then('the project namespaced_name should be "{expected}"')
def step_check_project_namespaced_name(context: Context, expected: str) -> None:
"""Verify namespaced_name property."""
assert context.project.namespaced_name == expected
@then("the project should have {count:d} resource")
def step_check_project_resource_count(context: Context, count: int) -> None:
"""Verify resource count."""
assert len(context.project.resources) == count
@then('the project should have a resource named "{rname}"')
def step_check_project_has_resource(context: Context, rname: str) -> None:
"""Verify project has a resource with the given name."""
found = context.project.get_resource(rname)
assert found is not None, f"Expected resource '{rname}' not found"
@then('the retrieved resource name should be "{expected}"')
def step_check_retrieved_resource_name(context: Context, expected: str) -> None:
"""Verify retrieved resource name."""
assert context.retrieved_resource is not None
assert context.retrieved_resource.name == expected
@then("the retrieved resource should be none")
def step_check_retrieved_resource_none(context: Context) -> None:
"""Verify retrieved resource is None."""
assert context.retrieved_resource is None
# parse_namespaced_name steps
@when('I parse the project namespaced name "{namespaced}"')
def step_parse_project_namespaced_name(context: Context, namespaced: str) -> None:
"""Parse a namespaced name string via Project.parse_namespaced_name."""
namespace, name = Project.parse_namespaced_name(namespaced)
context.parsed_project_namespace = namespace
context.parsed_project_name = name
@when('I try to parse the project namespaced name "{namespaced}"')
def step_try_parse_project_namespaced_name(context: Context, namespaced: str) -> None:
"""Attempt to parse an invalid namespaced name string."""
context.error = None
try:
Project.parse_namespaced_name(namespaced)
except ValueError as e:
context.error = e
@then('the parsed project namespace should be "{expected}"')
def step_check_parsed_project_namespace(context: Context, expected: str) -> None:
"""Verify parsed project namespace."""
assert context.parsed_project_namespace == expected
@then('the parsed project name should be "{expected}"')
def step_check_parsed_project_name(context: Context, expected: str) -> None:
"""Verify parsed project name."""
assert context.parsed_project_name == expected
@then("a project namespaced name parse error should be raised")
def step_check_project_namespaced_name_error(context: Context) -> None:
"""Verify a namespaced name parse error was raised."""
assert context.error is not None, (
"Expected a ValueError for invalid namespaced name but none was raised"
)
# Name validation steps
@when('I try to create a Project with invalid name "{name}"')
def step_try_create_project_bad_name(context: Context, name: str) -> None:
"""Attempt to create a Project with an invalid name."""
context.error = None
try:
context.project = Project(
project_id=VALID_ULID,
name=name,
path=Path("/tmp/test"),
)
except (ValidationError, ValueError) as e:
context.error = e
@then("a name validation error should be raised")
def step_check_name_error(context: Context) -> None:
"""Verify a name validation error was raised."""
assert context.error is not None, (
"Expected a name validation error but none was raised"
)