Skip to content

Commit e6e6c30

Browse files
committed
refactoring and more tests
Signed-off-by: Neil South <[email protected]>
1 parent af896bc commit e6e6c30

File tree

12 files changed

+404
-78
lines changed

12 files changed

+404
-78
lines changed

src/Api/Storage/RemoteAppExecution.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,12 @@ public class RemoteAppExecution
3535
public List<string> Files { get; set; } = new();
3636
public FileExportStatus Status { get; set; }
3737
public Dictionary<string, string> OriginalValues { get; set; } = new();
38+
public Dictionary<string, string> ProxyValues { get; set; } = new();
39+
}
40+
public class RemoteAppExecutionTest
41+
{
42+
[JsonPropertyName("_id")]
43+
public string Id { get; set; } = default!;
44+
public DateTime RequestTime { get; set; } = DateTime.UtcNow;
3845
}
3946
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2021-2022 MONAI Consortium
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using System.Text.Json;
18+
using System.Text.Json.Serialization;
19+
using Microsoft.EntityFrameworkCore;
20+
using Microsoft.EntityFrameworkCore.ChangeTracking;
21+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
22+
using Monai.Deploy.InformaticsGateway.Api.Storage;
23+
24+
namespace Monai.Deploy.InformaticsGateway.Database.EntityFramework.Configuration
25+
{
26+
#pragma warning disable CS8604, CS8603
27+
28+
internal class RemoteAppExecutionConfiguration : IEntityTypeConfiguration<RemoteAppExecution>
29+
{
30+
public void Configure(EntityTypeBuilder<RemoteAppExecution> builder)
31+
{
32+
var jsonSerializerSettings = new JsonSerializerOptions
33+
{
34+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
35+
};
36+
37+
builder.HasKey(j => j.Id);
38+
39+
builder.Property(j => j.OutgoingUid).IsRequired();
40+
builder.Property(j => j.ExportTaskId).IsRequired();
41+
//builder.Property(j => j.Status).IsRequired();
42+
builder.Property(j => j.CorrelationId).IsRequired();
43+
builder.Property(j => j.OriginalValues)
44+
.HasConversion(
45+
v => JsonSerializer.Serialize(v, jsonSerializerSettings),
46+
v => JsonSerializer.Deserialize<Dictionary<string, string>>(v, jsonSerializerSettings));
47+
builder.Property(j => j.ProxyValues)
48+
.HasConversion(
49+
v => JsonSerializer.Serialize(v, jsonSerializerSettings),
50+
v => JsonSerializer.Deserialize<Dictionary<string, string>>(v, jsonSerializerSettings));
51+
52+
builder.Property(j => j.Files)
53+
.HasConversion(
54+
v => JsonSerializer.Serialize(v, jsonSerializerSettings),
55+
v => JsonSerializer.Deserialize<List<string>>(v, jsonSerializerSettings));
56+
57+
builder.HasIndex(p => p.OutgoingUid, "idx_outgoing_key");
58+
59+
}
60+
}
61+
62+
#pragma warning restore CS8604, CS8603
63+
}

src/Database/EntityFramework/InformaticsGatewayContext.cs

100644100755
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public InformaticsGatewayContext(DbContextOptions<InformaticsGatewayContext> opt
4040
public virtual DbSet<Payload> Payloads { get; set; }
4141
public virtual DbSet<StorageMetadataWrapper> StorageMetadataWrapperEntities { get; set; }
4242
public virtual DbSet<DicomAssociationInfo> DicomAssociationHistories { get; set; }
43+
public virtual DbSet<RemoteAppExecution> RemoteAppExecutions { get; set; }
4344

4445
protected override void OnModelCreating(ModelBuilder modelBuilder)
4546
{
@@ -52,6 +53,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
5253
modelBuilder.ApplyConfiguration(new PayloadConfiguration());
5354
modelBuilder.ApplyConfiguration(new StorageMetadataWrapperEntityConfiguration());
5455
modelBuilder.ApplyConfiguration(new DicomAssociationInfoConfiguration());
56+
modelBuilder.ApplyConfiguration(new RemoteAppExecutionConfiguration());
5557
}
5658

5759
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

src/Database/EntityFramework/Repositories/RemoteAppExecutionRepository.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ namespace Monai.Deploy.InformaticsGateway.Database.EntityFramework.Repositories
3131
{
3232
public class RemoteAppExecutionRepository : IRemoteAppExecutionRepository, IDisposable
3333
{
34-
private readonly ILogger<PayloadRepository> _logger;
34+
private readonly ILogger<RemoteAppExecutionRepository> _logger;
3535
private readonly IServiceScope _scope;
3636
private readonly InformaticsGatewayContext _informaticsGatewayContext;
3737
private readonly AsyncRetryPolicy _retryPolicy;
@@ -44,7 +44,7 @@ public class RemoteAppExecutionRepository : IRemoteAppExecutionRepository, IDisp
4444

4545
public RemoteAppExecutionRepository(
4646
IServiceScopeFactory serviceScopeFactory,
47-
ILogger<PayloadRepository> logger,
47+
ILogger<RemoteAppExecutionRepository> logger,
4848
IOptions<InformaticsGatewayConfiguration> options)
4949
{
5050
Guard.Against.Null(serviceScopeFactory, nameof(serviceScopeFactory));
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright 2022 MONAI Consortium
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
using FellowOakDicom;
18+
using Microsoft.EntityFrameworkCore;
19+
using Microsoft.Extensions.DependencyInjection;
20+
using Microsoft.Extensions.Logging;
21+
using Microsoft.Extensions.Options;
22+
using Microsoft.VisualBasic;
23+
using Monai.Deploy.InformaticsGateway.Api;
24+
using Monai.Deploy.InformaticsGateway.Api.Storage;
25+
using Monai.Deploy.InformaticsGateway.Configuration;
26+
using Monai.Deploy.InformaticsGateway.Database.EntityFramework.Repositories;
27+
using Monai.Deploy.InformaticsGateway.Database.EntityFramework.Test;
28+
using Moq;
29+
namespace Monai.Deploy.InformaticsGateway.Database.MongoDB.Integration.Test
30+
{
31+
[Collection("SqliteDatabase")]
32+
public class RemoteAppRepositoryTest
33+
{
34+
private readonly SqliteDatabaseFixture _databaseFixture;
35+
36+
private readonly Mock<IServiceScopeFactory> _serviceScopeFactory;
37+
private readonly Mock<ILogger<RemoteAppExecutionRepository>> _logger;
38+
private readonly IOptions<InformaticsGatewayConfiguration> _options;
39+
40+
private readonly Mock<IServiceScope> _serviceScope;
41+
private readonly IServiceProvider _serviceProvider;
42+
43+
public RemoteAppRepositoryTest(SqliteDatabaseFixture databaseFixture)
44+
{
45+
46+
_databaseFixture = databaseFixture ?? throw new ArgumentNullException(nameof(databaseFixture));
47+
48+
_serviceScopeFactory = new Mock<IServiceScopeFactory>();
49+
_logger = new Mock<ILogger<RemoteAppExecutionRepository>>();
50+
_options = Microsoft.Extensions.Options.Options.Create(new InformaticsGatewayConfiguration());
51+
52+
_serviceScope = new Mock<IServiceScope>();
53+
var services = new ServiceCollection();
54+
services.AddScoped(p => _logger.Object);
55+
services.AddScoped(p => databaseFixture.DatabaseContext);
56+
57+
_serviceProvider = services.BuildServiceProvider();
58+
_serviceScopeFactory.Setup(p => p.CreateScope()).Returns(_serviceScope.Object);
59+
_serviceScope.Setup(p => p.ServiceProvider).Returns(_serviceProvider);
60+
61+
_options.Value.Database.Retries.DelaysMilliseconds = new[] { 1, 1, 1 };
62+
_logger.Setup(p => p.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
63+
databaseFixture.DatabaseContext.Set<RemoteAppExecution>();
64+
databaseFixture.DatabaseContext.SaveChanges();
65+
}
66+
67+
[Fact]
68+
public async Task GivenAexecution_WhenAddingToDatabase_ExpectItToBeSaved()
69+
{
70+
var outgoingUid = Guid.NewGuid().ToString();
71+
var dataset = new DicomDataset();
72+
var date = new DateTime(
73+
DateTime.Now.Year,
74+
DateTime.Now.Month,
75+
DateTime.Now.Day,
76+
DateTime.Now.Hour,
77+
DateTime.Now.Minute,
78+
DateTime.Now.Second).ToUniversalTime();
79+
80+
var execution = new RemoteAppExecution
81+
{
82+
CorrelationId = Guid.NewGuid().ToString(),
83+
ExportTaskId = "ExportTaskId",
84+
WorkflowInstanceId = "WorkflowInstanceId",
85+
OutgoingUid = outgoingUid,
86+
StudyUid = Guid.NewGuid().ToString(),
87+
RequestTime = date,
88+
OriginalValues = new Dictionary<string, string> {
89+
{ DicomTag.StudyInstanceUID.ToString(), "StudyInstanceUID" },
90+
{ DicomTag.SeriesInstanceUID.ToString(), "SeriesInstanceUID" }
91+
},
92+
ProxyValues = new Dictionary<string, string> {
93+
{ DicomTag.StudyInstanceUID.ToString(), "StudyInstanceUID" },
94+
{ DicomTag.SeriesInstanceUID.ToString(), "SeriesInstanceUID" }
95+
}
96+
};
97+
98+
99+
var store = new RemoteAppExecutionRepository(_serviceScopeFactory.Object, _logger.Object, _options);
100+
await store.AddAsync(execution).ConfigureAwait(false);
101+
102+
var actual = await store.GetAsync(execution.OutgoingUid).ConfigureAwait(false);
103+
104+
Task.Delay(1000).Wait();
105+
Assert.NotNull(actual);
106+
Assert.Equal(execution.CorrelationId, actual!.CorrelationId);
107+
Assert.Equal(execution.ExportTaskId, actual!.ExportTaskId);
108+
Assert.Equal(execution.WorkflowInstanceId, actual!.WorkflowInstanceId);
109+
Assert.Equal(execution.OutgoingUid, actual!.OutgoingUid);
110+
Assert.Equal(execution.CorrelationId, actual!.CorrelationId);
111+
Assert.Equal(execution.WorkflowInstanceId, actual!.WorkflowInstanceId);
112+
Assert.Equal(execution.StudyUid, actual!.StudyUid);
113+
Assert.Equal(execution.RequestTime, actual!.RequestTime);
114+
Assert.Equal(execution.OriginalValues, actual!.OriginalValues);
115+
Assert.Equal(execution.ProxyValues, actual!.ProxyValues);
116+
Assert.Equal(2, actual!.OriginalValues.Count);
117+
118+
await store.RemoveAsync(execution.OutgoingUid).ConfigureAwait(false);
119+
120+
actual = await _databaseFixture.DatabaseContext.Set<RemoteAppExecution>().FirstOrDefaultAsync(p => p.OutgoingUid == execution.OutgoingUid).ConfigureAwait(false);
121+
Assert.Null(actual);
122+
}
123+
}
124+
}

src/Database/MongoDB/Integration.Test/RemoteAppRepositoryTest.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class RemoteAppRepositoryTest
4242

4343
public RemoteAppRepositoryTest(MongoDatabaseFixture databaseFixture)
4444
{
45+
4546
_databaseFixture = databaseFixture ?? throw new ArgumentNullException(nameof(databaseFixture));
4647

4748
_serviceScopeFactory = new Mock<IServiceScopeFactory>();
@@ -66,7 +67,13 @@ public async Task GivenAexecution_WhenAddingToDatabase_ExpectItToBeSaved()
6667
{
6768
var outgoingUid = Guid.NewGuid().ToString();
6869
var dataset = new DicomDataset();
69-
var date = new DateTime(2023, 5, 21, 6, 7, 8).ToUniversalTime();
70+
var date = new DateTime(
71+
DateTime.Now.Year,
72+
DateTime.Now.Month,
73+
DateTime.Now.Day,
74+
DateTime.Now.Hour,
75+
DateTime.Now.Minute,
76+
DateTime.Now.Second).ToUniversalTime();
7077

7178
var execution = new RemoteAppExecution
7279
{
@@ -79,6 +86,10 @@ public async Task GivenAexecution_WhenAddingToDatabase_ExpectItToBeSaved()
7986
OriginalValues = new Dictionary<string, string> {
8087
{ DicomTag.StudyInstanceUID.ToString(), "StudyInstanceUID" },
8188
{ DicomTag.SeriesInstanceUID.ToString(), "SeriesInstanceUID" }
89+
},
90+
ProxyValues = new Dictionary<string, string> {
91+
{ DicomTag.StudyInstanceUID.ToString(), "StudyInstanceUID" },
92+
{ DicomTag.SeriesInstanceUID.ToString(), "SeriesInstanceUID" }
8293
}
8394
};
8495

@@ -87,7 +98,8 @@ public async Task GivenAexecution_WhenAddingToDatabase_ExpectItToBeSaved()
8798
await store.AddAsync(execution).ConfigureAwait(false);
8899

89100
var collection = _databaseFixture.Database.GetCollection<RemoteAppExecution>(nameof(RemoteAppExecution));
90-
var actual = await collection.Find(p => p.OutgoingUid == execution.OutgoingUid).FirstOrDefaultAsync().ConfigureAwait(false);
101+
102+
var actual = await store.GetAsync(execution.OutgoingUid).ConfigureAwait(false);
91103

92104
Task.Delay(1000).Wait();
93105
Assert.NotNull(actual);
@@ -100,7 +112,13 @@ public async Task GivenAexecution_WhenAddingToDatabase_ExpectItToBeSaved()
100112
Assert.Equal(execution.StudyUid, actual!.StudyUid);
101113
Assert.Equal(execution.RequestTime, actual!.RequestTime);
102114
Assert.Equal(execution.OriginalValues, actual!.OriginalValues);
115+
Assert.Equal(execution.ProxyValues, actual!.ProxyValues);
103116
Assert.Equal(2, actual!.OriginalValues.Count);
117+
118+
await store.RemoveAsync(execution.OutgoingUid).ConfigureAwait(false);
119+
120+
actual = await collection.Find(p => p.OutgoingUid == execution.OutgoingUid).FirstOrDefaultAsync().ConfigureAwait(false);
121+
Assert.Null(actual);
104122
}
105123
}
106124
}

src/Database/MongoDB/Repositories/RemoteAppExecutionRepository.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,17 @@ public RemoteAppExecutionRepository(IServiceScopeFactory serviceScopeFactory,
6161

6262
private void CreateIndexes()
6363
{
64-
var options = new CreateIndexOptions { Unique = true, ExpireAfter = TimeSpan.FromDays(7), Name = "RequestTime" };
64+
var options = new CreateIndexOptions { Unique = true };
6565
var indexDefinitionState = Builders<RemoteAppExecution>.IndexKeys.Ascending(_ => _.OutgoingUid);
6666
var indexModel = new CreateIndexModel<RemoteAppExecution>(indexDefinitionState, options);
6767

6868
_collection.Indexes.CreateOne(indexModel);
69+
70+
options = new CreateIndexOptions { ExpireAfter = TimeSpan.FromDays(7), Name = "RequestTime" };
71+
indexDefinitionState = Builders<RemoteAppExecution>.IndexKeys.Ascending(_ => _.RequestTime);
72+
indexModel = new CreateIndexModel<RemoteAppExecution>(indexDefinitionState, options);
73+
74+
_collection.Indexes.CreateOne(indexModel);
6975
}
7076

7177
public async Task<bool> AddAsync(RemoteAppExecution item, CancellationToken cancellationToken = default)

src/InformaticsGateway/Common/IDicomToolkit.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
using System;
1919
using System.IO;
20+
using System.Linq;
2021
using System.Text.RegularExpressions;
2122
using System.Threading.Tasks;
2223
using FellowOakDicom;
@@ -32,5 +33,48 @@ public interface IDicomToolkit
3233
StudySerieSopUids GetStudySeriesSopInstanceUids(DicomFile dicomFile);
3334

3435
static DicomTag GetDicomTagByName(string tag) => DicomDictionary.Default[tag] ?? DicomDictionary.Default[Regex.Replace(tag, @"\s+", "", RegexOptions.None, TimeSpan.FromSeconds(1))];
36+
37+
static DicomTag[] GetTagArrayFromStringArray(string values)
38+
{
39+
var names = values.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
40+
return names.Select(n => IDicomToolkit.GetDicomTagByName(n)).ToArray();
41+
}
42+
43+
static T GetTagProxyValue<T>(DicomTag tag) where T : class
44+
{
45+
// partial implementation for now see
46+
// https://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html
47+
// for full list
48+
var output = "";
49+
switch (tag.DictionaryEntry.ValueRepresentations[0].Code)
50+
{
51+
case "UI":
52+
case "LO":
53+
case "LT":
54+
{
55+
output = DicomUIDGenerator.GenerateDerivedFromUUID().UID;
56+
return output as T;
57+
}
58+
case "SH":
59+
case "AE":
60+
case "CS":
61+
case "PN":
62+
case "ST":
63+
case "UT":
64+
{
65+
output = "no Value";
66+
break;
67+
}
68+
}
69+
return output as T;
70+
}
71+
72+
static (ushort group, ushort element) ParseDicomTagStringToTwoShorts(string input)
73+
{
74+
var trim = input.Substring(1, input.Length - 2);
75+
var array = trim.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
76+
return (Convert.ToUInt16(array[0], 16), Convert.ToUInt16(array[1], 16));
77+
}
78+
3579
}
3680
}

0 commit comments

Comments
 (0)