3dd2280378
CI / lint (pull_request) Successful in 33s
CI / typecheck (pull_request) Successful in 49s
CI / quality (pull_request) Successful in 49s
CI / security (pull_request) Successful in 1m19s
CI / build (pull_request) Successful in 28s
CI / push-validation (pull_request) Successful in 20s
CI / helm (pull_request) Successful in 36s
CI / e2e_tests (pull_request) Successful in 4m41s
CI / integration_tests (pull_request) Successful in 4m44s
CI / unit_tests (pull_request) Failing after 7m9s
CI / docker (pull_request) Has been skipped
CI / coverage (pull_request) Successful in 19m52s
CI / status-check (pull_request) Failing after 2s
CI / benchmark-publish (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Successful in 57m18s
- Change validate_clone_into_url() return type from bool to None - Raise ValueError for empty or invalid git repository URLs - Update BDD steps to catch ValueError and set clone_url_valid accordingly - Aligns with contract requirement from PR #8304 review feedback
281 lines
10 KiB
Python
281 lines
10 KiB
Python
"""Step definitions for container_clone_into.feature.
|
|
|
|
Tests the CloneIntoHandler module: URL validation, argument validation,
|
|
successful clone, failure handling, and container-instance resource type
|
|
CLI argument registration.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.resource.handlers.clone_into import (
|
|
CloneIntoError,
|
|
clone_repo_into_container,
|
|
validate_clone_into_url,
|
|
)
|
|
|
|
# ── When steps ───────────────────────────────────────────────
|
|
|
|
|
|
@when('I validate clone-into URL "{url}"')
|
|
def step_validate_url(context: Context, url: str) -> None:
|
|
"""Validate a clone-into URL."""
|
|
context.clone_url_error = None
|
|
try:
|
|
validate_clone_into_url(url)
|
|
context.clone_url_valid = True
|
|
except ValueError as exc:
|
|
context.clone_url_valid = False
|
|
context.clone_url_error = exc
|
|
|
|
|
|
@when("I validate clone-into URL with whitespace only")
|
|
def step_validate_whitespace_url(context: Context) -> None:
|
|
"""Validate a whitespace-only clone-into URL."""
|
|
context.clone_url_error = None
|
|
try:
|
|
validate_clone_into_url(" ")
|
|
context.clone_url_valid = True
|
|
except ValueError as exc:
|
|
context.clone_url_valid = False
|
|
context.clone_url_error = exc
|
|
|
|
|
|
@when("I call clone_repo_into_container with empty container_id")
|
|
def step_clone_empty_container_id(context: Context) -> None:
|
|
"""Call clone_repo_into_container with an empty container_id."""
|
|
context.clone_error = None
|
|
try:
|
|
clone_repo_into_container("", "https://github.com/org/repo.git")
|
|
except ValueError as exc:
|
|
context.clone_error = exc
|
|
|
|
|
|
@when("I call clone_repo_into_container with empty repo_url")
|
|
def step_clone_empty_repo_url(context: Context) -> None:
|
|
"""Call clone_repo_into_container with an empty repo_url."""
|
|
context.clone_error = None
|
|
try:
|
|
clone_repo_into_container("abc123def456", "")
|
|
except ValueError as exc:
|
|
context.clone_error = exc
|
|
|
|
|
|
@when('I clone "{repo_url}" into container "{container_id}"')
|
|
def step_clone_into_container(
|
|
context: Context, repo_url: str, container_id: str
|
|
) -> None:
|
|
"""Clone a repository into a container."""
|
|
context.clone_error = None
|
|
context.clone_result = None
|
|
try:
|
|
context.clone_result = clone_repo_into_container(container_id, repo_url)
|
|
except (CloneIntoError, ValueError) as exc:
|
|
context.clone_error = exc
|
|
|
|
|
|
@when(
|
|
'I clone repo "{repo_url}" into container "{container_id}" with target "{target_dir}"'
|
|
)
|
|
def step_clone_into_container_at(
|
|
context: Context, repo_url: str, container_id: str, target_dir: str
|
|
) -> None:
|
|
"""Clone a repository into a container at a specific target directory."""
|
|
context.clone_error = None
|
|
context.clone_result = None
|
|
try:
|
|
context.clone_result = clone_repo_into_container(
|
|
container_id, repo_url, target_dir
|
|
)
|
|
except (CloneIntoError, ValueError) as exc:
|
|
context.clone_error = exc
|
|
|
|
|
|
@when('I look up the "{type_name}" resource type spec')
|
|
def step_look_up_resource_type_spec(context: Context, type_name: str) -> None:
|
|
"""Look up a resource type spec from BUILTIN_TYPES."""
|
|
from cleveragents.application.services._resource_registry_data import BUILTIN_TYPES
|
|
|
|
context.resource_type_spec = None
|
|
for entry in BUILTIN_TYPES:
|
|
if entry.get("name") == type_name:
|
|
context.resource_type_spec = entry
|
|
break
|
|
|
|
|
|
# ── Given steps ──────────────────────────────────────────────
|
|
|
|
|
|
@given("a mock docker exec that succeeds for clone")
|
|
def step_mock_docker_exec_success(context: Context) -> None:
|
|
"""Patch subprocess.run to simulate a successful docker exec git clone."""
|
|
mock_result = MagicMock()
|
|
mock_result.returncode = 0
|
|
mock_result.stdout = ""
|
|
mock_result.stderr = ""
|
|
|
|
patcher = patch(
|
|
"cleveragents.resource.handlers.clone_into.subprocess.run",
|
|
return_value=mock_result,
|
|
)
|
|
patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
|
|
|
|
@given('a mock docker exec that fails for clone with stderr "{stderr_msg}"')
|
|
def step_mock_docker_exec_failure(context: Context, stderr_msg: str) -> None:
|
|
"""Patch subprocess.run to simulate a failed docker exec git clone."""
|
|
mock_result = MagicMock()
|
|
mock_result.returncode = 1
|
|
mock_result.stdout = ""
|
|
mock_result.stderr = stderr_msg
|
|
|
|
patcher = patch(
|
|
"cleveragents.resource.handlers.clone_into.subprocess.run",
|
|
return_value=mock_result,
|
|
)
|
|
patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
|
|
|
|
@given(
|
|
'a CloneIntoError with repo "{repo_url}" container "{container_id}" stderr "{stderr}"'
|
|
)
|
|
def step_create_clone_into_error(
|
|
context: Context, repo_url: str, container_id: str, stderr: str
|
|
) -> None:
|
|
"""Create a CloneIntoError instance for attribute testing."""
|
|
context.clone_into_error = CloneIntoError(
|
|
repo_url=repo_url,
|
|
container_id=container_id,
|
|
stderr=stderr,
|
|
)
|
|
|
|
|
|
# ── Then steps ───────────────────────────────────────────────
|
|
|
|
|
|
@then("the clone-into URL should be valid")
|
|
def step_url_should_be_valid(context: Context) -> None:
|
|
assert context.clone_url_valid is True, (
|
|
"Expected URL to be valid but validate_clone_into_url returned False"
|
|
)
|
|
|
|
|
|
@then("the clone-into URL should be invalid")
|
|
def step_url_should_be_invalid(context: Context) -> None:
|
|
assert context.clone_url_valid is False, (
|
|
"Expected URL to be invalid but validate_clone_into_url returned True"
|
|
)
|
|
|
|
|
|
@then('it should raise ValueError mentioning "{field}"')
|
|
def step_should_raise_value_error(context: Context, field: str) -> None:
|
|
assert context.clone_error is not None, (
|
|
"Expected a ValueError but no error was raised"
|
|
)
|
|
assert isinstance(context.clone_error, ValueError), (
|
|
f"Expected ValueError, got {type(context.clone_error).__name__}"
|
|
)
|
|
assert field.lower() in str(context.clone_error).lower(), (
|
|
f"Expected '{field}' in error message, got: {context.clone_error!r}"
|
|
)
|
|
|
|
|
|
@then("the clone should succeed")
|
|
def step_clone_should_succeed(context: Context) -> None:
|
|
assert context.clone_error is None, (
|
|
f"Expected clone to succeed but got error: {context.clone_error!r}"
|
|
)
|
|
assert context.clone_result is not None, "Expected a clone result but got None"
|
|
|
|
|
|
@then('the clone target should be "{expected_target}"')
|
|
def step_clone_target(context: Context, expected_target: str) -> None:
|
|
assert context.clone_result == expected_target, (
|
|
f"Expected clone target '{expected_target}', got '{context.clone_result}'"
|
|
)
|
|
|
|
|
|
@then("the clone should raise CloneIntoError")
|
|
def step_clone_should_raise(context: Context) -> None:
|
|
assert context.clone_error is not None, (
|
|
"Expected CloneIntoError but no error was raised"
|
|
)
|
|
assert isinstance(context.clone_error, CloneIntoError), (
|
|
f"Expected CloneIntoError, got {type(context.clone_error).__name__}"
|
|
)
|
|
|
|
|
|
@then('the CloneIntoError should mention "{text}"')
|
|
def step_clone_error_mentions(context: Context, text: str) -> None:
|
|
error_str = str(context.clone_error)
|
|
assert text.lower() in error_str.lower(), (
|
|
f"Expected '{text}' in CloneIntoError message, got: {error_str!r}"
|
|
)
|
|
|
|
|
|
@then('the CloneIntoError repo_url should be "{expected}"')
|
|
def step_clone_error_repo_url(context: Context, expected: str) -> None:
|
|
assert context.clone_into_error.repo_url == expected, (
|
|
f"Expected repo_url '{expected}', got '{context.clone_into_error.repo_url}'"
|
|
)
|
|
|
|
|
|
@then('the CloneIntoError container_id should be "{expected}"')
|
|
def step_clone_error_container_id(context: Context, expected: str) -> None:
|
|
assert context.clone_into_error.container_id == expected, (
|
|
f"Expected container_id '{expected}', "
|
|
f"got '{context.clone_into_error.container_id}'"
|
|
)
|
|
|
|
|
|
@then('the CloneIntoError stderr should be "{expected}"')
|
|
def step_clone_error_stderr(context: Context, expected: str) -> None:
|
|
assert context.clone_into_error.stderr == expected, (
|
|
f"Expected stderr '{expected}', got '{context.clone_into_error.stderr}'"
|
|
)
|
|
|
|
|
|
@then('the spec should have a CLI argument named "{arg_name}"')
|
|
def step_spec_has_cli_arg(context: Context, arg_name: str) -> None:
|
|
spec = context.resource_type_spec
|
|
assert spec is not None, "No resource type spec found"
|
|
cli_args: list[dict[str, Any]] = spec.get("cli_args", [])
|
|
arg_names = [arg.get("name") for arg in cli_args]
|
|
assert arg_name in arg_names, f"Expected CLI argument '{arg_name}' in {arg_names}"
|
|
|
|
|
|
@then('the clone-into argument should be of type "{expected_type}"')
|
|
def step_clone_into_arg_type(context: Context, expected_type: str) -> None:
|
|
spec = context.resource_type_spec
|
|
assert spec is not None, "No resource type spec found"
|
|
cli_args: list[dict[str, Any]] = spec.get("cli_args", [])
|
|
clone_into_arg = next(
|
|
(arg for arg in cli_args if arg.get("name") == "clone-into"), None
|
|
)
|
|
assert clone_into_arg is not None, "clone-into argument not found"
|
|
assert clone_into_arg.get("type") == expected_type, (
|
|
f"Expected type '{expected_type}', got '{clone_into_arg.get('type')}'"
|
|
)
|
|
|
|
|
|
@then("the clone-into argument should not be required")
|
|
def step_clone_into_not_required(context: Context) -> None:
|
|
spec = context.resource_type_spec
|
|
assert spec is not None, "No resource type spec found"
|
|
cli_args: list[dict[str, Any]] = spec.get("cli_args", [])
|
|
clone_into_arg = next(
|
|
(arg for arg in cli_args if arg.get("name") == "clone-into"), None
|
|
)
|
|
assert clone_into_arg is not None, "clone-into argument not found"
|
|
assert clone_into_arg.get("required") is False, (
|
|
f"Expected clone-into to be optional (required=False), "
|
|
f"got required={clone_into_arg.get('required')}"
|
|
)
|