From 9635c8a641ff7c69c70aa9490cf9bf729d91e00b Mon Sep 17 00:00:00 2001 From: Donald Gray Date: Mon, 22 Jun 2026 17:21:32 +0100 Subject: [PATCH 1/2] Update message completed format Include Created date. Use camelCase props. Don't write nulls --- .../Jobs/TextBuildJob.cs | 4 ++-- .../JobCompletionNotification.cs | 1 + .../Services/Notifications/SnsJobNotifier.cs | 10 ++++++++- .../BuilderApi/SnsJobNotifierTests.cs | 22 ++++++++++++++++++- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/TextServices.Builder.Api/Jobs/TextBuildJob.cs b/src/TextServices.Builder.Api/Jobs/TextBuildJob.cs index 95b4c2d..68b38a7 100644 --- a/src/TextServices.Builder.Api/Jobs/TextBuildJob.cs +++ b/src/TextServices.Builder.Api/Jobs/TextBuildJob.cs @@ -82,7 +82,7 @@ public async Task ExecuteAsync(string jobId, IJobCancellationToken cancellationT jobId, wordCount, imageCount, errors.Count); await jobNotifier.Notify( - new JobCompletionNotification(job.Id, job.Status, job.Finished, + new JobCompletionNotification(job.Id, job.Status, job.Created, job.Finished, job.TotalPages, job.TotalWordCount, job.Errors), cancellationToken.ShutdownToken); } @@ -96,7 +96,7 @@ await jobNotifier.Notify( await db.SaveChangesAsync(); await jobNotifier.Notify( - new JobCompletionNotification(job.Id, job.Status, job.Finished, + new JobCompletionNotification(job.Id, job.Status, job.Created, job.Finished, job.TotalPages, job.TotalWordCount, job.Errors), cancellationToken.ShutdownToken); } diff --git a/src/TextServices.Builder.Api/Services/Notifications/JobCompletionNotification.cs b/src/TextServices.Builder.Api/Services/Notifications/JobCompletionNotification.cs index 45638c7..c6a8ab5 100644 --- a/src/TextServices.Builder.Api/Services/Notifications/JobCompletionNotification.cs +++ b/src/TextServices.Builder.Api/Services/Notifications/JobCompletionNotification.cs @@ -5,6 +5,7 @@ namespace TextServices.Builder.Api.Services.Notifications; public record JobCompletionNotification( string JobId, JobStatus Status, + DateTimeOffset? Created, DateTimeOffset? Finished, int TotalPages, int TotalWordCount, diff --git a/src/TextServices.Builder.Api/Services/Notifications/SnsJobNotifier.cs b/src/TextServices.Builder.Api/Services/Notifications/SnsJobNotifier.cs index 3391275..24e4fc8 100644 --- a/src/TextServices.Builder.Api/Services/Notifications/SnsJobNotifier.cs +++ b/src/TextServices.Builder.Api/Services/Notifications/SnsJobNotifier.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Text.Json.Serialization; using Amazon.SimpleNotificationService; using Amazon.SimpleNotificationService.Model; using Microsoft.Extensions.Options; @@ -12,6 +13,13 @@ internal sealed class SnsJobNotifier( IOptions options, ILogger logger) : IJobNotifier { + private static readonly JsonSerializerOptions SerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = { new JsonStringEnumConverter() }, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + }; + public async Task Notify(JobCompletionNotification notification, CancellationToken ct = default) { var topicArn = options.Value.Notifications.TopicArn; @@ -25,7 +33,7 @@ public async Task Notify(JobCompletionNotification notification, CancellationTok await sns.PublishAsync(new PublishRequest { TopicArn = topicArn, - Message = JsonSerializer.Serialize(notification), + Message = JsonSerializer.Serialize(notification, SerializerOptions), MessageAttributes = new Dictionary { ["MessageType"] = new() diff --git a/src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs b/src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs index 4efcf60..1b666a1 100644 --- a/src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs +++ b/src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs @@ -15,7 +15,7 @@ public sealed class SnsJobNotifierTests private static JobCompletionNotification MakeNotification( string jobId = "my/job", JobStatus status = JobStatus.Completed) => - new(jobId, status, DateTimeOffset.UtcNow, 10, 500, null); + new(jobId, status, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 10, 500, null); // ------------------------------------------------------------------------- // No-op when TopicArn is absent @@ -78,6 +78,26 @@ public async Task Notify_SetsMessageTypeAttribute(JobStatus status, string expec .MustHaveHappenedOnceExactly(); } + [Fact] + public async Task Notify_SerialisesMessageWithCamelCaseStringEnumAndOmitsNullErrors() + { + var sns = A.Fake(); + PublishRequest? captured = null; + A.CallTo(() => sns.PublishAsync(A._, A._)) + .Invokes((PublishRequest r, CancellationToken _) => captured = r) + .Returns(new PublishResponse()); + + var sut = MakeNotifier(sns, "arn:aws:sns:eu-west-1:123:test-topic"); + + await sut.Notify(MakeNotification("books/b123", JobStatus.Waiting)); + + captured.ShouldNotBeNull(); + var message = captured.Message; + message.ShouldContain("\"jobId\":\"books/b123\""); + message.ShouldContain("\"status\":\"Waiting\""); + message.ShouldNotContain("errors"); + } + // ------------------------------------------------------------------------- // Error handling // ------------------------------------------------------------------------- From ca4fda0e88564e0272ff93244662dbe781cb934f Mon Sep 17 00:00:00 2001 From: Donald Gray Date: Mon, 22 Jun 2026 17:22:19 +0100 Subject: [PATCH 2/2] Downgrade mediatr + remove "created" from notif Created field is when job was created, which can be fetched --- src/TextServices.Builder.Api/Jobs/TextBuildJob.cs | 4 ++-- .../Services/Notifications/JobCompletionNotification.cs | 1 - src/TextServices.Builder.Api/TextServices.Builder.Api.csproj | 2 +- src/TextServices.Search.Api/TextServices.Search.Api.csproj | 2 +- src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/TextServices.Builder.Api/Jobs/TextBuildJob.cs b/src/TextServices.Builder.Api/Jobs/TextBuildJob.cs index 68b38a7..95b4c2d 100644 --- a/src/TextServices.Builder.Api/Jobs/TextBuildJob.cs +++ b/src/TextServices.Builder.Api/Jobs/TextBuildJob.cs @@ -82,7 +82,7 @@ public async Task ExecuteAsync(string jobId, IJobCancellationToken cancellationT jobId, wordCount, imageCount, errors.Count); await jobNotifier.Notify( - new JobCompletionNotification(job.Id, job.Status, job.Created, job.Finished, + new JobCompletionNotification(job.Id, job.Status, job.Finished, job.TotalPages, job.TotalWordCount, job.Errors), cancellationToken.ShutdownToken); } @@ -96,7 +96,7 @@ await jobNotifier.Notify( await db.SaveChangesAsync(); await jobNotifier.Notify( - new JobCompletionNotification(job.Id, job.Status, job.Created, job.Finished, + new JobCompletionNotification(job.Id, job.Status, job.Finished, job.TotalPages, job.TotalWordCount, job.Errors), cancellationToken.ShutdownToken); } diff --git a/src/TextServices.Builder.Api/Services/Notifications/JobCompletionNotification.cs b/src/TextServices.Builder.Api/Services/Notifications/JobCompletionNotification.cs index c6a8ab5..45638c7 100644 --- a/src/TextServices.Builder.Api/Services/Notifications/JobCompletionNotification.cs +++ b/src/TextServices.Builder.Api/Services/Notifications/JobCompletionNotification.cs @@ -5,7 +5,6 @@ namespace TextServices.Builder.Api.Services.Notifications; public record JobCompletionNotification( string JobId, JobStatus Status, - DateTimeOffset? Created, DateTimeOffset? Finished, int TotalPages, int TotalWordCount, diff --git a/src/TextServices.Builder.Api/TextServices.Builder.Api.csproj b/src/TextServices.Builder.Api/TextServices.Builder.Api.csproj index 2d4814a..7a1c499 100644 --- a/src/TextServices.Builder.Api/TextServices.Builder.Api.csproj +++ b/src/TextServices.Builder.Api/TextServices.Builder.Api.csproj @@ -16,7 +16,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/TextServices.Search.Api/TextServices.Search.Api.csproj b/src/TextServices.Search.Api/TextServices.Search.Api.csproj index 9a111c4..2fa1628 100644 --- a/src/TextServices.Search.Api/TextServices.Search.Api.csproj +++ b/src/TextServices.Search.Api/TextServices.Search.Api.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs b/src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs index 1b666a1..18ce0e6 100644 --- a/src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs +++ b/src/TextServices.Tests/BuilderApi/SnsJobNotifierTests.cs @@ -15,7 +15,7 @@ public sealed class SnsJobNotifierTests private static JobCompletionNotification MakeNotification( string jobId = "my/job", JobStatus status = JobStatus.Completed) => - new(jobId, status, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 10, 500, null); + new(jobId, status, DateTimeOffset.UtcNow, 10, 500, null); // ------------------------------------------------------------------------- // No-op when TopicArn is absent