Export/Ebics: Add Tests to validate against schemas and fix issues
This commit is contained in:
		| @@ -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> | ||||
|                     """); | ||||
|   | ||||
| @@ -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)); | ||||
|     } | ||||
|   | ||||
| @@ -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; | ||||
|             } | ||||
|   | ||||
							
								
								
									
										113
									
								
								Tests/HelperTests/EbicsTest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								Tests/HelperTests/EbicsTest.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -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)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user