chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.exceptions.process_exceptions import ProcessInvalidConfigurationException
|
||||
from semantic_kernel.functions.kernel_function import KernelFunction
|
||||
from semantic_kernel.functions.kernel_function_decorator import kernel_function
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_message_channel import (
|
||||
KernelProcessMessageChannel,
|
||||
)
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_step_context import (
|
||||
KernelProcessStepContext,
|
||||
)
|
||||
from semantic_kernel.processes.step_utils import (
|
||||
find_input_channels,
|
||||
get_fully_qualified_name,
|
||||
get_step_class_from_qualified_name,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_function() -> Callable[..., Any]:
|
||||
@kernel_function
|
||||
def mock_function(input: str, context: KernelProcessStepContext) -> None:
|
||||
pass
|
||||
|
||||
return mock_function
|
||||
|
||||
|
||||
def test_find_input_channels_empty_functions():
|
||||
channel = MagicMock(spec=KernelProcessMessageChannel)
|
||||
functions = {}
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
find_input_channels(channel, functions)
|
||||
assert str(exc_info.value) == "The step has not been initialized."
|
||||
|
||||
|
||||
def test_find_input_channels_with_required_params(mock_function):
|
||||
channel = MagicMock(spec=KernelProcessMessageChannel)
|
||||
|
||||
function = KernelFunction.from_method(method=mock_function, plugin_name="test")
|
||||
|
||||
functions = {"TestFunction": function}
|
||||
|
||||
inputs = find_input_channels(channel, functions)
|
||||
|
||||
assert "TestFunction" in inputs
|
||||
assert "input" in inputs["TestFunction"]
|
||||
assert "context" in inputs["TestFunction"]
|
||||
|
||||
|
||||
@kernel_function
|
||||
def skipped_function(param1: "Kernel", param2: int = 0) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def test_find_input_channels_with_only_skipped_params():
|
||||
channel = MagicMock(spec=KernelProcessMessageChannel)
|
||||
|
||||
function = KernelFunction.from_method(method=skipped_function, plugin_name="test")
|
||||
functions = {"SkippedFunction": function}
|
||||
|
||||
inputs = find_input_channels(channel, functions)
|
||||
|
||||
expected_inputs = {"SkippedFunction": {}}
|
||||
|
||||
assert inputs == expected_inputs
|
||||
|
||||
|
||||
@kernel_function
|
||||
def function1(param1: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@kernel_function
|
||||
def function2(param2: KernelProcessStepContext, param3: float = 0.0) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def test_find_input_channels_multiple_functions():
|
||||
channel = MagicMock(spec=KernelProcessMessageChannel)
|
||||
|
||||
function1_instance = KernelFunction.from_method(method=function1, plugin_name="test")
|
||||
function2_instance = KernelFunction.from_method(method=function2, plugin_name="test")
|
||||
|
||||
functions = {
|
||||
"Function1": function1_instance,
|
||||
"Function2": function2_instance,
|
||||
}
|
||||
|
||||
inputs = find_input_channels(channel, functions)
|
||||
|
||||
assert "Function1" in inputs
|
||||
assert "param1" in inputs["Function1"]
|
||||
assert inputs["Function1"]["param1"] is None
|
||||
|
||||
assert "Function2" in inputs
|
||||
assert "param2" in inputs["Function2"]
|
||||
assert isinstance(inputs["Function2"]["param2"], KernelProcessStepContext)
|
||||
assert "param3" not in inputs["Function2"]
|
||||
|
||||
|
||||
def test_get_fully_qualified_name_standard_class():
|
||||
class TestClass:
|
||||
pass
|
||||
|
||||
result = get_fully_qualified_name(TestClass)
|
||||
expected = f"{TestClass.__module__}.TestClass"
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_get_fully_qualified_name_builtin_type():
|
||||
result = get_fully_qualified_name(int)
|
||||
expected = "builtins.int"
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_get_fully_qualified_name_third_party_class():
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
result = get_fully_qualified_name(MagicMock)
|
||||
expected = "unittest.mock.MagicMock"
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_get_fully_qualified_name_nested_class():
|
||||
class OuterClass:
|
||||
class InnerClass:
|
||||
pass
|
||||
|
||||
result = get_fully_qualified_name(OuterClass.InnerClass)
|
||||
expected = f"{OuterClass.__module__}.InnerClass"
|
||||
assert result == expected
|
||||
|
||||
|
||||
# --- Tests for get_step_class_from_qualified_name ---
|
||||
|
||||
|
||||
def test_get_step_class_empty_allowlist_blocks_all():
|
||||
"""An empty allowlist sequence should block all modules."""
|
||||
with pytest.raises(ProcessInvalidConfigurationException, match="not in the allowed module prefixes"):
|
||||
get_step_class_from_qualified_name(
|
||||
"semantic_kernel.processes.kernel_process.kernel_process_step.KernelProcessStep",
|
||||
allowed_module_prefixes=[],
|
||||
)
|
||||
|
||||
|
||||
def test_get_step_class_none_allowlist_allows_any():
|
||||
"""Passing None should disable the allowlist check entirely."""
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
|
||||
|
||||
cls = get_step_class_from_qualified_name(
|
||||
"semantic_kernel.processes.kernel_process.kernel_process_step.KernelProcessStep",
|
||||
allowed_module_prefixes=None,
|
||||
)
|
||||
assert cls is KernelProcessStep
|
||||
|
||||
|
||||
def test_get_step_class_prefix_without_dot_exact_match():
|
||||
"""A prefix without a trailing dot should match the exact module name."""
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
|
||||
|
||||
cls = get_step_class_from_qualified_name(
|
||||
"semantic_kernel.processes.kernel_process.kernel_process_step.KernelProcessStep",
|
||||
allowed_module_prefixes=["semantic_kernel.processes.kernel_process.kernel_process_step"],
|
||||
)
|
||||
assert cls is KernelProcessStep
|
||||
|
||||
|
||||
def test_get_step_class_prefix_without_dot_segment_boundary():
|
||||
"""A prefix without a trailing dot must not match partial segment names."""
|
||||
with pytest.raises(ProcessInvalidConfigurationException, match="not in the allowed module prefixes"):
|
||||
get_step_class_from_qualified_name(
|
||||
"semantic_kernel_evil.some_module.SomeClass",
|
||||
allowed_module_prefixes=["semantic_kernel"],
|
||||
)
|
||||
|
||||
|
||||
def test_get_step_class_prefix_with_dot_matches_submodule():
|
||||
"""A prefix with a trailing dot should match submodules."""
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_step import KernelProcessStep
|
||||
|
||||
cls = get_step_class_from_qualified_name(
|
||||
"semantic_kernel.processes.kernel_process.kernel_process_step.KernelProcessStep",
|
||||
allowed_module_prefixes=["semantic_kernel."],
|
||||
)
|
||||
assert cls is KernelProcessStep
|
||||
|
||||
|
||||
def test_get_step_class_default_allowlist_blocks_non_sk_module():
|
||||
"""Default allowlist should block modules outside semantic_kernel."""
|
||||
with pytest.raises(ProcessInvalidConfigurationException, match="not in the allowed module prefixes"):
|
||||
get_step_class_from_qualified_name("os.path.SomeClass")
|
||||
|
||||
|
||||
def test_get_step_class_invalid_format():
|
||||
"""Invalid class name format should raise an exception."""
|
||||
with pytest.raises(ProcessInvalidConfigurationException, match="Invalid step class name format"):
|
||||
get_step_class_from_qualified_name("NoModule")
|
||||
Reference in New Issue
Block a user