using Xunit; using FluentAssertions; using NSubstitute; using System; using System.Text.Json; using FreeCode.Core.Models; using FreeCode.Core.Enums; namespace FreeCode.Tests.Unit.Models; public sealed class SDKMessageTests { [Fact] public void SDKMessage_UserMessageWrapsMessage() { var message = CreateMessage(); SDKMessage sdkMessage = new SDKMessage.UserMessage(message); sdkMessage.Should().BeOfType(); ((SDKMessage.UserMessage)sdkMessage).Message.Should().BeSameAs(message); } [Fact] public void SDKMessage_AssistantMessageStoresTextAndMessageId() { var sdkMessage = new SDKMessage.AssistantMessage("hello", "msg-1"); sdkMessage.Text.Should().Be("hello"); sdkMessage.MessageId.Should().Be("msg-1"); } [Fact] public void SDKMessage_StreamingDeltaStoresText() { var sdkMessage = new SDKMessage.StreamingDelta("partial"); sdkMessage.Text.Should().Be("partial"); } [Fact] public void SDKMessage_ToolUseStartStoresAllFields() { var input = ParseJson("{\"path\":\"/tmp\"}"); var sdkMessage = new SDKMessage.ToolUseStart("tool-use-1", "read", input); sdkMessage.ToolUseId.Should().Be("tool-use-1"); sdkMessage.ToolName.Should().Be("read"); sdkMessage.Input.GetProperty("path").GetString().Should().Be("/tmp"); } [Fact] public void SDKMessage_ToolUseResultStoresAllFields() { var message = CreateMessage(); var sdkMessage = new SDKMessage.ToolUseResult("tool-use-1", "ok", true, message); sdkMessage.ToolUseId.Should().Be("tool-use-1"); sdkMessage.Output.Should().Be("ok"); sdkMessage.ShouldContinue.Should().BeTrue(); sdkMessage.Message.Should().BeSameAs(message); } [Fact] public void SDKMessage_CompactBoundaryStoresReason() { var sdkMessage = new SDKMessage.CompactBoundary("token_limit"); sdkMessage.Reason.Should().Be("token_limit"); } [Fact] public void SDKMessage_AssistantErrorStoresError() { var sdkMessage = new SDKMessage.AssistantError("boom"); sdkMessage.Error.Should().Be("boom"); } [Fact] public void SDKMessage_PermissionDenialStoresFields() { var sdkMessage = new SDKMessage.PermissionDenial("bash", "tool-use-1"); sdkMessage.ToolName.Should().Be("bash"); sdkMessage.ToolUseId.Should().Be("tool-use-1"); } [Fact] public void SDKMessage_AssistantMessage_UsesValueEquality() { var left = new SDKMessage.AssistantMessage("hello", "msg-1"); var right = new SDKMessage.AssistantMessage("hello", "msg-1"); left.Should().Be(right); } [Fact] public void SDKMessage_AssistantMessage_IsNotEqualWhenTextDiffers() { var left = new SDKMessage.AssistantMessage("hello", "msg-1"); var right = new SDKMessage.AssistantMessage("bye", "msg-1"); left.Should().NotBe(right); } [Fact] public void SDKMessage_UserMessage_SupportsPatternMatching() { SDKMessage sdkMessage = new SDKMessage.UserMessage(CreateMessage()); var matched = sdkMessage is SDKMessage.UserMessage userMessage && userMessage.Message.Role == MessageRole.User; matched.Should().BeTrue(); } [Fact] public void SDKMessage_ToolUseStart_CanStoreEmptyToolName() { var sdkMessage = new SDKMessage.ToolUseStart("tool-use-1", string.Empty, ParseJson("{}")); sdkMessage.ToolName.Should().BeEmpty(); } [Fact] public void SDKMessage_ToolUseResult_PreservesFalseContinuationFlag() { var sdkMessage = new SDKMessage.ToolUseResult("tool-use-1", "output", false, CreateMessage()); sdkMessage.ShouldContinue.Should().BeFalse(); } [Fact] public void SDKMessage_StreamingDelta_AllowsEmptyText() { var sdkMessage = new SDKMessage.StreamingDelta(string.Empty); sdkMessage.Text.Should().BeEmpty(); } [Fact] public void SDKMessage_AssistantError_AllowsEmptyErrorText() { var sdkMessage = new SDKMessage.AssistantError(string.Empty); sdkMessage.Error.Should().BeEmpty(); } [Fact] public void SDKMessage_CompactBoundary_AllowsEmptyReason() { var sdkMessage = new SDKMessage.CompactBoundary(string.Empty); sdkMessage.Reason.Should().BeEmpty(); } [Fact] public void SDKMessage_PermissionDenial_AllowsEmptyFields() { var sdkMessage = new SDKMessage.PermissionDenial(string.Empty, string.Empty); sdkMessage.ToolName.Should().BeEmpty(); sdkMessage.ToolUseId.Should().BeEmpty(); } [Fact] public void SDKMessage_ToolUseStart_InputRemainsObjectJson() { var sdkMessage = new SDKMessage.ToolUseStart("tool-use-1", "read", ParseJson("{\"path\":\"/repo\"}")); sdkMessage.Input.ValueKind.Should().Be(JsonValueKind.Object); } private static Message CreateMessage() { return new Message { MessageId = "msg-1", Role = MessageRole.User, Content = new object(), Timestamp = new DateTime(2026, 4, 6, 10, 0, 0, DateTimeKind.Utc) }; } private static JsonElement ParseJson(string json) { using var document = JsonDocument.Parse(json); return document.RootElement.Clone(); } }