Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API endpoint for sms notification summaries #GCPActive #418

Merged
merged 19 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading