Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit 770fce3

Browse files
feat: Added AppConfiguration to FileTransformFunction
1 parent 1318ab6 commit 770fce3

6 files changed

Lines changed: 79 additions & 19 deletions

File tree

src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ public class AppConfiguration :
44
IFileDiscoveryFunctionConfiguration,
55
IFileExtractFunctionConfiguration,
66
IFileExtractQueueClientConfiguration,
7-
IFileTransformQueueClientConfiguration
7+
IFileTransformQueueClientConfiguration,
8+
IFileTransformFunctionConfiguration
89
{
910
public string NbssMeshMailboxId => GetRequired("NbssMailboxId");
1011

1112
public string FileExtractQueueName => GetRequired("FileExtractQueueName");
1213

1314
public string FileTransformQueueName => GetRequired("FileTransformQueueName");
1415

16+
public int StaleHours => throw new NotImplementedException();
17+
1518
private static string GetRequired(string key)
1619
{
1720
var value = Environment.GetEnvironmentVariable(key);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace ServiceLayer.Mesh.Configuration;
2+
3+
public interface IFileTransformFunctionConfiguration
4+
{
5+
int StaleHours { get; }
6+
}

src/ServiceLayer.Mesh/Functions/FileTransformFunction.cs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using Google.Protobuf.WellKnownTypes;
12
using Microsoft.Azure.Functions.Worker;
23
using Microsoft.EntityFrameworkCore;
34
using Microsoft.Extensions.Logging;
5+
using ServiceLayer.Mesh.Configuration;
46
using ServiceLayer.Mesh.Data;
57
using ServiceLayer.Mesh.Messaging;
68
using ServiceLayer.Mesh.Models;
@@ -11,7 +13,8 @@ namespace ServiceLayer.Mesh.Functions;
1113
public class FileTransformFunction(
1214
ILogger<FileTransformFunction> logger,
1315
ServiceLayerDbContext serviceLayerDbContext,
14-
IMeshFilesBlobStore meshFileBlobStore)
16+
IMeshFilesBlobStore meshFileBlobStore,
17+
IFileTransformFunctionConfiguration configuration)
1518
{
1619
[Function("FileTransformFunction")]
1720
public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQueueMessage message)
@@ -26,9 +29,8 @@ public async Task Run([QueueTrigger("%FileTransformQueueName%")] FileTransformQu
2629
return;
2730
}
2831

29-
if (file.Status != MeshFileStatus.Extracted)
32+
if (!IsFileSuitableForTransformation(file))
3033
{
31-
logger.LogWarning("File with id: {fileId} found in MeshFiles table but is not suitable for transformation. Status: {status}", message.FileId, file.Status);
3234
return;
3335
}
3436

@@ -47,4 +49,22 @@ private async Task UpdateFileStatusForTransformation(MeshFile file)
4749
file.LastUpdatedUtc = DateTime.UtcNow;
4850
await serviceLayerDbContext.SaveChangesAsync();
4951
}
52+
53+
private bool IsFileSuitableForTransformation(MeshFile file)
54+
{
55+
// We only want to transform files if they are in a Extracted state,
56+
// or are in a Transforming state and were last touched over 12 hours ago.
57+
var expectedStatuses = new[] { MeshFileStatus.Extracted, MeshFileStatus.Transforming };
58+
if (!expectedStatuses.Contains(file.Status) ||
59+
(file.Status == MeshFileStatus.Transforming && file.LastUpdatedUtc > DateTime.UtcNow.AddHours(-configuration.StaleHours)))
60+
{
61+
logger.LogWarning(
62+
"File with id: {fileId} found in MeshFiles table but is not suitable for transformation. Status: {status}, LastUpdatedUtc: {lastUpdatedUtc}.",
63+
file.FileId,
64+
file.Status,
65+
file.LastUpdatedUtc.ToTimestamp());
66+
return false;
67+
}
68+
return true;
69+
}
5070
}

src/ServiceLayer.Mesh/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
services.AddTransient<IFileExtractFunctionConfiguration, AppConfiguration>();
6464
services.AddTransient<IFileExtractQueueClientConfiguration, AppConfiguration>();
6565
services.AddTransient<IFileTransformQueueClientConfiguration, AppConfiguration>();
66+
services.AddTransient<IFileTransformFunctionConfiguration, AppConfiguration>();
6667
});
6768

6869

src/ServiceLayer.Mesh/Storage/MeshFilesBlobStore.cs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,7 @@ public class MeshFilesBlobStore(BlobContainerClient blobContainerClient) : IMesh
88
public async Task<Stream> DownloadAsync(MeshFile file)
99
{
1010
var blobClient = blobContainerClient.GetBlobClient(file.BlobPath);
11-
12-
var dataStream = new MemoryStream();
13-
14-
await blobClient.DownloadToAsync(dataStream);
15-
16-
dataStream.Close();
17-
18-
return dataStream;
11+
return (await blobClient.DownloadAsync()).Value.Content;
1912
}
2013

2114
public async Task<string> UploadAsync(MeshFile file, byte[] data)

tests/ServiceLayer.Mesh.Tests/Functions/FileTransformFunctionTests.cs

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
using Google.Protobuf.WellKnownTypes;
12
using Microsoft.EntityFrameworkCore;
23
using Microsoft.Extensions.Logging;
34
using Moq;
5+
using ServiceLayer.Mesh.Configuration;
46
using ServiceLayer.Mesh.Data;
57
using ServiceLayer.Mesh.Functions;
68
using ServiceLayer.Mesh.Messaging;
@@ -11,27 +13,28 @@ namespace ServiceLayer.Mesh.Tests.Functions;
1113

1214
public class FileTransformFunctionTests
1315
{
14-
private readonly Mock<ILogger<FileTransformFunction>> _loggerMock;
15-
private readonly Mock<IMeshFilesBlobStore> _blobStoreMock;
16+
private readonly Mock<ILogger<FileTransformFunction>> _loggerMock = new();
17+
private readonly Mock<IMeshFilesBlobStore> _blobStoreMock = new();
18+
private readonly Mock<IFileTransformFunctionConfiguration> _configuration = new();
1619
private readonly ServiceLayerDbContext _dbContext;
1720
private readonly FileTransformFunction _function;
1821

1922
public FileTransformFunctionTests()
2023
{
21-
_loggerMock = new Mock<ILogger<FileTransformFunction>>();
22-
_blobStoreMock = new Mock<IMeshFilesBlobStore>();
23-
2424
var options = new DbContextOptionsBuilder<ServiceLayerDbContext>()
2525
.UseInMemoryDatabase(Guid.NewGuid().ToString())
2626
.ConfigureWarnings(warnings =>
2727
warnings.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.InMemoryEventId.TransactionIgnoredWarning))
2828
.Options;
2929
_dbContext = new ServiceLayerDbContext(options);
3030

31+
_configuration.Setup(c => c.StaleHours).Returns(12);
32+
3133
_function = new FileTransformFunction(
3234
_loggerMock.Object,
3335
_dbContext,
34-
_blobStoreMock.Object
36+
_blobStoreMock.Object,
37+
_configuration.Object
3538
);
3639
}
3740

@@ -83,7 +86,7 @@ public async Task Run_FileStatusInvalid_ExitsSilently()
8386
x => x.Log(
8487
LogLevel.Warning,
8588
It.IsAny<EventId>(),
86-
It.Is<It.IsAnyType>((v, t) => v.ToString() == $"File with id: {message.FileId} found in MeshFiles table but is not suitable for transformation. Status: {file.Status}"),
89+
It.Is<It.IsAnyType>((v, t) => v.ToString() == $"File with id: {message.FileId} found in MeshFiles table but is not suitable for transformation. Status: {file.Status}, LastUpdatedUtc: {file.LastUpdatedUtc.ToTimestamp()}."),
8790
null,
8891
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
8992
), Times.Once);
@@ -92,6 +95,40 @@ public async Task Run_FileStatusInvalid_ExitsSilently()
9295
_blobStoreMock.Verify(x => x.DownloadAsync(It.IsAny<MeshFile>()), Times.Never);
9396
}
9497

98+
[Fact]
99+
public async Task Run_FileStatusTransformingButNotTimedOut_ExitsSilently()
100+
{
101+
// Arrange
102+
var file = new MeshFile
103+
{
104+
FileType = MeshFileType.NbssAppointmentEvents,
105+
MailboxId = "test-mailbox",
106+
FileId = "file-1",
107+
Status = MeshFileStatus.Transforming,
108+
LastUpdatedUtc = DateTime.UtcNow.AddHours(-11) // Not timed out
109+
};
110+
_dbContext.MeshFiles.Add(file);
111+
await _dbContext.SaveChangesAsync();
112+
113+
var message = new FileTransformQueueMessage { FileId = "file-1" };
114+
115+
// Act
116+
await _function.Run(message);
117+
118+
// Assert
119+
_loggerMock.Verify(
120+
x => x.Log(
121+
LogLevel.Warning,
122+
It.IsAny<EventId>(),
123+
It.Is<It.IsAnyType>((v, t) => v.ToString() == $"File with id: {message.FileId} found in MeshFiles table but is not suitable for transformation. Status: {file.Status}, LastUpdatedUtc: {file.LastUpdatedUtc.ToTimestamp()}."),
124+
null,
125+
It.IsAny<Func<It.IsAnyType, Exception?, string>>()
126+
), Times.Once);
127+
var fileFromDb = await _dbContext.MeshFiles.SingleOrDefaultAsync(x => x.FileId == file.FileId);
128+
Assert.Equal(MeshFileStatus.Transforming, fileFromDb?.Status);
129+
_blobStoreMock.Verify(x => x.DownloadAsync(It.IsAny<MeshFile>()), Times.Never);
130+
}
131+
95132
[Fact]
96133
public async Task Run_FileValid_DownloadsBlob()
97134
{

0 commit comments

Comments
 (0)