File: ChatCompletion\ChatResponseFormatTests.cs
Web Access
Project: src\test\Libraries\Microsoft.Extensions.AI.Abstractions.Tests\Microsoft.Extensions.AI.Abstractions.Tests.csproj (Microsoft.Extensions.AI.Abstractions.Tests)
// 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; }
    }
}