From befe6a753b44081a601f765e8b60ecd53f2f5dd1 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner <lorenz.stechauner@necronda.net> Date: Wed, 31 Jan 2024 16:54:51 +0100 Subject: [PATCH] Export/Ebics: Add Tests to validate against schemas and fix issues --- Elwig/Helpers/Export/Ebics.cs | 23 ++-- Elwig/Models/Dtos/Transaction.cs | 3 +- Elwig/Windows/PaymentVariantsWindow.xaml.cs | 8 +- Tests/HelperTests/EbicsTest.cs | 113 ++++++++++++++++++++ 4 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 Tests/HelperTests/EbicsTest.cs diff --git a/Elwig/Helpers/Export/Ebics.cs b/Elwig/Helpers/Export/Ebics.cs index 8abb5b5..eeb15c7 100644 --- a/Elwig/Helpers/Export/Ebics.cs +++ b/Elwig/Helpers/Export/Ebics.cs @@ -2,12 +2,13 @@ using Elwig.Models.Dtos; using Elwig.Models.Entities; using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; namespace Elwig.Helpers.Export { - public class Ebics(PaymentVar variant, string filename) : IBankingExporter { + public class Ebics(PaymentVar variant, string filename, int version) : IBankingExporter { public static string FileExtension => "xml"; @@ -16,6 +17,7 @@ namespace Elwig.Helpers.Export { private readonly int Year = variant.Year; private readonly string Name = variant.Name; private readonly int AvNr = variant.AvNr; + private readonly int Version = version; public void Dispose() { GC.SuppressFinalize(this); @@ -32,6 +34,8 @@ namespace Elwig.Helpers.Export { } public async Task ExportAsync(IEnumerable<Transaction> transactions, IProgress<double>? progress = null) { + if (transactions.Any(tx => tx.Amount < 0)) + throw new ArgumentException("Tranaction amount may not be negative"); progress?.Report(0.0); var nbOfTxs = transactions.Count(); int count = nbOfTxs + 2, i = 0; @@ -41,9 +45,7 @@ namespace Elwig.Helpers.Export { await Writer.WriteLineAsync($""" <?xml version="1.0" encoding="UTF-8"?> - <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09 pain.001.001.09.xsd"> + <Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.{Version:00}"> <CstmrCdtTrfInitn> <GrpHdr> <MsgId>{msgId}</MsgId> @@ -57,10 +59,10 @@ namespace Elwig.Helpers.Export { <PmtMtd>TRF</PmtMtd> <NbOfTxs>{nbOfTxs}</NbOfTxs> <CtrlSum>{Transaction.FormatAmount(ctrlSum)}</CtrlSum> - <ReqdExctnDt><Dt>{Date:yyyy-MM-dd}</Dt></ReqdExctnDt> + <ReqdExctnDt>{(Version >= 8 ? "<Dt>" : "")}{Date:yyyy-MM-dd}{(Version >= 8 ? "</Dt>" : "")}</ReqdExctnDt> <Dbtr><Nm>{App.Client.NameFull}</Nm></Dbtr> - <DbtrAcct><Id><IBAN>{App.Client.Iban?.Replace(" ", "")}</IBAN></Id></DbtrAcct> - <DbtrAgt><FinInstnId><BICFI>{App.Client.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></DbtrAgt> + <DbtrAcct><Id><IBAN>{App.Client.Iban!.Replace(" ", "")}</IBAN></Id></DbtrAcct> + <DbtrAgt><FinInstnId>{(Version >= 4 ? "<BICFI>" : "<BIC>")}{App.Client.Bic ?? "NOTPROVIDED"}{(Version >= 4 ? "</BICFI>" : "</BIC>")}</FinInstnId></DbtrAgt> """); progress?.Report(100.0 * ++i / count); @@ -74,15 +76,14 @@ namespace Elwig.Helpers.Export { <PmtId><EndToEndId>{id}</EndToEndId></PmtId> <Amt><InstdAmt Ccy="{tx.Currency}">{Transaction.FormatAmount(tx.Amount)}</InstdAmt></Amt> <Cdtr> - <Nm>{a.Name}</Nm> + <Nm>{a.Name[..Math.Min(140, a.Name.Length)]}</Nm> <PstlAdr> - <StrtNm>{a1}</StrtNm><BldgNb>{a2}</BldgNb> + <StrtNm>{a1?[..Math.Min(70, a1.Length)]}</StrtNm><BldgNb>{a2?[..Math.Min(16, a2.Length)]}</BldgNb> <PstCd>{a.PostalDest.AtPlz?.Plz}</PstCd><TwnNm>{a.PostalDest.AtPlz?.Ort.Name}</TwnNm> <Ctry>{a.PostalDest.Country.Alpha2}</Ctry> </PstlAdr> </Cdtr> - <CdtrAcct><Id><IBAN>{tx.Member.Iban}</IBAN></Id></CdtrAcct> - <CdtrAgt><FinInstnId><BICFI>{tx.Member.Bic ?? "NOTPROVIDED"}</BICFI></FinInstnId></CdtrAgt> + <CdtrAcct><Id><IBAN>{tx.Member.Iban!}</IBAN></Id></CdtrAcct> <RmtInf><Ustrd>{info}</Ustrd></RmtInf> </CdtTrfTxInf> """); diff --git a/Elwig/Models/Dtos/Transaction.cs b/Elwig/Models/Dtos/Transaction.cs index 860bc39..4cac4bf 100644 --- a/Elwig/Models/Dtos/Transaction.cs +++ b/Elwig/Models/Dtos/Transaction.cs @@ -1,4 +1,5 @@ using Elwig.Models.Entities; +using System; using System.Collections.Generic; using System.Linq; @@ -19,7 +20,7 @@ namespace Elwig.Models.Dtos { .ToList(); } - public static string FormatAmountCent(long cents) => $"{cents / 100}.{cents % 100:00}"; + public static string FormatAmountCent(long cents) => $"{cents / 100}.{Math.Abs(cents % 100):00}"; public static string FormatAmount(decimal amount) => FormatAmountCent((int)(amount * 100)); } diff --git a/Elwig/Windows/PaymentVariantsWindow.xaml.cs b/Elwig/Windows/PaymentVariantsWindow.xaml.cs index 50d7dd4..479a775 100644 --- a/Elwig/Windows/PaymentVariantsWindow.xaml.cs +++ b/Elwig/Windows/PaymentVariantsWindow.xaml.cs @@ -307,8 +307,12 @@ namespace Elwig.Windows { if (d.ShowDialog() == true) { ExportButton.IsEnabled = false; Mouse.OverrideCursor = Cursors.AppStarting; - using var e = new Ebics(v, d.FileName); - await e.ExportAsync(Transaction.FromPaymentVariant(v)); + try { + using var e = new Ebics(v, d.FileName, 9); + await e.ExportAsync(Transaction.FromPaymentVariant(v)); + } catch (Exception exc) { + MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } Mouse.OverrideCursor = null; ExportButton.IsEnabled = true; } diff --git a/Tests/HelperTests/EbicsTest.cs b/Tests/HelperTests/EbicsTest.cs new file mode 100644 index 0000000..d84280b --- /dev/null +++ b/Tests/HelperTests/EbicsTest.cs @@ -0,0 +1,113 @@ +using Elwig.Helpers; +using Elwig.Helpers.Export; +using Elwig.Models.Dtos; +using Elwig.Models.Entities; +using System.Reflection; +using System.Xml; + +namespace Tests.HelperTests { + // see https://www.iso20022.org/iso-20022-message-definitions + // and https://www.iso20022.org/catalogue-messages/iso-20022-messages-archive?search=pain + [TestFixture] + public class EbicsTest { + + public static readonly string FileName = Path.Combine(Path.GetTempPath(), "test_ebics.xml"); + public static readonly string Iban = "AT123456789012345678"; + + private static void ValidateSchema(string xmlPath, int version) { + XmlDocument xml = new(); + xml.Load(xmlPath); + var schema = new XmlTextReader(Assembly.GetExecutingAssembly() + .GetManifestResourceStream($"Tests.Resources.Schemas.pain.001.001.{version:00}.xsd")!); + xml.Schemas.Add(null, schema); + xml.Validate(null); + } + + private static async Task CreateXmlFile(int version) { + var v = new PaymentVar() { + Year = 2020, + AvNr = 1, + Name = "Endauszahlung", + TransferDate = new DateOnly(2021, 6, 15), + }; + using var ctx = new AppDbContext(); + var members = ctx.Members.ToList(); + Assert.That(members, Has.Count.GreaterThan(0)); + using var exporter = new Ebics(v, FileName, version); + await exporter.ExportAsync(members.Select(m => new Transaction(m, 1234.56m, "EUR", m.MgNr % 100))); + } + + [TearDown] + public static void RemoveXmlFile() { + File.Delete(FileName); + } + + [Test] + [Ignore("Version has no need to be supported")] + public async Task Test_CustomerCreditTransferInitiationV01() { + await CreateXmlFile(1); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 1)); + } + + [Test] + [Ignore("Version has no need to be supported")] + public async Task Test_CustomerCreditTransferInitiationV02() { + await CreateXmlFile(2); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 2)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV03() { + await CreateXmlFile(3); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 3)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV04() { + await CreateXmlFile(4); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 4)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV05() { + await CreateXmlFile(5); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 5)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV06() { + await CreateXmlFile(6); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 6)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV07() { + await CreateXmlFile(7); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 7)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV08() { + await CreateXmlFile(8); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 8)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV09() { + await CreateXmlFile(9); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 9)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV10() { + await CreateXmlFile(10); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 10)); + } + + [Test] + public async Task Test_CustomerCreditTransferInitiationV11() { + await CreateXmlFile(11); + Assert.DoesNotThrow(() => ValidateSchema(FileName, 11)); + } + } +}