Unit test improvements.

This commit is contained in:
2026-07-30 12:35:33 +02:00
parent fe9b68dba0
commit 8145ff1054
22 changed files with 283 additions and 160 deletions
@@ -2,39 +2,15 @@
using FinancialApi.Application.Handlers; using FinancialApi.Application.Handlers;
using FinancialApi.Application.Models; using FinancialApi.Application.Models;
using FinancialApi.Domain.Entities; using FinancialApi.Domain.Entities;
using FinancialApi.Domain.Exceptions;
using FluentAssertions; using FluentAssertions;
using FluentAssertions.Execution; using FluentAssertions.Execution;
using Microsoft.Extensions.Time.Testing;
namespace FinancialApi.Application.UnitTests; namespace FinancialApi.Application.UnitTests;
public class ApplyFeeHandlerTests public class ApplyFeeHandlerTests
{ {
[Fact]
public void GivenValidAccounts_BasicFee_ShouldValidateEverything()
{
// Arrange
var command = new ApplyFeeCommand(1, 100);
var account = new Account(1, 100, AccountType.Debit);
var revenueAccount = new Account(99999, 0, AccountType.Credit);
var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow());
// Act
var intents = ApplyFeeHandler.Handle(command, pair);
// Assert
using var _ = new AssertionScope();
intents.CustomerWrite.Entity.Balance.Should()
.Be(account.Balance - command.Amount);
intents.RevenueWrite.Entity.Balance.Should()
.Be(revenueAccount.Balance + command.Amount);
intents.JournalWrite.Entity.Lines.Count.Should()
.BeGreaterThanOrEqualTo(2);
intents.Event.AccountId.Should()
.Be(account.Id);
intents.Event.Amount.Should()
.Be(command.Amount);
}
public class LiabilityAccount public class LiabilityAccount
{ {
[Theory] [Theory]
@@ -49,17 +25,18 @@ public class ApplyFeeHandlerTests
) )
{ {
// Arrange // Arrange
var timeProvider = new FakeTimeProvider();
var command = new ApplyFeeCommand(1, feeAmount); var command = new ApplyFeeCommand(1, feeAmount);
var account = new Account(1, startingBalance, AccountType.Liability); var account = new Account(1, startingBalance, AccountType.Liability);
var revenueAccount = new Account(99999, 0, AccountType.Credit); var revenueAccount = new Account(99999, 0, AccountType.Credit);
var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); var feeContext = new FeeContext(account, revenueAccount, timeProvider.GetUtcNow());
// Act // Act
var intents = ApplyFeeHandler.Handle(command, pair); var (customerWrite, revenueWrite, journalWrite, @event) = ApplyFeeHandler.Handle(command, feeContext);
// Assert // Assert
using var _ = new AssertionScope(); using var _ = new AssertionScope();
intents.CustomerWrite.Entity.Balance.Should() customerWrite.Entity.Balance.Should()
.Be(expectedBalance); .Be(expectedBalance);
} }
@@ -70,13 +47,15 @@ public class ApplyFeeHandlerTests
[InlineData(1000, 1999999)] [InlineData(1000, 1999999)]
public void GivenStartingBalanceLessThanFee_ApplyFee_ShouldFail(decimal startingBalance, decimal feeAmount) public void GivenStartingBalanceLessThanFee_ApplyFee_ShouldFail(decimal startingBalance, decimal feeAmount)
{ {
// Arrange
var timeProvider = new FakeTimeProvider();
var command = new ApplyFeeCommand(1, feeAmount); var command = new ApplyFeeCommand(1, feeAmount);
var account = new Account(1, startingBalance, AccountType.Liability); var account = new Account(1, startingBalance, AccountType.Liability);
var revenueAccount = new Account(99999, 0, AccountType.Credit); var revenueAccount = new Account(99999, 0, AccountType.Credit);
var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); var feeContext = new FeeContext(account, revenueAccount, timeProvider.GetUtcNow());
// Act // Act
var act = () => ApplyFeeHandler.Handle(command, pair); var act = () => ApplyFeeHandler.Handle(command, feeContext);
// Assert // Assert
using var _ = new AssertionScope(); using var _ = new AssertionScope();
@@ -98,17 +77,19 @@ public class ApplyFeeHandlerTests
decimal expectedBalance decimal expectedBalance
) )
{ {
// Arrange
var timeProvider = new FakeTimeProvider();
var command = new ApplyFeeCommand(1, feeAmount); var command = new ApplyFeeCommand(1, feeAmount);
var account = new Account(1, startingBalance, AccountType.Credit); var account = new Account(1, startingBalance, AccountType.Credit);
var revenueAccount = new Account(99999, 0, AccountType.Credit); var revenueAccount = new Account(99999, 0, AccountType.Credit);
var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); var feeContext = new FeeContext(account, revenueAccount, timeProvider.GetUtcNow());
// Act // Act
var intents = ApplyFeeHandler.Handle(command, pair); var (customerWrite, revenueWrite, journalWrite, @event) = ApplyFeeHandler.Handle(command, feeContext);
// Assert // Assert
using var _ = new AssertionScope(); using var _ = new AssertionScope();
intents.CustomerWrite.Entity.Balance.Should() customerWrite.Entity.Balance.Should()
.Be(expectedBalance); .Be(expectedBalance);
} }
@@ -123,17 +104,19 @@ public class ApplyFeeHandlerTests
decimal expectedBalance decimal expectedBalance
) )
{ {
// Arrange
var timeProvider = new FakeTimeProvider();
var command = new ApplyFeeCommand(1, feeAmount); var command = new ApplyFeeCommand(1, feeAmount);
var account = new Account(1, startingBalance, AccountType.Credit); var account = new Account(1, startingBalance, AccountType.Credit);
var revenueAccount = new Account(99999, 0, AccountType.Credit); var revenueAccount = new Account(99999, 0, AccountType.Credit);
var pair = new FeeContext(account, revenueAccount, TimeProvider.System.GetUtcNow()); var feeContext = new FeeContext(account, revenueAccount, timeProvider.GetUtcNow());
// Act // Act
var intents = ApplyFeeHandler.Handle(command, pair); var (customerWrite, revenueWrite, journalWrite, @event) = ApplyFeeHandler.Handle(command, feeContext);
// Assert // Assert
using var _ = new AssertionScope(); using var _ = new AssertionScope();
intents.CustomerWrite.Entity.Balance.Should() customerWrite.Entity.Balance.Should()
.Be(expectedBalance); .Be(expectedBalance);
} }
} }
@@ -0,0 +1,67 @@
using FinancialApi.Application.Commands;
using FinancialApi.Application.Events;
using FinancialApi.Application.Handlers;
using FinancialApi.Application.Models;
using FinancialApi.Domain.Entities;
using FluentAssertions;
using FluentAssertions.Execution;
using Microsoft.Extensions.Time.Testing;
using Wolverine.Persistence;
namespace FinancialApi.Application.UnitTests;
public class ReverseJournalHandlerTests
{
[Fact]
public void GivenValidJournalEntry_Reversal_ShouldResetAccountBalances()
{
// Arrange
var timeProvider = new FakeTimeProvider();
var expectedSourceAccountBalance = 100.0m;
var expectedDestAccountBalance = 100.0m;
var (initialSourceAccountWrite, initialDestAccountWrite, initialJournalWrite, initialEvent) =
CreateInitialTransferTransaction(
expectedSourceAccountBalance,
expectedDestAccountBalance,
10,
timeProvider
);
var command = new ReverseJournalCommand(initialJournalWrite.Entity.Id, "Invalid Transaction");
var context = new JournalReversalContext(
initialJournalWrite.Entity,
[initialSourceAccountWrite.Entity, initialDestAccountWrite.Entity],
timeProvider.GetUtcNow()
);
// Act
var (journalWrite, accountWrites, @event) = ReverseJournalHandler.Handle(command, context);
// Assert
using var _ = new AssertionScope();
accountWrites[0]
.Entity.Balance.Should()
.Be(expectedSourceAccountBalance);
accountWrites[1]
.Entity.Balance.Should()
.Be(expectedDestAccountBalance);
journalWrite.Entity.Lines.Should()
.HaveCount(initialJournalWrite.Entity.Lines.Count);
}
private (IStorageAction<Account> SourceWrite, IStorageAction<Account> DestWrite, IStorageAction<BalancedJournalEntry>
JournalWrite, FundsTransferredEvent Event) CreateInitialTransferTransaction(
decimal sourceStartingBalance,
decimal destStartingBalance,
decimal transferAmount,
TimeProvider timeProvider
)
{
var command = new TransferFundsCommand(1, 2, transferAmount);
var sourceAccount = new Account(1, sourceStartingBalance, AccountType.Liability);
var destAccount = new Account(2, destStartingBalance, AccountType.Liability);
var transferContext = new TransferContext(sourceAccount, destAccount, timeProvider.GetUtcNow());
// Act
return TransferFundsHandler.Handle(command, transferContext);
}
}
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Time.Testing;
namespace FinancialApi.Application.UnitTests; namespace FinancialApi.Application.UnitTests;
public class ApplyTransferHandlerTests public class TransferFundsHandlerTests
{ {
public class ApplyFeeHandlerTests public class ApplyFeeHandlerTests
{ {
@@ -17,21 +17,22 @@ public class ApplyTransferHandlerTests
{ {
// Arrange // Arrange
var fakeTimeProvider = new FakeTimeProvider(); var fakeTimeProvider = new FakeTimeProvider();
var command = new TransferFundsCommand(1, 2, 100); var command = new TransferFundsCommand(1, 2, 100);
var sourceAccount = new Account(1, 100, AccountType.Liability); var sourceAccount = new Account(1, 100, AccountType.Liability);
var destAccount = new Account(1, 100, AccountType.Liability); var destAccount = new Account(1, 100, AccountType.Liability);
var transferContext = new TransferContext(sourceAccount, destAccount, fakeTimeProvider.GetUtcNow()); var transferContext = new TransferContext(sourceAccount, destAccount, fakeTimeProvider.GetUtcNow());
// Act // Act
var intents = TransferFundsHandler.Handle(command, transferContext); var (sourceWrite, destWrite, journalWrite, @event) = TransferFundsHandler.Handle(command, transferContext);
// Assert // Assert
using var _ = new AssertionScope(); using var _ = new AssertionScope();
intents.SourceWrite.Entity.Balance.Should() sourceWrite.Entity.Balance.Should()
.Be(0); .Be(0);
intents.DestWrite.Entity.Balance.Should() destWrite.Entity.Balance.Should()
.Be(200); .Be(200);
journalWrite.Entity.Lines.Should()
.HaveCountGreaterThanOrEqualTo(2);
} }
} }
} }
@@ -23,7 +23,7 @@ public static class ApplyFeeHandler
} }
public static ( IStorageAction<Account> CustomerWrite, IStorageAction<Account> RevenueWrite, public static ( IStorageAction<Account> CustomerWrite, IStorageAction<Account> RevenueWrite,
IStorageAction<JournalEntry> JournalWrite, FeeAppliedEvent Event ) Handle( IStorageAction<BalancedJournalEntry> JournalWrite, FeeAppliedEvent Event ) Handle(
ApplyFeeCommand cmd, ApplyFeeCommand cmd,
FeeContext context FeeContext context
) )
@@ -31,11 +31,11 @@ public static class ReverseJournalHandler
throw new InvalidOperationException($"Journal Entry {cmd.OriginalJournalId} does not exist"); throw new InvalidOperationException($"Journal Entry {cmd.OriginalJournalId} does not exist");
} }
public static ( IStorageAction<JournalEntry> JournalWrite, UnitOfWork<Account> AccountWrites, JournalReversedEvent public static (IStorageAction<BalancedJournalEntry> JournalWrite, UnitOfWork<Account> AccountWrites, JournalReversedEvent
Event) Handle(ReverseJournalCommand cmd, JournalReversalContext context) Event) Handle(ReverseJournalCommand cmd, JournalReversalContext context)
{ {
var reversalJournal = BalancedJournal.CreateReversal( var reversalJournal = BalancedJournal.CreateReversal(
context.OriginalJournalEntry, context.OriginalBalancedJournalEntry,
$"Reversal: {cmd.Reason}", $"Reversal: {cmd.Reason}",
context.TimeStamp context.TimeStamp
); );
@@ -27,8 +27,8 @@ public static class TransferFundsHandler
return new TransferContext(source, dest, timeProvider.GetUtcNow()); return new TransferContext(source, dest, timeProvider.GetUtcNow());
} }
public static ( IStorageAction<Account> SourceWrite, IStorageAction<Account> DestWrite, IStorageAction<JournalEntry> public static (IStorageAction<Account> SourceWrite, IStorageAction<Account> DestWrite, IStorageAction<BalancedJournalEntry>
JournalWrite, FundsTransferredEvent Event ) Handle(TransferFundsCommand cmd, TransferContext context) JournalWrite, FundsTransferredEvent Event) Handle(TransferFundsCommand cmd, TransferContext context)
{ {
var updatedSource = context.Source.ApplyPosting(cmd.Amount, EntryType.Debit); var updatedSource = context.Source.ApplyPosting(cmd.Amount, EntryType.Debit);
var updatedDest = context.Destination.ApplyPosting(cmd.Amount, EntryType.Credit); var updatedDest = context.Destination.ApplyPosting(cmd.Amount, EntryType.Credit);
@@ -4,5 +4,5 @@ namespace FinancialApi.Application.Interfaces;
public interface IReversalQuery public interface IReversalQuery
{ {
Task<(JournalEntry?, List<Account>?)> GetReversalDataAsync(Guid journalEntryId); Task<(BalancedJournalEntry?, List<Account>?)> GetReversalDataAsync(Guid journalEntryId);
} }
@@ -3,7 +3,7 @@ using FinancialApi.Domain.Entities;
namespace FinancialApi.Application.Models; namespace FinancialApi.Application.Models;
public record JournalReversalContext( public record JournalReversalContext(
JournalEntry OriginalJournalEntry, BalancedJournalEntry OriginalBalancedJournalEntry,
IReadOnlyList<Account> AffectedAccounts, IReadOnlyList<Account> AffectedAccounts,
DateTimeOffset TimeStamp DateTimeOffset TimeStamp
); );
@@ -1,7 +1,8 @@
using FinancialApi.Domain.Entities; using FinancialApi.Domain.Entities;
using FinancialApi.Domain.Exceptions;
using FluentAssertions; using FluentAssertions;
namespace FinancialApi.Application.UnitTests; namespace FinancialApi.Domain.Tests;
public class AccountTests public class AccountTests
{ {
@@ -0,0 +1,54 @@
using FinancialApi.Domain.Aggregates;
using FinancialApi.Domain.Entities;
using FinancialApi.Domain.Exceptions;
using FluentAssertions;
using Microsoft.Extensions.Time.Testing;
namespace FinancialApi.Domain.Tests;
public class BalancedJournalEntryTests
{
[Fact]
public void GivenBalanceJournal_ShouldSucceed()
{
// Arrange
var timeProvider = new FakeTimeProvider();
List<JournalLine> lines = [new(1, 100, EntryType.Debit), new(1, 100, EntryType.Credit)];
// Act
var act = () => BalancedJournal.Create(Guid.NewGuid(), "Transfer", timeProvider.GetUtcNow(), lines);
// Assert
act.Should()
.NotThrow();
}
[Fact]
public void GivenUnbalancedJournal_ShouldFail()
{
// Arrange
var timeProvider = new FakeTimeProvider();
List<JournalLine> lines = [new(1, 10, EntryType.Debit), new(1, 100, EntryType.Credit)];
// Act
var act = () => BalancedJournal.Create(Guid.NewGuid(), "Transfer", timeProvider.GetUtcNow(), lines);
// Assert
act.Should()
.Throw<GaapViolationException>();
}
[Fact]
public void GivenEmptyJournal_ShouldFail()
{
// Arrange
var timeProvider = new FakeTimeProvider();
// Act
var act = () => BalancedJournal.Create(Guid.NewGuid(), "Transfer", timeProvider.GetUtcNow(), []);
// Assert
act.Should()
.Throw<GaapViolationException>();
}
}
@@ -1,34 +1,40 @@
using FinancialApi.Domain.Entities; using FinancialApi.Domain.Entities;
using FinancialApi.Domain.Exceptions;
namespace FinancialApi.Domain.Aggregates; namespace FinancialApi.Domain.Aggregates;
public record BalancedJournal public record BalancedJournal
{ {
public JournalEntry Entry { get; } public BalancedJournalEntry Entry { get; }
private BalancedJournal(JournalEntry entry) private BalancedJournal(BalancedJournalEntry entry)
{ {
Entry = entry; Entry = entry;
} }
public static BalancedJournal Create(Guid id, string description, DateTimeOffset createdAt, List<JournalLine> lines) public static BalancedJournal Create(Guid id, string description, DateTimeOffset createdAt, List<JournalLine> lines)
{ {
if (lines.Count(l => l.Type == EntryType.Debit) == 0)
{
throw new GaapViolationException($"GAAP Violation: A journal entry needs at least one debit line.");
}
if (lines.Count(l => l.Type == EntryType.Credit) == 0)
{
throw new GaapViolationException($"A journal entry needs at least one credit line.");
}
var debits = lines.Where(l => l.Type == EntryType.Debit) var debits = lines.Where(l => l.Type == EntryType.Debit)
.Sum(l => l.Amount); .Sum(l => l.Amount);
var credits = lines.Where(l => l.Type == EntryType.Credit) var credits = lines.Where(l => l.Type == EntryType.Credit)
.Sum(l => l.Amount); .Sum(l => l.Amount);
if (debits != credits) return debits != credits
{ ? throw new GaapViolationException($"Total Debits ({debits}) must equal Total Credits ({credits})!")
throw new InvalidOperationException( : new BalancedJournal(new BalancedJournalEntry(id, description, createdAt, lines));
$"GAAP Violation: Total Debits ({debits}) must equal Total Credits ({credits})!"
);
}
return new BalancedJournal(new JournalEntry(id, description, createdAt, lines));
} }
public static BalancedJournal CreateReversal(JournalEntry original, string reason, DateTimeOffset now) public static BalancedJournal CreateReversal(BalancedJournalEntry original, string reason, DateTimeOffset now)
{ {
var reversedLines = original.Lines.Select(l => var reversedLines = original.Lines.Select(l =>
l with { Type = l.Type == EntryType.Debit ? EntryType.Credit : EntryType.Debit } l with { Type = l.Type == EntryType.Debit ? EntryType.Credit : EntryType.Debit }
@@ -1,4 +1,6 @@
namespace FinancialApi.Domain.Entities; using FinancialApi.Domain.Exceptions;
namespace FinancialApi.Domain.Entities;
public enum AccountType public enum AccountType
{ {
@@ -1,15 +1,13 @@
namespace FinancialApi.Domain.Entities; namespace FinancialApi.Domain.Entities;
public record JournalEntry public record BalancedJournalEntry
{ {
public Guid Id { get; init; } public Guid Id { get; init; }
public string Description { get; init; } public string Description { get; init; }
public DateTimeOffset CreatedAt { get; init; } public DateTimeOffset CreatedAt { get; init; }
public List<JournalLine> Lines { get; init; } = []; public List<JournalLine> Lines { get; init; } = [];
internal JournalEntry() { } internal BalancedJournalEntry(Guid id, string description, DateTimeOffset createdAt, List<JournalLine> lines)
internal JournalEntry(Guid id, string description, DateTimeOffset createdAt, List<JournalLine> lines)
{ {
Lines = lines; Lines = lines;
Id = id; Id = id;
@@ -1,3 +0,0 @@
namespace FinancialApi.Domain.Entities;
public class InsufficientFundsException(string message) : Exception(message);
@@ -1,3 +0,0 @@
namespace FinancialApi.Domain.Entities;
public class InvalidStartingBalanceException(string message) : Exception(message);
@@ -0,0 +1,3 @@
namespace FinancialApi.Domain.Exceptions;
public class GaapViolationException(string message) : Exception(message);
@@ -0,0 +1,3 @@
namespace FinancialApi.Domain.Exceptions;
public class InsufficientFundsException(string message) : GaapViolationException(message);
@@ -0,0 +1,3 @@
namespace FinancialApi.Domain.Exceptions;
public class InvalidStartingBalanceException(string message) : GaapViolationException(message);
@@ -6,7 +6,7 @@ namespace FinancialApi.Infrastructure;
public class AccountDbContext(DbContextOptions<AccountDbContext> options) : DbContext(options) public class AccountDbContext(DbContextOptions<AccountDbContext> options) : DbContext(options)
{ {
public DbSet<Account> Accounts => Set<Account>(); public DbSet<Account> Accounts => Set<Account>();
public DbSet<JournalEntry> JournalEntries => Set<JournalEntry>(); public DbSet<BalancedJournalEntry> JournalEntries => Set<BalancedJournalEntry>();
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
@@ -20,7 +20,7 @@ public class AccountDbContext(DbContextOptions<AccountDbContext> options) : DbCo
new Account(99999, 0.00m, AccountType.Revenue) new Account(99999, 0.00m, AccountType.Revenue)
); );
modelBuilder.Entity<JournalEntry>(builder => modelBuilder.Entity<BalancedJournalEntry>(builder =>
{ {
builder.HasKey(x => x.Id); builder.HasKey(x => x.Id);
builder.OwnsMany( builder.OwnsMany(
@@ -6,7 +6,7 @@ namespace FinancialApi.Infrastructure;
public class ReversalQuery(AccountDbContext db) : IReversalQuery public class ReversalQuery(AccountDbContext db) : IReversalQuery
{ {
public async Task<(JournalEntry?, List<Account>?)> GetReversalDataAsync(Guid journalEntryId) public async Task<(BalancedJournalEntry?, List<Account>?)> GetReversalDataAsync(Guid journalEntryId)
{ {
var entry = await db.JournalEntries.Include(x => x.Lines) var entry = await db.JournalEntries.Include(x => x.Lines)
.AsNoTracking() .AsNoTracking()
@@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Domain", "Fina
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Application.UnitTests", "FinancialApi.Application.UnitTests\FinancialApi.Application.UnitTests.csproj", "{93DBA3AA-3C03-431C-A41C-DDADFAC964D5}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Application.UnitTests", "FinancialApi.Application.UnitTests\FinancialApi.Application.UnitTests.csproj", "{93DBA3AA-3C03-431C-A41C-DDADFAC964D5}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FinancialApi.Domain.Tests", "FinancialApi.Domain.Tests\FinancialApi.Domain.Tests.csproj", "{C15E3FE0-6E3D-4FFC-892E-D469536ADC13}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -42,5 +44,9 @@ Global
{93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Release|Any CPU.Build.0 = Release|Any CPU {93DBA3AA-3C03-431C-A41C-DDADFAC964D5}.Release|Any CPU.Build.0 = Release|Any CPU
{C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C15E3FE0-6E3D-4FFC-892E-D469536ADC13}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal
@@ -1,9 +1,11 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConfiguredTaskAwaitable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe929cb9673c947ad9269facdac90006adb3400_003F14_003Fd939fe0a_003FConfiguredTaskAwaitable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConfiguredTaskAwaitable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe929cb9673c947ad9269facdac90006adb3400_003F14_003Fd939fe0a_003FConfiguredTaskAwaitable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConstructorBindingFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fda476e261fcf49b394c1074b25b4cdce2b2960_003Fd4_003Fb1aa95c1_003FConstructorBindingFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AConstructorBindingFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fda476e261fcf49b394c1074b25b4cdce2b2960_003Fd4_003Fb1aa95c1_003FConstructorBindingFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbContext_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fda476e261fcf49b394c1074b25b4cdce2b2960_003F37_003F1b1010ed_003FDbContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALateBoundTestFramework_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fcffa676d9bfc6c7a1c9d0d25ead15b3b0c9e12e3187bf397eaa52260e5fd91_003FLateBoundTestFramework_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ALateBoundTestFramework_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003Fcffa676d9bfc6c7a1c9d0d25ead15b3b0c9e12e3187bf397eaa52260e5fd91_003FLateBoundTestFramework_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMessageContext_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb263759e5fefb34bcd589e63aeba791cef6f591ecbc93d1a7e62befa9e8bc6_003FMessageContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMessageContext_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb263759e5fefb34bcd589e63aeba791cef6f591ecbc93d1a7e62befa9e8bc6_003FMessageContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARelationalLoggerExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb797502679b4aa8376708777b4db8d11e31a798d7e6bead76c15bdfa30834892_003FRelationalLoggerExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ARelationalLoggerExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E2_003Fresharper_002Dhost_003FSourcesCache_003Fb797502679b4aa8376708777b4db8d11e31a798d7e6bead76c15bdfa30834892_003FRelationalLoggerExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E2_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fe929cb9673c947ad9269facdac90006adb3400_003Ff0_003F6b0e8a92_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWolverineRuntime_002EHostService_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003F97bae8f1ffdd86c14edef353d7a85d3d3773b56c68e3a62e8815d1afaacbe797_003FWolverineRuntime_002EHostService_002Ecs/@EntryIndexedValue">ForceIncluded</s:String> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AWolverineRuntime_002EHostService_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2026_002E1_003Fresharper_002Dhost_003FSourcesCache_003F97bae8f1ffdd86c14edef353d7a85d3d3773b56c68e3a62e8815d1afaacbe797_003FWolverineRuntime_002EHostService_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=ea7499a6_002Ddeba_002D4139_002Dac76_002D505e3f7f2642/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt; <s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=ea7499a6_002Ddeba_002D4139_002Dac76_002D505e3f7f2642/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Solution /&gt; &lt;Solution /&gt;