using Xunit; using FluentAssertions; using NSubstitute; using System.Collections.Generic; using FreeCode.Core.Models; namespace FreeCode.Tests.Unit.Models; public sealed class OperationResultAndTokenUsageTests { [Fact] public void PermissionResult_Allowed_ReturnsAllowedWithNullReason() { var result = PermissionResult.Allowed(); result.IsAllowed.Should().BeTrue(); result.Reason.Should().BeNull(); } [Fact] public void PermissionResult_Denied_ReturnsDeniedWithReason() { var result = PermissionResult.Denied("not allowed"); result.IsAllowed.Should().BeFalse(); result.Reason.Should().Be("not allowed"); } [Fact] public void PermissionResult_Constructor_CanCreateAllowedWithReason() { var result = new PermissionResult(true, "manual"); result.IsAllowed.Should().BeTrue(); result.Reason.Should().Be("manual"); } [Fact] public void PermissionResult_UsesValueEquality() { var left = PermissionResult.Denied("reason"); var right = PermissionResult.Denied("reason"); left.Should().Be(right); } [Fact] public void ValidationResult_Success_ReturnsValidWithoutErrors() { var result = ValidationResult.Success(); result.IsValid.Should().BeTrue(); result.Errors.Should().BeEmpty(); } [Fact] public void ValidationResult_Failure_ReturnsInvalidWithSingleError() { var result = ValidationResult.Failure(["missing field"]); result.IsValid.Should().BeFalse(); result.Errors.Should().ContainSingle().Which.Should().Be("missing field"); } [Fact] public void ValidationResult_Failure_PreservesErrorOrder() { var result = ValidationResult.Failure(["first", "second", "third"]); result.Errors.Should().ContainInOrder("first", "second", "third"); } [Fact] public void ValidationResult_Failure_AllowsEmptyEnumerable() { var result = ValidationResult.Failure([]); result.IsValid.Should().BeFalse(); result.Errors.Should().BeEmpty(); } [Fact] public void ValidationResult_Success_UsesValueEquality() { var left = ValidationResult.Success(); var right = ValidationResult.Success(); left.Should().Be(right); } [Fact] public void ValidationResult_Failure_CopiesInputSequence() { var errors = new List { "first" }; var result = ValidationResult.Failure(errors); errors.Add("second"); result.Errors.Should().ContainSingle().Which.Should().Be("first"); } [Fact] public void TokenUsage_HoldsAllCounts() { var usage = new TokenUsage(10, 20, 30, 40); usage.InputTokens.Should().Be(10); usage.OutputTokens.Should().Be(20); usage.CacheCreationTokens.Should().Be(30); usage.CacheReadTokens.Should().Be(40); } [Fact] public void TokenUsage_UsesValueEquality() { var left = new TokenUsage(1, 2, 3, 4); var right = new TokenUsage(1, 2, 3, 4); left.Should().Be(right); } [Fact] public void TokenUsage_DeconstructsIntoOriginalValues() { var usage = new TokenUsage(1, 2, 3, 4); var (input, output, cacheCreation, cacheRead) = usage; input.Should().Be(1); output.Should().Be(2); cacheCreation.Should().Be(3); cacheRead.Should().Be(4); } [Fact] public void TokenUsage_AllowsZeroCounts() { var usage = new TokenUsage(0, 0, 0, 0); usage.Should().Be(new TokenUsage(0, 0, 0, 0)); } [Fact] public void TokenUsage_PassivelyStoresNegativeCounts() { var usage = new TokenUsage(-1, -2, -3, -4); usage.InputTokens.Should().Be(-1); usage.OutputTokens.Should().Be(-2); usage.CacheCreationTokens.Should().Be(-3); usage.CacheReadTokens.Should().Be(-4); } [Fact] public void TokenUsage_CanBeSummedByConsumerCode() { var usage = new TokenUsage(10, 20, 30, 40); var total = usage.InputTokens + usage.OutputTokens + usage.CacheCreationTokens + usage.CacheReadTokens; total.Should().Be(100); } [Fact] public void TokenUsage_CanBeStoredInsideASubstituteConsumer() { var usage = new TokenUsage(2, 4, 6, 8); var holder = new TokenUsageHolder(usage); holder.Usage.Should().Be(usage); } private sealed class TokenUsageHolder(TokenUsage usage) : ITokenUsageHolder { public TokenUsage Usage { get; } = usage; } public interface ITokenUsageHolder { TokenUsage Usage { get; } } }