File: UsageDetailsTests.cs
Project: ..\..\..\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.Text.Json;
using Xunit;
 
#pragma warning disable MEAI001 // Experimental API
 
namespace Microsoft.Extensions.AI;
 
public class UsageDetailsTests
{
    [Fact]
    public void Constructor_PropsDefault()
    {
        UsageDetails details = new();
        Assert.Null(details.InputTokenCount);
        Assert.Null(details.OutputTokenCount);
        Assert.Null(details.TotalTokenCount);
        Assert.Null(details.CachedInputTokenCount);
        Assert.Null(details.ReasoningTokenCount);
        Assert.Null(details.InputAudioTokenCount);
        Assert.Null(details.InputTextTokenCount);
        Assert.Null(details.OutputAudioTokenCount);
        Assert.Null(details.OutputTextTokenCount);
        Assert.Null(details.AdditionalCounts);
    }
 
    [Fact]
    public void Properties_Roundtrip()
    {
        UsageDetails details = new()
        {
            InputTokenCount = 10,
            OutputTokenCount = 20,
            TotalTokenCount = 30,
            CachedInputTokenCount = 5,
            ReasoningTokenCount = 8,
            InputAudioTokenCount = 50,
            InputTextTokenCount = 60,
            OutputAudioTokenCount = 70,
            OutputTextTokenCount = 80,
            AdditionalCounts = new() { ["custom"] = 100 }
        };
 
        Assert.Equal(10, details.InputTokenCount);
        Assert.Equal(20, details.OutputTokenCount);
        Assert.Equal(30, details.TotalTokenCount);
        Assert.Equal(5, details.CachedInputTokenCount);
        Assert.Equal(8, details.ReasoningTokenCount);
        Assert.Equal(50, details.InputAudioTokenCount);
        Assert.Equal(60, details.InputTextTokenCount);
        Assert.Equal(70, details.OutputAudioTokenCount);
        Assert.Equal(80, details.OutputTextTokenCount);
        Assert.NotNull(details.AdditionalCounts);
        Assert.Equal(100, details.AdditionalCounts["custom"]);
    }
 
    [Fact]
    public void Add_NullUsage_Throws()
    {
        UsageDetails details = new();
        Assert.Throws<ArgumentNullException>("usage", () => details.Add(null!));
    }
 
    [Fact]
    public void Add_SumsAllProperties()
    {
        UsageDetails details1 = new()
        {
            InputTokenCount = 10,
            OutputTokenCount = 20,
            TotalTokenCount = 30,
            CachedInputTokenCount = 5,
            ReasoningTokenCount = 8,
            InputAudioTokenCount = 10,
            InputTextTokenCount = 20,
            OutputAudioTokenCount = 30,
            OutputTextTokenCount = 40,
        };
 
        UsageDetails details2 = new()
        {
            InputTokenCount = 15,
            OutputTokenCount = 25,
            TotalTokenCount = 40,
            CachedInputTokenCount = 7,
            ReasoningTokenCount = 12,
            InputAudioTokenCount = 15,
            InputTextTokenCount = 25,
            OutputAudioTokenCount = 35,
            OutputTextTokenCount = 45,
        };
 
        details1.Add(details2);
 
        Assert.Equal(25, details1.InputTokenCount);
        Assert.Equal(45, details1.OutputTokenCount);
        Assert.Equal(70, details1.TotalTokenCount);
        Assert.Equal(12, details1.CachedInputTokenCount);
        Assert.Equal(20, details1.ReasoningTokenCount);
        Assert.Equal(25, details1.InputAudioTokenCount);
        Assert.Equal(45, details1.InputTextTokenCount);
        Assert.Equal(65, details1.OutputAudioTokenCount);
        Assert.Equal(85, details1.OutputTextTokenCount);
    }
 
    [Fact]
    public void Add_WithNullValues_HandlesCorrectly()
    {
        UsageDetails details1 = new()
        {
            InputTokenCount = 10,
            CachedInputTokenCount = 5,
        };
 
        UsageDetails details2 = new()
        {
            OutputTokenCount = 25,
            ReasoningTokenCount = 12,
        };
 
        details1.Add(details2);
 
        Assert.Equal(10, details1.InputTokenCount);
        Assert.Equal(25, details1.OutputTokenCount);
        Assert.Null(details1.TotalTokenCount);
        Assert.Equal(5, details1.CachedInputTokenCount);
        Assert.Equal(12, details1.ReasoningTokenCount);
    }
 
    [Fact]
    public void Add_FromNullToValue_SetsValue()
    {
        UsageDetails details1 = new();
 
        UsageDetails details2 = new()
        {
            CachedInputTokenCount = 5,
            ReasoningTokenCount = 10,
        };
 
        details1.Add(details2);
 
        Assert.Equal(5, details1.CachedInputTokenCount);
        Assert.Equal(10, details1.ReasoningTokenCount);
    }
 
    [Fact]
    public void Add_AdditionalCounts_MergesCorrectly()
    {
        UsageDetails details1 = new()
        {
            AdditionalCounts = new() { ["key1"] = 10, ["key2"] = 20 }
        };
 
        UsageDetails details2 = new()
        {
            AdditionalCounts = new() { ["key2"] = 30, ["key3"] = 40 }
        };
 
        details1.Add(details2);
 
        Assert.NotNull(details1.AdditionalCounts);
        Assert.Equal(10, details1.AdditionalCounts["key1"]);
        Assert.Equal(50, details1.AdditionalCounts["key2"]);
        Assert.Equal(40, details1.AdditionalCounts["key3"]);
    }
 
    [Fact]
    public void Serialization_Roundtrips()
    {
        UsageDetails details = new()
        {
            InputTokenCount = 10,
            OutputTokenCount = 20,
            TotalTokenCount = 30,
            CachedInputTokenCount = 5,
            ReasoningTokenCount = 8,
            InputAudioTokenCount = 50,
            InputTextTokenCount = 60,
            OutputAudioTokenCount = 70,
            OutputTextTokenCount = 80,
            AdditionalCounts = new() { ["custom"] = 100 }
        };
 
        string json = JsonSerializer.Serialize(details, AIJsonUtilities.DefaultOptions);
        UsageDetails? deserialized = JsonSerializer.Deserialize<UsageDetails>(json, AIJsonUtilities.DefaultOptions);
 
        Assert.NotNull(deserialized);
        Assert.Equal(details.InputTokenCount, deserialized.InputTokenCount);
        Assert.Equal(details.OutputTokenCount, deserialized.OutputTokenCount);
        Assert.Equal(details.TotalTokenCount, deserialized.TotalTokenCount);
        Assert.Equal(details.CachedInputTokenCount, deserialized.CachedInputTokenCount);
        Assert.Equal(details.ReasoningTokenCount, deserialized.ReasoningTokenCount);
        Assert.Equal(details.InputAudioTokenCount, deserialized.InputAudioTokenCount);
        Assert.Equal(details.InputTextTokenCount, deserialized.InputTextTokenCount);
        Assert.Equal(details.OutputAudioTokenCount, deserialized.OutputAudioTokenCount);
        Assert.Equal(details.OutputTextTokenCount, deserialized.OutputTextTokenCount);
        Assert.NotNull(deserialized.AdditionalCounts);
        Assert.Equal(100, deserialized.AdditionalCounts["custom"]);
    }
 
    [Fact]
    public void Serialization_WithNullProperties_Roundtrips()
    {
        UsageDetails details = new()
        {
            InputTokenCount = 10,
            OutputTokenCount = 20,
        };
 
        string json = JsonSerializer.Serialize(details, AIJsonUtilities.DefaultOptions);
        UsageDetails? deserialized = JsonSerializer.Deserialize<UsageDetails>(json, AIJsonUtilities.DefaultOptions);
 
        Assert.NotNull(deserialized);
        Assert.Equal(10, deserialized.InputTokenCount);
        Assert.Equal(20, deserialized.OutputTokenCount);
        Assert.Null(deserialized.TotalTokenCount);
        Assert.Null(deserialized.CachedInputTokenCount);
        Assert.Null(deserialized.ReasoningTokenCount);
        Assert.Null(deserialized.InputAudioTokenCount);
        Assert.Null(deserialized.InputTextTokenCount);
        Assert.Null(deserialized.OutputAudioTokenCount);
        Assert.Null(deserialized.OutputTextTokenCount);
    }
 
    [Fact]
    public void JsonDeserialization_KnownPayload()
    {
        const string Json = """
            {
              "inputTokenCount": 10,
              "outputTokenCount": 20,
              "totalTokenCount": 30,
              "cachedInputTokenCount": 5,
              "reasoningTokenCount": 8,
              "inputAudioTokenCount": 50,
              "inputTextTokenCount": 60,
              "outputAudioTokenCount": 70,
              "outputTextTokenCount": 80,
              "additionalCounts": {
                "custom": 100
              }
            }
            """;
 
        UsageDetails? result = JsonSerializer.Deserialize<UsageDetails>(Json, AIJsonUtilities.DefaultOptions);
 
        Assert.NotNull(result);
        Assert.Equal(10, result.InputTokenCount);
        Assert.Equal(20, result.OutputTokenCount);
        Assert.Equal(30, result.TotalTokenCount);
        Assert.Equal(5, result.CachedInputTokenCount);
        Assert.Equal(8, result.ReasoningTokenCount);
        Assert.Equal(50, result.InputAudioTokenCount);
        Assert.Equal(60, result.InputTextTokenCount);
        Assert.Equal(70, result.OutputAudioTokenCount);
        Assert.Equal(80, result.OutputTextTokenCount);
        Assert.NotNull(result.AdditionalCounts);
        Assert.Single(result.AdditionalCounts);
        Assert.Equal(100, result.AdditionalCounts["custom"]);
    }
}