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>
92 lines
3.9 KiB
Markdown
92 lines
3.9 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```csharp
|
|
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.
|