fix(resource-type): require letter-start for namespace/name in _NAMESPACED_RE #3290
@@ -0,0 +1,46 @@
|
||||
Feature: Resource type name/namespace must start with a letter
|
||||
The spec requires that namespace and name components in the
|
||||
``[[server:]namespace/]name`` format start with a letter.
|
||||
The ``_NAMESPACED_RE`` pattern in both ``resource_type.py`` and
|
||||
``_resource_type_validation.py`` must enforce this constraint.
|
||||
|
||||
Background:
|
||||
Given the resource type model module is imported
|
||||
|
||||
# -- Rejection of digit-starting namespace --------------------------------
|
||||
|
||||
Scenario: Custom resource type with digit-starting namespace is rejected
|
||||
When I create a custom resource type with name "123abc/my-type"
|
||||
Then a validation error should be raised mentioning invalid resource type name
|
||||
|
||||
Scenario: Custom resource type with digit-starting name component is rejected
|
||||
When I create a custom resource type with name "local/456-type"
|
||||
Then a validation error should be raised mentioning invalid resource type name
|
||||
|
||||
Scenario: Custom resource type with digit-starting both components is rejected
|
||||
When I create a custom resource type with name "1ns/2name"
|
||||
Then a validation error should be raised mentioning invalid resource type name
|
||||
|
||||
# -- Valid namespaced names still accepted --------------------------------
|
||||
|
||||
Scenario: Custom resource type with letter-starting namespace and name is accepted
|
||||
When I create a custom resource type with name "local/my-type"
|
||||
Then no validation error should be raised
|
||||
|
||||
Scenario: Custom resource type with org-style name is accepted
|
||||
When I create a custom resource type with name "myorg/custom-db"
|
||||
Then no validation error should be raised
|
||||
|
||||
Scenario: Custom resource type with underscore in namespace is accepted
|
||||
When I create a custom resource type with name "my_org/my_type"
|
||||
Then no validation error should be raised
|
||||
|
||||
# -- Digit-starting names rejected via inherits field ---------------------
|
||||
|
||||
Scenario: inherits field with digit-starting namespace is rejected
|
||||
When I create a resource type spec with inherits set to "123abc/parent-type"
|
||||
Then a validation error should be raised mentioning invalid inherits type name
|
||||
|
||||
Scenario: inherits field with digit-starting name component is rejected
|
||||
When I create a resource type spec with inherits set to "myorg/456-parent"
|
||||
Then a validation error should be raised mentioning invalid inherits type name
|
||||
@@ -0,0 +1,92 @@
|
||||
"""Step definitions for resource_type_digit_start_validation.feature.
|
||||
|
||||
Tests that _NAMESPACED_RE in resource_type.py and _resource_type_validation.py
|
||||
correctly rejects namespace/name components that start with a digit.
|
||||
|
||||
Spec requirement: namespace and name components must start with a letter.
|
||||
|
||||
Note: The step "a validation error should be raised mentioning invalid inherits
|
||||
type name" is defined in resource_type_model_coverage_steps.py and reused here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from behave import then, when
|
||||
from pydantic import ValidationError
|
||||
|
||||
from cleveragents.domain.models.core.resource_type import (
|
||||
ResourceKind,
|
||||
ResourceTypeSpec,
|
||||
SandboxStrategy,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _make_custom_physical_spec(**overrides) -> dict:
|
||||
"""Build a minimal valid custom (non-built-in) physical ResourceTypeSpec dict."""
|
||||
defaults = dict(
|
||||
name="myorg/my-type",
|
||||
resource_kind=ResourceKind.PHYSICAL,
|
||||
sandbox_strategy=SandboxStrategy.GIT_WORKTREE,
|
||||
built_in=False,
|
||||
)
|
||||
defaults.update(overrides)
|
||||
return defaults
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WHEN steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when('I create a custom resource type with name "{name}"')
|
||||
def step_create_custom_resource_type(context, name: str) -> None:
|
||||
"""Attempt to create a custom ResourceTypeSpec with the given name."""
|
||||
try:
|
||||
ResourceTypeSpec(**_make_custom_physical_spec(name=name))
|
||||
context.rt_error = None
|
||||
except ValidationError as exc:
|
||||
context.rt_error = exc
|
||||
|
||||
|
||||
@when('I create a resource type spec with inherits set to "{inherits_name}"')
|
||||
def step_create_with_inherits_digit(context, inherits_name: str) -> None:
|
||||
"""Attempt to create a ResourceTypeSpec with the given inherits value."""
|
||||
try:
|
||||
ResourceTypeSpec(**_make_custom_physical_spec(inherits=inherits_name))
|
||||
context.rt_error = None
|
||||
except ValidationError as exc:
|
||||
context.rt_error = exc
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# THEN steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then("a validation error should be raised mentioning invalid resource type name")
|
||||
def step_verify_invalid_name_error(context) -> None:
|
||||
"""Assert a ValidationError was raised mentioning invalid resource type name."""
|
||||
assert context.rt_error is not None, (
|
||||
"Expected a ValidationError for an invalid resource type name, "
|
||||
"but no exception was raised."
|
||||
)
|
||||
assert isinstance(context.rt_error, ValidationError), (
|
||||
f"Expected ValidationError, got {type(context.rt_error).__name__}: "
|
||||
f"{context.rt_error}"
|
||||
)
|
||||
error_str = str(context.rt_error)
|
||||
assert "Invalid resource type name" in error_str, (
|
||||
f"Expected 'Invalid resource type name' in error message, got: {error_str}"
|
||||
)
|
||||
|
||||
|
||||
@then("no validation error should be raised")
|
||||
def step_verify_no_error(context) -> None:
|
||||
"""Assert no ValidationError was raised."""
|
||||
assert context.rt_error is None, (
|
||||
f"Expected no ValidationError, but got: {context.rt_error}"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ import re
|
||||
from typing import Any
|
||||
|
||||
_BUILTIN_NAME_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*$")
|
||||
_NAMESPACED_RE = re.compile(r"^[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+$")
|
||||
_NAMESPACED_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*/[a-zA-Z][a-zA-Z0-9_-]*$")
|
||||
|
||||
MAX_SCAN_DEPTH = 10
|
||||
|
||||
|
||||
@@ -44,8 +44,8 @@ from cleveragents.domain.models.core._resource_type_validation import (
|
||||
#: CLI-safe argument name: lowercase alphanumeric, hyphens, underscores.
|
||||
_CLI_ARG_NAME_RE = re.compile(r"^[a-z][a-z0-9_-]*$")
|
||||
|
||||
#: Namespaced name: ``namespace/name`` with alphanumeric, hyphens, underscores.
|
||||
_NAMESPACED_RE = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_-]*/[a-zA-Z0-9][a-zA-Z0-9_-]*$")
|
||||
#: Namespaced name: ``namespace/name`` — both components must start with a letter.
|
||||
_NAMESPACED_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*/[a-zA-Z][a-zA-Z0-9_-]*$")
|
||||
|
||||
#: Built-in (unnamespaced) name: simple alphanumeric + hyphens/underscores.
|
||||
_BUILTIN_NAME_RE = re.compile(r"^[a-zA-Z][a-zA-Z0-9_-]*$")
|
||||
|
||||
Reference in New Issue
Block a user