AccountingNext/Schaad.Accounting.Tests/RepositoryCacheTestShould.cs
Claudio Schaad 8926fb1e97 PR G: test project with xxxTestShould / DoThisWhenThat / Shouldly
Add Schaad.Accounting.Tests (xUnit + NSubstitute + Shouldly, net9.0)
wired into Accounting.sln with a direct Reference to
Schaad.Finance.Api.dll so it flows into the test binary.

Naming convention: file/class is <Subject>TestShould, each test method
is DoThisWhenThat. Reads as a sentence:
"ViewService test should sum activa and passiva totals separately when
getting balance view". All assertions use Shouldly (.ShouldBe,
.ShouldBeNull, .ShouldContain, ...) rather than xUnit Assert.*.

38 tests across seven files:

- ViewServiceTestShould: balance math (activa/passiva totals, per-
  account balance from start balance + credits - debits, FX conversion
  to CHF) and bank-transaction auto-matching (booking-rule text,
  value-matching preference, same-accounts-last-month fallback,
  open-transaction filter).
- TransactionRepositoryTestShould: FX round-trip against a temp XML
  directory, unknown-id -> null, and the mutation-on-read regression
  from PR A (a second Get on the same FX transaction used to divide
  by FxRate again).
- FormattingTestShould: Swiss thousands separator, two-decimal
  rounding, culture independence.
- RepositoryCacheTestShould: loader called once, per-key isolation,
  invalidation forces reload, case-insensitive keys.
- AccountRepositoryTestShould: id assignment, in-place update,
  currency defaulting, delete, bank-account lookup, bank-balance
  update, and constructor re-run against an existing file.
- ChartServiceTestShould: honours settingsService.GetYear() without
  mutating it (locks in PR E), account vs sub-class grouping
  heuristic, skips empty accounts.
- FileServiceTestShould: CSV header + running balance for debit and
  credit lines, ordering by BookingDate/ValueDate/Value, no in-place
  value mutation (locks in PR A).

CLAUDE.md gains a `dotnet test` line. IMPROVEMENT_PLAN.md notes that
this landed as three PRs (G, H, I) and was squashed on request.

Also add `*.DotSettings.user` to .gitignore so Rider's per-user
solution settings don't get accidentally staged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-02 21:25:48 +02:00

58 lines
1.6 KiB
C#

using Schaad.Accounting.Repositories;
using Shouldly;
namespace Schaad.Accounting.Tests;
public class RepositoryCacheTestShould
{
[Fact]
public void CallLoaderOnceWhenKeyIsRequestedRepeatedly()
{
var cache = new RepositoryCache();
var loaderCalls = 0;
var first = cache.GetOrLoad("key", () => { loaderCalls++; return new List<int> { 1, 2 }; });
var second = cache.GetOrLoad("key", () => { loaderCalls++; return new List<int> { 99 }; });
loaderCalls.ShouldBe(1);
second.ShouldBeSameAs(first);
second.ShouldBe(new[] { 1, 2 });
}
[Fact]
public void KeepEntriesSeparateWhenKeysDiffer()
{
var cache = new RepositoryCache();
var a = cache.GetOrLoad("a", () => new List<int> { 1 });
var b = cache.GetOrLoad("b", () => new List<int> { 2 });
b.ShouldNotBeSameAs(a);
a.ShouldBe(new[] { 1 });
b.ShouldBe(new[] { 2 });
}
[Fact]
public void ReloadFromLoaderWhenKeyIsInvalidated()
{
var cache = new RepositoryCache();
var loaderCalls = 0;
cache.GetOrLoad("k", () => { loaderCalls++; return new List<int> { 1 }; });
cache.Invalidate("k");
cache.GetOrLoad("k", () => { loaderCalls++; return new List<int> { 2 }; });
loaderCalls.ShouldBe(2);
}
[Fact]
public void MatchKeysCaseInsensitivelyWhenLookingUp()
{
var cache = new RepositoryCache();
cache.GetOrLoad("Path/To/File.xml", () => new List<int> { 1 });
var upper = cache.GetOrLoad("PATH/TO/FILE.XML", () => new List<int> { 99 });
upper.ShouldBe(new[] { 1 });
}
}