diff --git a/src/Altinn.Notifications.Core/Enums/EmailNotificationResultType.cs b/src/Altinn.Notifications.Core/Enums/EmailNotificationResultType.cs
index fa4432a9..be84764b 100644
--- a/src/Altinn.Notifications.Core/Enums/EmailNotificationResultType.cs
+++ b/src/Altinn.Notifications.Core/Enums/EmailNotificationResultType.cs
@@ -16,7 +16,7 @@ public enum EmailNotificationResultType
Sending,
///
- /// Email notification sent and delivered
+ /// Email notification sent
///
Succeeded,
diff --git a/src/Altinn.Notifications.Core/Models/Notification/SendOperationResult.cs b/src/Altinn.Notifications.Core/Models/Notification/SendOperationResult.cs
index 3b2072f6..97a3ce67 100644
--- a/src/Altinn.Notifications.Core/Models/Notification/SendOperationResult.cs
+++ b/src/Altinn.Notifications.Core/Models/Notification/SendOperationResult.cs
@@ -1,6 +1,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models.Orders;
namespace Altinn.Notifications.Core.Models.Notification;
@@ -24,6 +25,21 @@ public class SendOperationResult
///
public EmailNotificationResultType? SendResult { get; set; }
+ ///
+ /// Json serializes the
+ ///
+ public string Serialize()
+ {
+ return JsonSerializer.Serialize(
+ this,
+ new JsonSerializerOptions
+ {
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ Converters = { new JsonStringEnumConverter() }
+ });
+ }
+
///
/// Deserialize a json string into the
///
diff --git a/src/Altinn.Notifications.Core/Repository/Interfaces/IEmailNotificationRepository.cs b/src/Altinn.Notifications.Core/Repository/Interfaces/IEmailNotificationRepository.cs
index e04d88b5..ab69ca75 100644
--- a/src/Altinn.Notifications.Core/Repository/Interfaces/IEmailNotificationRepository.cs
+++ b/src/Altinn.Notifications.Core/Repository/Interfaces/IEmailNotificationRepository.cs
@@ -22,9 +22,9 @@ public interface IEmailNotificationRepository
public Task> GetNewNotifications();
///
- /// Sets result status of an email notification
+ /// Sets result status of an email notification and update operation id
///
- public Task SetResultStatus(Guid notificationId, EmailNotificationResultType status);
+ public Task SetResultStatus(Guid notificationId, EmailNotificationResultType status, string? operationId);
///
/// Retrieves all email recipients for an order
diff --git a/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs b/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs
index ac305b7a..5c4ec57c 100644
--- a/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs
+++ b/src/Altinn.Notifications.Core/Services/EmailNotificationService.cs
@@ -64,15 +64,15 @@ public async Task SendNotifications()
bool success = await _producer.ProduceAsync(_emailQueueTopicName, email.Serialize());
if (!success)
{
- await _repository.SetResultStatus(email.NotificationId, EmailNotificationResultType.New);
+ await _repository.SetResultStatus(email.NotificationId, EmailNotificationResultType.New, null);
}
}
}
///
- public async Task UpdateStatusNotification(SendOperationResult sendOperationResult)
+ public async Task UpdateSendStatus(SendOperationResult sendOperationResult)
{
- await _repository.SetResultStatus(sendOperationResult.NotificationId, (EmailNotificationResultType)sendOperationResult.SendResult!);
+ await _repository.SetResultStatus(sendOperationResult.NotificationId, (EmailNotificationResultType)sendOperationResult.SendResult!, sendOperationResult.OperationId);
}
private async Task CreateNotificationForRecipient(Guid orderId, DateTime requestedSendTime, string recipientId, string toAddress, EmailNotificationResultType result)
diff --git a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationService.cs b/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationService.cs
index d695f448..39fcd4de 100644
--- a/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationService.cs
+++ b/src/Altinn.Notifications.Core/Services/Interfaces/IEmailNotificationService.cs
@@ -21,5 +21,5 @@ public interface IEmailNotificationService
///
/// Update send status for a notification
///
- public Task UpdateStatusNotification(SendOperationResult sendOperationResult);
+ public Task UpdateSendStatus(SendOperationResult sendOperationResult);
}
\ No newline at end of file
diff --git a/src/Altinn.Notifications.Integrations/Extensions/ServiceCollectionExtensions.cs b/src/Altinn.Notifications.Integrations/Extensions/ServiceCollectionExtensions.cs
index 256dc7de..44b54595 100644
--- a/src/Altinn.Notifications.Integrations/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Altinn.Notifications.Integrations/Extensions/ServiceCollectionExtensions.cs
@@ -32,6 +32,7 @@ public static void AddKafkaServices(this IServiceCollection services, IConfigura
.AddSingleton()
.AddHostedService()
.AddHostedService()
+ .AddHostedService()
.Configure(config.GetSection(nameof(KafkaSettings)));
}
diff --git a/src/Altinn.Notifications.Integrations/Kafka/Consumers/EmailStatusConsumer.cs b/src/Altinn.Notifications.Integrations/Kafka/Consumers/EmailStatusConsumer.cs
index a33cd2a4..8c9c5156 100644
--- a/src/Altinn.Notifications.Integrations/Kafka/Consumers/EmailStatusConsumer.cs
+++ b/src/Altinn.Notifications.Integrations/Kafka/Consumers/EmailStatusConsumer.cs
@@ -47,7 +47,7 @@ private async Task ProcessOrder(string message)
return;
}
- await _emailNotificationsService.UpdateStatusNotification(result);
+ await _emailNotificationsService.UpdateSendStatus(result);
}
private async Task RetryOrder(string message)
diff --git a/src/Altinn.Notifications.Persistence/Migration/v0.06/01-alter-tables.sql b/src/Altinn.Notifications.Persistence/Migration/v0.06/01-alter-tables.sql
index 7b715238..0558fecd 100644
--- a/src/Altinn.Notifications.Persistence/Migration/v0.06/01-alter-tables.sql
+++ b/src/Altinn.Notifications.Persistence/Migration/v0.06/01-alter-tables.sql
@@ -1,2 +1,2 @@
ALTER TABLE notifications.emailnotifications
-ADD COLUMN operationid text;
\ No newline at end of file
+ADD COLUMN IF NOT EXISTS operationid text;
\ No newline at end of file
diff --git a/src/Altinn.Notifications.Persistence/Repository/EmailNotificationRepository.cs b/src/Altinn.Notifications.Persistence/Repository/EmailNotificationRepository.cs
index 6265211d..aec6a832 100644
--- a/src/Altinn.Notifications.Persistence/Repository/EmailNotificationRepository.cs
+++ b/src/Altinn.Notifications.Persistence/Repository/EmailNotificationRepository.cs
@@ -75,11 +75,12 @@ public async Task> GetNewNotifications()
}
///
- public async Task SetResultStatus(Guid notificationId, EmailNotificationResultType status)
+ public async Task SetResultStatus(Guid notificationId, EmailNotificationResultType status, string? operationId)
{
await using NpgsqlCommand pgcom = _dataSource.CreateCommand(_setResultStatus);
pgcom.Parameters.AddWithValue(NpgsqlDbType.Uuid, notificationId);
pgcom.Parameters.AddWithValue(NpgsqlDbType.Text, status.ToString());
+ pgcom.Parameters.AddWithValue(NpgsqlDbType.Text, operationId ?? (object)DBNull.Value);
await pgcom.ExecuteNonQueryAsync();
}
diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/TestingConsumers/EmailStatusConsumerTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/TestingConsumers/EmailStatusConsumerTests.cs
new file mode 100644
index 00000000..1630bee0
--- /dev/null
+++ b/test/Altinn.Notifications.IntegrationTests/Notifications.Integrations/TestingConsumers/EmailStatusConsumerTests.cs
@@ -0,0 +1,75 @@
+using Altinn.Notifications.Core.Enums;
+using Altinn.Notifications.Core.Models.Notification;
+using Altinn.Notifications.Core.Models.Orders;
+using Altinn.Notifications.Integrations.Kafka.Consumers;
+using Altinn.Notifications.IntegrationTests.Utils;
+
+using Microsoft.Extensions.Hosting;
+
+using Xunit;
+
+namespace Altinn.Notifications.IntegrationTests.Notifications.Core.Consumers;
+
+public class EmailStatusConsumerTests : IDisposable
+{
+ private readonly string _statusUpdatedTopicName = Guid.NewGuid().ToString();
+ private readonly string _sendersRef = $"ref-{Guid.NewGuid()}";
+
+ [Fact]
+ public async Task RunTask_ConfirmExpectedSideEffects()
+ {
+ // Arrange
+ Dictionary vars = new()
+ {
+ { "KafkaSettings__EmailStatusUpdatedTopicName", _statusUpdatedTopicName },
+ { "KafkaSettings__Admin__TopicList", $"[\"{_statusUpdatedTopicName}\"]" }
+ };
+
+ using EmailStatusConsumer consumerService = (EmailStatusConsumer)ServiceUtil
+ .GetServices(new List() { typeof(IHostedService) }, vars)
+ .First(s => s.GetType() == typeof(EmailStatusConsumer))!;
+
+ (NotificationOrder Order, EmailNotification Notification) = await PostgreUtil.PopulateDBWithOrderAndEmailNotification(_sendersRef);
+
+ SendOperationResult sendOperationResult = new()
+ {
+ NotificationId = Notification.Id,
+ OperationId = Guid.NewGuid().ToString(),
+ SendResult = EmailNotificationResultType.Succeeded
+ };
+ await KafkaUtil.PublishMessageOnTopic(_statusUpdatedTopicName, sendOperationResult.Serialize());
+
+
+ // Act
+ await consumerService.StartAsync(CancellationToken.None);
+ await Task.Delay(10000);
+ await consumerService.StopAsync(CancellationToken.None);
+
+ // Assert
+
+ string emailNotificationStatus = await SelectEmailNotificationStatus(Notification.Id);
+ Assert.Equal(emailNotificationStatus, EmailNotificationResultType.Succeeded.ToString());
+
+ }
+
+ public async void Dispose()
+ {
+ await Dispose(true);
+
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual async Task Dispose(bool disposing)
+ {
+ string sql = $"delete from notifications.orders where sendersreference = '{_sendersRef}'";
+
+ await PostgreUtil.RunSql(sql);
+ await KafkaUtil.DeleteTopicAsync(_statusUpdatedTopicName);
+ }
+
+ private async Task SelectEmailNotificationStatus(Guid notificationId)
+ {
+ string sql = $"select result from notifications.emailnotifications where alternateid = '{notificationId}'";
+ return await PostgreUtil.RunSqlReturnStringOutput(sql);
+ }
+}
diff --git a/test/Altinn.Notifications.IntegrationTests/Utils/PostgreUtil.cs b/test/Altinn.Notifications.IntegrationTests/Utils/PostgreUtil.cs
index bedccd7d..c85ffb84 100644
--- a/test/Altinn.Notifications.IntegrationTests/Utils/PostgreUtil.cs
+++ b/test/Altinn.Notifications.IntegrationTests/Utils/PostgreUtil.cs
@@ -102,6 +102,6 @@ public static async Task RunSql(string query)
NpgsqlDataSource dataSource = (NpgsqlDataSource)ServiceUtil.GetServices(new List() { typeof(NpgsqlDataSource) })[0]!;
await using NpgsqlCommand pgcom = dataSource.CreateCommand(query);
- await pgcom.ExecuteNonQueryAsync();
+ pgcom.ExecuteNonQuery();
}
}
\ No newline at end of file
diff --git a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs
index c18a0666..9d19c3ab 100644
--- a/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs
+++ b/test/Altinn.Notifications.Tests/Notifications.Core/TestingServices/EmailNotificationServiceTests.cs
@@ -56,7 +56,7 @@ public async Task SendNotifications_ProducerReturnsFalse_RepositoryCalledToUpdat
.ReturnsAsync(new List() { _email });
repoMock
- .Setup(r => r.SetResultStatus(It.IsAny(), It.Is(t => t == EmailNotificationResultType.New)));
+ .Setup(r => r.SetResultStatus(It.IsAny(), It.Is(t => t == EmailNotificationResultType.New), It.IsAny()));
var producerMock = new Mock();
producerMock.Setup(p => p.ProduceAsync(It.Is(s => s.Equals(_emailQueueTopicName)), It.IsAny()))
@@ -136,6 +136,32 @@ public async Task CreateEmailNotification_ToAddressDefined_ResultFailedRecipient
repoMock.Verify();
}
+ [Fact]
+ public async Task UpdateStatusNotification()
+ {
+ // Arrange
+ Guid notificationid = Guid.NewGuid();
+ string operationId = Guid.NewGuid().ToString();
+
+ SendOperationResult sendOperationResult = new()
+ {
+ NotificationId = notificationid,
+ OperationId = operationId,
+ SendResult= EmailNotificationResultType.Succeeded
+ };
+
+ var repoMock = new Mock();
+ repoMock.Setup(r => r.SetResultStatus(It.Is(n => n == notificationid), It.Is(e => e == EmailNotificationResultType.Succeeded), It.Is(s => s.Equals(operationId))));
+
+ var service = GetTestService(repo: repoMock.Object);
+
+ // Act
+ await service.UpdateSendStatus(sendOperationResult);
+ // Assert
+
+ repoMock.Verify();
+ }
+
private static EmailNotificationService GetTestService(IEmailNotificationRepository? repo = null, IKafkaProducer? producer = null, Guid? guidOutput = null, DateTime? dateTimeOutput = null)
{
var guidService = new Mock();