Skip to content

Commit

Permalink
API endpoint for sms notification summaries #GCPActive (#418)
Browse files Browse the repository at this point in the history
  • Loading branch information
acn-sbuad authored Feb 9, 2024
1 parent 263f9fa commit b081211
Show file tree
Hide file tree
Showing 9 changed files with 679 additions and 2 deletions.
61 changes: 61 additions & 0 deletions src/Altinn.Notifications/Controllers/SmsNotificationsController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Altinn.Notifications.Configuration;
using Altinn.Notifications.Core.Models.Notification;
using Altinn.Notifications.Core.Services.Interfaces;
using Altinn.Notifications.Core.Shared;
using Altinn.Notifications.Extensions;
using Altinn.Notifications.Mappers;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

using Swashbuckle.AspNetCore.Annotations;

namespace Altinn.Notifications.Controllers
{
/// <summary>
/// Controller for all operations related to sms notifications
/// </summary>
[Authorize(Policy = AuthorizationConstants.POLICY_CREATE_SCOPE_OR_PLATFORM_ACCESS)]
[Route("notifications/api/v1/orders/{id}/notifications/sms")]
[ApiController]
[SwaggerResponse(401, "Caller is unauthorized")]
[SwaggerResponse(403, "Caller is not authorized to access the requested resource")]
public class SmsNotificationsController : ControllerBase
{
private readonly ISmsNotificationSummaryService _summaryService;

/// <summary>
/// Initializes a new instance of the <see cref="SmsNotificationsController"/> class.
/// </summary>
/// <param name="summaryService">The notifications summary service</param>
public SmsNotificationsController(ISmsNotificationSummaryService summaryService)
{
_summaryService = summaryService;
}

/// <summary>
/// Endpoint for retrieving a summary of all sms notifications related to an order
/// </summary>
/// <param name="id">The order id</param>
/// <returns>Sumarized order details and a list containing all sms notifications and their send status</returns>
[HttpGet]
[Produces("application/json")]
[SwaggerResponse(200, "The notification order was accepted", typeof(SmsNotificationSummaryExt))]
[SwaggerResponse(404, "No notification order mathching the id was found")]
public async Task<ActionResult<SmsNotificationSummaryExt>> Get([FromRoute] Guid id)
{
string? expectedCreator = HttpContext.GetOrg();

if (expectedCreator == null)
{
return Forbid();
}

Result<SmsNotificationSummary, ServiceError> result = await _summaryService.GetSummary(id, expectedCreator);

return result.Match(
summary => Ok(summary.MapToSmsNotificationSummaryExt()),
error => StatusCode(error.ErrorCode, error.ErrorMessage));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Altinn.Notifications.Mappers
/// <summary>
/// Mapper for <see cref="EmailNotificationSummaryExt"/>
/// </summary>
public static class NotificationSummaryMapper
public static class EmailNotificationSummaryMapper
{
/// <summary>
/// Maps a <see cref="EmailNotificationSummary"/> to a <see cref="EmailNotificationSummaryExt"/>
Expand Down
57 changes: 57 additions & 0 deletions src/Altinn.Notifications/Mappers/SmsNotificationSummaryMapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Altinn.Notifications.Core.Models.Notification;

namespace Altinn.Notifications.Mappers
{
/// <summary>
/// Mapper for <see cref="SmsNotificationSummaryExt"/>
/// </summary>
public static class SmsNotificationSummaryMapper
{
/// <summary>
/// Maps a <see cref="SmsNotificationSummary"/> to a <see cref="SmsNotificationSummaryExt"/>
/// </summary>
public static SmsNotificationSummaryExt MapToSmsNotificationSummaryExt(this SmsNotificationSummary summary)
{
return new SmsNotificationSummaryExt()
{
OrderId = summary.OrderId,
SendersReference = summary.SendersReference,
Generated = summary.Generated,
Succeeded = summary.Succeeded,
Notifications = summary.Notifications.MapToSmsNotificationWithResultExt(),
};
}

/// <summary>
/// Maps a list of <see cref="SmsNotificationWithResult"/> to a list of <see cref="SmsNotificationWithResultExt"/>
/// </summary>
public static List<SmsNotificationWithResultExt> MapToSmsNotificationWithResultExt(this List<SmsNotificationWithResult> notifications)
{
List<SmsNotificationWithResultExt> result = notifications.Select(n => n.MapToSmsNotificationWithResultExt()).ToList();

return result;
}

/// <summary>
/// Maps a <see cref="SmsNotificationWithResult"/> to a <see cref="SmsNotificationWithResultExt"/>
/// </summary>
public static SmsNotificationWithResultExt MapToSmsNotificationWithResultExt(this SmsNotificationWithResult notification)
{
return new SmsNotificationWithResultExt()
{
Id = notification.Id,
Succeeded = notification.Succeeded,
Recipient = new()
{
MobileNumber = notification.Recipient.MobileNumber
},
SendStatus = new()
{
Status = notification.ResultStatus.Result.ToString(),
StatusDescription = notification.ResultStatus.ResultDescription,
LastUpdate = notification.ResultStatus.ResultTime
}
};
}
}
}
43 changes: 43 additions & 0 deletions src/Altinn.Notifications/Models/SmsNotificationSummaryExt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Text.Json.Serialization;

namespace Altinn.Notifications.Core.Models.Notification
{
/// <summary>
/// A class representing an sms notification summary
/// </summary>
/// <remarks>
/// External representaion to be used in the API.
/// </remarks>
public class SmsNotificationSummaryExt
{
/// <summary>
/// The order id
/// </summary>
[JsonPropertyName("orderId")]
public Guid OrderId { get; set; }

/// <summary>
/// The senders reference
/// </summary>
[JsonPropertyName("sendersReference")]
public string? SendersReference { get; set; }

/// <summary>
/// The number of generated sms notifications
/// </summary>
[JsonPropertyName("generated")]
public int Generated { get; set; }

/// <summary>
/// The number of sms notifications that were sent successfully
/// </summary>
[JsonPropertyName("succeeded")]
public int Succeeded { get; set; }

/// <summary>
/// A list of notifications with send result
/// </summary>
[JsonPropertyName("notifications")]
public List<SmsNotificationWithResultExt> Notifications { get; set; } = [];
}
}
39 changes: 39 additions & 0 deletions src/Altinn.Notifications/Models/SmsNotificationWithResultExt.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Text.Json.Serialization;

using Altinn.Notifications.Models;

namespace Altinn.Notifications.Core.Models.Notification
{
/// <summary>
/// A class representing an sms notification with result
/// </summary>
/// <remarks>
/// External representaion to be used in the API.
/// </remarks>
public class SmsNotificationWithResultExt
{
/// <summary>
/// The notification id
/// </summary>
[JsonPropertyName("id")]
public Guid Id { get; set; }

/// <summary>
/// Boolean indicating if the sending of the notification was successful
/// </summary>
[JsonPropertyName("succeeded")]
public bool Succeeded { get; set; }

/// <summary>
/// The recipient of the notification
/// </summary>
[JsonPropertyName("recipient")]
public RecipientExt Recipient { get; set; } = new();

/// <summary>
/// The result status of the notification
/// </summary>
[JsonPropertyName("sendStatus")]
public StatusExt SendStatus { get; set; } = new();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;

using Altinn.Common.AccessToken.Services;
using Altinn.Notifications.Core.Models.Notification;
using Altinn.Notifications.Core.Models.Orders;
using Altinn.Notifications.IntegrationTests.Utils;
using Altinn.Notifications.Tests.Notifications.Mocks.Authentication;
using Altinn.Notifications.Tests.Notifications.Utils;

using AltinnCore.Authentication.JwtCookie;

using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Logging;

using Xunit;

namespace Altinn.Notifications.IntegrationTests.Notifications.SmsNotificationsController
{
public class GetTests : IClassFixture<IntegrationTestWebApplicationFactory<Controllers.SmsNotificationsController>>, IAsyncLifetime
{
private readonly string _basePath;
private readonly IntegrationTestWebApplicationFactory<Controllers.SmsNotificationsController> _factory;
private readonly List<Guid> _orderIdsToDelete;

public GetTests(IntegrationTestWebApplicationFactory<Controllers.SmsNotificationsController> factory)
{
_basePath = $"/notifications/api/v1/orders";
_factory = factory;
_orderIdsToDelete = new List<Guid>();
}

public async Task InitializeAsync()
{
await Task.CompletedTask;
}

async Task IAsyncLifetime.DisposeAsync()
{
if (_orderIdsToDelete.Count != 0)
{
string deleteSql = $@"DELETE from notifications.orders o where o.alternateid in ('{string.Join("','", _orderIdsToDelete)}')";
await PostgreUtil.RunSql(deleteSql);
}
}

[Fact]
public async Task Get_NonExistingOrder_NotFound()
{
// Arrange
string uri = $"{_basePath}/{Guid.NewGuid()}/notifications/sms";

HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:serviceowner/notifications.create"));

HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri);

// Act
HttpResponseMessage response = await client.SendAsync(httpRequestMessage);

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task Get_OrderIdForAnotherCreator_NotFound()
{
// Arrange
NotificationOrder order = await PostgreUtil.PopulateDBWithSmsOrder();
_orderIdsToDelete.Add(order.Id);

string uri = $"{_basePath}/{order.Id}/notifications/sms";

HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("nav", scope: "altinn:serviceowner/notifications.create"));

HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri);

// Act
HttpResponseMessage response = await client.SendAsync(httpRequestMessage);

// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

[Fact]
public async Task Get_ValidOrderId_Ok()
{
// Arrange
(NotificationOrder order, SmsNotification notification) = await PostgreUtil.PopulateDBWithOrderAndSmsNotification();
string uri = $"{_basePath}/{order.Id}/notifications/sms";
_orderIdsToDelete.Add(order.Id);

HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:serviceowner/notifications.create"));

HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri);

// Act
HttpResponseMessage response = await client.SendAsync(httpRequestMessage);
string responseString = await response.Content.ReadAsStringAsync();

// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
SmsNotificationSummaryExt? summary = JsonSerializer.Deserialize<SmsNotificationSummaryExt>(responseString);
Assert.True(summary?.Notifications.Count > 0);
Assert.Equal(order.Id, summary?.OrderId);
Assert.Equal(notification.Id, summary?.Notifications[0].Id);
Assert.Equal(1, summary?.Generated);
Assert.Equal(0, summary?.Succeeded);
}

private HttpClient GetTestClient()
{
HttpClient client = _factory.WithWebHostBuilder(builder =>
{
IdentityModelEventSource.ShowPII = true;

builder.ConfigureTestServices(services =>
{
// Set up mock authentication and authorization
services.AddSingleton<IPostConfigureOptions<JwtCookieOptions>, JwtCookiePostConfigureOptionsStub>();
services.AddSingleton<IPublicSigningKeyProvider, PublicSigningKeyProviderMock>();
});
}).CreateClient();

return client;
}
}
}
Loading

0 comments on commit b081211

Please sign in to comment.