AccountingNext/CLAUDE.md
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

3.9 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Commands

# Build
dotnet build

# Run (dev server at http://localhost:5225)
cd Schaad.Accounting.UI && dotnet run

# Test
dotnet test

# Publish
dotnet publish -c Release

Tests live in Schaad.Accounting.Tests (xUnit + NSubstitute + Shouldly). There is no lint command.

Architecture

AccountingNext is a Swiss personal accounting app built with ASP.NET Core 9 + Blazor (interactive server-side rendering) and Microsoft Fluent UI. Data is stored as XML files on disk — no database.

Projects

Project Role
Schaad.Accounting.UI Blazor web app — pages, dialogs, layout
Schaad.Accounting.Services Business logic — balances, FX conversion, file import, charts
Schaad.Accounting.Db Repository layer — XML serialization/deserialization
Schaad.Accounting.Common Shared models, DTOs, interfaces

Data flow

Blazor Pages/Dialogs (UI)
  → Services (ViewService, FileService, ChartService)
  → Repositories (AccountRepository, TransactionRepository, …)
  → XML files: {DataPath}/{year}/{mandator}/Data/*.xml

Data is multi-year and multi-mandator. Each combination has its own directory under DataPath (configured in appsettings.Development.json). When a new year is opened, the previous year's data is copied as the starting point.

Key services

  • ViewService — aggregates account balances, applies FX rates, builds datasets for the UI
  • FileService — imports bank statements (MT940, CAMT053 via external Schaad.Finance.dll), parses PDFs, manages backups
  • ChartService — produces data for Plotly charts (assets, spendings over time)
  • SettingsService — manages the active year, mandator, and file paths

UI structure

Pages live in Schaad.Accounting.UI/Components/Pages/. Each page typically has a companion Dialogs/ subfolder with Fluent UI dialog components for CRUD operations. The app is hardcoded to the de-CH culture.

Key external dependencies

  • Microsoft.FluentUI.AspNetCore.Components — UI components (FluentDataGrid, FluentDialog, etc.)
  • Plotly.Blazor — charts
  • FreeSpire.PDF — PDF parsing for bank statement imports
  • Schaad.Finance.dll / Schaad.Finance.Api.dll — proprietary DLLs for MT940/CAMT053 parsing and FX rate lookups (FixerIo API key in appsettings.Development.json)

Domain constants

ClassIds in Schaad.Accounting.Common defines the Swiss accounting chart-of-accounts classes: Activa=1, Passiva=2, Income=3, Expenses=4. These are the leading digit of an account number (accounts are 4-digit; Account.Class = Number / 1000).

Testing conventions

Tests live in Schaad.Accounting.Tests and use xUnit + NSubstitute (mocks) + Shouldly (assertions).

  • File and class name: <Subject>TestShould — one file per subject under test. Examples: ViewServiceTestShould.cs, TransactionRepositoryTestShould.cs, FormattingTestShould.cs.
  • Test method name: DoThisWhenThat — describes the behavior first, then the condition. Read together the class + method form a sentence:
    • ViewServiceTestShould.SumActivaAndPassivaTotalsSeparatelyWhenGettingBalanceView
    • TransactionRepositoryTestShould.ReturnNullWhenGettingUnknownTransactionId
  • Assertions: use Shouldly (value.ShouldBe(expected), list.ShouldBeEmpty(), x.ShouldBeNull(), list.ShouldContain(...), first.ShouldBeSameAs(second), etc.). Do not use xUnit Assert.*.

Example:

public class FormattingTestShould
{
    [Fact]
    public void RoundToTwoDecimalsWhenValueHasMorePrecision()
    {
        1.234m.ToFormattedString().ShouldBe("1.23");
    }
}

Repository tests that hit real XML I/O use a temp directory via Path.GetTempPath() and clean up in IDisposable.Dispose. Pure service tests use NSubstitute mocks for every dependency and don't touch the filesystem.