Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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,87 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
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,6 +378,11 @@ protected virtual async ValueTask ActivateStepAsync()
if (stepStateType.HasValue)
{
stateType = Type.GetType(stepStateType.Value);
if (stateType is not null && !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;
Comment thread
westey-m marked this conversation as resolved.
Outdated
}
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