account-tracking/src/AccountTracking.Api.Tests/CsvImportServiceTests.cs

124 lines
4.3 KiB
C#

using AccountTracking.Api.Data;
using AccountTracking.Api.Services;
using Microsoft.EntityFrameworkCore;
namespace AccountTracking.Api.Tests;
public class CsvImportServiceTests
{
private static AppDbContext CreateDb()
{
var opts = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
return new AppDbContext(opts);
}
private static CsvImportService CreateService(AppDbContext db)
=> new(db);
// Minimal valid CSV with two data rows
private const string ValidCsv = """
Pohyby na účtu 216868554/0300 dne 19.03.2026
číslo účtu;datum zaúčtování;částka;měna;zůstatek;číslo protiúčtu;kód banky protiúčtu;jméno protistrany;adresa protistrany;konstantní symbol;variabilní symbol;specifický symbol;označení operace;název trálého příkazu;vlastní poznámka;zpráva;kategorie;původní transakce;kurz;E2E identifikace;reference plátce;původní plátce;konečný příjemce;ID transakce
216868554/0300;15.03.2026;-1 740,80;CZK;93 346,57;;;;;;205000001;;Transakce platební kartou;;;Zpráva 1;Potraviny;;;;;;;tx-001
216868554/0300;14.03.2026;-800,00;CZK;95 087,37;;;;;;205000002;;Transakce platební kartou;;;Zpráva 2;Restaurace;;;;;;;tx-002
""";
private const string CsvMissingHeader = """
Pohyby na účtu
Nějaky obsah bez správné hlavičky
datum;částka
""";
private const string CsvMissingRequiredColumn = """
číslo účtu;datum zaúčtování;měna;zůstatek;ID transakce
216868554/0300;15.03.2026;CZK;93346,57;tx-001
""";
[Fact]
public async Task Import_ParsesRowsCorrectly()
{
using var db = CreateDb();
var svc = CreateService(db);
var result = await svc.ImportAsync(ValidCsv, "test.csv");
Assert.True(result.IsSuccess);
Assert.Equal(2, result.Value!.RecordsImported);
Assert.Equal(0, result.Value.RecordsSkipped);
var transactions = await db.Transactions.ToListAsync();
Assert.Equal(2, transactions.Count);
var tx = transactions.First(t => t.TransactionId == "tx-001");
Assert.Equal(-1740.80m, tx.Amount);
Assert.Equal(93346.57m, tx.Balance);
Assert.Equal("Potraviny", tx.Category);
Assert.Equal(new DateOnly(2026, 3, 15), tx.BookingDate);
}
[Fact]
public async Task Import_SkipsDuplicateTransactionIds()
{
using var db = CreateDb();
var svc = CreateService(db);
await svc.ImportAsync(ValidCsv, "first.csv");
var result = await svc.ImportAsync(ValidCsv, "second.csv");
Assert.True(result.IsSuccess);
Assert.Equal(0, result.Value!.RecordsImported);
Assert.Equal(2, result.Value.RecordsSkipped);
Assert.Equal(2, await db.Transactions.CountAsync());
}
[Fact]
public async Task Import_FindsHeaderByScanning_NotByLineNumber()
{
// Header is on line 3 in ValidCsv — but we scan, not hardcode
using var db = CreateDb();
var svc = CreateService(db);
var result = await svc.ImportAsync(ValidCsv, "test.csv");
Assert.True(result.IsSuccess);
}
[Fact]
public async Task Import_ReturnsError_WhenHeaderNotFound()
{
using var db = CreateDb();
var svc = CreateService(db);
var result = await svc.ImportAsync(CsvMissingHeader, "bad.csv");
Assert.False(result.IsSuccess);
Assert.Contains("hlavičku", result.Error, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task Import_ReturnsError_WhenRequiredColumnMissing()
{
using var db = CreateDb();
var svc = CreateService(db);
var result = await svc.ImportAsync(CsvMissingRequiredColumn, "bad.csv");
Assert.False(result.IsSuccess);
Assert.Contains("částka", result.Error, StringComparison.OrdinalIgnoreCase);
}
[Theory]
[InlineData("1 740,80", 1740.80)]
[InlineData("-4 263,15", -4263.15)]
[InlineData("-800,00", -800.00)]
[InlineData("93 346,57", 93346.57)]
public void ParseAmount_HandlesChechLocaleFormat(string input, decimal expected)
{
var result = CsvImportService.ParseCzechDecimal(input);
Assert.Equal(expected, result);
}
}