Refactor EntryService to allow shared DisplayId for transfers; update related tests and migration files
This commit is contained in:
169
src/Duempelkas.Infrastructure/Migrations/20260403093901_AllowSharedDisplayIdForTransfers.Designer.cs
generated
Normal file
169
src/Duempelkas.Infrastructure/Migrations/20260403093901_AllowSharedDisplayIdForTransfers.Designer.cs
generated
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Duempelkas.Infrastructure.Persistence;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Duempelkas.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(FinanceDbContext))]
|
||||||
|
[Migration("20260403093901_AllowSharedDisplayIdForTransfers")]
|
||||||
|
partial class AllowSharedDisplayIdForTransfers
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "10.0.5");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Duempelkas.Domain.Entities.Account", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("CarryoverBalance")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(200)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Accounts");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Duempelkas.Domain.Entities.Entry", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AccountId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<decimal>("Amount")
|
||||||
|
.HasColumnType("decimal(18,2)");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Date")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DisplayId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(20)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER")
|
||||||
|
.HasDefaultValue(false);
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("TransferLinkId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayId");
|
||||||
|
|
||||||
|
b.HasIndex("TransferLinkId");
|
||||||
|
|
||||||
|
b.HasIndex("AccountId", "Date");
|
||||||
|
|
||||||
|
b.ToTable("Entries");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Duempelkas.Domain.Entities.TransferLink", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedUtc")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Note")
|
||||||
|
.HasMaxLength(500)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SourceEntryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("TargetEntryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("SourceEntryId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.HasIndex("TargetEntryId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("TransferLinks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Duempelkas.Domain.Entities.Entry", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Duempelkas.Domain.Entities.Account", "Account")
|
||||||
|
.WithMany("Entries")
|
||||||
|
.HasForeignKey("AccountId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Duempelkas.Domain.Entities.TransferLink", "TransferLink")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TransferLinkId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("Account");
|
||||||
|
|
||||||
|
b.Navigation("TransferLink");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Duempelkas.Domain.Entities.TransferLink", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Duempelkas.Domain.Entities.Entry", "SourceEntry")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("SourceEntryId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Duempelkas.Domain.Entities.Entry", "TargetEntry")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("TargetEntryId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("SourceEntry");
|
||||||
|
|
||||||
|
b.Navigation("TargetEntry");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Duempelkas.Domain.Entities.Account", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Entries");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Duempelkas.Infrastructure.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AllowSharedDisplayIdForTransfers : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Entries_DisplayId",
|
||||||
|
table: "Entries");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Entries_DisplayId",
|
||||||
|
table: "Entries",
|
||||||
|
column: "DisplayId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Entries_DisplayId",
|
||||||
|
table: "Entries");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Entries_DisplayId",
|
||||||
|
table: "Entries",
|
||||||
|
column: "DisplayId",
|
||||||
|
unique: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,8 +80,7 @@ namespace Duempelkas.Infrastructure.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("DisplayId")
|
b.HasIndex("DisplayId");
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.HasIndex("TransferLinkId");
|
b.HasIndex("TransferLinkId");
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public class EntryConfiguration : IEntityTypeConfiguration<Entry>
|
|||||||
{
|
{
|
||||||
builder.HasKey(e => e.Id);
|
builder.HasKey(e => e.Id);
|
||||||
builder.Property(e => e.DisplayId).IsRequired().HasMaxLength(20);
|
builder.Property(e => e.DisplayId).IsRequired().HasMaxLength(20);
|
||||||
builder.HasIndex(e => e.DisplayId).IsUnique();
|
builder.HasIndex(e => e.DisplayId);
|
||||||
builder.Property(e => e.Title).IsRequired().HasMaxLength(500);
|
builder.Property(e => e.Title).IsRequired().HasMaxLength(500);
|
||||||
builder.Property(e => e.Amount).HasColumnType("decimal(18,2)");
|
builder.Property(e => e.Amount).HasColumnType("decimal(18,2)");
|
||||||
builder.Property(e => e.Type).HasConversion<int>();
|
builder.Property(e => e.Type).HasConversion<int>();
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class EntryService : IEntryService
|
|||||||
{
|
{
|
||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
var displayId = await GenerateDisplayIdAsync(db, accountId, date.Year);
|
var displayId = await GenerateDisplayIdAsync(db, date.Year);
|
||||||
|
|
||||||
var entry = new Entry
|
var entry = new Entry
|
||||||
{
|
{
|
||||||
@@ -88,12 +88,12 @@ public class EntryService : IEntryService
|
|||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
await using var transaction = await db.Database.BeginTransactionAsync();
|
await using var transaction = await db.Database.BeginTransactionAsync();
|
||||||
|
|
||||||
var sourceDisplayId = await GenerateDisplayIdAsync(db, sourceAccountId, date.Year);
|
var transferDisplayId = await GenerateDisplayIdAsync(db, date.Year);
|
||||||
|
|
||||||
var sourceEntry = new Entry
|
var sourceEntry = new Entry
|
||||||
{
|
{
|
||||||
AccountId = sourceAccountId,
|
AccountId = sourceAccountId,
|
||||||
DisplayId = sourceDisplayId,
|
DisplayId = transferDisplayId,
|
||||||
Type = EntryType.Expense,
|
Type = EntryType.Expense,
|
||||||
Date = date,
|
Date = date,
|
||||||
Title = title,
|
Title = title,
|
||||||
@@ -103,12 +103,10 @@ public class EntryService : IEntryService
|
|||||||
db.Entries.Add(sourceEntry);
|
db.Entries.Add(sourceEntry);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
var targetDisplayId = await GenerateDisplayIdAsync(db, targetAccountId, date.Year);
|
|
||||||
|
|
||||||
var targetEntry = new Entry
|
var targetEntry = new Entry
|
||||||
{
|
{
|
||||||
AccountId = targetAccountId,
|
AccountId = targetAccountId,
|
||||||
DisplayId = targetDisplayId,
|
DisplayId = transferDisplayId,
|
||||||
Type = EntryType.Income,
|
Type = EntryType.Income,
|
||||||
Date = date,
|
Date = date,
|
||||||
Title = title,
|
Title = title,
|
||||||
@@ -234,13 +232,12 @@ public class EntryService : IEntryService
|
|||||||
if (otherEntry.AccountId != newLinkedAccountId)
|
if (otherEntry.AccountId != newLinkedAccountId)
|
||||||
{
|
{
|
||||||
otherEntry.AccountId = newLinkedAccountId;
|
otherEntry.AccountId = newLinkedAccountId;
|
||||||
otherEntry.DisplayId = await GenerateDisplayIdAsync(db, newLinkedAccountId, date.Year);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<string> GenerateDisplayIdAsync(FinanceDbContext db, int accountId, int year)
|
private static async Task<string> GenerateDisplayIdAsync(FinanceDbContext db, int year)
|
||||||
{
|
{
|
||||||
var prefix = $"{year}-";
|
var prefix = $"{year}-";
|
||||||
var maxDisplayId = await db.Entries
|
var maxDisplayId = await db.Entries
|
||||||
|
|||||||
123
tests/Duempelkas.Tests/EntryServiceBookingTests.cs
Normal file
123
tests/Duempelkas.Tests/EntryServiceBookingTests.cs
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
using Duempelkas.Domain.Entities;
|
||||||
|
using Duempelkas.Domain.Enums;
|
||||||
|
using Duempelkas.Infrastructure.Persistence;
|
||||||
|
using Duempelkas.Infrastructure.Services;
|
||||||
|
using FluentAssertions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Duempelkas.Tests;
|
||||||
|
|
||||||
|
public class EntryServiceBookingTests : IDisposable
|
||||||
|
{
|
||||||
|
private readonly FinanceDbContext _db;
|
||||||
|
private readonly EntryService _entryService;
|
||||||
|
private readonly string _connectionString = $"Data Source=duempelkas-entry-tests-{Guid.NewGuid():N};Mode=Memory;Cache=Shared";
|
||||||
|
|
||||||
|
public EntryServiceBookingTests()
|
||||||
|
{
|
||||||
|
var options = new DbContextOptionsBuilder<FinanceDbContext>()
|
||||||
|
.UseSqlite(_connectionString)
|
||||||
|
.Options;
|
||||||
|
|
||||||
|
_db = new FinanceDbContext(options);
|
||||||
|
_db.Database.OpenConnection();
|
||||||
|
_db.Database.EnsureCreated();
|
||||||
|
|
||||||
|
var dbFactory = new TestDbContextFactory(options);
|
||||||
|
_entryService = new EntryService(dbFactory);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateEntry_AssignsSequentialDisplayIds_PerYear()
|
||||||
|
{
|
||||||
|
var accountA = new Account { Name = "Konto A" };
|
||||||
|
var accountB = new Account { Name = "Konto B" };
|
||||||
|
_db.Accounts.AddRange(accountA, accountB);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var first = await _entryService.CreateEntryAsync(accountA.Id, EntryType.Income, new DateTime(2026, 1, 10), "Einnahme A", 100m);
|
||||||
|
var second = await _entryService.CreateEntryAsync(accountB.Id, EntryType.Expense, new DateTime(2026, 1, 11), "Ausgabe B", 20m);
|
||||||
|
var third = await _entryService.CreateEntryAsync(accountA.Id, EntryType.Income, new DateTime(2027, 1, 1), "Neues Jahr", 5m);
|
||||||
|
|
||||||
|
first.DisplayId.Should().Be("2026-001");
|
||||||
|
second.DisplayId.Should().Be("2026-002");
|
||||||
|
third.DisplayId.Should().Be("2027-001");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateEntry_AfterTransfer_UsesNextDisplayId()
|
||||||
|
{
|
||||||
|
var source = new Account { Name = "Barkasse" };
|
||||||
|
var target = new Account { Name = "Girokonto" };
|
||||||
|
_db.Accounts.AddRange(source, target);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
await _entryService.CreateTransferAsync(source.Id, target.Id, new DateTime(2026, 3, 15), "Umbuchung", 500m);
|
||||||
|
var booking = await _entryService.CreateEntryAsync(source.Id, EntryType.Income, new DateTime(2026, 3, 16), "Einzahlung", 50m);
|
||||||
|
|
||||||
|
booking.DisplayId.Should().Be("2026-002");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateEntry_UpdatesBookingFields()
|
||||||
|
{
|
||||||
|
var account = new Account { Name = "Konto" };
|
||||||
|
_db.Accounts.Add(account);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var entry = await _entryService.CreateEntryAsync(account.Id, EntryType.Expense, new DateTime(2026, 2, 2), "Alt", 10m);
|
||||||
|
|
||||||
|
await _entryService.UpdateEntryAsync(entry.Id, new DateTime(2026, 2, 3), "Neu", 25m);
|
||||||
|
|
||||||
|
_db.ChangeTracker.Clear();
|
||||||
|
var updated = await _db.Entries.SingleAsync(e => e.Id == entry.Id);
|
||||||
|
|
||||||
|
updated.Date.Should().Be(new DateTime(2026, 2, 3));
|
||||||
|
updated.Title.Should().Be("Neu");
|
||||||
|
updated.Amount.Should().Be(25m);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DeleteAndRestoreEntry_TogglesSoftDelete_ForBookingOnly()
|
||||||
|
{
|
||||||
|
var account = new Account { Name = "Konto" };
|
||||||
|
_db.Accounts.Add(account);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var entry = await _entryService.CreateEntryAsync(account.Id, EntryType.Expense, new DateTime(2026, 2, 2), "Buchung", 10m);
|
||||||
|
|
||||||
|
await _entryService.DeleteEntryAsync(entry.Id);
|
||||||
|
|
||||||
|
_db.ChangeTracker.Clear();
|
||||||
|
var deleted = await _db.Entries.SingleAsync(e => e.Id == entry.Id);
|
||||||
|
deleted.IsDeleted.Should().BeTrue();
|
||||||
|
|
||||||
|
await _entryService.RestoreEntryAsync(entry.Id);
|
||||||
|
|
||||||
|
_db.ChangeTracker.Clear();
|
||||||
|
var restored = await _db.Entries.SingleAsync(e => e.Id == entry.Id);
|
||||||
|
restored.IsDeleted.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_db.Database.CloseConnection();
|
||||||
|
_db.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestDbContextFactory : IDbContextFactory<FinanceDbContext>
|
||||||
|
{
|
||||||
|
private readonly DbContextOptions<FinanceDbContext> _options;
|
||||||
|
|
||||||
|
public TestDbContextFactory(DbContextOptions<FinanceDbContext> options)
|
||||||
|
{
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FinanceDbContext CreateDbContext() => new(_options);
|
||||||
|
|
||||||
|
public Task<FinanceDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default)
|
||||||
|
=> Task.FromResult(new FinanceDbContext(_options));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,11 +13,12 @@ public class TransferServiceTests : IDisposable
|
|||||||
private readonly FinanceDbContext _db;
|
private readonly FinanceDbContext _db;
|
||||||
private readonly EntryService _entryService;
|
private readonly EntryService _entryService;
|
||||||
private readonly BalanceQueryService _balanceQueryService;
|
private readonly BalanceQueryService _balanceQueryService;
|
||||||
|
private readonly string _connectionString = $"Data Source=duempelkas-transfer-tests-{Guid.NewGuid():N};Mode=Memory;Cache=Shared";
|
||||||
|
|
||||||
public TransferServiceTests()
|
public TransferServiceTests()
|
||||||
{
|
{
|
||||||
var options = new DbContextOptionsBuilder<FinanceDbContext>()
|
var options = new DbContextOptionsBuilder<FinanceDbContext>()
|
||||||
.UseSqlite("Data Source=duempelkas-transfer-tests;Mode=Memory;Cache=Shared")
|
.UseSqlite(_connectionString)
|
||||||
.Options;
|
.Options;
|
||||||
|
|
||||||
_db = new FinanceDbContext(options);
|
_db = new FinanceDbContext(options);
|
||||||
@@ -52,7 +53,8 @@ public class TransferServiceTests : IDisposable
|
|||||||
var targetEntry = entries.Single(e => e.AccountId == accountB.Id);
|
var targetEntry = entries.Single(e => e.AccountId == accountB.Id);
|
||||||
targetEntry.Type.Should().Be(EntryType.Income);
|
targetEntry.Type.Should().Be(EntryType.Income);
|
||||||
targetEntry.Amount.Should().Be(100.00m);
|
targetEntry.Amount.Should().Be(100.00m);
|
||||||
targetEntry.DisplayId.Should().Be("2026-002");
|
targetEntry.DisplayId.Should().Be("2026-001");
|
||||||
|
targetEntry.DisplayId.Should().Be(sourceEntry.DisplayId);
|
||||||
|
|
||||||
var links = await _db.TransferLinks.ToListAsync();
|
var links = await _db.TransferLinks.ToListAsync();
|
||||||
links.Should().HaveCount(1);
|
links.Should().HaveCount(1);
|
||||||
@@ -105,6 +107,29 @@ public class TransferServiceTests : IDisposable
|
|||||||
await act.Should().ThrowAsync<InvalidOperationException>();
|
await act.Should().ThrowAsync<InvalidOperationException>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task UpdateTransfer_ChangeLinkedAccount_KeepsSharedDisplayId()
|
||||||
|
{
|
||||||
|
var source = new Account { Name = "Barkasse" };
|
||||||
|
var initialTarget = new Account { Name = "Girokonto" };
|
||||||
|
var newTarget = new Account { Name = "Sparkonto" };
|
||||||
|
_db.Accounts.AddRange(source, initialTarget, newTarget);
|
||||||
|
await _db.SaveChangesAsync();
|
||||||
|
|
||||||
|
await _entryService.CreateTransferAsync(source.Id, initialTarget.Id, new DateTime(2026, 3, 15), "Umbuchung", 500.00m);
|
||||||
|
var sourceEntry = await _db.Entries.SingleAsync(e => e.AccountId == source.Id);
|
||||||
|
|
||||||
|
await _entryService.UpdateTransferAsync(sourceEntry.Id, newTarget.Id, new DateTime(2026, 3, 16), "Umbuchung angepasst", 550.00m);
|
||||||
|
|
||||||
|
_db.ChangeTracker.Clear();
|
||||||
|
|
||||||
|
var updatedSourceEntry = await _db.Entries.SingleAsync(e => e.AccountId == source.Id);
|
||||||
|
var updatedTargetEntry = await _db.Entries.SingleAsync(e => e.AccountId == newTarget.Id);
|
||||||
|
|
||||||
|
updatedSourceEntry.DisplayId.Should().Be("2026-001");
|
||||||
|
updatedTargetEntry.DisplayId.Should().Be(updatedSourceEntry.DisplayId);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_db.Database.CloseConnection();
|
_db.Database.CloseConnection();
|
||||||
|
|||||||
@@ -13,11 +13,12 @@ public class BalanceCalculationTests : IDisposable
|
|||||||
private readonly FinanceDbContext _db;
|
private readonly FinanceDbContext _db;
|
||||||
private readonly BalanceQueryService _balanceQueryService;
|
private readonly BalanceQueryService _balanceQueryService;
|
||||||
private readonly EntryService _entryService;
|
private readonly EntryService _entryService;
|
||||||
|
private readonly string _connectionString = $"Data Source=duempelkas-balance-tests-{Guid.NewGuid():N};Mode=Memory;Cache=Shared";
|
||||||
|
|
||||||
public BalanceCalculationTests()
|
public BalanceCalculationTests()
|
||||||
{
|
{
|
||||||
var options = new DbContextOptionsBuilder<FinanceDbContext>()
|
var options = new DbContextOptionsBuilder<FinanceDbContext>()
|
||||||
.UseSqlite("Data Source=duempelkas-balance-tests;Mode=Memory;Cache=Shared")
|
.UseSqlite(_connectionString)
|
||||||
.Options;
|
.Options;
|
||||||
|
|
||||||
_db = new FinanceDbContext(options);
|
_db = new FinanceDbContext(options);
|
||||||
|
|||||||
Reference in New Issue
Block a user