Skip to content

Commit

Permalink
getting image delivery channels to work with the default policy
Browse files Browse the repository at this point in the history
  • Loading branch information
JackLewis-digirati committed Feb 6, 2025
1 parent 2bfbcea commit 15d0747
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 34 deletions.
150 changes: 139 additions & 11 deletions src/protagonist/API.Tests/Integration/ModifyAssetTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class ModifyAssetTests : IClassFixture<ProtagonistAppFactory<Startup>>
{
private readonly DlcsContext dbContext;
private readonly HttpClient httpClient;
private static readonly IAssetNotificationSender NotificationSender = A.Fake<IAssetNotificationSender>();
private static readonly IAssetNotificationSender AssetNotificationSender = A.Fake<IAssetNotificationSender>();
private readonly IAmazonS3 amazonS3;
private static readonly IEngineClient EngineClient = A.Fake<IEngineClient>();

Expand All @@ -57,7 +57,7 @@ public ModifyAssetTests(
.WithLocalStack(storageFixture.LocalStackFixture)
.WithTestServices(services =>
{
services.AddSingleton<IAssetNotificationSender>(_ => NotificationSender);
services.AddSingleton(_ => AssetNotificationSender);
services.AddScoped<IEngineClient>(_ => EngineClient);
services.AddAuthentication("API-Test")
.AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
Expand Down Expand Up @@ -149,6 +149,46 @@ public async Task Put_NewImageAsset_Creates_Asset_WithDeliveryChannelsSetToNone(
i.DeliveryChannelPolicyId == KnownDeliveryChannelPolicies.None);
}

[Fact]
public async Task Put_NewImageAsset_Creates_Asset_WithDeliveryChannelsSetToDefault()
{
var customerAndSpace = await CreateCustomerAndSpace();

var assetId = new AssetId(customerAndSpace.customer, customerAndSpace.space, nameof(Put_NewImageAsset_Creates_Asset_WithDeliveryChannelsSetToDefault));
var hydraImageBody = $@"{{
""@type"": ""Image"",
""origin"": ""https://example.org/{assetId.Asset}.tiff"",
""family"": ""I"",
""mediaType"": ""image/tiff"",
""deliveryChannels"": [
{{
""channel"": ""default""
}}]
}}";
A.CallTo(() =>
EngineClient.SynchronousIngest(
A<Asset>.That.Matches(r => r.Id == assetId),
A<CancellationToken>._))
.Returns(HttpStatusCode.OK);

// act
var content = new StringContent(hydraImageBody, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(customerAndSpace.customer).PutAsync(assetId.ToApiResourcePath(), content);

// assert
response.StatusCode.Should().Be(HttpStatusCode.Created);
response.Headers.Location.PathAndQuery.Should().Be(assetId.ToApiResourcePath());

var asset = dbContext.Images.Include(i => i.ImageDeliveryChannels).Single(x => x.Id == assetId);
asset.Id.Should().Be(assetId);
asset.MaxUnauthorised.Should().Be(-1);
asset.ImageDeliveryChannels
.Should().HaveCount(2).And.Contain(i =>
i.Channel == AssetDeliveryChannels.Image &&
i.DeliveryChannelPolicyId == KnownDeliveryChannelPolicies.ImageDefault).And
.Contain(i => i.Channel == AssetDeliveryChannels.Thumbnails);
}

[Fact]
public async Task Put_NewImageAsset_Creates_Asset_WithCustomDefaultDeliveryChannel()
{
Expand Down Expand Up @@ -1060,10 +1100,32 @@ public async Task Put_Existing_Asset_ClearsError_AndMarksAsIngesting()
}

[Fact]
public async Task Put_Existing_Asset_Returns400_IfDeliveryChannelsNull()
public async Task Put_Existing_Asset_ReturnsPreviousDeliveryChannels_IfDeliveryChannelsAreNull()
{
var assetId = AssetIdGenerator.GetAssetId();
await dbContext.Images.AddTestAsset(assetId);

var thumbsPolicy = dbContext.DeliveryChannelPolicies.Single(dcp => dcp.Name == "example-thumbs-policy");

var deliveryChannels = new List<ImageDeliveryChannel>()
{
new()
{
Channel = AssetDeliveryChannels.File,
DeliveryChannelPolicyId = KnownDeliveryChannelPolicies.FileNone
},
new()
{
Channel = AssetDeliveryChannels.Thumbnails,
DeliveryChannelPolicyId = thumbsPolicy.Id
},
new()
{
Channel = AssetDeliveryChannels.Image,
DeliveryChannelPolicyId = KnownDeliveryChannelPolicies.ImageDefault
}
};

await dbContext.Images.AddTestAsset(assetId, imageDeliveryChannels: deliveryChannels);
await dbContext.SaveChangesAsync();

var hydraImageBody = $@"{{
Expand All @@ -1073,14 +1135,80 @@ public async Task Put_Existing_Asset_Returns400_IfDeliveryChannelsNull()
""mediaType"": ""image/tiff""
}}";

A.CallTo(() =>
EngineClient.SynchronousIngest(
A<Asset>.That.Matches(r => r.Id == assetId),
A<CancellationToken>._))
.Returns(HttpStatusCode.OK);

// act
var content = new StringContent(hydraImageBody, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(99).PutAsync(assetId.ToApiResourcePath(), content);

// assert
response.StatusCode.Should().Be(HttpStatusCode.OK);

var asset = dbContext.Images.Include(i => i.ImageDeliveryChannels).Single(x => x.Id == assetId);
asset.Id.Should().Be(assetId);
asset.MaxUnauthorised.Should().Be(-1);
asset.ImageDeliveryChannels.Should().HaveCount(3).And.Contain(i =>
i.Channel == AssetDeliveryChannels.Image &&
i.DeliveryChannelPolicyId == KnownDeliveryChannelPolicies.ImageDefault).And
.Contain(i => i.Channel == AssetDeliveryChannels.Thumbnails && i.DeliveryChannelPolicyId == thumbsPolicy.Id)
.And.Contain(i =>
i.Channel == AssetDeliveryChannels.File &&
i.DeliveryChannelPolicyId == KnownDeliveryChannelPolicies.FileNone);
}

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

var deliveryChannels = new List<ImageDeliveryChannel>()
{
new()
{
Channel = AssetDeliveryChannels.File,
DeliveryChannelPolicyId = KnownDeliveryChannelPolicies.FileNone
}
};

await dbContext.Images.AddTestAsset(assetId, imageDeliveryChannels: deliveryChannels);
await dbContext.SaveChangesAsync();

var hydraImageBody = $@"{{
""@type"": ""Image"",
""origin"": ""https://example.org/{assetId.Asset}.tiff"",
""family"": ""I"",
""mediaType"": ""image/tiff"",
""deliveryChannels"": [
{{
""channel"": ""default""
}}]
}}";

A.CallTo(() =>
EngineClient.SynchronousIngest(
A<Asset>.That.Matches(r => r.Id == assetId),
A<CancellationToken>._))
.Returns(HttpStatusCode.OK);

// act
var content = new StringContent(hydraImageBody, Encoding.UTF8, "application/json");
var response = await httpClient.AsCustomer(99).PutAsync(assetId.ToApiResourcePath(), content);

// assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
var body = await response.Content.ReadAsStringAsync();
body.Should().Contain("Delivery channels are required when updating an existing Asset");
response.StatusCode.Should().Be(HttpStatusCode.OK);

var asset = dbContext.Images.Include(i => i.ImageDeliveryChannels).Single(x => x.Id == assetId);
asset.Id.Should().Be(assetId);
asset.MaxUnauthorised.Should().Be(-1);
asset.ImageDeliveryChannels
.Should().HaveCount(2).And.Contain(i =>
i.Channel == AssetDeliveryChannels.Image &&
i.DeliveryChannelPolicyId == KnownDeliveryChannelPolicies.ImageDefault).And
.Contain(i => i.Channel == AssetDeliveryChannels.Thumbnails);
}

[Fact]
Expand Down Expand Up @@ -2331,7 +2459,7 @@ public async Task Delete_RemovesAssetAndAssociatedEntities_FromDb()
ec.Customer == 99 && ec.Scope == "1" && ec.Type == KnownEntityCounters.SpaceImages);
dbSpaceCounter.Next.Should().Be(currentSpaceImagesCounter - 1);

A.CallTo(() => NotificationSender.SendAssetModifiedMessage(
A.CallTo(() => AssetNotificationSender.SendAssetModifiedMessage(
A<AssetModificationRecord>.That.Matches(r => r.ChangeType == ChangeType.Delete && r.DeleteFrom == ImageCacheType.None),
A<CancellationToken>._)).MustHaveHappened();
}
Expand Down Expand Up @@ -2392,7 +2520,7 @@ public async Task Delete_NotifiesForCdnAndInternalCacheRemoval_FromAssetNotified
ec.Customer == 99 && ec.Scope == "1" && ec.Type == KnownEntityCounters.SpaceImages);
dbSpaceCounter.Next.Should().Be(currentSpaceImagesCounter - 1);

A.CallTo(() => NotificationSender.SendAssetModifiedMessage(
A.CallTo(() => AssetNotificationSender.SendAssetModifiedMessage(
A<AssetModificationRecord>.That.Matches(r => r.ChangeType == ChangeType.Delete &&
(int)r.DeleteFrom == 12),
A<CancellationToken>._)).MustHaveHappened();
Expand Down Expand Up @@ -2450,7 +2578,7 @@ public async Task Delete_RemovesAssetWithoutImageLocation_FromDb()
ec.Customer == 99 && ec.Scope == "1" && ec.Type == KnownEntityCounters.SpaceImages);
dbSpaceCounter.Next.Should().Be(currentSpaceImagesCounter - 1);

A.CallTo(() => NotificationSender.SendAssetModifiedMessage(
A.CallTo(() => AssetNotificationSender.SendAssetModifiedMessage(
A<AssetModificationRecord>.That.Matches(r => r.ChangeType == ChangeType.Delete),
A<CancellationToken>._)).MustHaveHappened();
}
Expand Down Expand Up @@ -2486,7 +2614,7 @@ public async Task Delete_IncludesImageDeliveryChannels_InAssetModifiedMessage()
// Assert
response.StatusCode.Should().Be(HttpStatusCode.NoContent);

A.CallTo(() => NotificationSender.SendAssetModifiedMessage(
A.CallTo(() => AssetNotificationSender.SendAssetModifiedMessage(
A<AssetModificationRecord>.That.Matches(r =>
r.ChangeType == ChangeType.Delete &&
r.Before.ImageDeliveryChannels.Count == 3),
Expand Down
4 changes: 2 additions & 2 deletions src/protagonist/API/Features/Image/AssetBeforeProcessing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ namespace API.Features.Image;

public class AssetBeforeProcessing
{
public AssetBeforeProcessing(Asset asset, DeliveryChannelsBeforeProcessing[] deliveryChannelsBeforeProcessing)
public AssetBeforeProcessing(Asset asset, DeliveryChannelsBeforeProcessing[]? deliveryChannelsBeforeProcessing)
{
Asset = asset;
DeliveryChannelsBeforeProcessing = deliveryChannelsBeforeProcessing;
}

public Asset Asset { get; }

public DeliveryChannelsBeforeProcessing[] DeliveryChannelsBeforeProcessing { get; }
public DeliveryChannelsBeforeProcessing[]? DeliveryChannelsBeforeProcessing { get; set; }
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/protagonist/API/Features/Image/ImageController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ private Task<IActionResult> PutOrPatchAsset(int customerId, int spaceId, string
// See https://github.com/dlcs/protagonist/issues/338
var method = hydraAsset is ImageWithFile ? "PUT" : Request.Method;

var deliveryChannelsBeforeProcessing = (hydraAsset.DeliveryChannels ?? Array.Empty<DeliveryChannel>())
var deliveryChannelsBeforeProcessing = hydraAsset.DeliveryChannels?
.Select(d => new DeliveryChannelsBeforeProcessing(d.Channel, d.Policy)).ToArray();

var assetBeforeProcessing = new AssetBeforeProcessing(asset, deliveryChannelsBeforeProcessing);
Expand Down
2 changes: 1 addition & 1 deletion src/protagonist/API/Features/Image/ImagesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public async Task<IActionResult> PatchImages(
{
var asset = hydraImage.ToDlcsModel(customerId, spaceId);

var deliveryChannelsBeforeProcessing = (hydraImage.DeliveryChannels ?? Array.Empty<DeliveryChannel>())
var deliveryChannelsBeforeProcessing = hydraImage.DeliveryChannels?
.Select(d => new DeliveryChannelsBeforeProcessing(d.Channel, d.Policy)).ToArray();

var assetBeforeProcessing = new AssetBeforeProcessing(asset, deliveryChannelsBeforeProcessing);
Expand Down
14 changes: 12 additions & 2 deletions src/protagonist/API/Features/Image/Ingest/AssetProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public async Task<ProcessAssetResult> Process(AssetBeforeProcessing assetBeforeP
try
{
var assetFromDatabase = await assetRepository.GetAsset(assetBeforeProcessing.Asset.Id, true, true);
var updatedDeliveryChannels = false;

if (assetFromDatabase == null)
{
Expand Down Expand Up @@ -91,7 +92,15 @@ public async Task<ProcessAssetResult> Process(AssetBeforeProcessing assetBeforeP

counts.CustomerStorage.NumberOfStoredImages++;
}
else if (assetBeforeProcessing.DeliveryChannelsBeforeProcessing.IsNullOrEmpty() && alwaysReingest)
else if (assetBeforeProcessing.DeliveryChannelsBeforeProcessing == null && alwaysReingest)
{
assetBeforeProcessing.DeliveryChannelsBeforeProcessing = assetFromDatabase.ImageDeliveryChannels
.Select(d => new DeliveryChannelsBeforeProcessing(d.Channel, d.DeliveryChannelPolicy.Name))
.ToArray();

updatedDeliveryChannels = true;
}
else if (assetBeforeProcessing.DeliveryChannelsBeforeProcessing?.Length == 0 && alwaysReingest)
{
return new ProcessAssetResult
{
Expand All @@ -102,6 +111,7 @@ public async Task<ProcessAssetResult> Process(AssetBeforeProcessing assetBeforeP
};
}


var existingAsset = assetFromDatabase?.Clone();
var assetPreparationResult =
AssetPreparer.PrepareAssetForUpsert(assetFromDatabase, assetBeforeProcessing.Asset, false, isBatchUpdate,
Expand All @@ -120,7 +130,7 @@ public async Task<ProcessAssetResult> Process(AssetBeforeProcessing assetBeforeP
var requiresEngineNotification = assetPreparationResult.RequiresReingest || alwaysReingest;

var deliveryChannelChanged = await deliveryChannelProcessor.ProcessImageDeliveryChannels(assetFromDatabase,
updatedAsset, assetBeforeProcessing.DeliveryChannelsBeforeProcessing);
updatedAsset, updatedDeliveryChannels, assetBeforeProcessing.DeliveryChannelsBeforeProcessing);
if (deliveryChannelChanged)
{
requiresEngineNotification = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ public DeliveryChannelProcessor(IDefaultDeliveryChannelRepository defaultDeliver
/// </param>
/// <param name="deliveryChannelsBeforeProcessing">List of deliveryChannels submitted in body</param>
/// <returns>Boolean indicating whether asset requires processing Engine</returns>
public async Task<bool> ProcessImageDeliveryChannels(Asset? existingAsset, Asset updatedAsset,
DeliveryChannelsBeforeProcessing[] deliveryChannelsBeforeProcessing)
public async Task<bool> ProcessImageDeliveryChannels(Asset? existingAsset, Asset updatedAsset,
bool deliveryChannelsUpdated, DeliveryChannelsBeforeProcessing[]? deliveryChannelsBeforeProcessing)
{
if (existingAsset == null ||
if (existingAsset == null || deliveryChannelsUpdated ||
DeliveryChannelsRequireReprocessing(existingAsset, deliveryChannelsBeforeProcessing))
{
try
Expand All @@ -54,9 +54,8 @@ public async Task<bool> ProcessImageDeliveryChannels(Asset? existingAsset, Asset
return false;
}

private bool DeliveryChannelsRequireReprocessing(Asset originalAsset, DeliveryChannelsBeforeProcessing[] deliveryChannelsBeforeProcessing)
private bool DeliveryChannelsRequireReprocessing(Asset originalAsset, DeliveryChannelsBeforeProcessing[]? deliveryChannelsBeforeProcessing)
{
// PUT prevents empty delivery channels from being passed here, but PATCH doesn't
if (deliveryChannelsBeforeProcessing.IsNullOrEmpty()) return false;
if (originalAsset.ImageDeliveryChannels.Count != deliveryChannelsBeforeProcessing.Length) return true;

Expand All @@ -73,20 +72,20 @@ private bool DeliveryChannelsRequireReprocessing(Asset originalAsset, DeliveryCh
return false;
}

private async Task<bool> SetImageDeliveryChannels(Asset asset, DeliveryChannelsBeforeProcessing[] deliveryChannelsBeforeProcessing, bool isUpdate)
private async Task<bool> SetImageDeliveryChannels(Asset asset, DeliveryChannelsBeforeProcessing[]? deliveryChannelsBeforeProcessing, bool isUpdate)
{
var assetId = asset.Id;
var isDefaultChannel = deliveryChannelsBeforeProcessing?.Any(d => d.Channel == AssetDeliveryChannels.Default) ??
false;

if (!isUpdate)
// if it's a new asset, or has "default" specified, figure out the delivery channels based on media type
if (!isUpdate || isDefaultChannel)
{
logger.LogTrace("Asset {AssetId} is new, resetting ImageDeliveryChannels", assetId);
logger.LogTrace("Setting delivery channels for asset {AssetId}", assetId);
asset.ImageDeliveryChannels = new List<ImageDeliveryChannel>();

// Only valid for creation - set image delivery channels to default values for media type
if (deliveryChannelsBeforeProcessing.IsNullOrEmpty())

if (deliveryChannelsBeforeProcessing.IsNullOrEmpty() || isDefaultChannel)
{
logger.LogDebug("Asset {AssetId} is new, no deliveryChannels specified. Assigning defaults for mediaType",
assetId);
await AddDeliveryChannelsForMediaType(asset);
return true;
}
Expand All @@ -103,16 +102,19 @@ private async Task<bool> SetImageDeliveryChannels(Asset asset, DeliveryChannelsB
var changeMade = false;
var handledChannels = new List<string>();
var assetImageDeliveryChannels = asset.ImageDeliveryChannels;

foreach (var deliveryChannel in deliveryChannelsBeforeProcessing)
{
handledChannels.Add(deliveryChannel.Channel);
var deliveryChannelPolicy = await GetDeliveryChannelPolicy(asset, deliveryChannel);
var currentChannel = assetImageDeliveryChannels.SingleOrDefault(idc => idc.Channel == deliveryChannel.Channel);
var currentChannel =
assetImageDeliveryChannels.SingleOrDefault(idc => idc.Channel == deliveryChannel.Channel);

// No current ImageDeliveryChannel for channel so this is an addition
if (currentChannel == null)
{
logger.LogTrace("Adding new deliveryChannel {DeliveryChannel}, Policy {PolicyName} to Asset {AssetId}",
logger.LogTrace(
"Adding new deliveryChannel {DeliveryChannel}, Policy {PolicyName} to Asset {AssetId}",
deliveryChannel.Channel, deliveryChannelPolicy.Name, assetId);

assetImageDeliveryChannels.Add(new ImageDeliveryChannel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ private static List<AssetBeforeProcessing> CreateAssetsBeforeProcessing(int cust
{
var assetsBeforeProcessing = images.Members!
.Select(i => new AssetBeforeProcessing(i.ToDlcsModel(customerId),
(i.DeliveryChannels ?? Array.Empty<DeliveryChannel>())
i.DeliveryChannels?
.Select(d => new DeliveryChannelsBeforeProcessing(d.Channel, d.Policy)).ToArray())).ToList();
return assetsBeforeProcessing;
}
Expand Down
Loading

0 comments on commit 15d0747

Please sign in to comment.