Skip to content

Commit a89215e

Browse files
authored
Merge branch 'main' into codex/semantic-kernel-azure-response-format
2 parents bd6b3fa + 84ab822 commit a89215e

12 files changed

Lines changed: 363 additions & 25 deletions

File tree

dotnet/SK-release.slnf

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,6 @@
3232
"src\\Connectors\\Connectors.Onnx\\Connectors.Onnx.csproj",
3333
"src\\Connectors\\Connectors.OpenAI\\Connectors.OpenAI.csproj",
3434

35-
"src\\VectorData\\AzureAISearch\\AzureAISearch.csproj",
36-
"src\\VectorData\\Chroma\\Chroma.csproj",
37-
"src\\VectorData\\CosmosMongoDB\\CosmosMongoDB.csproj",
38-
"src\\VectorData\\CosmosNoSql\\CosmosNoSql.csproj",
39-
"src\\VectorData\\InMemory\\InMemory.csproj",
40-
"src\\VectorData\\Milvus\\Milvus.csproj",
41-
"src\\VectorData\\MongoDB\\MongoDB.csproj",
42-
"src\\VectorData\\PgVector\\PgVector.csproj",
43-
"src\\VectorData\\Pinecone\\Pinecone.csproj",
44-
"src\\VectorData\\Qdrant\\Qdrant.csproj",
45-
"src\\VectorData\\Redis\\Redis.csproj",
46-
"src\\VectorData\\SqliteVec\\SqliteVec.csproj",
47-
"src\\VectorData\\SqlServer\\SqlServer.csproj",
48-
"src\\VectorData\\VectorData.Abstractions\\VectorData.Abstractions.csproj",
49-
"src\\VectorData\\Weaviate\\Weaviate.csproj",
50-
5135
"src\\Experimental\\Orchestration.Flow\\Experimental.Orchestration.Flow.csproj",
5236

5337
"src\\Experimental\\Process.Abstractions\\Process.Abstractions.csproj",

dotnet/src/Experimental/Process.Core/ProcessStepBuilder.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,10 @@ internal ProcessStepBuilderTyped(Type stepType, string id, ProcessBuilder? proce
278278
: base(id, processBuilder)
279279
{
280280
Verify.NotNull(stepType);
281+
if (!typeof(KernelProcessStep).IsAssignableFrom(stepType))
282+
{
283+
throw new ArgumentException($"Type '{stepType.FullName}' must be a subclass of KernelProcessStep.", nameof(stepType));
284+
}
281285

282286
this._stepType = stepType;
283287
this.FunctionsDict = this.GetFunctionMetadataMap();
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Collections.Generic;
4+
using Microsoft.SemanticKernel;
5+
using Xunit;
6+
7+
namespace SemanticKernel.Process.Dapr.Runtime.UnitTests;
8+
9+
/// <summary>
10+
/// Unit tests for the <see cref="DaprStepInfo"/> class.
11+
/// </summary>
12+
public class DaprStepInfoTests
13+
{
14+
/// <summary>
15+
/// Tests that ToKernelProcessStepInfo throws when InnerStepDotnetType is not a KernelProcessStep subclass.
16+
/// </summary>
17+
[Fact]
18+
public void ToKernelProcessStepInfoThrowsForInvalidStepType()
19+
{
20+
// Arrange
21+
var stepInfo = new DaprStepInfo
22+
{
23+
InnerStepDotnetType = typeof(string).AssemblyQualifiedName!,
24+
State = new KernelProcessStepState("TestStep", version: "v1"),
25+
Edges = new Dictionary<string, List<KernelProcessEdge>>()
26+
};
27+
28+
// Act & Assert
29+
var ex = Assert.Throws<KernelException>(() => stepInfo.ToKernelProcessStepInfo());
30+
Assert.Contains("is not a valid KernelProcessStep type", ex.Message);
31+
}
32+
33+
/// <summary>
34+
/// Tests that ToKernelProcessStepInfo throws when InnerStepDotnetType cannot be resolved.
35+
/// </summary>
36+
[Fact]
37+
public void ToKernelProcessStepInfoThrowsForUnresolvableType()
38+
{
39+
// Arrange
40+
var stepInfo = new DaprStepInfo
41+
{
42+
InnerStepDotnetType = "NonExistent.Type, NonExistent.Assembly",
43+
State = new KernelProcessStepState("TestStep", version: "v1"),
44+
Edges = new Dictionary<string, List<KernelProcessEdge>>()
45+
};
46+
47+
// Act & Assert
48+
Assert.Throws<KernelException>(() => stepInfo.ToKernelProcessStepInfo());
49+
}
50+
51+
/// <summary>
52+
/// Tests that ToKernelProcessStepInfo succeeds for a valid KernelProcessStep subclass.
53+
/// </summary>
54+
[Fact]
55+
public void ToKernelProcessStepInfoSucceedsForValidStepType()
56+
{
57+
// Arrange
58+
var stepInfo = new DaprStepInfo
59+
{
60+
InnerStepDotnetType = typeof(ValidTestStep).AssemblyQualifiedName!,
61+
State = new KernelProcessStepState("TestStep", version: "v1"),
62+
Edges = new Dictionary<string, List<KernelProcessEdge>>()
63+
};
64+
65+
// Act
66+
var result = stepInfo.ToKernelProcessStepInfo();
67+
68+
// Assert
69+
Assert.NotNull(result);
70+
Assert.Equal(typeof(ValidTestStep), result.InnerStepType);
71+
}
72+
73+
/// <summary>
74+
/// A valid test step for type validation testing.
75+
/// </summary>
76+
public sealed class ValidTestStep : KernelProcessStep
77+
{
78+
/// <summary>
79+
/// A test function.
80+
/// </summary>
81+
[KernelFunction]
82+
public void TestFunction()
83+
{
84+
}
85+
}
86+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
3+
using System.Text.Json;
4+
using Microsoft.SemanticKernel;
5+
using Microsoft.SemanticKernel.Process.Serialization;
6+
using Xunit;
7+
8+
namespace SemanticKernel.Process.Dapr.Runtime.UnitTests;
9+
10+
/// <summary>
11+
/// Unit tests for the <see cref="TypeInfo"/> class.
12+
/// </summary>
13+
public class TypeInfoTests
14+
{
15+
/// <summary>
16+
/// Tests that ConvertValue deserializes a JsonElement to the correct type.
17+
/// </summary>
18+
[Fact]
19+
public void ConvertValueDeserializesJsonElement()
20+
{
21+
// Arrange
22+
var json = JsonSerializer.SerializeToElement(42);
23+
var typeName = typeof(int).AssemblyQualifiedName;
24+
25+
// Act
26+
var result = TypeInfo.ConvertValue(typeName, json);
27+
28+
// Assert
29+
Assert.Equal(42, result);
30+
}
31+
32+
/// <summary>
33+
/// Tests that ConvertValue throws when the type name cannot be resolved.
34+
/// </summary>
35+
[Fact]
36+
public void ConvertValueThrowsForUnresolvableType()
37+
{
38+
// Arrange
39+
var json = JsonSerializer.SerializeToElement(42);
40+
41+
// Act & Assert
42+
Assert.Throws<KernelException>(() =>
43+
TypeInfo.ConvertValue("NonExistent.Type, NonExistent.Assembly", json));
44+
}
45+
46+
/// <summary>
47+
/// Tests that ConvertValue returns non-JsonElement values unchanged.
48+
/// </summary>
49+
[Fact]
50+
public void ConvertValueReturnsNonJsonElementUnchanged()
51+
{
52+
// Arrange
53+
var value = "plain string";
54+
55+
// Act
56+
var result = TypeInfo.ConvertValue(typeof(string).AssemblyQualifiedName, value);
57+
58+
// Assert
59+
Assert.Equal("plain string", result);
60+
}
61+
62+
/// <summary>
63+
/// Tests that ConvertValue returns null when value is null.
64+
/// </summary>
65+
[Fact]
66+
public void ConvertValueReturnsNullWhenValueIsNull()
67+
{
68+
// Act
69+
var result = TypeInfo.ConvertValue(typeof(string).AssemblyQualifiedName, null);
70+
71+
// Assert
72+
Assert.Null(result);
73+
}
74+
}

dotnet/src/Experimental/Process.Runtime.Dapr/Actors/StepActor.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ private void InitializeStep(DaprStepInfo stepInfo, string? parentProcessId, stri
102102
throw new KernelException($"Could not load the inner step type '{stepInfo.InnerStepDotnetType}'.").Log(this._logger);
103103
}
104104

105+
if (!typeof(KernelProcessStep).IsAssignableFrom(this._innerStepType))
106+
{
107+
throw new KernelException($"Type '{stepInfo.InnerStepDotnetType}' is not a valid KernelProcessStep type.").Log(this._logger);
108+
}
109+
105110
this.ParentProcessId = parentProcessId;
106111
this._stepInfo = stepInfo;
107112
this._stepState = this._stepInfo.State;
@@ -373,8 +378,18 @@ protected virtual async ValueTask ActivateStepAsync()
373378
if (stepStateType.HasValue)
374379
{
375380
stateType = Type.GetType(stepStateType.Value);
381+
if (stateType is null)
382+
{
383+
throw new KernelException($"Type '{stepStateType.Value}' could not be resolved to a valid KernelProcessStepState type.").Log(this._logger);
384+
}
385+
386+
if (!typeof(KernelProcessStepState).IsAssignableFrom(stateType))
387+
{
388+
throw new KernelException($"Type '{stepStateType.Value}' is not a valid KernelProcessStepState type.").Log(this._logger);
389+
}
390+
376391
var stateObjectJson = await this.StateManager.GetStateAsync<string>(ActorStateKeys.StepStateJson).ConfigureAwait(false);
377-
stateObject = JsonSerializer.Deserialize(stateObjectJson, stateType!) as KernelProcessStepState;
392+
stateObject = JsonSerializer.Deserialize(stateObjectJson, stateType) as KernelProcessStepState;
378393
}
379394
else
380395
{

dotnet/src/Experimental/Process.Runtime.Dapr/DaprStepInfo.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public KernelProcessStepInfo ToKernelProcessStepInfo()
5050
throw new KernelException($"Unable to create inner step type from assembly qualified name `{this.InnerStepDotnetType}`");
5151
}
5252

53+
if (!typeof(KernelProcessStep).IsAssignableFrom(innerStepType))
54+
{
55+
throw new KernelException($"Type '{this.InnerStepDotnetType}' is not a valid KernelProcessStep type.");
56+
}
57+
5358
return new KernelProcessStepInfo(innerStepType, this.State, this.Edges);
5459
}
5560

dotnet/src/Experimental/Process.Runtime.Dapr/Serialization/TypeInfo.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ internal static class TypeInfo
3939
}
4040

4141
Type? valueType = Type.GetType(assemblyQualifiedTypeName);
42-
return ((JsonElement)value).Deserialize(valueType!);
42+
if (valueType is null)
43+
{
44+
throw new KernelException($"Could not load type '{assemblyQualifiedTypeName}'.");
45+
}
46+
47+
return ((JsonElement)value).Deserialize(valueType);
4348
}
4449
}

dotnet/src/Experimental/Process.UnitTests/Core/ProcessBuilderTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,37 @@ public void OnFunctionErrorCreatesEdgeBuilder()
167167
Assert.EndsWith("Global.OnError", edgeBuilder.EventData.EventId);
168168
}
169169

170+
/// <summary>
171+
/// Tests that AddStepFromType(Type) throws ArgumentException for non-KernelProcessStep types.
172+
/// </summary>
173+
[Fact]
174+
public void AddStepFromTypeWithInvalidTypeThrowsArgumentException()
175+
{
176+
// Arrange
177+
var processBuilder = new ProcessBuilder(ProcessName);
178+
179+
// Act & Assert
180+
var ex = Assert.Throws<ArgumentException>(() => processBuilder.AddStepFromType(typeof(string)));
181+
Assert.Contains("must be a subclass of KernelProcessStep", ex.Message);
182+
}
183+
184+
/// <summary>
185+
/// Tests that AddStepFromType(Type) succeeds for valid KernelProcessStep types.
186+
/// </summary>
187+
[Fact]
188+
public void AddStepFromTypeWithValidTypeAddsStep()
189+
{
190+
// Arrange
191+
var processBuilder = new ProcessBuilder(ProcessName);
192+
193+
// Act
194+
var stepBuilder = processBuilder.AddStepFromType(typeof(TestStep), StepName);
195+
196+
// Assert
197+
Assert.Single(processBuilder.Steps);
198+
Assert.Equal(StepName, stepBuilder.Name);
199+
}
200+
170201
/// <summary>
171202
/// A class that represents a step for testing.
172203
/// </summary>

dotnet/src/VectorData/Redis/RedisFilterTranslator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ private void TranslateAny(Expression source, LambdaExpression lambda)
258258

259259
private static string SanitizeStringConstant(string value)
260260
#if NET
261-
=> value.Replace("\"", "\\\"", StringComparison.Ordinal);
261+
=> value.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal);
262262
#else
263-
=> value.Replace("\"", "\\\"");
263+
=> value.Replace("\\", "\\\\").Replace("\"", "\\\"");
264264
#endif
265265
}

dotnet/src/VectorData/SqlServer/SqlServerCommandBuilder.cs

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ internal static List<SqlCommand> CreateTable(
3131
if (ifNotExists)
3232
{
3333
sb.Append("IF OBJECT_ID(N'");
34-
sb.AppendTableName(schema, tableName);
34+
sb.AppendTableNameInsideLiteral(schema, tableName);
3535
sb.AppendLine("', N'U') IS NULL");
3636
}
3737
sb.AppendLine("BEGIN");
@@ -125,22 +125,22 @@ internal static List<SqlCommand> CreateTable(
125125
// Full-text indexes require a unique index (we use the primary key)
126126
sb.AppendLine("DECLARE @pkIndexName NVARCHAR(128);");
127127
sb.Append("SELECT @pkIndexName = name FROM sys.indexes WHERE object_id = OBJECT_ID(N'");
128-
sb.AppendTableName(schema, tableName);
128+
sb.AppendTableNameInsideLiteral(schema, tableName);
129129
sb.AppendLine("') AND is_primary_key = 1;");
130130

131131
sb.AppendLine("DECLARE @ftSql NVARCHAR(MAX);");
132132
sb.Append("SET @ftSql = N'CREATE FULLTEXT INDEX ON ");
133-
sb.AppendTableName(schema, tableName).Append(" (");
133+
sb.AppendTableNameInsideLiteral(schema, tableName).Append(" (");
134134
for (int i = 0; i < fullTextProperties.Count; i++)
135135
{
136-
sb.AppendIdentifier(fullTextProperties[i].StorageName);
136+
sb.AppendIdentifierInsideLiteral(fullTextProperties[i].StorageName);
137137
if (i < fullTextProperties.Count - 1)
138138
{
139139
sb.Append(',');
140140
}
141141
}
142142
sb.Append(") KEY INDEX ' + QUOTENAME(@pkIndexName) + N' ON ");
143-
sb.AppendIdentifier(catalogName).AppendLine("';");
143+
sb.AppendIdentifierInsideLiteral(catalogName).AppendLine("';");
144144
sb.AppendLine("EXEC sp_executesql @ftSql;");
145145
}
146146

@@ -897,6 +897,30 @@ internal static StringBuilder AppendIdentifier(this StringBuilder sb, string ide
897897
return sb;
898898
}
899899

900+
/// <summary>
901+
/// Same as <see cref="AppendTableName"/>, but for use inside a SQL string literal (N'...'),
902+
/// where single quotes must be escaped by doubling them.
903+
/// </summary>
904+
internal static StringBuilder AppendTableNameInsideLiteral(this StringBuilder sb, string? schema, string tableName)
905+
{
906+
int start = sb.Length;
907+
sb.AppendTableName(schema, tableName);
908+
sb.Replace("'", "''", start, sb.Length - start);
909+
return sb;
910+
}
911+
912+
/// <summary>
913+
/// Same as <see cref="AppendIdentifier"/>, but for use inside a SQL string literal (N'...'),
914+
/// where single quotes must be escaped by doubling them.
915+
/// </summary>
916+
internal static StringBuilder AppendIdentifierInsideLiteral(this StringBuilder sb, string identifier)
917+
{
918+
int start = sb.Length;
919+
sb.AppendIdentifier(identifier);
920+
sb.Replace("'", "''", start, sb.Length - start);
921+
return sb;
922+
}
923+
900924
private static StringBuilder AppendIdentifiers(this StringBuilder sb,
901925
IEnumerable<PropertyModel> properties,
902926
string? prefix = null,

0 commit comments

Comments
 (0)