Skip to content

Commit cdc2984

Browse files
committed
gh-41 New SpecFlow test project for integration test.
Add DIMSE test scenarios
1 parent b10600e commit cdc2984

18 files changed

+1564
-9
lines changed

.editorconfig

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,39 @@ trim_trailing_whitespace = true
1515
# Generated code
1616
[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}]
1717
generated_code = true
18+
dotnet_style_operator_placement_when_wrapping = beginning_of_line
19+
tab_width = 4
20+
end_of_line = crlf
21+
dotnet_style_coalesce_expression = true:suggestion
22+
dotnet_style_null_propagation = true:suggestion
23+
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
24+
dotnet_style_prefer_auto_properties = true:suggestion
25+
dotnet_style_object_initializer = true:suggestion
26+
dotnet_style_collection_initializer = true:suggestion
27+
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
28+
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
29+
dotnet_style_prefer_conditional_expression_over_return = true:silent
30+
dotnet_style_explicit_tuple_names = true:suggestion
31+
dotnet_style_prefer_inferred_tuple_names = true:suggestion
32+
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
33+
dotnet_style_prefer_compound_assignment = true:suggestion
34+
dotnet_style_prefer_simplified_interpolation = true:suggestion
35+
dotnet_style_namespace_match_folder = true:suggestion
36+
dotnet_style_readonly_field = true:suggestion
37+
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
38+
dotnet_style_predefined_type_for_member_access = true:suggestion
39+
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
40+
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
41+
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent
42+
dotnet_code_quality_unused_parameters = all:suggestion
43+
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
44+
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
45+
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
46+
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
47+
dotnet_style_qualification_for_field = false:suggestion
48+
dotnet_style_qualification_for_property = false:suggestion
49+
dotnet_style_qualification_for_method = false:suggestion
50+
dotnet_style_qualification_for_event = false:suggestion
1851

1952
# C# files
2053
[*.cs]
@@ -54,15 +87,15 @@ dotnet_style_predefined_type_for_member_access = true:suggestion
5487
# name all constant fields using PascalCase
5588
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
5689
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
57-
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
90+
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
5891
dotnet_naming_symbols.constant_fields.applicable_kinds = field
5992
dotnet_naming_symbols.constant_fields.required_modifiers = const
6093
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
6194

6295
# static fields should have s_ prefix
6396
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
6497
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
65-
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
98+
dotnet_naming_rule.static_fields_should_have_prefix.style = pascal_case_style
6699
dotnet_naming_symbols.static_fields.applicable_kinds = field
67100
dotnet_naming_symbols.static_fields.required_modifiers = static
68101
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
@@ -72,7 +105,7 @@ dotnet_naming_style.static_prefix_style.capitalization = camel_case
72105
# internal and private fields should be _camelCase
73106
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
74107
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
75-
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
108+
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
76109
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
77110
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
78111
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
@@ -153,6 +186,20 @@ csharp_space_between_square_brackets = false
153186

154187
# License header
155188
file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
189+
csharp_style_namespace_declarations = block_scoped:silent
190+
csharp_style_prefer_null_check_over_type_check = true:suggestion
191+
csharp_style_prefer_local_over_anonymous_function = true:suggestion
192+
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
193+
csharp_style_prefer_tuple_swap = true:suggestion
194+
csharp_style_deconstructed_variable_declaration = true:suggestion
195+
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
196+
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
197+
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
198+
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
199+
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
200+
csharp_style_prefer_pattern_matching = true:silent
201+
csharp_style_prefer_not_pattern = true:suggestion
202+
csharp_style_prefer_extended_property_pattern = true:suggestion
156203

157204
# C++ Files
158205
[*.{cpp,h,in}]

src/Client.Common/ProblemException.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2021 MONAI Consortium
1+
// Copyright 2022 MONAI Consortium
22
// Licensed under the Apache License, Version 2.0 (the "License");
33
// you may not use this file except in compliance with the License.
44
// You may obtain a copy of the License at
@@ -11,7 +11,7 @@
1111

1212
/*
1313
* Apache License, Version 2.0
14-
* Copyright 2021 NVIDIA Corporation
14+
* Copyright 2022 NVIDIA Corporation
1515
*
1616
* Licensed under the Apache License, Version 2.0 (the "License");
1717
* you may not use this file except in compliance with the License.
@@ -33,18 +33,18 @@ namespace Monai.Deploy.InformaticsGateway.Client.Common
3333
[Serializable]
3434
public class ProblemException : Exception
3535
{
36-
private readonly ProblemDetails _problemDetails;
36+
public ProblemDetails ProblemDetails { get; private set; }
3737

3838
public ProblemException(ProblemDetails problemDetails) : base(problemDetails.Detail)
3939
{
40-
_problemDetails = problemDetails ?? throw new ArgumentNullException(nameof(problemDetails));
40+
ProblemDetails = problemDetails ?? throw new ArgumentNullException(nameof(problemDetails));
4141
}
4242

4343
public override string Message => this.ToString();
4444

4545
public override string ToString()
4646
{
47-
return $"HTTP Status: {_problemDetails.Status}. {_problemDetails.Detail}";
47+
return $"HTTP Status: {ProblemDetails.Status}. {ProblemDetails.Detail}";
4848
}
4949
}
5050
}

src/Monai.Deploy.InformaticsGateway.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "MessageBroker", "MessageBro
4545
EndProject
4646
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Monai.Deploy.InformaticsGateway.MessageBroker.RabbitMq", "MessageBroker\RabbitMq\Monai.Deploy.InformaticsGateway.MessageBroker.RabbitMq.csproj", "{F1550E33-47B4-40FA-973E-44551480BED1}"
4747
EndProject
48+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{B8E99EF7-84EA-4D11-B722-9EE81B89CD86}"
49+
EndProject
50+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Monai.Deploy.InformaticsGateway.Integration.Test", "..\tests\Integration.Test\Monai.Deploy.InformaticsGateway.Integration.Test.csproj", "{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}"
51+
EndProject
4852
Global
4953
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5054
Debug|Any CPU = Debug|Any CPU
@@ -283,13 +287,34 @@ Global
283287
{F1550E33-47B4-40FA-973E-44551480BED1}.Release|x64.Build.0 = Release|Any CPU
284288
{F1550E33-47B4-40FA-973E-44551480BED1}.Release|x86.ActiveCfg = Release|Any CPU
285289
{F1550E33-47B4-40FA-973E-44551480BED1}.Release|x86.Build.0 = Release|Any CPU
290+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
291+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Debug|Any CPU.Build.0 = Debug|Any CPU
292+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Debug|x64.ActiveCfg = Debug|Any CPU
293+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Debug|x64.Build.0 = Debug|Any CPU
294+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Debug|x86.ActiveCfg = Debug|Any CPU
295+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Debug|x86.Build.0 = Debug|Any CPU
296+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Release|Any CPU.ActiveCfg = Release|Any CPU
297+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Release|Any CPU.Build.0 = Release|Any CPU
298+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Release|x64.ActiveCfg = Release|Any CPU
299+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Release|x64.Build.0 = Release|Any CPU
300+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Release|x86.ActiveCfg = Release|Any CPU
301+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B}.Release|x86.Build.0 = Release|Any CPU
286302
EndGlobalSection
287303
GlobalSection(SolutionProperties) = preSolution
288304
HideSolutionNode = FALSE
289305
EndGlobalSection
290306
GlobalSection(NestedProjects) = preSolution
307+
{90BC467C-39C9-4EBB-98CF-3289BC8E8A72} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
308+
{78668564-B90C-4661-A0EF-373E040F2270} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
309+
{C7CBB953-DCBB-4B15-A562-C0C4CCF93B19} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
310+
{7A429A5D-D845-42D0-A823-68E84BCC3659} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
311+
{74E3DCE4-6095-4A77-A12C-05D96840B02D} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
312+
{06B4CB86-317A-41BA-9FEB-B9346E061254} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
313+
{E1583FDB-9DC1-46E7-BB14-AFC2A7608600} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
314+
{28099DFC-8937-4508-848C-35C99E56C121} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
291315
{18CC4631-8E97-47E7-A89F-01A54BBFED94} = {B0BD0BF4-9D20-4385-88CF-5F715B06E3F8}
292316
{F1550E33-47B4-40FA-973E-44551480BED1} = {7D07DC2E-1CC5-4B1F-8112-920542BB78A7}
317+
{0BB99F0E-669F-4884-9DB1-AFA16CC9C67B} = {B8E99EF7-84EA-4D11-B722-9EE81B89CD86}
293318
EndGlobalSection
294319
GlobalSection(ExtensibilityGlobals) = postSolution
295320
SolutionGuid = {E23DC856-D033-49F6-9BC6-9F1D0ECD05CB}

src/coverlet.runsettings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<DataCollector friendlyName="XPlat code coverage">
66
<Configuration>
77
<Format>opencover</Format>
8-
<Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*,[xunit.*]*,[Grpc.Core*]*,[System.*]*,[Microsoft.*]*</Exclude>
8+
<Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*,[xunit.*]*,[Grpc.Core*]*,[System.*]*,[Microsoft.*]*,[Integration.Test.*]*</Exclude>
99
<Include></Include>
1010
<ExcludeByAttribute></ExcludeByAttribute>
1111
<ExcludeByFile></ExcludeByFile> <!-- Globbing filter -->
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2022 MONAI Consortium
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
// Unless required by applicable law or agreed to in writing, software
7+
// distributed under the License is distributed on an "AS IS" BASIS,
8+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
// See the License for the specific language governing permissions and
10+
// limitations under the License.
11+
12+
namespace Monai.Deploy.InformaticsGateway.Integration.Test.Drivers
13+
{
14+
public static class Extensions
15+
{
16+
public static long NextLong(this Random random, long minValue, long maxValue)
17+
{
18+
byte[] buf = new byte[8];
19+
random.NextBytes(buf);
20+
long longRand = BitConverter.ToInt64(buf, 0);
21+
22+
return (Math.Abs(longRand % (maxValue - minValue)) + minValue);
23+
}
24+
}
25+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2022 MONAI Consortium
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
// http://www.apache.org/licenses/LICENSE-2.0
6+
// Unless required by applicable law or agreed to in writing, software
7+
// distributed under the License is distributed on an "AS IS" BASIS,
8+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
// See the License for the specific language governing permissions and
10+
// limitations under the License.
11+
12+
using System.Configuration;
13+
using System.Reflection;
14+
using Microsoft.Extensions.Configuration;
15+
using Newtonsoft.Json;
16+
using TechTalk.SpecFlow.Infrastructure;
17+
18+
namespace Monai.Deploy.InformaticsGateway.Integration.Test.Drivers
19+
{
20+
public sealed class Configurations
21+
{
22+
private readonly IConfiguration _config;
23+
private readonly ISpecFlowOutputHelper _outputHelper;
24+
25+
public TestRunnerSettings TestRunnerOptions { get; private set; }
26+
public InformaticsGatewaySettings InformaticsGatewayOptions { get; private set; }
27+
public MessageBrokerSettings MessageBrokerOptions { get; private set; }
28+
public Dictionary<string, StudySpec> StudySpecs { get; private set; }
29+
public StorageServiceSettings StorageServiceOptions { get; private set; }
30+
31+
public Configurations(ISpecFlowOutputHelper outputHelper)
32+
{
33+
TestRunnerOptions = new TestRunnerSettings();
34+
InformaticsGatewayOptions = new InformaticsGatewaySettings();
35+
MessageBrokerOptions = new MessageBrokerSettings();
36+
StorageServiceOptions = new StorageServiceSettings();
37+
_outputHelper = outputHelper ?? throw new ArgumentNullException(nameof(outputHelper));
38+
StudySpecs = LoadStudySpecs() ?? throw new NullReferenceException("study.json not found or empty.");
39+
_config = new ConfigurationBuilder()
40+
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
41+
.Build();
42+
43+
LoadConfiguration();
44+
}
45+
46+
private Dictionary<string, StudySpec> LoadStudySpecs()
47+
{
48+
var assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
49+
var studyJsonPath = Path.Combine(assemblyPath ?? string.Empty, "study.json");
50+
51+
if (!File.Exists(studyJsonPath))
52+
{
53+
throw new FileNotFoundException($"study.json not found in {studyJsonPath}");
54+
}
55+
56+
var studyJson = File.ReadAllText(studyJsonPath);
57+
return JsonConvert.DeserializeObject<Dictionary<string, StudySpec>>(studyJson);
58+
}
59+
60+
private void LoadConfiguration()
61+
{
62+
_config.GetSection(nameof(TestRunnerSettings)).Bind(TestRunnerOptions);
63+
_config.GetSection(nameof(InformaticsGatewaySettings)).Bind(InformaticsGatewayOptions);
64+
_config.GetSection(nameof(MessageBrokerSettings)).Bind(MessageBrokerOptions);
65+
_config.GetSection(nameof(StorageServiceSettings)).Bind(StorageServiceOptions);
66+
67+
if (InformaticsGatewayOptions.TemporaryDataStore == "$DATA_PATH")
68+
{
69+
InformaticsGatewayOptions.TemporaryDataStore = Environment.GetEnvironmentVariable("DATA_PATH") ?? throw new ConfigurationErrorsException("Environment variable 'DATA_PATH' is undefined.");
70+
}
71+
72+
_outputHelper.WriteLine("Informatics Gateway data path = {0}", InformaticsGatewayOptions.TemporaryDataStore);
73+
if (TestRunnerOptions.HostIp == "$HOST_IP")
74+
{
75+
TestRunnerOptions.HostIp = Environment.GetEnvironmentVariable("HOST_IP") ?? throw new ConfigurationErrorsException("Environment variable 'HOST_IP' is undefined.");
76+
}
77+
_outputHelper.WriteLine("Test Runner Host/IP = {0}", TestRunnerOptions.HostIp);
78+
if (InformaticsGatewayOptions.Host == "$HOST_IP")
79+
{
80+
InformaticsGatewayOptions.Host = Environment.GetEnvironmentVariable("HOST_IP") ?? throw new ConfigurationErrorsException("Environment variable 'HOST_IP' is undefined.");
81+
}
82+
_outputHelper.WriteLine("Informatics Gateway Host/IP = {0}", TestRunnerOptions.HostIp);
83+
if (MessageBrokerOptions.Endpoint == "$HOST_IP")
84+
{
85+
MessageBrokerOptions.Endpoint = Environment.GetEnvironmentVariable("HOST_IP") ?? throw new ConfigurationErrorsException("Environment variable 'HOST_IP' is undefined.");
86+
}
87+
_outputHelper.WriteLine("Message Broker Host/IP = {0}", TestRunnerOptions.HostIp);
88+
if (StorageServiceOptions.Host == "$HOST_IP")
89+
{
90+
StorageServiceOptions.Host = Environment.GetEnvironmentVariable("HOST_IP") ?? throw new ConfigurationErrorsException("Environment variable 'HOST_IP' is undefined.");
91+
}
92+
_outputHelper.WriteLine("Storage Service Host/IP = {0}", TestRunnerOptions.HostIp);
93+
}
94+
}
95+
96+
public class StorageServiceSettings
97+
{
98+
public string Host { get; set; }
99+
public int Port { get; set; }
100+
public string AccessToken { get; set; }
101+
public string AccessKey { get; set; }
102+
103+
public string Endpoint => $"{Host}:{Port}";
104+
}
105+
106+
public class TestRunnerSettings
107+
{
108+
/// <summary>
109+
/// Gets or sets the Host/IP Address used when createing a DICOM source.
110+
/// If not specified, the test runner would query and use first available IPv4 IP Address.
111+
/// </summary>
112+
/// <value></value>
113+
public string HostIp { get; set; }
114+
}
115+
116+
public class InformaticsGatewaySettings
117+
{
118+
/// <summary>
119+
/// Gets or set the path where the temporary payloads are stored.
120+
/// </summary>
121+
public string TemporaryDataStore { get; set; }
122+
123+
/// <summary>
124+
/// Gets or set host name or IP address of the Informatics Gateway.
125+
/// </summary>
126+
public string Host { get; set; }
127+
128+
/// <summary>
129+
/// Gets or sets the DIMSE port of the Informatics Gateway.
130+
/// </summary>
131+
public int DimsePort { get; set; }
132+
133+
/// <summary>
134+
/// Gets or sets the RESTful API port of the Informatics Gateway.
135+
/// </summary>
136+
public int ApiPort { get; set; }
137+
138+
/// <summary>
139+
/// Gets or sets the name of the bucket used by the storage service.
140+
/// </summary>
141+
public string StorageServiceBucketName { get; set; }
142+
143+
/// <summary>
144+
/// Gets the API endpoint of the Informatics Gateway.
145+
/// </summary>
146+
public Uri ApiEndpoint => new Uri($"http://{Host}:{ApiPort}");
147+
}
148+
149+
/// <summary>
150+
/// Maps modality type specs from study.json
151+
/// </summary>
152+
public class StudySpec
153+
{
154+
private const int OneMiB = 1048576;
155+
public int SeriesMin { get; set; }
156+
public int SeriesMax { get; set; }
157+
public int InstanceMin { get; set; }
158+
public int InstanceMax { get; set; }
159+
public float SizeMin { get; set; }
160+
public float SizeMax { get; set; }
161+
162+
public long SizeMinBytes
163+
{
164+
get { return (long)(SizeMin * OneMiB); }
165+
}
166+
167+
public long SizeMaxBytes
168+
{
169+
get { return (long)(SizeMax * OneMiB); }
170+
}
171+
}
172+
173+
public class MessageBrokerSettings
174+
{
175+
public string Endpoint { get; set; }
176+
public string Username { get; set; }
177+
public string Password { get; set; }
178+
public string VirtualHost { get; set; }
179+
public string Exchange { get; set; }
180+
}
181+
}

0 commit comments

Comments
 (0)