feat: implement set-based invoice payment calculations and enhance email logging configuration
Playwright Tests / test (push) Canceled after 0s
Playwright Tests / test (push) Canceled after 0s
This commit is contained in:
@@ -70,6 +70,7 @@ public class ProcessWebComServiceTests
|
||||
private sealed class CapturingComService : ProcessWebComService
|
||||
{
|
||||
public readonly List<(bool success, List<string> log)> Entries = new();
|
||||
public readonly List<string> Configs = new();
|
||||
|
||||
public CapturingComService(
|
||||
IOptions<ProcessWebComSettings> settings,
|
||||
@@ -83,6 +84,7 @@ public class ProcessWebComServiceTests
|
||||
DateTime sent, bool success, IEnumerable<string> errors)
|
||||
{
|
||||
Entries.Add((success, errors.ToList()));
|
||||
Configs.Add(config);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -267,6 +269,90 @@ public class ProcessWebComServiceTests
|
||||
Assert.Equal(when, pl.Single(p => p.ParameterName == "@DateSent").Value);
|
||||
}
|
||||
|
||||
// ── Audit-log config: fds__emaillog.config must be the JSON fds_wgd_lastEmails parses ─
|
||||
// Regression: the service logged config = "" on every path; OPENJSON('') throws, which took
|
||||
// the "last emails" dashboard widget down and lost who/what/attachments from the audit trail.
|
||||
[Fact]
|
||||
public void BuildAuditConfig_FullInput_ProducesLegacyShapeWithoutBody()
|
||||
{
|
||||
var files = new Dictionary<string, byte[]> { ["Rechnung R2026-0119.pdf"] = [1, 2], ["x.xml"] = [3] };
|
||||
|
||||
string json = ProcessWebComService.BuildAuditConfig(
|
||||
"SanitärFuchs - Rechnung", "buchhaltung@example.de", "Buchhaltung Erne", "chef@example.de", files.Keys);
|
||||
|
||||
var o = JObject.Parse(json);
|
||||
Assert.Equal("SanitärFuchs - Rechnung", (string?)o["subject"]);
|
||||
Assert.Equal("", (string?)o["from"]);
|
||||
Assert.Equal(new[] { "\"Buchhaltung Erne\" <buchhaltung@example.de>" }, o["to"]!.Select(t => (string?)t));
|
||||
Assert.Equal(new[] { "chef@example.de" }, o["cc"]!.Select(t => (string?)t));
|
||||
Assert.Empty(o["bcc"]!);
|
||||
Assert.Equal(new[] { "Rechnung R2026-0119.pdf", "x.xml" }, o["attachments"]!.Select(t => (string?)t));
|
||||
Assert.Null(o["body"]); // never the HTML body
|
||||
Assert.DoesNotContain("AQI=", json); // never attachment bytes (base64 of 1,2)
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, "a@b.de", "a@b.de")] // no name → bare address
|
||||
[InlineData("", "a@b.de", "a@b.de")]
|
||||
[InlineData(" ", "a@b.de", "a@b.de")]
|
||||
[InlineData("Ann", "a@b.de", "\"Ann\" <a@b.de>")] // name → quoted display form the widget unwraps
|
||||
[InlineData(" Ann ", "a@b.de", "\"Ann\" <a@b.de>")]
|
||||
public void BuildAuditConfig_RecipientName_FormatsToArrayEntry(string? name, string email, string expected)
|
||||
{
|
||||
var o = JObject.Parse(ProcessWebComService.BuildAuditConfig("S", email, name, null, null));
|
||||
|
||||
Assert.Equal(new[] { expected }, o["to"]!.Select(t => (string?)t));
|
||||
Assert.Empty(o["cc"]!);
|
||||
Assert.Empty(o["attachments"]!);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
public void BuildAuditConfig_NullOrEmptySubject_StillValidJson(string? subject)
|
||||
{
|
||||
var o = JObject.Parse(ProcessWebComService.BuildAuditConfig(subject!, "a@b.de", null, null, null));
|
||||
Assert.Equal("", (string?)o["subject"]);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> AllAuditPaths() =>
|
||||
[
|
||||
[ "success", HttpStatusCode.OK, true, "kunde@example.de" ],
|
||||
[ "api-error", HttpStatusCode.InternalServerError, true, "kunde@example.de" ],
|
||||
[ "disabled", HttpStatusCode.OK, false, "kunde@example.de" ],
|
||||
[ "invalid-email", HttpStatusCode.OK, true, "not-an-email" ],
|
||||
];
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(AllAuditPaths))]
|
||||
public async Task SendEmailAsync_EveryAuditPath_LogsParsableConfigWithRecipientSubjectAndAttachments(
|
||||
string path, HttpStatusCode status, bool enabled, string email)
|
||||
{
|
||||
var svc = CreateCapturing(new StubHandler(status), enabled: enabled);
|
||||
var att = new Dictionary<string, byte[]> { ["Mahnung.pdf"] = [9, 9] };
|
||||
|
||||
await svc.SendEmailAsync("inv_cfg_" + path, "Subj " + path, "<p>x</p>", email, "Kunde", att);
|
||||
|
||||
string config = Assert.Single(svc.Configs);
|
||||
var o = JObject.Parse(config); // was "" → OPENJSON error in fds_wgd_lastEmails
|
||||
Assert.Equal("Subj " + path, (string?)o["subject"]);
|
||||
Assert.Equal(new[] { $"\"Kunde\" <{email}>" }, o["to"]!.Select(t => (string?)t));
|
||||
Assert.Equal(new[] { "Mahnung.pdf" }, o["attachments"]!.Select(t => (string?)t));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendEmailAsync_OverrideRecipientSet_ConfigRecordsActualRecipientAndPrefixedSubject()
|
||||
{
|
||||
var svc = CreateCapturing(new StubHandler(HttpStatusCode.OK), overrideRecipient: "dev-inbox@example.test");
|
||||
|
||||
await svc.SendEmailAsync("inv_cfg_ovr", "Subject", "<p>x</p>", "kunde@example.de", "Kunde", null, cc: "cc@example.de");
|
||||
|
||||
var o = JObject.Parse(Assert.Single(svc.Configs));
|
||||
Assert.Equal(new[] { "\"Kunde\" <dev-inbox@example.test>" }, o["to"]!.Select(t => (string?)t)); // where it really went
|
||||
Assert.Empty(o["cc"]!); // cc is dropped by the override
|
||||
Assert.StartsWith("[DEV → kunde@example.de] ", (string?)o["subject"]); // original recipient stays traceable
|
||||
}
|
||||
|
||||
// ── Dev/test recipient override safety net ─────────────────────────────────
|
||||
[Fact]
|
||||
public async Task SendEmailAsync_OverrideRecipientSet_RedirectsToOverrideAddress()
|
||||
|
||||
Reference in New Issue
Block a user