|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.Json;
using Xunit;
#pragma warning disable SA1204 // Static elements should appear before instance elements
namespace Microsoft.Extensions.AI;
public class ChatResponseFormatTests
{
private static JsonElement EmptySchema => JsonDocument.Parse("{}").RootElement;
[Fact]
public void Singletons_Idempotent()
{
Assert.Same(ChatResponseFormat.Text, ChatResponseFormat.Text);
Assert.Same(ChatResponseFormat.Json, ChatResponseFormat.Json);
}
[Fact]
public void Constructor_InvalidArgs_Throws()
{
Assert.Throws<ArgumentException>("schemaName", () => new ChatResponseFormatJson(null, "name"));
Assert.Throws<ArgumentException>("schemaDescription", () => new ChatResponseFormatJson(null, null, "description"));
Assert.Throws<ArgumentException>("schemaName", () => new ChatResponseFormatJson(null, "name", "description"));
}
[Fact]
public void Constructor_PropsDefaulted()
{
ChatResponseFormatJson f = new(null);
Assert.Null(f.Schema);
Assert.Null(f.SchemaName);
Assert.Null(f.SchemaDescription);
}
[Fact]
public void Constructor_PropsRoundtrip()
{
ChatResponseFormatJson f = new(EmptySchema, "name", "description");
Assert.Equal("{}", JsonSerializer.Serialize(f.Schema, TestJsonSerializerContext.Default.JsonElement));
Assert.Equal("name", f.SchemaName);
Assert.Equal("description", f.SchemaDescription);
}
[Fact]
public void Serialization_TextRoundtrips()
{
string json = JsonSerializer.Serialize(ChatResponseFormat.Text, TestJsonSerializerContext.Default.ChatResponseFormat);
Assert.Equal("""{"$type":"text"}""", json);
ChatResponseFormat? result = JsonSerializer.Deserialize(json, TestJsonSerializerContext.Default.ChatResponseFormat);
Assert.Equal(ChatResponseFormat.Text, result);
}
[Fact]
public void Serialization_JsonRoundtrips()
{
string json = JsonSerializer.Serialize(ChatResponseFormat.Json, TestJsonSerializerContext.Default.ChatResponseFormat);
Assert.Equal("""{"$type":"json"}""", json);
ChatResponseFormat? result = JsonSerializer.Deserialize(json, TestJsonSerializerContext.Default.ChatResponseFormat);
var actual = Assert.IsType<ChatResponseFormatJson>(result);
Assert.Null(actual.Schema);
Assert.Null(actual.SchemaDescription);
Assert.Null(actual.SchemaName);
}
[Fact]
public void Serialization_ForJsonSchemaRoundtrips()
{
string json = JsonSerializer.Serialize(
ChatResponseFormat.ForJsonSchema(JsonSerializer.Deserialize<JsonElement>("[1,2,3]", AIJsonUtilities.DefaultOptions), "name", "description"),
TestJsonSerializerContext.Default.ChatResponseFormat);
Assert.Equal("""{"$type":"json","schema":[1,2,3],"schemaName":"name","schemaDescription":"description"}""", json);
ChatResponseFormat? result = JsonSerializer.Deserialize(json, TestJsonSerializerContext.Default.ChatResponseFormat);
var actual = Assert.IsType<ChatResponseFormatJson>(result);
Assert.Equal("[1,2,3]", JsonSerializer.Serialize(actual.Schema, TestJsonSerializerContext.Default.JsonElement));
Assert.Equal("name", actual.SchemaName);
Assert.Equal("description", actual.SchemaDescription);
}
[Fact]
public void ForJsonSchema_NullType_Throws()
{
Assert.Throws<ArgumentNullException>("schemaType", () => ChatResponseFormat.ForJsonSchema(null!));
Assert.Throws<ArgumentNullException>("schemaType", () => ChatResponseFormat.ForJsonSchema(null!, TestJsonSerializerContext.Default.Options));
Assert.Throws<ArgumentNullException>("schemaType", () => ChatResponseFormat.ForJsonSchema(null!, TestJsonSerializerContext.Default.Options, "name"));
Assert.Throws<ArgumentNullException>("schemaType", () => ChatResponseFormat.ForJsonSchema(null!, TestJsonSerializerContext.Default.Options, "name", "description"));
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ForJsonSchema_PrimitiveType_Succeeds(bool generic)
{
ChatResponseFormatJson format = generic ?
ChatResponseFormat.ForJsonSchema<int>() :
ChatResponseFormat.ForJsonSchema(typeof(int));
Assert.NotNull(format);
Assert.NotNull(format.Schema);
Assert.Equal("""{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"integer"}""", format.Schema.ToString());
Assert.Equal("Int32", format.SchemaName);
Assert.Null(format.SchemaDescription);
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ForJsonSchema_IncludedType_Succeeds(bool generic)
{
ChatResponseFormatJson format = generic ?
ChatResponseFormat.ForJsonSchema<DataContent>() :
ChatResponseFormat.ForJsonSchema(typeof(DataContent));
Assert.NotNull(format);
Assert.NotNull(format.Schema);
Assert.Contains("\"uri\"", format.Schema.ToString());
Assert.Equal("DataContent", format.SchemaName);
Assert.Null(format.SchemaDescription);
}
public static IEnumerable<object?[]> ForJsonSchema_ComplexType_Succeeds_MemberData() =>
from generic in new[] { false, true }
from name in new string?[] { null, "CustomName" }
from description in new string?[] { null, "CustomDescription" }
select new object?[] { generic, name, description };
[Theory]
[MemberData(nameof(ForJsonSchema_ComplexType_Succeeds_MemberData))]
public void ForJsonSchema_ComplexType_Succeeds(bool generic, string? name, string? description)
{
ChatResponseFormatJson format = generic ?
ChatResponseFormat.ForJsonSchema<SomeType>(TestJsonSerializerContext.Default.Options, name, description) :
ChatResponseFormat.ForJsonSchema(typeof(SomeType), TestJsonSerializerContext.Default.Options, name, description);
Assert.NotNull(format);
Assert.Equal(
"""
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"description": "abcd",
"type": "object",
"properties": {
"someInteger": {
"description": "efg",
"type": "integer"
},
"someString": {
"description": "hijk",
"type": [
"string",
"null"
]
}
}
}
""",
JsonSerializer.Serialize(format.Schema, AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement))));
Assert.Equal(name ?? "SomeType", format.SchemaName);
Assert.Equal(description ?? "abcd", format.SchemaDescription);
}
[Description("abcd")]
public class SomeType
{
[Description("efg")]
public int SomeInteger { get; set; }
[Description("hijk")]
public string? SomeString { get; set; }
}
}
|