Complete DI migration: wire all business services end-to-end
Move the intranet off the static-helper / Active-Record pattern onto
constructor-injected services, removing controller coupling and the
sync-over-async (Task.Run().Wait()) hot spots in the data classes.
Services now registered and consumed via DI:
- IBankingService, IPdfService, IMfrClientFactory (singletons)
- IWidgetService, IReportService, IInvoiceService, IReminderService (scoped)
Key changes:
- FuchsWidgetService: real widget logic (sql_table/indicator/html +
rendering_options) ported from the static class, which is deleted.
- FuchsReportService + FuchsVisualization: report engine decoupled from
IntranetController (takes connStr/dbSec/userAccountId); static
FuchsReports deleted.
- InvoiceService / ReminderService: implement load/register/render/store
(previously NotImplementedException stubs). FdsInvoiceData /
FdsReminderData are now pure data holders — all DB + PDF work moved into
the services, async throughout (no Task.Run().Wait()).
- Controllers inject and call the services; all `new FdsMfrClient()` calls
go through IMfrClientFactory.
- Deleted dead code: static Banking, FuchsWidgets, FuchsReports, and the
unused IDbConnectionFactory.
- InternalsVisibleTo("Fuchs.Tests") for testing internal mapping logic.
Tests: 127 passing (Banking tests moved to the service; added data-holder
tests for FdsInvoiceData/FdsReminderData). Full solution builds clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+19
-14
@@ -1,17 +1,20 @@
|
||||
using System.Data;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Fuchs.intranet;
|
||||
using Fuchs.Services;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using programmersdigest.MT940Parser;
|
||||
using Xunit;
|
||||
|
||||
namespace Fuchs.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Banking helper robustness tests.
|
||||
/// Banking service robustness tests.
|
||||
/// </summary>
|
||||
public class BankingDebitCreditMarkTests
|
||||
{
|
||||
private static readonly BankingService Svc = new(NullLogger<BankingService>.Instance);
|
||||
|
||||
[Theory]
|
||||
[InlineData(DebitCreditMark.Credit, "C")]
|
||||
[InlineData(DebitCreditMark.Debit, "D")]
|
||||
@@ -19,18 +22,20 @@ public class BankingDebitCreditMarkTests
|
||||
[InlineData(DebitCreditMark.ReverseDebit, "RD")]
|
||||
public void DebitCreditMarkAbb_ReturnsExpected(DebitCreditMark mark, string expected)
|
||||
{
|
||||
Assert.Equal(expected, Banking.DebitCreditMarkAbb(mark));
|
||||
Assert.Equal(expected, Svc.DebitCreditMarkAbb(mark));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DebitCreditMarkAbb_UndefinedValue_ReturnsEmpty()
|
||||
{
|
||||
Assert.Equal("", Banking.DebitCreditMarkAbb((DebitCreditMark)999));
|
||||
Assert.Equal("", Svc.DebitCreditMarkAbb((DebitCreditMark)999));
|
||||
}
|
||||
}
|
||||
|
||||
public class BankingParseToDatatableTests
|
||||
{
|
||||
private static readonly BankingService Svc = new(NullLogger<BankingService>.Instance);
|
||||
|
||||
private static readonly string MinimalMT940 =
|
||||
"\r\n:20:STARTUMSE\r\n" +
|
||||
":25:DE12345678901234567890\r\n" +
|
||||
@@ -48,7 +53,7 @@ public class BankingParseToDatatableTests
|
||||
public void ParseToDatatable_ValidMT940_ReturnsOneRow()
|
||||
{
|
||||
using var stream = ToStream(MinimalMT940);
|
||||
var table = Banking.ParseToDatatable(stream);
|
||||
var table = Svc.ParseToDatatable(stream);
|
||||
Assert.Equal(1, table.Rows.Count);
|
||||
}
|
||||
|
||||
@@ -56,7 +61,7 @@ public class BankingParseToDatatableTests
|
||||
public void ParseToDatatable_ValidMT940_HasAccountColumn()
|
||||
{
|
||||
using var stream = ToStream(MinimalMT940);
|
||||
var table = Banking.ParseToDatatable(stream);
|
||||
var table = Svc.ParseToDatatable(stream);
|
||||
Assert.True(table.Columns.Contains("AccountIdentification"));
|
||||
Assert.Equal("DE12345678901234567890", table.Rows[0]["AccountIdentification"]);
|
||||
}
|
||||
@@ -65,7 +70,7 @@ public class BankingParseToDatatableTests
|
||||
public void ParseToDatatable_ValidMT940_HasAmountColumn()
|
||||
{
|
||||
using var stream = ToStream(MinimalMT940);
|
||||
var table = Banking.ParseToDatatable(stream);
|
||||
var table = Svc.ParseToDatatable(stream);
|
||||
Assert.True(table.Columns.Contains("Amount"));
|
||||
Assert.Equal(500m, table.Rows[0]["Amount"]);
|
||||
}
|
||||
@@ -74,7 +79,7 @@ public class BankingParseToDatatableTests
|
||||
public void ParseToDatatable_ValidMT940_HasDebitCreditMark()
|
||||
{
|
||||
using var stream = ToStream(MinimalMT940);
|
||||
var table = Banking.ParseToDatatable(stream);
|
||||
var table = Svc.ParseToDatatable(stream);
|
||||
Assert.Equal("C", table.Rows[0]["DebitCreditMark"]);
|
||||
}
|
||||
|
||||
@@ -82,7 +87,7 @@ public class BankingParseToDatatableTests
|
||||
public void ParseToDatatable_EmptyStream_ReturnsEmptyTable()
|
||||
{
|
||||
using var stream = ToStream("");
|
||||
var table = Banking.ParseToDatatable(stream);
|
||||
var table = Svc.ParseToDatatable(stream);
|
||||
Assert.Equal(0, table.Rows.Count);
|
||||
}
|
||||
|
||||
@@ -90,7 +95,7 @@ public class BankingParseToDatatableTests
|
||||
public void ParseToDatatable_EmptyStream_HasDefaultSchema()
|
||||
{
|
||||
using var stream = ToStream("");
|
||||
var table = Banking.ParseToDatatable(stream);
|
||||
var table = Svc.ParseToDatatable(stream);
|
||||
Assert.True(table.Columns.Contains("AccountIdentification"));
|
||||
Assert.True(table.Columns.Contains("Amount"));
|
||||
Assert.True(table.Columns.Contains("DebitCreditMark"));
|
||||
@@ -104,7 +109,7 @@ public class BankingParseToDatatableTests
|
||||
schema.Columns.Add("Amount", typeof(decimal));
|
||||
|
||||
using var stream = ToStream(MinimalMT940);
|
||||
var table = Banking.ParseToDatatable(stream, schemaDatatable: schema);
|
||||
var table = Svc.ParseToDatatable(stream, schemaDatatable: schema);
|
||||
Assert.Equal(2, table.Columns.Count);
|
||||
}
|
||||
|
||||
@@ -113,7 +118,7 @@ public class BankingParseToDatatableTests
|
||||
{
|
||||
var multi = MinimalMT940 + "\n" + MinimalMT940;
|
||||
using var stream = ToStream(multi);
|
||||
var table = Banking.ParseToDatatable(stream);
|
||||
var table = Svc.ParseToDatatable(stream);
|
||||
Assert.Equal(2, table.Rows.Count);
|
||||
}
|
||||
|
||||
@@ -121,7 +126,7 @@ public class BankingParseToDatatableTests
|
||||
public void ParseToDatatable_MalformedContent_DoesNotThrow()
|
||||
{
|
||||
using var stream = ToStream("This is not MT940 data at all");
|
||||
var ex = Record.Exception(() => Banking.ParseToDatatable(stream));
|
||||
var ex = Record.Exception(() => Svc.ParseToDatatable(stream));
|
||||
Assert.Null(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using Fuchs.intranet;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Fuchs.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the refactored data holders (FdsInvoiceData / FdsReminderData).
|
||||
/// These are now pure data objects — persistence/PDF moved to the DI services.
|
||||
/// </summary>
|
||||
public class FdsDataTests
|
||||
{
|
||||
[Fact]
|
||||
public void FdsInvoiceData_Empty_DefaultsToDraftWithNoRegistration()
|
||||
{
|
||||
var inv = new FdsInvoiceData();
|
||||
Assert.True(inv.IsDraft);
|
||||
Assert.Null(inv.InvoiceRegistration);
|
||||
Assert.Equal("", inv.Id);
|
||||
Assert.Equal("R", inv.InvoiceType); // fallback when no registration
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FdsInvoiceData_ParsesFormSections()
|
||||
{
|
||||
var jo = JObject.Parse(
|
||||
"{ \"admin\": { \"type\": \"R\", \"customerid\": \"42\" }, " +
|
||||
" \"new\": { \"title\": \"Test\", \"total_gross\": \"119.00\" }, " +
|
||||
" \"sms\": { \"tscn\": \"10\" }, " +
|
||||
" \"req\": [] }");
|
||||
var inv = new FdsInvoiceData(jo);
|
||||
|
||||
Assert.NotNull(inv.Admin);
|
||||
Assert.NotNull(inv.NewValues);
|
||||
Assert.NotNull(inv.Sms);
|
||||
Assert.Equal("R", inv.Admin!.getString("type"));
|
||||
Assert.Equal("Test", inv.NewValues!.getString("title"));
|
||||
Assert.True(inv.IsDraft);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FdsInvoiceData_BuildInvoiceParams_PicksHighestVatAndMapsFields()
|
||||
{
|
||||
// two line items with different VAT rates → highest (19) selected
|
||||
var jo = JObject.Parse(
|
||||
"{ \"admin\": { \"type\": \"R\", \"customerid\": \"7\", \"p13b\": false }, " +
|
||||
" \"new\": { \"title\": \"Bad\", \"total_gross\": \"119\", \"total_net\": \"100\", \"vat_19_net\": \"100\", \"paymentterm\": \"10d\" }, " +
|
||||
" \"req\": [ { \"items\": [ { \"vat\": \"7%\" }, { \"vat\": \"19%\" } ] } ] }");
|
||||
var inv = new FdsInvoiceData(jo);
|
||||
|
||||
var pl = inv.BuildInvoiceParams(change: false, invId: "");
|
||||
var vat = pl.Find(p => p.ParameterName == "@InvoiceVAT_1");
|
||||
var title = pl.Find(p => p.ParameterName == "@InvoiceTitle");
|
||||
|
||||
Assert.NotNull(vat);
|
||||
Assert.Equal("19", vat!.Value);
|
||||
Assert.Equal("Bad", title!.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FdsReminderData_Empty_DefaultsToDraft()
|
||||
{
|
||||
var rem = new FdsReminderData();
|
||||
Assert.True(rem.IsDraft);
|
||||
Assert.Null(rem.ReminderRegistration);
|
||||
Assert.Equal("", rem.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FdsReminderData_ParsesFormSections()
|
||||
{
|
||||
var jo = JObject.Parse(
|
||||
"{ \"new\": { \"amount\": \"50\", \"invoiceemail\": \"a@b.de\" }, " +
|
||||
" \"rem\": { \"type\": \"f\", \"invid\": \"123\" } }");
|
||||
var rem = new FdsReminderData(jo);
|
||||
|
||||
Assert.NotNull(rem.NewValues);
|
||||
Assert.NotNull(rem.Rem);
|
||||
Assert.Equal("50", rem.NewValues!.getString("amount"));
|
||||
Assert.Equal("f", rem.Rem!.getString("type"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user