Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ internal ProcessStepBuilderTyped(Type stepType, string id, ProcessBuilder? proce
: base(id, processBuilder)
{
Verify.NotNull(stepType);
if (!typeof(KernelProcessStep).IsAssignableFrom(stepType))
{
throw new ArgumentException($"Type '{stepType.FullName}' must be a subclass of KernelProcessStep.", nameof(stepType));
}

this._stepType = stepType;
this.FunctionsDict = this.GetFunctionMetadataMap();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Collections.Generic;
using Microsoft.SemanticKernel;
using Xunit;

namespace SemanticKernel.Process.Dapr.Runtime.UnitTests;

/// <summary>
/// Unit tests for the <see cref="DaprStepInfo"/> class.
/// </summary>
public class DaprStepInfoTests
{
/// <summary>
/// Tests that ToKernelProcessStepInfo throws when InnerStepDotnetType is not a KernelProcessStep subclass.
/// </summary>
[Fact]
public void ToKernelProcessStepInfoThrowsForInvalidStepType()
{
// Arrange
var stepInfo = new DaprStepInfo
{
InnerStepDotnetType = typeof(string).AssemblyQualifiedName!,
State = new KernelProcessStepState("TestStep", version: "v1"),
Edges = new Dictionary<string, List<KernelProcessEdge>>()
};

// Act & Assert
var ex = Assert.Throws<KernelException>(() => stepInfo.ToKernelProcessStepInfo());
Assert.Contains("is not a valid KernelProcessStep type", ex.Message);
}

/// <summary>
/// Tests that ToKernelProcessStepInfo throws when InnerStepDotnetType cannot be resolved.
/// </summary>
[Fact]
public void ToKernelProcessStepInfoThrowsForUnresolvableType()
{
// Arrange
var stepInfo = new DaprStepInfo
{
InnerStepDotnetType = "NonExistent.Type, NonExistent.Assembly",
State = new KernelProcessStepState("TestStep", version: "v1"),
Edges = new Dictionary<string, List<KernelProcessEdge>>()
};

// Act & Assert
Assert.Throws<KernelException>(() => stepInfo.ToKernelProcessStepInfo());
}

/// <summary>
/// Tests that ToKernelProcessStepInfo succeeds for a valid KernelProcessStep subclass.
/// </summary>
[Fact]
public void ToKernelProcessStepInfoSucceedsForValidStepType()
{
// Arrange
var stepInfo = new DaprStepInfo
{
InnerStepDotnetType = typeof(ValidTestStep).AssemblyQualifiedName!,
State = new KernelProcessStepState("TestStep", version: "v1"),
Edges = new Dictionary<string, List<KernelProcessEdge>>()
};

// Act
var result = stepInfo.ToKernelProcessStepInfo();

// Assert
Assert.NotNull(result);
Assert.Equal(typeof(ValidTestStep), result.InnerStepType);
}

/// <summary>
/// A valid test step for type validation testing.
/// </summary>
public sealed class ValidTestStep : KernelProcessStep
{
/// <summary>
/// A test function.
/// </summary>
[KernelFunction]
public void TestFunction()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Text.Json;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Process.Serialization;
using Xunit;

namespace SemanticKernel.Process.Dapr.Runtime.UnitTests;

/// <summary>
/// Unit tests for the <see cref="TypeInfo"/> class.
/// </summary>
public class TypeInfoTests
{
/// <summary>
/// Tests that ConvertValue deserializes a JsonElement to the correct type.
/// </summary>
[Fact]
public void ConvertValueDeserializesJsonElement()
{
// Arrange
var json = JsonSerializer.SerializeToElement(42);
var typeName = typeof(int).AssemblyQualifiedName;

// Act
var result = TypeInfo.ConvertValue(typeName, json);

// Assert
Assert.Equal(42, result);
}

/// <summary>
/// Tests that ConvertValue throws when the type name cannot be resolved.
/// </summary>
[Fact]
public void ConvertValueThrowsForUnresolvableType()
{
// Arrange
var json = JsonSerializer.SerializeToElement(42);

// Act & Assert
Assert.Throws<KernelException>(() =>
TypeInfo.ConvertValue("NonExistent.Type, NonExistent.Assembly", json));
}

/// <summary>
/// Tests that ConvertValue returns non-JsonElement values unchanged.
/// </summary>
[Fact]
public void ConvertValueReturnsNonJsonElementUnchanged()
{
// Arrange
var value = "plain string";

// Act
var result = TypeInfo.ConvertValue(typeof(string).AssemblyQualifiedName, value);

// Assert
Assert.Equal("plain string", result);
}

/// <summary>
/// Tests that ConvertValue returns null when value is null.
/// </summary>
[Fact]
public void ConvertValueReturnsNullWhenValueIsNull()
{
// Act
var result = TypeInfo.ConvertValue(typeof(string).AssemblyQualifiedName, null);

// Assert
Assert.Null(result);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ private void InitializeStep(DaprStepInfo stepInfo, string? parentProcessId, stri
throw new KernelException($"Could not load the inner step type '{stepInfo.InnerStepDotnetType}'.").Log(this._logger);
}

if (!typeof(KernelProcessStep).IsAssignableFrom(this._innerStepType))
{
throw new KernelException($"Type '{stepInfo.InnerStepDotnetType}' is not a valid KernelProcessStep type.").Log(this._logger);
}

this.ParentProcessId = parentProcessId;
this._stepInfo = stepInfo;
this._stepState = this._stepInfo.State;
Expand Down Expand Up @@ -373,8 +378,18 @@ protected virtual async ValueTask ActivateStepAsync()
if (stepStateType.HasValue)
{
stateType = Type.GetType(stepStateType.Value);
if (stateType is null)
{
throw new KernelException($"Type '{stepStateType.Value}' could not be resolved to a valid KernelProcessStepState type.").Log(this._logger);
}

if (!typeof(KernelProcessStepState).IsAssignableFrom(stateType))
{
throw new KernelException($"Type '{stepStateType.Value}' is not a valid KernelProcessStepState type.").Log(this._logger);
}

var stateObjectJson = await this.StateManager.GetStateAsync<string>(ActorStateKeys.StepStateJson).ConfigureAwait(false);
stateObject = JsonSerializer.Deserialize(stateObjectJson, stateType!) as KernelProcessStepState;
stateObject = JsonSerializer.Deserialize(stateObjectJson, stateType) as KernelProcessStepState;
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public KernelProcessStepInfo ToKernelProcessStepInfo()
throw new KernelException($"Unable to create inner step type from assembly qualified name `{this.InnerStepDotnetType}`");
}

if (!typeof(KernelProcessStep).IsAssignableFrom(innerStepType))
{
throw new KernelException($"Type '{this.InnerStepDotnetType}' is not a valid KernelProcessStep type.");
}

return new KernelProcessStepInfo(innerStepType, this.State, this.Edges);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ internal static class TypeInfo
}

Type? valueType = Type.GetType(assemblyQualifiedTypeName);
return ((JsonElement)value).Deserialize(valueType!);
if (valueType is null)
{
throw new KernelException($"Could not load type '{assemblyQualifiedTypeName}'.");
}

return ((JsonElement)value).Deserialize(valueType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,37 @@ public void OnFunctionErrorCreatesEdgeBuilder()
Assert.EndsWith("Global.OnError", edgeBuilder.EventData.EventId);
}

/// <summary>
/// Tests that AddStepFromType(Type) throws ArgumentException for non-KernelProcessStep types.
/// </summary>
[Fact]
public void AddStepFromTypeWithInvalidTypeThrowsArgumentException()
{
// Arrange
var processBuilder = new ProcessBuilder(ProcessName);

// Act & Assert
var ex = Assert.Throws<ArgumentException>(() => processBuilder.AddStepFromType(typeof(string)));
Assert.Contains("must be a subclass of KernelProcessStep", ex.Message);
}

/// <summary>
/// Tests that AddStepFromType(Type) succeeds for valid KernelProcessStep types.
/// </summary>
[Fact]
public void AddStepFromTypeWithValidTypeAddsStep()
{
// Arrange
var processBuilder = new ProcessBuilder(ProcessName);

// Act
var stepBuilder = processBuilder.AddStepFromType(typeof(TestStep), StepName);

// Assert
Assert.Single(processBuilder.Steps);
Assert.Equal(StepName, stepBuilder.Name);
}

/// <summary>
/// A class that represents a step for testing.
/// </summary>
Expand Down
Loading