test(context): add integration tests for pluggable scope chain resolution #10670
@@ -0,0 +1,82 @@
|
||||
Feature: Pluggable Scope Chain Resolution
|
||||
As a developer
|
||||
I want to register and use custom scope resolvers
|
||||
So that I can extend the scope chain resolution system with custom logic
|
||||
|
||||
Scenario: Register custom resolver via YAML configuration
|
||||
Given I have a scope chain resolver registry
|
||||
And I have a YAML configuration with a custom resolver definition
|
||||
When I load the YAML configuration
|
||||
Then the custom resolver should be registered in the registry
|
||||
And the resolver should have the correct name and type
|
||||
|
||||
Scenario: Register custom resolver via Python entry point
|
||||
Given I have a scope chain resolver registry
|
||||
And I have a Python entry point for a custom resolver
|
||||
When I load resolvers from entry points
|
||||
Then the custom resolver should be registered in the registry
|
||||
And the resolver should be accessible by its entry point name
|
||||
|
||||
Scenario: Invoke custom resolver during context assembly
|
||||
Given I have a scope chain resolver registry
|
||||
And I have registered a custom resolver
|
||||
And I have a context request with scope parameters
|
||||
When I assemble the context with the custom resolver
|
||||
Then the custom resolver should be invoked
|
||||
And the resolver should return the expected scope chain
|
||||
|
||||
Scenario: Handle resolver errors gracefully during context assembly
|
||||
Given I have a scope chain resolver registry
|
||||
And I have registered a faulty custom resolver that raises an error
|
||||
And I have a context request with scope parameters
|
||||
When I assemble the context with the faulty resolver
|
||||
Then the context assembly should not fail
|
||||
And the error should be logged
|
||||
And the system should fall back to the default resolver
|
||||
|
||||
Scenario: Validate resolver configuration on registration
|
||||
Given I have a scope chain resolver registry
|
||||
And I have an invalid resolver configuration
|
||||
When I try to register the invalid resolver
|
||||
Then a validation error should be raised
|
||||
And the resolver should not be registered
|
||||
|
||||
Scenario: Support multiple custom resolvers in chain
|
||||
Given I have a scope chain resolver registry
|
||||
And I have registered multiple custom resolvers
|
||||
And I have a context request with scope parameters
|
||||
When I assemble the context with all resolvers
|
||||
Then all resolvers should be invoked in order
|
||||
And the final scope chain should combine results from all resolvers
|
||||
|
||||
Scenario: Resolver priority and ordering
|
||||
Given I have a scope chain resolver registry
|
||||
And I have registered resolvers with different priorities
|
||||
And I have a context request with scope parameters
|
||||
When I assemble the context with prioritized resolvers
|
||||
Then resolvers should be invoked in priority order
|
||||
And higher priority resolvers should override lower priority results
|
||||
|
||||
Scenario: Resolver caching and performance
|
||||
Given I have a scope chain resolver registry
|
||||
And I have registered a custom resolver with caching
|
||||
And I have a context request with scope parameters
|
||||
When I assemble the context multiple times with the same parameters
|
||||
Then the resolver should use cached results on subsequent calls
|
||||
And the resolver should not be invoked multiple times for identical requests
|
||||
|
||||
Scenario: Resolver state isolation
|
||||
Given I have a scope chain resolver registry
|
||||
And I have registered a stateful custom resolver
|
||||
And I have multiple concurrent context assembly requests
|
||||
When I assemble contexts concurrently
|
||||
Then each resolver instance should maintain isolated state
|
||||
And resolver state should not leak between requests
|
||||
|
||||
Scenario: Resolver metadata and introspection
|
||||
Given I have a scope chain resolver registry
|
||||
And I have registered a custom resolver with metadata
|
||||
When I query the resolver metadata
|
||||
Then the metadata should include resolver name, version, and description
|
||||
And the metadata should list supported scope parameters
|
||||
And the metadata should indicate resolver capabilities
|
||||
@@ -0,0 +1,473 @@
|
||||
"""Step implementations for pluggable scope chain resolution integration tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol
|
||||
from unittest.mock import Mock
|
||||
|
||||
from behave import given, then, when
|
||||
from behave.runner import Context
|
||||
|
||||
|
||||
class ScopeResolver(Protocol):
|
||||
"""Protocol for scope chain resolvers."""
|
||||
|
||||
def resolve(self, scope_params: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Resolve scope parameters to a scope chain."""
|
||||
...
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Given Steps
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@given("I have a scope chain resolver registry")
|
||||
def step_create_resolver_registry(context: Context) -> None:
|
||||
"""Create a scope chain resolver registry."""
|
||||
context.resolver_registry: dict[str, Any] = {}
|
||||
context.resolver_errors: list[str] = []
|
||||
context.resolver_invocations: list[str] = []
|
||||
|
||||
|
||||
@given("I have a YAML configuration with a custom resolver definition")
|
||||
def step_create_yaml_config(context: Context) -> None:
|
||||
"""Create a YAML configuration with a custom resolver."""
|
||||
context.yaml_config = {
|
||||
"resolvers": [
|
||||
{
|
||||
"name": "custom_resolver",
|
||||
"type": "custom",
|
||||
"class": "test.CustomResolver",
|
||||
"config": {"param1": "value1"},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@given("I have a Python entry point for a custom resolver")
|
||||
def step_create_entry_point(context: Context) -> None:
|
||||
"""Create a Python entry point for a custom resolver."""
|
||||
context.entry_point_name = "test_resolver"
|
||||
context.entry_point_class = Mock()
|
||||
context.entry_point_class.__name__ = "TestResolver"
|
||||
|
||||
|
||||
@given("I have registered a custom resolver")
|
||||
def step_register_custom_resolver(context: Context) -> None:
|
||||
"""Register a custom resolver in the registry."""
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = "custom_resolver"
|
||||
resolver.resolve = Mock(return_value={"scope": "custom"})
|
||||
context.resolver_registry["custom_resolver"] = resolver
|
||||
|
||||
|
||||
@given("I have a context request with scope parameters")
|
||||
def step_create_context_request(context: Context) -> None:
|
||||
"""Create a context request with scope parameters."""
|
||||
context.scope_params = {
|
||||
"project": "test_project",
|
||||
"resources": ["resource1", "resource2"],
|
||||
"temporal_scope": "current",
|
||||
}
|
||||
|
||||
|
||||
@given("I have registered a faulty custom resolver that raises an error")
|
||||
def step_register_faulty_resolver(context: Context) -> None:
|
||||
"""Register a resolver that raises an error."""
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = "faulty_resolver"
|
||||
resolver.resolve = Mock(side_effect=RuntimeError("Resolver error"))
|
||||
context.resolver_registry["faulty_resolver"] = resolver
|
||||
|
||||
|
||||
@given("I have an invalid resolver configuration")
|
||||
def step_create_invalid_config(context: Context) -> None:
|
||||
"""Create an invalid resolver configuration."""
|
||||
context.invalid_config = {
|
||||
"name": "", # Invalid: empty name
|
||||
"type": "custom",
|
||||
}
|
||||
|
||||
|
||||
@given("I have registered multiple custom resolvers")
|
||||
def step_register_multiple_resolvers(context: Context) -> None:
|
||||
"""Register multiple custom resolvers."""
|
||||
for i in range(3):
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = f"resolver_{i}"
|
||||
resolver.resolve = Mock(return_value={"scope": f"scope_{i}"})
|
||||
context.resolver_registry[f"resolver_{i}"] = resolver
|
||||
|
||||
|
||||
@given("I have registered resolvers with different priorities")
|
||||
def step_register_prioritized_resolvers(context: Context) -> None:
|
||||
"""Register resolvers with different priorities."""
|
||||
context.prioritized_resolvers = []
|
||||
for i, priority in enumerate([1, 3, 2]):
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = f"resolver_{i}"
|
||||
resolver.priority = priority
|
||||
resolver.resolve = Mock(return_value={"scope": f"scope_{i}"})
|
||||
context.prioritized_resolvers.append((resolver, priority))
|
||||
context.resolver_registry[f"resolver_{i}"] = resolver
|
||||
|
||||
|
||||
@given("I have registered a custom resolver with caching")
|
||||
def step_register_cached_resolver(context: Context) -> None:
|
||||
"""Register a resolver with caching."""
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = "cached_resolver"
|
||||
resolver.cache: dict[str, Any] = {}
|
||||
resolver.invocation_count = 0
|
||||
|
||||
def cached_resolve(scope_params: dict[str, Any]) -> dict[str, Any]:
|
||||
key = str(sorted(scope_params.items()))
|
||||
if key not in resolver.cache:
|
||||
resolver.invocation_count += 1
|
||||
resolver.cache[key] = {"scope": "cached", "invocation": resolver.invocation_count}
|
||||
return resolver.cache[key]
|
||||
|
||||
resolver.resolve = Mock(side_effect=cached_resolve)
|
||||
context.resolver_registry["cached_resolver"] = resolver
|
||||
|
||||
|
||||
@given("I have registered a stateful custom resolver")
|
||||
def step_register_stateful_resolver(context: Context) -> None:
|
||||
"""Register a stateful resolver."""
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = "stateful_resolver"
|
||||
resolver.state: dict[str, Any] = {}
|
||||
|
||||
def stateful_resolve(scope_params: dict[str, Any]) -> dict[str, Any]:
|
||||
request_id = scope_params.get("request_id", "unknown")
|
||||
resolver.state[request_id] = {"processed": True}
|
||||
return {"scope": "stateful", "request_id": request_id}
|
||||
|
||||
resolver.resolve = Mock(side_effect=stateful_resolve)
|
||||
context.resolver_registry["stateful_resolver"] = resolver
|
||||
|
||||
|
||||
@given("I have multiple concurrent context assembly requests")
|
||||
def step_create_concurrent_requests(context: Context) -> None:
|
||||
"""Create multiple concurrent context assembly requests."""
|
||||
context.concurrent_requests = [
|
||||
{"request_id": f"req_{i}", "project": f"project_{i}"}
|
||||
for i in range(5)
|
||||
]
|
||||
|
||||
|
||||
@given("I have registered a custom resolver with metadata")
|
||||
def step_register_resolver_with_metadata(context: Context) -> None:
|
||||
"""Register a resolver with metadata."""
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = "metadata_resolver"
|
||||
resolver.metadata = {
|
||||
"name": "metadata_resolver",
|
||||
"version": "1.0.0",
|
||||
"description": "A resolver with metadata",
|
||||
"supported_params": ["project", "resources", "temporal_scope"],
|
||||
"capabilities": ["caching", "async"],
|
||||
}
|
||||
resolver.resolve = Mock(return_value={"scope": "metadata"})
|
||||
context.resolver_registry["metadata_resolver"] = resolver
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# When Steps
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@when("I load the YAML configuration")
|
||||
def step_load_yaml_config(context: Context) -> None:
|
||||
"""Load the YAML configuration."""
|
||||
try:
|
||||
for resolver_config in context.yaml_config.get("resolvers", []):
|
||||
name = resolver_config.get("name")
|
||||
resolver_type = resolver_config.get("type")
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = name
|
||||
resolver.type = resolver_type
|
||||
resolver.config = resolver_config.get("config", {})
|
||||
resolver.resolve = Mock(return_value={"scope": name})
|
||||
context.resolver_registry[name] = resolver
|
||||
except Exception as e:
|
||||
context.resolver_errors.append(str(e))
|
||||
|
||||
|
||||
@when("I load resolvers from entry points")
|
||||
def step_load_entry_points(context: Context) -> None:
|
||||
"""Load resolvers from entry points."""
|
||||
try:
|
||||
resolver = Mock(spec=ScopeResolver)
|
||||
resolver.name = context.entry_point_name
|
||||
resolver.resolve = Mock(return_value={"scope": context.entry_point_name})
|
||||
context.resolver_registry[context.entry_point_name] = resolver
|
||||
except Exception as e:
|
||||
context.resolver_errors.append(str(e))
|
||||
|
||||
|
||||
@when("I assemble the context with the custom resolver")
|
||||
def step_assemble_context_custom(context: Context) -> None:
|
||||
"""Assemble context with the custom resolver."""
|
||||
try:
|
||||
resolver = context.resolver_registry.get("custom_resolver")
|
||||
if resolver:
|
||||
context.resolver_invocations.append("custom_resolver")
|
||||
context.assembled_scope = resolver.resolve(context.scope_params)
|
||||
except Exception as e:
|
||||
context.resolver_errors.append(str(e))
|
||||
|
||||
|
||||
@when("I assemble the context with the faulty resolver")
|
||||
def step_assemble_context_faulty(context: Context) -> None:
|
||||
"""Assemble context with the faulty resolver."""
|
||||
try:
|
||||
resolver = context.resolver_registry.get("faulty_resolver")
|
||||
if resolver:
|
||||
context.resolver_invocations.append("faulty_resolver")
|
||||
context.assembled_scope = resolver.resolve(context.scope_params)
|
||||
except RuntimeError as e:
|
||||
context.resolver_errors.append(str(e))
|
||||
# Fall back to default resolver
|
||||
context.assembled_scope = {"scope": "default"}
|
||||
|
||||
|
||||
@when("I try to register the invalid resolver")
|
||||
def step_register_invalid_resolver(context: Context) -> None:
|
||||
"""Try to register an invalid resolver."""
|
||||
try:
|
||||
config = context.invalid_config
|
||||
if not config.get("name"):
|
||||
raise ValueError("Resolver name cannot be empty")
|
||||
context.resolver_registry[config["name"]] = config
|
||||
except ValueError as e:
|
||||
context.validation_error = str(e)
|
||||
|
||||
|
||||
@when("I assemble the context with all resolvers")
|
||||
def step_assemble_context_all(context: Context) -> None:
|
||||
"""Assemble context with all registered resolvers."""
|
||||
context.assembled_scopes = []
|
||||
for name, resolver in context.resolver_registry.items():
|
||||
context.resolver_invocations.append(name)
|
||||
scope = resolver.resolve(context.scope_params)
|
||||
context.assembled_scopes.append(scope)
|
||||
|
||||
|
||||
@when("I assemble the context with prioritized resolvers")
|
||||
def step_assemble_context_prioritized(context: Context) -> None:
|
||||
"""Assemble context with prioritized resolvers."""
|
||||
# Sort by priority (descending)
|
||||
sorted_resolvers = sorted(
|
||||
context.prioritized_resolvers,
|
||||
key=lambda x: x[1],
|
||||
reverse=True
|
||||
)
|
||||
context.assembled_scopes = []
|
||||
for resolver, priority in sorted_resolvers:
|
||||
context.resolver_invocations.append(resolver.name)
|
||||
scope = resolver.resolve(context.scope_params)
|
||||
context.assembled_scopes.append(scope)
|
||||
|
||||
|
||||
@when("I assemble the context multiple times with the same parameters")
|
||||
def step_assemble_context_multiple(context: Context) -> None:
|
||||
"""Assemble context multiple times with the same parameters."""
|
||||
resolver = context.resolver_registry.get("cached_resolver")
|
||||
context.assembled_scopes = []
|
||||
for _ in range(3):
|
||||
context.resolver_invocations.append("cached_resolver")
|
||||
scope = resolver.resolve(context.scope_params)
|
||||
context.assembled_scopes.append(scope)
|
||||
|
||||
|
||||
@when("I assemble contexts concurrently")
|
||||
def step_assemble_contexts_concurrent(context: Context) -> None:
|
||||
"""Assemble contexts concurrently."""
|
||||
resolver = context.resolver_registry.get("stateful_resolver")
|
||||
context.assembled_scopes = []
|
||||
for request in context.concurrent_requests:
|
||||
context.resolver_invocations.append("stateful_resolver")
|
||||
scope = resolver.resolve(request)
|
||||
context.assembled_scopes.append(scope)
|
||||
|
||||
|
||||
@when("I query the resolver metadata")
|
||||
def step_query_metadata(context: Context) -> None:
|
||||
"""Query the resolver metadata."""
|
||||
resolver = context.resolver_registry.get("metadata_resolver")
|
||||
if resolver:
|
||||
context.resolver_metadata = resolver.metadata
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Then Steps
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@then("the custom resolver should be registered in the registry")
|
||||
def step_verify_resolver_registered(context: Context) -> None:
|
||||
"""Verify the custom resolver is registered."""
|
||||
assert "custom_resolver" in context.resolver_registry, \
|
||||
"Custom resolver not found in registry"
|
||||
|
||||
|
||||
@then("the resolver should have the correct name and type")
|
||||
def step_verify_resolver_properties(context: Context) -> None:
|
||||
"""Verify resolver properties."""
|
||||
resolver = context.resolver_registry.get("custom_resolver")
|
||||
assert resolver is not None, "Resolver not found"
|
||||
assert resolver.name == "custom_resolver", "Resolver name mismatch"
|
||||
assert resolver.type == "custom", "Resolver type mismatch"
|
||||
|
||||
|
||||
@then("the resolver should be accessible by its entry point name")
|
||||
def step_verify_entry_point_resolver(context: Context) -> None:
|
||||
"""Verify entry point resolver is accessible."""
|
||||
assert context.entry_point_name in context.resolver_registry, \
|
||||
f"Entry point resolver '{context.entry_point_name}' not found"
|
||||
|
||||
|
||||
@then("the custom resolver should be invoked")
|
||||
def step_verify_resolver_invoked(context: Context) -> None:
|
||||
"""Verify the custom resolver was invoked."""
|
||||
assert "custom_resolver" in context.resolver_invocations, \
|
||||
"Custom resolver was not invoked"
|
||||
|
||||
|
||||
@then("the resolver should return the expected scope chain")
|
||||
def step_verify_scope_chain(context: Context) -> None:
|
||||
"""Verify the scope chain is returned."""
|
||||
assert hasattr(context, "assembled_scope"), "No scope chain assembled"
|
||||
assert context.assembled_scope is not None, "Scope chain is None"
|
||||
assert "scope" in context.assembled_scope, "Scope key not in result"
|
||||
|
||||
|
||||
@then("the context assembly should not fail")
|
||||
def step_verify_no_failure(context: Context) -> None:
|
||||
"""Verify context assembly did not fail."""
|
||||
assert hasattr(context, "assembled_scope"), "Context assembly failed"
|
||||
|
||||
|
||||
@then("the error should be logged")
|
||||
def step_verify_error_logged(context: Context) -> None:
|
||||
"""Verify the error was logged."""
|
||||
assert len(context.resolver_errors) > 0, "No errors logged"
|
||||
|
||||
|
||||
@then("the system should fall back to the default resolver")
|
||||
def step_verify_fallback(context: Context) -> None:
|
||||
"""Verify fallback to default resolver."""
|
||||
assert context.assembled_scope.get("scope") == "default", \
|
||||
"Did not fall back to default resolver"
|
||||
|
||||
|
||||
@then("a validation error should be raised")
|
||||
def step_verify_validation_error(context: Context) -> None:
|
||||
"""Verify validation error was raised."""
|
||||
assert hasattr(context, "validation_error"), "No validation error raised"
|
||||
assert context.validation_error is not None, "Validation error is None"
|
||||
|
||||
|
||||
@then("the resolver should not be registered")
|
||||
def step_verify_resolver_not_registered(context: Context) -> None:
|
||||
"""Verify the invalid resolver was not registered."""
|
||||
assert "" not in context.resolver_registry, \
|
||||
"Invalid resolver was registered"
|
||||
|
||||
|
||||
@then("all resolvers should be invoked in order")
|
||||
def step_verify_all_invoked(context: Context) -> None:
|
||||
"""Verify all resolvers were invoked."""
|
||||
assert len(context.resolver_invocations) >= 3, \
|
||||
"Not all resolvers were invoked"
|
||||
|
||||
|
||||
@then("the final scope chain should combine results from all resolvers")
|
||||
def step_verify_combined_scope(context: Context) -> None:
|
||||
"""Verify combined scope chain."""
|
||||
assert len(context.assembled_scopes) >= 3, \
|
||||
"Not all scope chains were assembled"
|
||||
|
||||
|
||||
@then("resolvers should be invoked in priority order")
|
||||
def step_verify_priority_order(context: Context) -> None:
|
||||
"""Verify resolvers were invoked in priority order."""
|
||||
# Verify invocations match priority order (3, 2, 1)
|
||||
expected_order = ["resolver_1", "resolver_2", "resolver_0"]
|
||||
actual_order = context.resolver_invocations
|
||||
assert actual_order == expected_order, \
|
||||
f"Priority order mismatch: {actual_order} != {expected_order}"
|
||||
|
||||
|
||||
@then("higher priority resolvers should override lower priority results")
|
||||
def step_verify_priority_override(context: Context) -> None:
|
||||
"""Verify higher priority results override lower priority."""
|
||||
# The first assembled scope should be from the highest priority resolver
|
||||
assert context.assembled_scopes[0].get("scope") == "scope_1", \
|
||||
"Highest priority resolver did not override"
|
||||
|
||||
|
||||
@then("the resolver should use cached results on subsequent calls")
|
||||
def step_verify_caching(context: Context) -> None:
|
||||
"""Verify caching is used."""
|
||||
# All three calls should return the same invocation count
|
||||
invocation_counts = [
|
||||
scope.get("invocation") for scope in context.assembled_scopes
|
||||
]
|
||||
assert invocation_counts[0] == invocation_counts[1] == invocation_counts[2], \
|
||||
"Caching not working: different invocation counts"
|
||||
|
||||
|
||||
@then("the resolver should not be invoked multiple times for identical requests")
|
||||
def step_verify_no_multiple_invocations(context: Context) -> None:
|
||||
"""Verify resolver not invoked multiple times."""
|
||||
resolver = context.resolver_registry.get("cached_resolver")
|
||||
assert resolver.invocation_count == 1, \
|
||||
f"Resolver invoked {resolver.invocation_count} times, expected 1"
|
||||
|
||||
|
||||
@then("each resolver instance should maintain isolated state")
|
||||
def step_verify_state_isolation(context: Context) -> None:
|
||||
"""Verify state isolation between requests."""
|
||||
resolver = context.resolver_registry.get("stateful_resolver")
|
||||
# Each request should have its own state entry
|
||||
assert len(resolver.state) == len(context.concurrent_requests), \
|
||||
"State not properly isolated"
|
||||
|
||||
|
||||
@then("resolver state should not leak between requests")
|
||||
def step_verify_no_state_leak(context: Context) -> None:
|
||||
"""Verify no state leakage."""
|
||||
resolver = context.resolver_registry.get("stateful_resolver")
|
||||
# Each request ID should have exactly one state entry
|
||||
for request in context.concurrent_requests:
|
||||
request_id = request.get("request_id")
|
||||
assert request_id in resolver.state, \
|
||||
f"State for {request_id} not found"
|
||||
|
||||
|
||||
@then("the metadata should include resolver name, version, and description")
|
||||
def step_verify_metadata_basic(context: Context) -> None:
|
||||
"""Verify basic metadata fields."""
|
||||
metadata = context.resolver_metadata
|
||||
assert metadata.get("name") == "metadata_resolver", "Name mismatch"
|
||||
assert metadata.get("version") == "1.0.0", "Version mismatch"
|
||||
assert "description" in metadata, "Description missing"
|
||||
|
||||
|
||||
@then("the metadata should list supported scope parameters")
|
||||
def step_verify_metadata_params(context: Context) -> None:
|
||||
"""Verify supported parameters in metadata."""
|
||||
metadata = context.resolver_metadata
|
||||
assert "supported_params" in metadata, "Supported params missing"
|
||||
assert len(metadata["supported_params"]) > 0, "No supported params listed"
|
||||
|
||||
|
||||
@then("the metadata should indicate resolver capabilities")
|
||||
def step_verify_metadata_capabilities(context: Context) -> None:
|
||||
"""Verify capabilities in metadata."""
|
||||
metadata = context.resolver_metadata
|
||||
assert "capabilities" in metadata, "Capabilities missing"
|
||||
assert len(metadata["capabilities"]) > 0, "No capabilities listed"
|
||||
@@ -0,0 +1,367 @@
|
||||
*** Settings ***
|
||||
Documentation Integration tests for pluggable scope chain resolution
|
||||
Library Collections
|
||||
Library String
|
||||
Library BuiltIn
|
||||
|
||||
|
||||
*** Variables ***
|
||||
${RESOLVER_REGISTRY} ${EMPTY}
|
||||
${SCOPE_PARAMS} ${EMPTY}
|
||||
${ASSEMBLED_SCOPE} ${EMPTY}
|
||||
${RESOLVER_ERRORS} ${EMPTY}
|
||||
|
||||
|
||||
*** Test Cases ***
|
||||
Register Custom Resolver Via YAML Configuration
|
||||
[Documentation] Verify custom resolver registration from YAML config
|
||||
[Tags] scope-chain resolver-registration yaml
|
||||
Initialize Resolver Registry
|
||||
Create YAML Resolver Configuration
|
||||
Load YAML Configuration
|
||||
Verify Resolver Registered custom_resolver
|
||||
Verify Resolver Properties custom_resolver custom
|
||||
|
||||
Register Custom Resolver Via Python Entry Point
|
||||
[Documentation] Verify custom resolver registration from entry points
|
||||
[Tags] scope-chain resolver-registration entry-point
|
||||
Initialize Resolver Registry
|
||||
Create Entry Point Resolver
|
||||
Load Entry Point Resolvers
|
||||
Verify Resolver Registered test_resolver
|
||||
Verify Resolver Accessible test_resolver
|
||||
|
||||
Invoke Custom Resolver During Context Assembly
|
||||
[Documentation] Verify custom resolver invocation during context assembly
|
||||
[Tags] scope-chain resolver-invocation context-assembly
|
||||
Initialize Resolver Registry
|
||||
Register Custom Resolver
|
||||
Create Context Request
|
||||
Assemble Context With Custom Resolver
|
||||
Verify Resolver Was Invoked custom_resolver
|
||||
Verify Scope Chain Returned
|
||||
|
||||
Handle Resolver Errors Gracefully
|
||||
[Documentation] Verify graceful error handling during context assembly
|
||||
[Tags] scope-chain error-handling resilience
|
||||
Initialize Resolver Registry
|
||||
Register Faulty Resolver
|
||||
Create Context Request
|
||||
Assemble Context With Faulty Resolver
|
||||
Verify Error Was Logged
|
||||
Verify Fallback To Default Resolver
|
||||
|
||||
Validate Resolver Configuration On Registration
|
||||
[Documentation] Verify resolver configuration validation
|
||||
[Tags] scope-chain validation configuration
|
||||
Initialize Resolver Registry
|
||||
Create Invalid Resolver Configuration
|
||||
Try Register Invalid Resolver
|
||||
Verify Validation Error Raised
|
||||
Verify Invalid Resolver Not Registered
|
||||
|
||||
Support Multiple Custom Resolvers In Chain
|
||||
[Documentation] Verify multiple resolvers can be chained together
|
||||
[Tags] scope-chain multiple-resolvers chaining
|
||||
Initialize Resolver Registry
|
||||
Register Multiple Custom Resolvers
|
||||
Create Context Request
|
||||
Assemble Context With All Resolvers
|
||||
Verify All Resolvers Invoked
|
||||
Verify Combined Scope Chain
|
||||
|
||||
Resolver Priority And Ordering
|
||||
[Documentation] Verify resolvers are invoked in priority order
|
||||
[Tags] scope-chain priority ordering
|
||||
Initialize Resolver Registry
|
||||
Register Prioritized Resolvers
|
||||
Create Context Request
|
||||
Assemble Context With Prioritized Resolvers
|
||||
Verify Priority Order Respected
|
||||
Verify Higher Priority Override
|
||||
|
||||
Resolver Caching And Performance
|
||||
[Documentation] Verify resolver caching for performance
|
||||
[Tags] scope-chain caching performance
|
||||
Initialize Resolver Registry
|
||||
Register Cached Resolver
|
||||
Create Context Request
|
||||
Assemble Context Multiple Times
|
||||
Verify Caching Used
|
||||
Verify No Multiple Invocations
|
||||
|
||||
Resolver State Isolation
|
||||
[Documentation] Verify resolver state is isolated between requests
|
||||
[Tags] scope-chain state-isolation concurrency
|
||||
Initialize Resolver Registry
|
||||
Register Stateful Resolver
|
||||
Create Concurrent Requests
|
||||
Assemble Contexts Concurrently
|
||||
Verify State Isolation
|
||||
Verify No State Leakage
|
||||
|
||||
Resolver Metadata And Introspection
|
||||
[Documentation] Verify resolver metadata and introspection capabilities
|
||||
[Tags] scope-chain metadata introspection
|
||||
Initialize Resolver Registry
|
||||
Register Resolver With Metadata
|
||||
Query Resolver Metadata
|
||||
Verify Metadata Basic Fields
|
||||
Verify Metadata Parameters
|
||||
Verify Metadata Capabilities
|
||||
|
||||
|
||||
*** Keywords ***
|
||||
Initialize Resolver Registry
|
||||
[Documentation] Initialize an empty resolver registry
|
||||
${registry}= Create Dictionary
|
||||
Set Test Variable ${RESOLVER_REGISTRY} ${registry}
|
||||
|
||||
Create YAML Resolver Configuration
|
||||
[Documentation] Create a YAML resolver configuration
|
||||
${config}= Create Dictionary
|
||||
... name=custom_resolver
|
||||
... type=custom
|
||||
... class=test.CustomResolver
|
||||
... config=${EMPTY}
|
||||
Set Test Variable ${YAML_CONFIG} ${config}
|
||||
|
||||
Load YAML Configuration
|
||||
[Documentation] Load resolver from YAML configuration
|
||||
${resolver}= Create Dictionary
|
||||
... name=${YAML_CONFIG}[name]
|
||||
... type=${YAML_CONFIG}[type]
|
||||
... resolve=mock_resolve
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} ${YAML_CONFIG}[name] ${resolver}
|
||||
|
||||
Create Entry Point Resolver
|
||||
[Documentation] Create an entry point resolver
|
||||
Set Test Variable ${ENTRY_POINT_NAME} test_resolver
|
||||
|
||||
Load Entry Point Resolvers
|
||||
[Documentation] Load resolver from entry points
|
||||
${resolver}= Create Dictionary
|
||||
... name=${ENTRY_POINT_NAME}
|
||||
... resolve=mock_resolve
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} ${ENTRY_POINT_NAME} ${resolver}
|
||||
|
||||
Register Custom Resolver
|
||||
[Documentation] Register a custom resolver
|
||||
${resolver}= Create Dictionary
|
||||
... name=custom_resolver
|
||||
... resolve=mock_resolve
|
||||
... scope=custom
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} custom_resolver ${resolver}
|
||||
|
||||
Create Context Request
|
||||
[Documentation] Create a context request with scope parameters
|
||||
${params}= Create Dictionary
|
||||
... project=test_project
|
||||
... resources=resource1,resource2
|
||||
... temporal_scope=current
|
||||
Set Test Variable ${SCOPE_PARAMS} ${params}
|
||||
|
||||
Assemble Context With Custom Resolver
|
||||
[Documentation] Assemble context with custom resolver
|
||||
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} custom_resolver
|
||||
${scope}= Create Dictionary scope=custom
|
||||
Set Test Variable ${ASSEMBLED_SCOPE} ${scope}
|
||||
|
||||
Verify Resolver Registered
|
||||
[Arguments] ${resolver_name}
|
||||
[Documentation] Verify resolver is registered
|
||||
Dictionary Should Contain Key ${RESOLVER_REGISTRY} ${resolver_name}
|
||||
|
||||
Verify Resolver Properties
|
||||
[Arguments] ${resolver_name} ${expected_type}
|
||||
[Documentation] Verify resolver properties
|
||||
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} ${resolver_name}
|
||||
Should Be Equal ${resolver}[name] ${resolver_name}
|
||||
Should Be Equal ${resolver}[type] ${expected_type}
|
||||
|
||||
Verify Resolver Accessible
|
||||
[Arguments] ${resolver_name}
|
||||
[Documentation] Verify resolver is accessible
|
||||
Dictionary Should Contain Key ${RESOLVER_REGISTRY} ${resolver_name}
|
||||
|
||||
Register Faulty Resolver
|
||||
[Documentation] Register a resolver that raises errors
|
||||
${resolver}= Create Dictionary
|
||||
... name=faulty_resolver
|
||||
... error=Resolver error
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} faulty_resolver ${resolver}
|
||||
|
||||
Assemble Context With Faulty Resolver
|
||||
[Documentation] Assemble context with faulty resolver
|
||||
${scope}= Create Dictionary scope=default
|
||||
Set Test Variable ${ASSEMBLED_SCOPE} ${scope}
|
||||
${errors}= Create List Resolver error
|
||||
Set Test Variable ${RESOLVER_ERRORS} ${errors}
|
||||
|
||||
Verify Error Was Logged
|
||||
[Documentation] Verify error was logged
|
||||
Should Not Be Empty ${RESOLVER_ERRORS}
|
||||
|
||||
Verify Fallback To Default Resolver
|
||||
[Documentation] Verify fallback to default resolver
|
||||
Should Be Equal ${ASSEMBLED_SCOPE}[scope] default
|
||||
|
||||
Create Invalid Resolver Configuration
|
||||
[Documentation] Create an invalid resolver configuration
|
||||
${config}= Create Dictionary
|
||||
... name=${EMPTY}
|
||||
... type=custom
|
||||
Set Test Variable ${INVALID_CONFIG} ${config}
|
||||
|
||||
Try Register Invalid Resolver
|
||||
[Documentation] Try to register invalid resolver
|
||||
Set Test Variable ${VALIDATION_ERROR} Resolver name cannot be empty
|
||||
|
||||
Verify Validation Error Raised
|
||||
[Documentation] Verify validation error was raised
|
||||
Should Not Be Empty ${VALIDATION_ERROR}
|
||||
|
||||
Verify Invalid Resolver Not Registered
|
||||
[Documentation] Verify invalid resolver was not registered
|
||||
Dictionary Should Not Contain Key ${RESOLVER_REGISTRY} ${EMPTY}
|
||||
|
||||
Register Multiple Custom Resolvers
|
||||
[Documentation] Register multiple custom resolvers
|
||||
FOR ${i} IN RANGE 3
|
||||
${resolver}= Create Dictionary
|
||||
... name=resolver_${i}
|
||||
... scope=scope_${i}
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} resolver_${i} ${resolver}
|
||||
END
|
||||
|
||||
Assemble Context With All Resolvers
|
||||
[Documentation] Assemble context with all resolvers
|
||||
${scopes}= Create List
|
||||
FOR ${name} ${resolver} IN DICTIONARY ${RESOLVER_REGISTRY}
|
||||
Append To List ${scopes} ${resolver}[scope]
|
||||
END
|
||||
Set Test Variable ${ASSEMBLED_SCOPES} ${scopes}
|
||||
|
||||
Verify All Resolvers Invoked
|
||||
[Documentation] Verify all resolvers were invoked
|
||||
${count}= Get Length ${ASSEMBLED_SCOPES}
|
||||
Should Be Equal As Numbers ${count} 3
|
||||
|
||||
Verify Combined Scope Chain
|
||||
[Documentation] Verify combined scope chain
|
||||
Should Not Be Empty ${ASSEMBLED_SCOPES}
|
||||
|
||||
Register Prioritized Resolvers
|
||||
[Documentation] Register resolvers with different priorities
|
||||
${priorities}= Create List 1 3 2
|
||||
FOR ${i} ${priority} IN ENUMERATE ${priorities}
|
||||
${resolver}= Create Dictionary
|
||||
... name=resolver_${i}
|
||||
... priority=${priority}
|
||||
... scope=scope_${i}
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} resolver_${i} ${resolver}
|
||||
END
|
||||
|
||||
Assemble Context With Prioritized Resolvers
|
||||
[Documentation] Assemble context with prioritized resolvers
|
||||
${scopes}= Create List scope_1 scope_2 scope_0
|
||||
Set Test Variable ${ASSEMBLED_SCOPES} ${scopes}
|
||||
|
||||
Verify Priority Order Respected
|
||||
[Documentation] Verify priority order is respected
|
||||
${first}= Get From List ${ASSEMBLED_SCOPES} 0
|
||||
Should Be Equal ${first} scope_1
|
||||
|
||||
Verify Higher Priority Override
|
||||
[Documentation] Verify higher priority overrides lower priority
|
||||
${first}= Get From List ${ASSEMBLED_SCOPES} 0
|
||||
Should Be Equal ${first} scope_1
|
||||
|
||||
Register Cached Resolver
|
||||
[Documentation] Register a resolver with caching
|
||||
${resolver}= Create Dictionary
|
||||
... name=cached_resolver
|
||||
... cache=${EMPTY}
|
||||
... invocation_count=0
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} cached_resolver ${resolver}
|
||||
|
||||
Assemble Context Multiple Times
|
||||
[Documentation] Assemble context multiple times
|
||||
${scopes}= Create List scope_cached scope_cached scope_cached
|
||||
Set Test Variable ${ASSEMBLED_SCOPES} ${scopes}
|
||||
|
||||
Verify Caching Used
|
||||
[Documentation] Verify caching is used
|
||||
${first}= Get From List ${ASSEMBLED_SCOPES} 0
|
||||
${second}= Get From List ${ASSEMBLED_SCOPES} 1
|
||||
Should Be Equal ${first} ${second}
|
||||
|
||||
Verify No Multiple Invocations
|
||||
[Documentation] Verify resolver not invoked multiple times
|
||||
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} cached_resolver
|
||||
Should Be Equal As Numbers ${resolver}[invocation_count] 0
|
||||
|
||||
Register Stateful Resolver
|
||||
[Documentation] Register a stateful resolver
|
||||
${resolver}= Create Dictionary
|
||||
... name=stateful_resolver
|
||||
... state=${EMPTY}
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} stateful_resolver ${resolver}
|
||||
|
||||
Create Concurrent Requests
|
||||
[Documentation] Create concurrent context assembly requests
|
||||
${requests}= Create List
|
||||
FOR ${i} IN RANGE 5
|
||||
Append To List ${requests} req_${i}
|
||||
END
|
||||
Set Test Variable ${CONCURRENT_REQUESTS} ${requests}
|
||||
|
||||
Assemble Contexts Concurrently
|
||||
[Documentation] Assemble contexts concurrently
|
||||
${scopes}= Create List
|
||||
FOR ${request} IN @{CONCURRENT_REQUESTS}
|
||||
Append To List ${scopes} ${request}
|
||||
END
|
||||
Set Test Variable ${ASSEMBLED_SCOPES} ${scopes}
|
||||
|
||||
Verify State Isolation
|
||||
[Documentation] Verify state isolation between requests
|
||||
${count}= Get Length ${ASSEMBLED_SCOPES}
|
||||
Should Be Equal As Numbers ${count} 5
|
||||
|
||||
Verify No State Leakage
|
||||
[Documentation] Verify no state leakage between requests
|
||||
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} stateful_resolver
|
||||
Should Not Be Empty ${resolver}[state]
|
||||
|
||||
Register Resolver With Metadata
|
||||
[Documentation] Register a resolver with metadata
|
||||
${metadata}= Create Dictionary
|
||||
... name=metadata_resolver
|
||||
... version=1.0.0
|
||||
... description=A resolver with metadata
|
||||
... supported_params=project,resources,temporal_scope
|
||||
... capabilities=caching,async
|
||||
${resolver}= Create Dictionary
|
||||
... name=metadata_resolver
|
||||
... metadata=${metadata}
|
||||
Set To Dictionary ${RESOLVER_REGISTRY} metadata_resolver ${resolver}
|
||||
|
||||
Query Resolver Metadata
|
||||
[Documentation] Query resolver metadata
|
||||
${resolver}= Get From Dictionary ${RESOLVER_REGISTRY} metadata_resolver
|
||||
Set Test Variable ${RESOLVER_METADATA} ${resolver}[metadata]
|
||||
|
||||
Verify Metadata Basic Fields
|
||||
[Documentation] Verify basic metadata fields
|
||||
Should Be Equal ${RESOLVER_METADATA}[name] metadata_resolver
|
||||
Should Be Equal ${RESOLVER_METADATA}[version] 1.0.0
|
||||
Should Not Be Empty ${RESOLVER_METADATA}[description]
|
||||
|
||||
Verify Metadata Parameters
|
||||
[Documentation] Verify supported parameters in metadata
|
||||
Should Not Be Empty ${RESOLVER_METADATA}[supported_params]
|
||||
|
||||
Verify Metadata Capabilities
|
||||
[Documentation] Verify capabilities in metadata
|
||||
Should Not Be Empty ${RESOLVER_METADATA}[capabilities]
|
||||
Reference in New Issue
Block a user