Skip to content
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
226 changes: 226 additions & 0 deletions src/protagonist/API.Tests/Integration/CustomerAdjunctQueueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using DLCS.AWS.SNS.Messaging;
using DLCS.Model.Assets;
using DLCS.Model.Messaging;
using DLCS.Model.Processing;
using DLCS.Repository;
using DLCS.Web.Response;
using FakeItEasy;
Expand All @@ -23,6 +24,7 @@
using Test.Helpers.Integration;
using Test.Helpers.Integration.Infrastructure;
using AdjunctBatch = DLCS.HydraModel.AdjunctBatch;
using CustomerAdjunctQueue = DLCS.HydraModel.CustomerAdjunctQueue;

namespace API.Tests.Integration;

Expand Down Expand Up @@ -420,6 +422,230 @@ public async Task PostAdjunctBatch_Returns201_AdjunctHasBatchIdSet()
adjunct.Batch.Should().Be(batchId, "adjunct should reference the created batch");
}

[Fact]
public async Task GetAdjunctQueue_Returns404_WhenQueueNotFound()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
}

[Fact]
public async Task GetAdjunctQueue_Returns200_WithSizeFromQueueRow_WhenNoBatches()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();
await dbContext.Queues.AddAsync(new Queue { Customer = assetId.Customer, Name = QueueNames.Adjunct, Size = 7 });
await dbContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var queue = await response.ReadAsHydraResponseAsync<CustomerAdjunctQueue>();
queue.Size.Should().Be(7);
queue.BatchesWaiting.Should().Be(0);
queue.AdjunctsWaiting.Should().Be(0);
}

[Fact]
public async Task GetAdjunctQueue_Returns200_WithBatchesWaitingAndAdjunctsWaiting_ExcludingInProgressAndFinishedBatches()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();
await dbContext.Queues.AddAsync(new Queue { Customer = assetId.Customer, Name = QueueNames.Adjunct, Size = 10 });
// finished - excluded
await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer, count: 5, completed: 5,
finished: DateTime.UtcNow);
// unfinished, nothing started yet - counts as waiting
await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer, count: 5, completed: 0);
// unfinished but already partially processed - excluded, still being worked on
await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer, count: 8, completed: 6);
// unfinished but has an error recorded - also excluded, platform has started working on it
await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer, count: 3, errors: 1);
await dbContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var queue = await response.ReadAsHydraResponseAsync<CustomerAdjunctQueue>();
queue.Size.Should().Be(10);
queue.BatchesWaiting.Should().Be(1, "only batch 2 has not started processing");
queue.AdjunctsWaiting.Should().Be(5, "only batch 2's 5 adjuncts have not started processing");
}

[Fact]
public async Task GetAdjunctQueue_Links_ResolveToWorkingCollectionEndpoints()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();
await dbContext.Queues.AddAsync(new Queue { Customer = assetId.Customer, Name = QueueNames.Adjunct, Size = 0 });
await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer, count: 1, completed: 0);
await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer, count: 1, completed: 1,
finished: DateTime.UtcNow);
await dbContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var queue = await response.ReadAsHydraResponseAsync<CustomerAdjunctQueue>();

// Assert
queue.Batches.Should().EndWith("/adjunctQueue/batches");
queue.Active.Should().EndWith("/adjunctQueue/active");
queue.Recent.Should().EndWith("/adjunctQueue/recent");

var batches = await (await httpClient.AsCustomer(assetId.Customer).GetAsync(queue.Batches))
.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
batches.TotalItems.Should().Be(2);

var active = await (await httpClient.AsCustomer(assetId.Customer).GetAsync(queue.Active))
.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
active.TotalItems.Should().Be(1);

var recent = await (await httpClient.AsCustomer(assetId.Customer).GetAsync(queue.Recent))
.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
recent.TotalItems.Should().Be(1);
}

[Fact]
public async Task GetAdjunctBatches_Returns200_Empty_WhenNoBatches()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue/batches");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var batches = await response.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
batches.Members.Should().BeEmpty();
}

[Fact]
public async Task GetAdjunctBatches_Returns200_MostRecentlySubmittedFirst_ByDefault()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();
var earlier = DateTime.UtcNow.AddMinutes(-10);
var later = DateTime.UtcNow;
var earlierBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer,
submitted: earlier, finished: DateTime.UtcNow)).Entity;
var laterBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer,
submitted: later)).Entity;
await dbContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue/batches");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var batches = await response.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
batches.Members.Select(b => b.GetLastPathElementAsInt()).Should().ContainInOrder(laterBatch.Id, earlierBatch.Id);
}

[Fact]
public async Task GetAdjunctBatches_Returns200_OrdersAscending_WhenOrderByRequested()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();
var earlier = DateTime.UtcNow.AddMinutes(-10);
var later = DateTime.UtcNow;
var earlierBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer,
submitted: earlier)).Entity;
var laterBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer,
submitted: later)).Entity;
await dbContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue/batches?orderBy=submitted");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var batches = await response.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
batches.Members.Select(b => b.GetLastPathElementAsInt()).Should().ContainInOrder(earlierBatch.Id, laterBatch.Id);
}

[Fact]
public async Task GetActiveAdjunctBatches_Returns200_OnlyUnfinishedBatches()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();
var activeBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer)).Entity;
await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer, finished: DateTime.UtcNow);
await dbContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue/active");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var batches = await response.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
batches.Members.Should().ContainSingle(b => b.GetLastPathElementAsInt() == activeBatch.Id);
}

[Fact]
public async Task GetActiveAdjunctBatches_Returns200_OrdersDescending_WhenOrderByDescendingRequested()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();
var earlier = DateTime.UtcNow.AddMinutes(-10);
var later = DateTime.UtcNow;
var earlierBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer,
submitted: earlier)).Entity;
var laterBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer,
submitted: later)).Entity;
await dbContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue/active?orderByDescending=submitted");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var batches = await response.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
batches.Members.Select(b => b.GetLastPathElementAsInt()).Should().ContainInOrder(laterBatch.Id, earlierBatch.Id);
}

[Fact]
public async Task GetRecentAdjunctBatches_Returns200_OnlyFinishedBatches_OrderedByFinishedDescending()
{
// Arrange
var assetId = AssetIdGenerator.GetAssetId();
await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer);
var olderFinishedBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer,
finished: DateTime.UtcNow.AddDays(-7))).Entity;
var newerFinishedBatch = (await dbContext.AdjunctBatches.AddTestAdjunctBatch(customer: assetId.Customer,
finished: DateTime.UtcNow)).Entity;
await dbContext.SaveChangesAsync();

// Act
var response = await httpClient.AsCustomer(assetId.Customer)
.GetAsync($"/customers/{assetId.Customer}/adjunctQueue/recent");

// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var batches = await response.ReadAsHydraResponseAsync<HydraCollection<AdjunctBatch>>();
batches.Members.Select(b => b.GetLastPathElementAsInt()).Should().ContainInOrder(newerFinishedBatch.Id, olderFinishedBatch.Id);
}

[Fact]
public async Task GetAdjunctBatch_Returns404_WhenBatchNotFound()
{
Expand Down
9 changes: 9 additions & 0 deletions src/protagonist/API/Features/AdjunctQueues/AdjunctQueryX.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,13 @@ public static IQueryable<Adjunct> AsOrderedAdjunctQuery(this IQueryable<Adjunct>
=> orderable.Descending
? query.OrderByDescending(a => a.Created)
: query.OrderBy(a => a.Created);

/// <summary>
/// Order adjunct batches by Submitted date - currently the only supported orderBy field for adjunct batches.
/// Any other/unrecognised orderBy value is silently ignored and Submitted is used.
/// </summary>
public static IQueryable<AdjunctBatch> AsOrderedAdjunctBatchQuery(this IQueryable<AdjunctBatch> query, IOrderableRequest orderable)
=> orderable.Descending
? query.OrderByDescending(b => b.Submitted)
: query.OrderBy(b => b.Submitted);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using HydraAdjunctQueue = DLCS.HydraModel.CustomerAdjunctQueue;
using EntityAdjunctQueue = DLCS.Model.Processing.AdjunctQueue;

namespace API.Features.AdjunctQueues.Converters;

/// <summary>
/// Conversion between API and EF forms of AdjunctQueue resource
/// </summary>
public static class AdjunctQueueConverter
{
/// <summary>
/// Convert AdjunctQueue entity to API resource
/// </summary>
public static HydraAdjunctQueue ToHydra(this EntityAdjunctQueue adjunctQueue, string baseUrl)
{
var hydra = new HydraAdjunctQueue(baseUrl, adjunctQueue.Customer);
hydra.Size = adjunctQueue.Size;
hydra.BatchesWaiting = adjunctQueue.BatchesWaiting;
hydra.AdjunctsWaiting = adjunctQueue.AdjunctsWaiting;

return hydra;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,86 @@ public class CustomerAdjunctQueueController(
IMediator mediator)
: HydraController(options.Value, mediator)
{
/// <summary>
/// Get details of default customer adjunct queue
/// </summary>
/// <param name="customerId">Id of customer to get adjunct queue details for</param>
/// <param name="cancellationToken">Current cancellation token</param>
/// <returns>Hydra JSON-LD CustomerAdjunctQueue object</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(CustomerAdjunctQueue))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetCustomerAdjunctQueue([FromRoute] int customerId,
CancellationToken cancellationToken)
{
return await HandleFetch(
new GetCustomerAdjunctQueue(customerId),
queue => queue.ToHydra(GetUrlRoots().BaseUrl),
errorTitle: "Get Customer Adjunct Queue failed",
cancellationToken: cancellationToken);
}

/// <summary>
/// Get details of all customer adjunct batches.
///
/// Supports ?page= and ?pageSize= query parameters for paging
/// </summary>
/// <param name="customerId">Id of customer</param>
/// <param name="cancellationToken">Current cancellation token</param>
/// <returns>Hydra JSON-LD collection of AdjunctBatch objects</returns>
[HttpGet]
[Route("batches")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(HydraCollection<AdjunctBatch>))]
public async Task<IActionResult> GetAdjunctBatches([FromRoute] int customerId, CancellationToken cancellationToken)
{
return await HandlePagedFetch<DLCS.Model.Assets.AdjunctBatch, GetAdjunctBatches, AdjunctBatch>(
new GetAdjunctBatches(customerId),
batch => batch.ToHydra(GetUrlRoots().BaseUrl),
errorTitle: "Get adjunct batches failed",
cancellationToken: cancellationToken);
}

/// <summary>
/// Get details of customer active adjunct batches. An "active" batch is one that is incomplete.
///
/// Supports ?page= and ?pageSize= query parameters for paging
/// </summary>
/// <param name="customerId">Id of customer</param>
/// <param name="cancellationToken">Current cancellation token</param>
/// <returns>Hydra JSON-LD collection of AdjunctBatch objects</returns>
[HttpGet]
[Route("active")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(HydraCollection<AdjunctBatch>))]
public async Task<IActionResult> GetActiveAdjunctBatches([FromRoute] int customerId, CancellationToken cancellationToken)
{
return await HandlePagedFetch<DLCS.Model.Assets.AdjunctBatch, GetActiveAdjunctBatches, AdjunctBatch>(
new GetActiveAdjunctBatches(customerId),
batch => batch.ToHydra(GetUrlRoots().BaseUrl),
errorTitle: "Get active adjunct batches failed",
cancellationToken: cancellationToken);
}

/// <summary>
/// Get details of customer recent adjunct batches. These are all batches that are finished, ordered by latest
/// finished.
///
/// Supports ?page= and ?pageSize= query parameters for paging
/// </summary>
/// <param name="customerId">Id of customer</param>
/// <param name="cancellationToken">Current cancellation token</param>
/// <returns>Hydra JSON-LD collection of AdjunctBatch objects</returns>
[HttpGet]
[Route("recent")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(HydraCollection<AdjunctBatch>))]
public async Task<IActionResult> GetRecentAdjunctBatches([FromRoute] int customerId, CancellationToken cancellationToken)
{
return await HandlePagedFetch<DLCS.Model.Assets.AdjunctBatch, GetRecentAdjunctBatches, AdjunctBatch>(
new GetRecentAdjunctBatches(customerId),
batch => batch.ToHydra(GetUrlRoots().BaseUrl),
errorTitle: "Get recent adjunct batches failed",
cancellationToken: cancellationToken);
}

/// <summary>
/// Get details of specified adjunct batch.
/// </summary>
Expand Down
Loading
Loading