Add ZIP-wrapped CAMT parsing and real bank file tests
Playwright Tests / test (push) Has been cancelled

- Support parsing camt.052 ZIP archives in CamtParser and BankingService
- Add CamtZipParserTests for ZIP parsing (single/multi/malformed/empty cases)
- Add CamtRealFileTests: integration tests for real bank exports (ZIP, XML, CAMT, MT940, STA) in git-ignored test data dir
- Update .gitignore and add README.md for test data usage
- Copy test data files in Fuchs.Tests.csproj; add .gitkeep
- Refactor BankingService CAMT mapping logic
- Overhaul connection string config: use {username}/{password} tokens, resolve from secrets at runtime, update appsettings and Key Vault keys
This commit is contained in:
Stefan
2026-07-01 22:06:29 +02:00
parent 8ecf97ed29
commit 3035ef1719
11 changed files with 507 additions and 45 deletions
+39
View File
@@ -36,6 +36,9 @@ public class Program
// Key Vault + DPAPI secret management (must run before FuchsOcmsIntranet.Initialize)
builder.AddSecretManagement();
// Assemble connection strings from templates + resolved credentials
AssembleConnectionStrings(builder.Configuration);
// Initialize the Fuchs intranet singleton with configuration
FuchsOcmsIntranet.Initialize(builder.Configuration);
@@ -163,4 +166,40 @@ public class Program
// One-time application start
Fuchs_intranet.SetPdfLicense();
}
/// <summary>
/// Replaces {username} and {password} tokens in connection string templates with the
/// resolved credential secrets, then overrides the config entries in-place.
/// When appsettings.Development.json supplies a complete connection string (no tokens),
/// the replace is a no-op and the original value is preserved.
/// </summary>
private static void AssembleConnectionStrings(ConfigurationManager config)
{
const string userToken = "{username}";
const string passToken = "{password}";
(string csName, string userKey, string passKey)[] pairs =
[
("ocms_ConnectionString", "ConnectionStrings:ocms_username", "ConnectionStrings:ocms_password"),
("fuchs_fds_ConnectionString", "ConnectionStrings:fuchs_fds_username", "ConnectionStrings:fuchs_fds_password"),
];
var overrides = new Dictionary<string, string?>();
foreach (var (csName, userKey, passKey) in pairs)
{
var template = config.GetConnectionString(csName);
if (string.IsNullOrEmpty(template)) continue;
if (!template.Contains(userToken, StringComparison.Ordinal) &&
!template.Contains(passToken, StringComparison.Ordinal)) continue;
var user = config[userKey] ?? "";
var pass = config[passKey] ?? "";
overrides[$"ConnectionStrings:{csName}"] = template
.Replace(userToken, user, StringComparison.Ordinal)
.Replace(passToken, pass, StringComparison.Ordinal);
}
if (overrides.Count > 0)
config.AddInMemoryCollection(overrides);
}
}
+39 -37
View File
@@ -1,4 +1,4 @@
using System.Data;
using System.Data;
using System.Diagnostics;
using CAMTParser;
using Fuchs.Observability;
@@ -45,10 +45,17 @@ public class BankingService : IBankingService
}
string format;
if (CamtParser.LooksLikeXml(bytes))
if (CamtParser.LooksLikeZip(bytes))
{
format = "camt.zip";
try { MapCamtEntries(tbl, new CamtParser().ParseZip(bytes)); }
catch (Exception ex) { _logger.LogError(ex, "CAMT ZIP statement parse failed."); }
}
else if (CamtParser.LooksLikeXml(bytes))
{
format = "camt";
FillFromCamt(tbl, bytes);
try { MapCamtEntries(tbl, new CamtParser().Parse(bytes)); }
catch (Exception ex) { _logger.LogError(ex, "CAMT statement parse failed."); }
}
else
{
@@ -129,53 +136,48 @@ public class BankingService : IBankingService
}
// ── CAMT (ISO 20022) ───────────────────────────────────────────────────────
private void FillFromCamt(DataTable tbl, byte[] bytes)
private void MapCamtEntries(DataTable tbl, List<CamtStatement> statements)
{
void SetNfo(DataRow nr, string key, object? value)
{
if (tbl.Columns.Contains(key) && value != null) nr[key] = value;
}
try
foreach (var stmt in statements)
{
var statements = new CamtParser().Parse(bytes);
foreach (var stmt in statements)
if (string.IsNullOrEmpty(stmt.AccountIdentification)) continue;
foreach (var e in stmt.Entries)
{
if (string.IsNullOrEmpty(stmt.AccountIdentification)) continue;
foreach (var e in stmt.Entries)
try
{
try
{
var nr = tbl.NewRow();
SetNfo(nr, "AccountIdentification", stmt.AccountIdentification);
if (e.Amount.HasValue) SetNfo(nr, "Amount", e.Amount);
if (e.EntryDate.HasValue) SetNfo(nr, "EntryDate", e.EntryDate);
if (e.ValueDate.HasValue) SetNfo(nr, "ValueDate", e.ValueDate);
SetNfo(nr, "FundsCode", e.Currency);
SetNfo(nr, "DebitCreditMark", e.MarkAbbreviation);
SetNfo(nr, "BankReference", e.BankReference);
SetNfo(nr, "EndToEndReference", e.EndToEndReference);
SetNfo(nr, "MandateReference", e.MandateReference);
SetNfo(nr, "CustomerReference", e.CustomerReference);
SetNfo(nr, "CreditorReference", e.CreditorReference);
SetNfo(nr, "AccountNumberOfPayer", e.CounterpartyIban);
SetNfo(nr, "BankCodeOfPayer", e.CounterpartyBic);
SetNfo(nr, "NameOfPayer", e.CounterpartyName);
SetNfo(nr, "PostingText", e.AdditionalInfo);
SetNfo(nr, "TransactionTypeIdCode",e.BankTransactionCode);
SetNfo(nr, "SepaRemittanceInformation",
string.IsNullOrEmpty(e.RemittanceUnstructured) ? e.RemittanceStructured : e.RemittanceUnstructured);
SetNfo(nr, "UnstructuredRemittanceInformation", e.RemittanceUnstructured);
SetNfo(nr, "UnstructuredData", e.RemittanceUnstructured);
SetNfo(nr, "IsUnstructuredData", e.IsUnstructuredData);
var nr = tbl.NewRow();
SetNfo(nr, "AccountIdentification", stmt.AccountIdentification);
if (e.Amount.HasValue) SetNfo(nr, "Amount", e.Amount);
if (e.EntryDate.HasValue) SetNfo(nr, "EntryDate", e.EntryDate);
if (e.ValueDate.HasValue) SetNfo(nr, "ValueDate", e.ValueDate);
SetNfo(nr, "FundsCode", e.Currency);
SetNfo(nr, "DebitCreditMark", e.MarkAbbreviation);
SetNfo(nr, "BankReference", e.BankReference);
SetNfo(nr, "EndToEndReference", e.EndToEndReference);
SetNfo(nr, "MandateReference", e.MandateReference);
SetNfo(nr, "CustomerReference", e.CustomerReference);
SetNfo(nr, "CreditorReference", e.CreditorReference);
SetNfo(nr, "AccountNumberOfPayer", e.CounterpartyIban);
SetNfo(nr, "BankCodeOfPayer", e.CounterpartyBic);
SetNfo(nr, "NameOfPayer", e.CounterpartyName);
SetNfo(nr, "PostingText", e.AdditionalInfo);
SetNfo(nr, "TransactionTypeIdCode",e.BankTransactionCode);
SetNfo(nr, "SepaRemittanceInformation",
string.IsNullOrEmpty(e.RemittanceUnstructured) ? e.RemittanceStructured : e.RemittanceUnstructured);
SetNfo(nr, "UnstructuredRemittanceInformation", e.RemittanceUnstructured);
SetNfo(nr, "UnstructuredData", e.RemittanceUnstructured);
SetNfo(nr, "IsUnstructuredData", e.IsUnstructuredData);
tbl.Rows.Add(nr);
}
catch (Exception ex) { _logger.LogWarning(ex, "CAMT entry parse error — account={Account}", stmt.AccountIdentification); }
tbl.Rows.Add(nr);
}
catch (Exception ex) { _logger.LogWarning(ex, "CAMT entry parse error — account={Account}", stmt.AccountIdentification); }
}
}
catch (Exception ex) { _logger.LogError(ex, "CAMT statement parse failed."); }
}
private static DataTable BuildDefaultSchema()
+5 -1
View File
@@ -1,4 +1,8 @@
{
{
"ConnectionStrings": {
"ocms_ConnectionString": "Server=localhost;Database=ocms;User Id=DEV_USERNAME;Password=DEV_PASSWORD;TrustServerCertificate=True;",
"fuchs_fds_ConnectionString": "Server=localhost;Database=fuchs_fds;User Id=DEV_USERNAME;Password=DEV_PASSWORD;TrustServerCertificate=True;"
},
"Logging": {
"LogLevel": {
"Default": "Debug",
+10 -4
View File
@@ -5,8 +5,10 @@
"CacheFilePath": "secrets.cache",
"SyncIntervalHours": 6,
"ManagedSecretKeys": [
"ConnectionStrings--ocms-ConnectionString",
"ConnectionStrings--fuchs-fds-ConnectionString",
"ConnectionStrings--ocms-username",
"ConnectionStrings--ocms-password",
"ConnectionStrings--fuchs-fds-username",
"ConnectionStrings--fuchs-fds-password",
"Fuchs--SMS-APIKey",
"Fuchs--Mailer--Token",
"Fuchs--fuchs-captcha-TOTP",
@@ -21,8 +23,12 @@
},
"AllowedHosts": "*",
"ConnectionStrings": {
"ocms_ConnectionString": "MANAGED_BY_KEYVAULT",
"fuchs_fds_ConnectionString": "MANAGED_BY_KEYVAULT"
"ocms_ConnectionString": "Server=DB_SERVER;Database=ocms;User Id={username};Password={password};TrustServerCertificate=True;",
"fuchs_fds_ConnectionString": "Server=DB_SERVER;Database=fuchs_fds;User Id={username};Password={password};TrustServerCertificate=True;",
"ocms_username": "MANAGED_BY_KEYVAULT",
"ocms_password": "MANAGED_BY_KEYVAULT",
"fuchs_fds_username": "MANAGED_BY_KEYVAULT",
"fuchs_fds_password": "MANAGED_BY_KEYVAULT"
},
"Fuchs": {
"ocms_guid": "00094b8f-a822-4e9c-b627-87802f93fca8",