Add ZIP-wrapped CAMT parsing and real bank file tests
Playwright Tests / test (push) Has been cancelled
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user