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>
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— chartsFreeSpire.PDF— PDF parsing for bank statement importsSchaad.Finance.dll/Schaad.Finance.Api.dll— proprietary DLLs for MT940/CAMT053 parsing and FX rate lookups (FixerIo API key inappsettings.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.SumActivaAndPassivaTotalsSeparatelyWhenGettingBalanceViewTransactionRepositoryTestShould.ReturnNullWhenGettingUnknownTransactionId
- Assertions: use Shouldly (
value.ShouldBe(expected),list.ShouldBeEmpty(),x.ShouldBeNull(),list.ShouldContain(...),first.ShouldBeSameAs(second), etc.). Do not use xUnitAssert.*.
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.