208 lines
5.5 KiB
C#
208 lines
5.5 KiB
C#
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 MessageTests
|
|
{
|
|
[Fact]
|
|
public void Message_HoldsRequiredFields()
|
|
{
|
|
var timestamp = new DateTime(2026, 4, 6, 10, 0, 0, DateTimeKind.Utc);
|
|
|
|
var message = new Message
|
|
{
|
|
MessageId = "msg-1",
|
|
Role = MessageRole.User,
|
|
Content = "hello",
|
|
Timestamp = timestamp,
|
|
ToolUseId = "tool-1",
|
|
ToolName = "bash"
|
|
};
|
|
|
|
message.MessageId.Should().Be("msg-1");
|
|
message.Role.Should().Be(MessageRole.User);
|
|
message.Content.Should().Be("hello");
|
|
message.Timestamp.Should().Be(timestamp);
|
|
message.ToolUseId.Should().Be("tool-1");
|
|
message.ToolName.Should().Be("bash");
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_DefaultTimestampIsUtcNowBased()
|
|
{
|
|
var before = DateTime.UtcNow.AddSeconds(-1);
|
|
|
|
var message = new Message
|
|
{
|
|
MessageId = "msg-1",
|
|
Role = MessageRole.Assistant,
|
|
Content = "reply"
|
|
};
|
|
|
|
var after = DateTime.UtcNow.AddSeconds(1);
|
|
|
|
message.Timestamp.Should().BeOnOrAfter(before);
|
|
message.Timestamp.Should().BeOnOrBefore(after);
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_AllowsNullContent()
|
|
{
|
|
var message = new Message
|
|
{
|
|
MessageId = "msg-1",
|
|
Role = MessageRole.User,
|
|
Content = null,
|
|
Timestamp = new DateTime(2026, 4, 6, 10, 0, 0, DateTimeKind.Utc)
|
|
};
|
|
|
|
message.Content.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_CanStoreStringContent()
|
|
{
|
|
var message = CreateMessage(content: "plain text");
|
|
|
|
message.Content.Should().Be("plain text");
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_CanStoreJsonContent()
|
|
{
|
|
var payload = ParseJson("{\"type\":\"text\",\"value\":\"hi\"}");
|
|
|
|
var message = CreateMessage(content: payload);
|
|
|
|
message.Content.Should().BeOfType<JsonElement>();
|
|
((JsonElement)message.Content!).GetProperty("type").GetString().Should().Be("text");
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_CanStoreSubstitutedContentObject()
|
|
{
|
|
var disposable = new object();
|
|
|
|
var message = CreateMessage(content: disposable);
|
|
|
|
message.Content.Should().BeSameAs(disposable);
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_ToolFieldsDefaultToNull()
|
|
{
|
|
var message = CreateMessage();
|
|
|
|
message.ToolUseId.Should().BeNull();
|
|
message.ToolName.Should().BeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_CanStoreToolMetadata()
|
|
{
|
|
var message = CreateMessage(toolUseId: "tool-1", toolName: "read");
|
|
|
|
message.ToolUseId.Should().Be("tool-1");
|
|
message.ToolName.Should().Be("read");
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_UsesValueEqualityForEquivalentValues()
|
|
{
|
|
var timestamp = new DateTime(2026, 4, 6, 10, 0, 0, DateTimeKind.Utc);
|
|
var left = CreateMessage(timestamp: timestamp);
|
|
var right = CreateMessage(timestamp: timestamp);
|
|
|
|
left.Should().Be(right);
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_IsNotEqualWhenRoleDiffers()
|
|
{
|
|
var timestamp = new DateTime(2026, 4, 6, 10, 0, 0, DateTimeKind.Utc);
|
|
var left = CreateMessage(role: MessageRole.User, timestamp: timestamp);
|
|
var right = CreateMessage(role: MessageRole.Assistant, timestamp: timestamp);
|
|
|
|
left.Should().NotBe(right);
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_WithExpressionClonesRecordWithChange()
|
|
{
|
|
var original = CreateMessage(content: "before");
|
|
var clone = original with { Content = "after" };
|
|
|
|
clone.MessageId.Should().Be(original.MessageId);
|
|
clone.Role.Should().Be(original.Role);
|
|
clone.Content.Should().Be("after");
|
|
original.Content.Should().Be("before");
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_AllowsEmptyMessageId()
|
|
{
|
|
var message = CreateMessage(messageId: string.Empty);
|
|
|
|
message.MessageId.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_AllowsEmptyToolUseId()
|
|
{
|
|
var message = CreateMessage(toolUseId: string.Empty);
|
|
|
|
message.ToolUseId.Should().BeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void Message_AllowsEmptyToolName()
|
|
{
|
|
var message = CreateMessage(toolName: string.Empty);
|
|
|
|
message.ToolName.Should().BeEmpty();
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(MessageRole.User)]
|
|
[InlineData(MessageRole.Assistant)]
|
|
[InlineData(MessageRole.System)]
|
|
[InlineData(MessageRole.Tool)]
|
|
public void Message_HoldsEachRole(MessageRole role)
|
|
{
|
|
var message = CreateMessage(role: role);
|
|
|
|
message.Role.Should().Be(role);
|
|
}
|
|
|
|
private static Message CreateMessage(
|
|
string messageId = "msg-1",
|
|
MessageRole role = MessageRole.User,
|
|
object? content = null,
|
|
DateTime? timestamp = null,
|
|
string? toolUseId = null,
|
|
string? toolName = null)
|
|
{
|
|
return new Message
|
|
{
|
|
MessageId = messageId,
|
|
Role = role,
|
|
Content = content ?? "hello",
|
|
Timestamp = timestamp ?? new DateTime(2026, 4, 6, 10, 0, 0, DateTimeKind.Utc),
|
|
ToolUseId = toolUseId,
|
|
ToolName = toolName
|
|
};
|
|
}
|
|
|
|
private static JsonElement ParseJson(string json)
|
|
{
|
|
using var document = JsonDocument.Parse(json);
|
|
return document.RootElement.Clone();
|
|
}
|
|
}
|