diff --git a/BasicUnitTest/SecurityEventTokenTests.cs b/BasicUnitTest/SecurityEventTokenTests.cs index f1ec0f6a119507af8896e09c3ca9d7f5c604f603..1763033e7d525e7cd0e67e59ae9406d339501bea 100644 --- a/BasicUnitTest/SecurityEventTokenTests.cs +++ b/BasicUnitTest/SecurityEventTokenTests.cs @@ -23,7 +23,7 @@ public class SecurityEventTokenTests { [Test] public void CreateJwt_AcceptSubmission() { - var token = _encryption.CreateAcceptSecurityEventToken(new SubmissionForPickupDto() { + var token = _encryption.CreateAcceptSecurityEventToken(new SubmissionForPickupDto { SubmissionId = Guid.NewGuid().ToString(), CaseId = Guid.NewGuid().ToString(), DestinationId = Guid.NewGuid().ToString() }); diff --git a/BasicUnitTest/SubscriberReceiveTests.cs b/BasicUnitTest/SubscriberReceiveTests.cs index 88473285446ee3b3e862d6e269483393efbe320d..0a33fbe52a09c0f9551676dc00b57a4a98aec495 100644 --- a/BasicUnitTest/SubscriberReceiveTests.cs +++ b/BasicUnitTest/SubscriberReceiveTests.cs @@ -8,8 +8,6 @@ using NUnit.Framework; namespace FluentApiTest; public class SubscriberReceiveTests { - private readonly string clientId = "clientId"; - private readonly string clientSecret = "clientSecret"; private IContainer _container = null!; private ILogger _logger = null!; private MockSettings _mockSettings = null!; diff --git a/DemoRunner/SubscriberDemo.cs b/DemoRunner/SubscriberDemo.cs index 1518afcdfe2dd74c4138af688f920476ba6d2c86..d08b9f6e51ed37bf55be0d21a7a410b1ca101399 100644 --- a/DemoRunner/SubscriberDemo.cs +++ b/DemoRunner/SubscriberDemo.cs @@ -30,7 +30,7 @@ public static class SubscriberDemo { var submissions = subscriber.GetAvailableSubmissions(); - foreach (var submission in submissions) { + foreach (var submission in submissions) try { var subscriberWithSubmission = subscriber.RequestSubmission(submission.SubmissionId); @@ -40,9 +40,9 @@ public static class SubscriberDemo { logger.LogInformation("Fachdaten: {Data}", subscriberWithSubmission.GetDataJson()); subscriberWithSubmission .AcceptSubmission(); - } catch (Exception e) { + } + catch (Exception e) { logger.LogError(e, "Fehler beim Abrufen der Einreichung"); } - } } } diff --git a/E2ETest/RejectSubmissionTest.cs b/E2ETest/RejectSubmissionTest.cs index 95b2345eb225668be1dab0da8d811fbe2148dd90..f69713c5a1e5a8650702273e822cf10ba215b0b4 100644 --- a/E2ETest/RejectSubmissionTest.cs +++ b/E2ETest/RejectSubmissionTest.cs @@ -6,8 +6,8 @@ using Microsoft.Extensions.Logging; namespace E2ETest; public class RejectSubmissionTest : EndToEndTestBase { - private string? _caseId; - private string? _submissionId; + private string _caseId = null!; + private string _submissionId = null!; [Order(10)] [Test] @@ -18,8 +18,8 @@ public class RejectSubmissionTest : EndToEndTestBase { .WithData(@"{""data"":""value""}") .Submit(); - _caseId = submission.CaseId; - _submissionId = submission.Id; + _caseId = submission.CaseId!; + _submissionId = submission.Id!; _caseId.Should().NotBeNull(); _submissionId.Should().NotBeNull(); @@ -29,7 +29,7 @@ public class RejectSubmissionTest : EndToEndTestBase { [Order(20)] public void Sender_GetSubmissionState() { // Act - var status = Sender.GetStatusForSubmission(_caseId); + var status = Sender.GetStatusForSubmission(_caseId, Settings.DestinationId); // Assert status.Count.Should().BeGreaterThan(0); @@ -41,7 +41,7 @@ public class RejectSubmissionTest : EndToEndTestBase { [Order(30)] public void Subscriber_GetSubmissionState() { // Act - var status = Subscriber.GetStatusForSubmission(_caseId); + var status = Subscriber.GetStatusForSubmission(_caseId, Settings.DestinationId); // Assert status.Count.Should().BeGreaterThan(0); @@ -64,14 +64,14 @@ public class RejectSubmissionTest : EndToEndTestBase { subscriberWithSubmission.RejectSubmission(Problems.SchemaViolation //,new Problems { Detail = "A critical problem" } - ); + ); } [Test] [Order(50)] public void Sender_GetSubmissionState_AfterRejecting() { // Act - var status = Sender.GetStatusForSubmission(_caseId); + var status = Sender.GetStatusForSubmission(_caseId, Settings.DestinationId); // Assert status.Count.Should().BeGreaterThan(0); diff --git a/E2ETest/StraightForwardTest.cs b/E2ETest/StraightForwardTest.cs index c12aaa2fbcb39519361ba8fd4dcfd052fa915b93..0eb0df15fcf30a010be572d890acfdf1a7c1713e 100644 --- a/E2ETest/StraightForwardTest.cs +++ b/E2ETest/StraightForwardTest.cs @@ -1,13 +1,12 @@ using FitConnect.Models; -using FitConnect.Models.v1.Api; using FluentAssertions; using Microsoft.Extensions.Logging; namespace E2ETest; public class StraightForwardTest : EndToEndTestBase { - private string? _caseId; - private string? _submissionId; + private string _caseId = null!; + private string _submissionId = null!; [Order(10)] [Test] @@ -18,8 +17,8 @@ public class StraightForwardTest : EndToEndTestBase { .WithData(@"{""data"":""value""}") .Submit(); - _caseId = submission.CaseId; - _submissionId = submission.Id; + _caseId = submission.CaseId!; + _submissionId = submission.Id!; _caseId.Should().NotBeNull(); _submissionId.Should().NotBeNull(); @@ -29,7 +28,7 @@ public class StraightForwardTest : EndToEndTestBase { [Order(20)] public void Sender_GetSubmissionState() { // Act - var status = Sender.GetStatusForSubmission(_caseId); + var status = Sender.GetStatusForSubmission(_caseId, Settings.DestinationId); // Assert status.Count.Should().BeGreaterThan(0); @@ -41,7 +40,7 @@ public class StraightForwardTest : EndToEndTestBase { [Order(30)] public void Subscriber_GetSubmissionState() { // Act - var status = Subscriber.GetStatusForSubmission(_caseId); + var status = Subscriber.GetStatusForSubmission(_caseId, Settings.DestinationId); // Assert status.Count.Should().BeGreaterThan(0); @@ -69,7 +68,7 @@ public class StraightForwardTest : EndToEndTestBase { [Order(50)] public void Sender_GetSubmissionState_AfterAccepting() { // Act - var status = Sender.GetStatusForSubmission(_caseId); + var status = Sender.GetStatusForSubmission(_caseId, Settings.DestinationId); // Assert status.Count.Should().BeGreaterThan(0); diff --git a/EncryptionTests/FileEncryptionTest.cs b/EncryptionTests/FileEncryptionTest.cs index 457630b02e541c551ac5f81a6a34c3133a7543ca..b81d8f394e6fbdd1c53a3cda92fd4049690df698 100644 --- a/EncryptionTests/FileEncryptionTest.cs +++ b/EncryptionTests/FileEncryptionTest.cs @@ -10,13 +10,13 @@ using NUnit.Framework; namespace SenderTest; public class FileEncryptionTest { - private string _encryptedFile; - private FitEncryption _encryption; - private byte[] sourceFile = null!; + private string _encryptedFile = null!; + private FitEncryption _encryption = null!; + private byte[] _sourceFile = null!; [SetUp] public void Setup() { - sourceFile = RandomNumberGenerator.GetBytes(4096); + _sourceFile = RandomNumberGenerator.GetBytes(4096); var container = Container.Create(); var keySet = container.Resolve<KeySet>(); _encryption = new FitEncryption(keySet, null); @@ -25,7 +25,7 @@ public class FileEncryptionTest { [Test] [Order(10)] public void EncryptFile() { - _encryptedFile = _encryption.Encrypt(sourceFile); + _encryptedFile = _encryption.Encrypt(_sourceFile); } [Test] diff --git a/EncryptionTests/JweTest.cs b/EncryptionTests/JweTest.cs index 207a5689f55a99833542c8673b16220fc5b18dcc..73beadb512ed5f5fa443a0a8ed0bec5eab34b0d2 100644 --- a/EncryptionTests/JweTest.cs +++ b/EncryptionTests/JweTest.cs @@ -13,10 +13,10 @@ using NUnit.Framework; namespace SenderTest; public class JweTest { - private IContainer _container; - private ILogger<JweTest> _logger; - private Sender _sender; - private MockSettings _settings; + private IContainer _container = null!; + private ILogger<JweTest> _logger = null!; + private Sender _sender = null!; + private MockSettings _settings = null!; [SetUp] diff --git a/FitConnect/Encryption/FitEncryption.cs b/FitConnect/Encryption/FitEncryption.cs index 6621e572500f706cb2d66b1ec9164ce1411a9476..d3e36b4e220a7e659530f87dad74c7d08c5b38e3 100644 --- a/FitConnect/Encryption/FitEncryption.cs +++ b/FitConnect/Encryption/FitEncryption.cs @@ -4,11 +4,9 @@ using System.Text; using FitConnect.Models.v1.Api; using FitConnect.Services.Models.v1.Submission; using IdentityModel; -using Jose; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; -using Newtonsoft.Json; namespace FitConnect.Encryption; @@ -22,7 +20,7 @@ public class KeySet { public class FitEncryption { private readonly IEncryptor _encryptor = new JoseEncryptor(); private readonly ILogger? _logger; - private JwtHeader _jwtHeader; + private JwtHeader? _jwtHeader; internal FitEncryption(ILogger? logger) { _logger = logger; @@ -51,6 +49,7 @@ public class FitEncryption { public string? PublicKeyEncryption { get; set; } public string? PublicKeySignatureVerification { get; set; } + public (string plainText, byte[] plainBytes, byte[] tag) Decrypt(string cypherText, string key) { return _encryptor.Decrypt(key, cypherText); @@ -88,23 +87,30 @@ public class FitEncryption { public string CreateRejectSecurityEventToken(string submissionId, string caseId, - string destinationId, Problems[]? problemsArray) => - CreateSecurityEventToken(submissionId, caseId, destinationId, + string destinationId, Problems[]? problemsArray) { + return CreateSecurityEventToken(submissionId, caseId, destinationId, "https://schema.fitko.de/fit-connect/events/reject-submission", new { problems = problemsArray }); + } public string CreateAcceptSecurityEventToken(SubmissionForPickupDto submission) { // string submissionSubmissionId,string submissionCaseId, string submissionDestinationId) { + + // NOMERGE Remove Dummy data var payload = new { authenticationTags = new { data = "UCGiqJxhBI3IFVdPalHHvA", metadata = "XFBoMYUZodetZdvTiFvSkQ", - attachments = new Dictionary<string, string>() { + attachments = new Dictionary<string, string> { { "0b799252-deb9-42b0-98d3-c50d24bbafe0", "rT99rwrBTbTI7IJM8fU3El" } } } }; - // TODO Add payload + // NOMERGE Add payload + if (submission?.SubmissionId == null || submission?.CaseId == null || + submission?.DestinationId == null) + throw new ArgumentException("SubmissionId, CaseId and DestinationId are required"); + return CreateSecurityEventToken(submission.SubmissionId, submission.CaseId, submission.DestinationId, "https://schema.fitko.de/fit-connect/events/accept-submission", null); @@ -169,7 +175,7 @@ public class FitEncryption { public static bool VerifyJwt(string signature, IEnumerable<JsonWebKey> keys, ILogger? logger = null) { - foreach (var key in keys) { + foreach (var key in keys) try { var valid = VerifyJwt(signature, key, logger); if (valid) return true; @@ -177,7 +183,6 @@ public class FitEncryption { catch (Exception e) { logger?.LogError(e, "Error verifying JWT"); } - } return false; } diff --git a/FitConnect/FitConnectClient.cs b/FitConnect/FitConnectClient.cs index c27f24ea30fe60d18d4df5133a14c6c3356e3a28..a46e63e14b482179d379e803171d5e0d24f27378 100644 --- a/FitConnect/FitConnectClient.cs +++ b/FitConnect/FitConnectClient.cs @@ -4,6 +4,7 @@ using FitConnect.Encryption; using FitConnect.Models; using FitConnect.Services; using FitConnect.Services.Interfaces; +using FitConnect.Services.Models.v1.Submission; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; @@ -64,16 +65,20 @@ public abstract class FitConnectClient { private FitConnectClient() { + // Take care of OAuthService if make the constructor public Encryption = new FitEncryption(Logger); } - public IOAuthService OAuthService { get; } - public ISubmissionService SubmissionService { get; } - public IRouteService RouteService { get; } - public IDestinationService DestinationService { get; } - public ICasesService CasesService { get; } - public ILogger? Logger { get; } - public FitEncryption Encryption { get; set; } + + public IOAuthService OAuthService { get; } = + null!; // To resolve the warning, is only applicable for the **private** constructor + + protected ISubmissionService SubmissionService { get; } = null!; + protected IRouteService RouteService { get; } = null!; + protected IDestinationService DestinationService { get; } = null!; + protected ICasesService CasesService { get; } = null!; + protected ILogger? Logger { get; } + protected FitEncryption Encryption { get; set; } public void SetProxy(WebProxy proxy) { OAuthService.Proxy = proxy; @@ -91,12 +96,33 @@ public abstract class FitConnectClient { /// Retrieve the events for the submission /// </summary> /// <param name="caseId"></param> + /// <param name="destinationId"></param> + /// <param name="skipTest"></param> + /// <returns></returns> + public List<SecurityEventToken> GetStatusForSubmission(string caseId, string destinationId, + bool skipTest = false) { + var events = SubmissionService.GetStatusForSubmissionAsync(caseId, destinationId, skipTest) + .Result; + return events?.Where(s => s != null) + .Select(e => new SecurityEventToken(e!)).ToList() ?? new List<SecurityEventToken>(); + } + + /// <summary> + /// Retrieve the events for the submission + /// </summary> + /// <param name="submission"></param> /// <param name="skipTest"></param> /// <returns></returns> - public List<SecurityEventToken> GetStatusForSubmission(string caseId, bool skipTest = false) { - var events = SubmissionService.GetStatusForSubmissionAsync(caseId, skipTest).Result; - return events.Where(s => s != null) - .Select(e => new SecurityEventToken(e!)).ToList(); + public List<SecurityEventToken> GetStatusForSubmission(SubmissionForPickupDto submission, + bool skipTest = false) { + if (submission?.CaseId == null || submission.DestinationId == null) + throw new ArgumentNullException(nameof(submission)); + + var events = SubmissionService + .GetStatusForSubmissionAsync(submission.CaseId, submission.DestinationId, skipTest) + .Result; + return events?.Select(e => new SecurityEventToken(e)).ToList() ?? + new List<SecurityEventToken>(); } /// <summary> @@ -106,7 +132,7 @@ public abstract class FitConnectClient { /// <param name="attachment"></param> /// <returns></returns> public KeyValuePair<string, string> Encrypt(string publicKey, Attachment attachment) { - var content = Encryption.Encrypt(attachment.Content, publicKey); + var content = Encryption.Encrypt(attachment.Content ?? Array.Empty<byte>(), publicKey); return new KeyValuePair<string, string>(attachment.Id, content); } diff --git a/FitConnect/Interfaces/IFitConnectClient.cs b/FitConnect/Interfaces/IFitConnectClient.cs new file mode 100644 index 0000000000000000000000000000000000000000..7e25169e1ccffac20e4115d9a0e769f8a376c64b --- /dev/null +++ b/FitConnect/Interfaces/IFitConnectClient.cs @@ -0,0 +1,26 @@ +using FitConnect.Models; +using FitConnect.Services.Models.v1.Submission; + +namespace FitConnect.Interfaces; + +public interface IFitConnectClient { + // public List<SecurityEventToken> GetStatusForSubmission(string caseId, bool skipTest = false); + + /// <summary> + /// Receives the SecurityEventTokens for a given case + /// </summary> + /// <param name="caseId">ID of the case</param> + /// <param name="destinationId">Skips signature validation if null!</param> + /// <param name="skipTest">Specifies if the SET signature test is skipped</param> + /// <returns>List of the <see cref="SecurityEventToken" /></returns> + public List<SecurityEventToken> GetStatusForSubmission(string caseId, string destinationId, + bool skipTest = false); + + /// <summary> + /// </summary> + /// <param name="submission"></param> + /// <param name="skipTest"></param> + /// <returns></returns> + public List<SecurityEventToken> GetStatusForSubmission(SubmissionForPickupDto submission, + bool skipTest = false); +} diff --git a/FitConnect/Interfaces/Sender/ISender.cs b/FitConnect/Interfaces/Sender/ISender.cs index 4f481f3578d80aa8421e854532030da91e15caf7..cbd6bfa5d0b8e8a31475276b4a42814f36fa5cc6 100644 --- a/FitConnect/Interfaces/Sender/ISender.cs +++ b/FitConnect/Interfaces/Sender/ISender.cs @@ -1,9 +1,7 @@ -using FitConnect.Interfaces.Subscriber; - namespace FitConnect.Interfaces.Sender; public interface ISender : IFitConnectClient { - public string PublicKey { get; } + public string? PublicKey { get; } /// <summary> /// Determines the destination id and configures the sender. diff --git a/FitConnect/Interfaces/Sender/ISenderWithAttachments.cs b/FitConnect/Interfaces/Sender/ISenderWithAttachments.cs index 073048275129459bf5485962d2759d684ae4e71c..d25ea83e297998541a8f27b64d5a39eeffb3103b 100644 --- a/FitConnect/Interfaces/Sender/ISenderWithAttachments.cs +++ b/FitConnect/Interfaces/Sender/ISenderWithAttachments.cs @@ -1,10 +1,6 @@ -using FitConnect.Models; - namespace FitConnect.Interfaces.Sender; public interface ISenderWithAttachments : ISenderReady { - public Submission Submission { get; } - /// <summary> /// Data as string. /// </summary> diff --git a/FitConnect/Interfaces/Sender/ISenderWithService.cs b/FitConnect/Interfaces/Sender/ISenderWithService.cs index 8eaef30e0d76cd322e309051735d5e07fbff6ad9..2ce1321028967510fc5d2bdcd9441a0215eba562 100644 --- a/FitConnect/Interfaces/Sender/ISenderWithService.cs +++ b/FitConnect/Interfaces/Sender/ISenderWithService.cs @@ -3,8 +3,6 @@ using FitConnect.Models; namespace FitConnect.Interfaces.Sender; public interface ISenderWithService { - public string PublicKey { get; set; } - /// <summary> /// Sends the submission with a list of attachments /// </summary> diff --git a/FitConnect/Interfaces/Subscriber/ISubscriber.cs b/FitConnect/Interfaces/Subscriber/ISubscriber.cs index ee383db057971fe3c0a3f887b54d197b4b97eec1..2f6c2489409826aafe2847d9413a3fef07541359 100644 --- a/FitConnect/Interfaces/Subscriber/ISubscriber.cs +++ b/FitConnect/Interfaces/Subscriber/ISubscriber.cs @@ -23,13 +23,3 @@ public interface ISubscriber : IFitConnectClient { public ISubscriberWithSubmission RequestSubmission(string? submissionId, bool skipSchemaTest = false); } - -public interface IFitConnectClient { - /// <summary> - /// Receives the SecurityEventTokens for a given case - /// </summary> - /// <param name="caseId">ID of the case</param> - /// <param name="skipTest">Specifies if the SET signature test is skipped</param> - /// <returns>List of the <see cref="SecurityEventToken" /></returns> - public List<SecurityEventToken> GetStatusForSubmission(string caseId, bool skipTest = false); -} diff --git a/FitConnect/Interfaces/Subscriber/ISubscriberWithSubmission.cs b/FitConnect/Interfaces/Subscriber/ISubscriberWithSubmission.cs index 30302d02f1909f31d888491d330c410a2926eb30..6d82850af24e57431622e8f8a80cc54f900f903c 100644 --- a/FitConnect/Interfaces/Subscriber/ISubscriberWithSubmission.cs +++ b/FitConnect/Interfaces/Subscriber/ISubscriberWithSubmission.cs @@ -4,14 +4,14 @@ using FitConnect.Models.v1.Api; namespace FitConnect.Interfaces.Subscriber; public interface ISubscriberWithSubmission { - public Submission Submission { get; } + public Submission? Submission { get; } /// <summary> /// Returns the data (Fachdaten) of the submission /// </summary> /// <returns></returns> public string? GetDataJson() { - return Submission.Data; + return Submission?.Data; } /// <summary> diff --git a/FitConnect/Models/Attachment.cs b/FitConnect/Models/Attachment.cs index c33d21d4867987ef6987e69ec08a5ffc90251066..86ed85f32c0fdafc7a38b9c8c001b08b88fceb1e 100644 --- a/FitConnect/Models/Attachment.cs +++ b/FitConnect/Models/Attachment.cs @@ -57,7 +57,7 @@ public class Attachment { public AttachmentSignature? Signature { get; } private string CalculateHash() { - return ByteToHexString(SHA512.Create().ComputeHash(Content)); + return ByteToHexString(SHA512.Create().ComputeHash(Content ?? Array.Empty<byte>())); } private static string ByteToHexString(IEnumerable<byte> data) { diff --git a/FitConnect/Models/Callback.cs b/FitConnect/Models/Callback.cs index b4d7e94fb7bd1cca3711e9ab5ed288d016b870aa..9794d32f12ad89f8ff1f6ed135b6b3de411b56ca 100644 --- a/FitConnect/Models/Callback.cs +++ b/FitConnect/Models/Callback.cs @@ -4,7 +4,7 @@ namespace FitConnect.Models; public record Callback(string? Url, string? Secret) { public static explicit operator Callback(CallbackDto dto) { - return new Callback(dto?.Url, null); + return new(dto.Url, null); } public static explicit operator CallbackDto(Callback model) { diff --git a/FitConnect/Models/FitConnectEnvironment.cs b/FitConnect/Models/FitConnectEnvironment.cs index a384481ead127de8ebcf5db4556ed23b83b072da..294cf80468e440db3aadfea90c00548fbb0fc195 100644 --- a/FitConnect/Models/FitConnectEnvironment.cs +++ b/FitConnect/Models/FitConnectEnvironment.cs @@ -1,10 +1,9 @@ namespace FitConnect.Models; public class FitConnectEnvironment { - // List of Domains // https://wiki.fit-connect.fitko.dev/de/Betrieb/Dokumentation/Domains - + public static readonly FitConnectEnvironment Testing = new( "https://auth-testing.fit-connect.fitko.dev/token", new[] { "https://submission-api-testing.fit-connect.fitko.dev" }, @@ -26,7 +25,12 @@ public class FitConnectEnvironment { "https://portal.auth.fit-connect.fitko.net" ); - public FitConnectEnvironment() { + public FitConnectEnvironment(string sspUrl, string tokenUrl, string[] submissionUrl, + string routingUrl) { + SspUrl = sspUrl; + TokenUrl = tokenUrl; + SubmissionUrl = submissionUrl; + RoutingUrl = routingUrl; } /// <summary> diff --git a/FitConnect/Models/SecurityEventToken.cs b/FitConnect/Models/SecurityEventToken.cs index 40d429eb32d266ef0ac0db84002cfdd33f04fd09..02204d91b8d5bd5988696f71eda6768bb76b662c 100644 --- a/FitConnect/Models/SecurityEventToken.cs +++ b/FitConnect/Models/SecurityEventToken.cs @@ -24,7 +24,11 @@ public class SecurityEventToken { public SecurityEventToken(string jwtEncodedString) { Token = new JsonWebToken(jwtEncodedString); EventType = DecodeEventType(Token.Claims); - var iat = Token.Claims.FirstOrDefault(c => c.Type == "iat").Value; + + if (Token.Claims.All(c => c.Type != "iat")) + return; + + var iat = Token.Claims.FirstOrDefault(c => c.Type == "iat")!.Value; if (long.TryParse(iat, out var timeEpoch)) EventTime = DateTime.UnixEpoch.AddSeconds(timeEpoch); } diff --git a/FitConnect/Models/Submission.cs b/FitConnect/Models/Submission.cs index 63bc466e4cabc6fbb950150fd37cf6d2afea16e7..bd9b20e8dbba5e95beaeaa7f32d5ab49e0a52aef 100644 --- a/FitConnect/Models/Submission.cs +++ b/FitConnect/Models/Submission.cs @@ -6,10 +6,10 @@ namespace FitConnect.Models; public class Submission { public string? Id { get; set; } public string? CaseId { get; set; } - public Destination? Destination { get; set; } = new(); + public Destination Destination { get; set; } = new(); public string DestinationId { - get => Destination.DestinationId; + get => Destination.DestinationId ?? throw new ArgumentNullException(nameof(DestinationId)); set => Destination.DestinationId = value; } @@ -55,20 +55,23 @@ public class Submission { return new Submission { Id = dto.SubmissionId, Callback = null, - DestinationId = dto.DestinationId, - ServiceType = null + DestinationId = dto.DestinationId ?? + throw new NullReferenceException(nameof(dto.DestinationId)), + ServiceType = new ServiceType() }; } public static explicit operator Submission(SubmissionDto dto) { return new Submission { Id = dto.SubmissionId, - Callback = (Callback)dto.Callback, - DestinationId = dto.DestinationId, - ServiceType = (ServiceType)dto.ServiceType, + Callback = dto.Callback == null ? null : (Callback)dto.Callback, + DestinationId = dto.DestinationId ?? + throw new NullReferenceException(nameof(dto.DestinationId)), + ServiceType = + dto.ServiceType == null ? new ServiceType() : (ServiceType)dto.ServiceType, EncryptedData = dto.EncryptedData, EncryptedMetadata = dto.EncryptedMetadata, - AttachmentIds = dto.Attachments, + AttachmentIds = dto.Attachments ?? new List<string>(), CaseId = dto.CaseId }; } diff --git a/FitConnect/Router.cs b/FitConnect/Router.cs index 0c9fff69ff588987286e940f8ca89922402fcc38..9f211eb2361c64e39151619bc9705b9f323477e0 100644 --- a/FitConnect/Router.cs +++ b/FitConnect/Router.cs @@ -83,9 +83,10 @@ public class Router : IRouter { var header = JsonConvert.DeserializeObject<dynamic>( Base64UrlEncoder.Decode(route.DestinationParametersSignature.Split('.')[0]) ); - var kid = (string)header.kid; - _logger?.LogInformation("Testing with kid: {kid}", kid); + var kid = (string)header?.kid!; + + _logger?.LogInformation("Testing with kid: {Kid}", kid); return FitEncryption.VerifyJwt(signature, new JsonWebKeySet(submissionKey).Keys.First(k => k.Kid == kid), _logger); } diff --git a/FitConnect/Sender.cs b/FitConnect/Sender.cs index 10d8d2028d4c6632873d4ee138f4d9d8bf699b88..a2e75be3246f191a0445d88a8d1ebc1fc626c324 100644 --- a/FitConnect/Sender.cs +++ b/FitConnect/Sender.cs @@ -38,6 +38,8 @@ public class Sender : FitConnectClient, ISender, ISenderWithDestination, container) { } + public Submission? Submission { get; set; } + public string? PublicKey { get; set; } @@ -75,7 +77,6 @@ public class Sender : FitConnectClient, ISender, ISenderWithDestination, return this; } - public Submission? Submission { get; set; } Submission ISenderReady.Submit() { if (Submission == null) { @@ -155,7 +156,7 @@ public class Sender : FitConnectClient, ISender, ISenderWithDestination, created.SubmissionId, Submission.CaseId); var encryptedAttachments = Encrypt(PublicKey!, Submission.Attachments); - UploadAttachmentsAsync(Submission.Id!, encryptedAttachments).Wait(); + UploadAttachmentsAsync(Submission.Id!, encryptedAttachments); return this; } @@ -232,7 +233,7 @@ public class Sender : FitConnectClient, ISender, ISenderWithDestination, /// </summary> /// <param name="submissionId">Submissions ID</param> /// <param name="encryptedAttachments">Encrypted attachments with id and content</param> - private async Task<bool> UploadAttachmentsAsync(string submissionId, + private bool UploadAttachmentsAsync(string submissionId, Dictionary<string, string> encryptedAttachments) { try { foreach (var (id, content) in encryptedAttachments) { diff --git a/FitConnect/Services/Interfaces/ISubmissionService.cs b/FitConnect/Services/Interfaces/ISubmissionService.cs index 2fd1bcc5160ee4d0739a0810045bcd75eeb29e14..a343a62a522a6a93bfd05ad5aa6c21f2c5eb161c 100644 --- a/FitConnect/Services/Interfaces/ISubmissionService.cs +++ b/FitConnect/Services/Interfaces/ISubmissionService.cs @@ -39,7 +39,7 @@ public interface ISubmissionService : IRestCallService { /// <param name="offset">RequestParam</param> /// <param name="limit">RequestParam</param> /// <returns></returns> - Task<SubmissionsForPickupDto> ListSubmissions(string? destinationId, int offset, + SubmissionsForPickupDto ListSubmissions(string? destinationId, int offset, int limit); /// <summary> @@ -82,9 +82,11 @@ public interface ISubmissionService : IRestCallService { /// Retrieves the events of a submission /// </summary> /// <param name="caseId"></param> + /// <param name="destinationId"></param> /// <param name="skipTest"></param> /// <returns></returns> - Task<List<string>?> GetStatusForSubmissionAsync(string caseId, bool skipTest = false); + Task<List<string>?> GetStatusForSubmissionAsync(string caseId, string destinationId, + bool skipTest = false); } public interface ICasesService { diff --git a/FitConnect/Services/Models/v1/Api/SecEventToken.cs b/FitConnect/Services/Models/v1/Api/SecEventToken.cs index 56da559ff57d21fe9a395cd4888b36862f143929..46a1e1d43e4f5d33069b6fbef0fc7397b850ecb6 100644 --- a/FitConnect/Services/Models/v1/Api/SecEventToken.cs +++ b/FitConnect/Services/Models/v1/Api/SecEventToken.cs @@ -5,7 +5,6 @@ // using FitConnect.Models.v1.Api; // // var welcome = Welcome.FromJson(jsonString); -#nullable enable namespace FitConnect.Models.v1.Api { using System; @@ -240,19 +239,19 @@ namespace FitConnect.Models.v1.Api }; [JsonProperty("instance", NullValueHandling = NullValueHandling.Ignore)] - public string? instance { get; set; } + public string instance { get; set; } [JsonProperty("detail", NullValueHandling = NullValueHandling.Ignore)] - public string? detail { get; set; } + public string detail { get; set; } [JsonProperty("title", NullValueHandling = NullValueHandling.Ignore)] - public string? title { get; set; } + public string title { get; set; } // [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] // public string? description { get; set; } [JsonProperty("type", NullValueHandling = NullValueHandling.Ignore)] - public string? type { get; set; } + public string type { get; set; } // [JsonProperty("items", NullValueHandling = NullValueHandling.Ignore)] // public Items? items { get; set; } diff --git a/FitConnect/Services/Models/v1/Destination/SubmissionSchemaDto.cs b/FitConnect/Services/Models/v1/Destination/SubmissionSchemaDto.cs index 8da11c5cdbd92303e6b7269c980d860349407716..4711ef29132294523b7d512afc292ee0e908fe05 100644 --- a/FitConnect/Services/Models/v1/Destination/SubmissionSchemaDto.cs +++ b/FitConnect/Services/Models/v1/Destination/SubmissionSchemaDto.cs @@ -1,12 +1,12 @@ using Newtonsoft.Json; namespace FitConnect.Services.Models.v1.Destination; - +#nullable disable public class SubmissionSchemaDto { [JsonProperty("mimeType")] // private SubmissionSchemaMimeTypeDto mimeType; public string mimeType; [JsonProperty("schemaUri")] - public string? SchemaUri { get; set; } + public string SchemaUri { get; set; } } diff --git a/FitConnect/Services/Models/v1/Routes/Routes.cs b/FitConnect/Services/Models/v1/Routes/Routes.cs index b966acaa42e54fbf822858886e18e38c20250f23..375913cdd3a7f39f58c0108f8375e104bd279c8d 100644 --- a/FitConnect/Services/Models/v1/Routes/Routes.cs +++ b/FitConnect/Services/Models/v1/Routes/Routes.cs @@ -1,5 +1,6 @@ // Root myDeserializedClass = JsonSerializer.Deserialize<Root>(myJsonResponse); +#nullable disable using Newtonsoft.Json; namespace FitConnect.Services.Models.v1.Routes; @@ -54,7 +55,7 @@ public class Key { [JsonProperty("e")] public string E { get; set; } - public string ToString() { + public override string ToString() { return JsonConvert.SerializeObject(this); } } diff --git a/FitConnect/Services/Models/v1/Submission/SubmissionReducedDto.cs b/FitConnect/Services/Models/v1/Submission/SubmissionReducedDto.cs index 36deb67e518f1fcf6b1c79cd800573dc5e603b3d..7f2c2312e1249acbc6a2702093cf16276f714738 100644 --- a/FitConnect/Services/Models/v1/Submission/SubmissionReducedDto.cs +++ b/FitConnect/Services/Models/v1/Submission/SubmissionReducedDto.cs @@ -4,7 +4,7 @@ namespace FitConnect.Services.Models.v1.Submission; public class SubmissionReducedDto { [JsonProperty("serviceType")] - private ServiceTypeDto _serviceTypeDto { get; set; } + private ServiceTypeDto? ServiceTypeDto { get; set; } [JsonProperty("attachments")] diff --git a/FitConnect/Services/RestCallService.cs b/FitConnect/Services/RestCallService.cs index f01c60475b1dcd260fa9cab1266724e6585befd9..c3573eb200466450b689004464ddf20c0197aa1b 100644 --- a/FitConnect/Services/RestCallService.cs +++ b/FitConnect/Services/RestCallService.cs @@ -36,7 +36,6 @@ internal class RestCallService : IRestCallService { } /// <summary> - /// /// </summary> /// <param name="endpoint">Endpoint is attached to the base url if not starts with http</param> /// <param name="method"></param> diff --git a/FitConnect/Services/SubmissionService.cs b/FitConnect/Services/SubmissionService.cs index cf09fa93613852b954dc0ea4070e8ab1c69c9862..76a7497fd5212c68e0e463c3c64411e2b1047fa8 100644 --- a/FitConnect/Services/SubmissionService.cs +++ b/FitConnect/Services/SubmissionService.cs @@ -4,15 +4,14 @@ using FitConnect.Services.Models.v1.Case; using FitConnect.Services.Models.v1.Submission; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; -using Namotion.Reflection; using Newtonsoft.Json; using NJsonSchema; namespace FitConnect.Services; internal class SubmissionService : RestCallService, ISubmissionService { - private readonly ILogger? _logger; private readonly string _baseUrl; + private readonly ILogger? _logger; private readonly IOAuthService _oAuthService; private readonly JsonWebKey? _signatureValidationKey; @@ -58,11 +57,11 @@ internal class SubmissionService : RestCallService, ISubmissionService { public bool AddSubmissionAttachment(string submissionId, string attachmentId, string encryptedAttachmentContent) { _oAuthService.EnsureAuthenticated(); - var result = RestCallForString( + RestCallForString( $"/submissions/{submissionId}/attachments/{attachmentId}", HttpMethod.Put, encryptedAttachmentContent, - "application/jose").Result; + "application/jose").Wait(); return true; } @@ -95,7 +94,7 @@ internal class SubmissionService : RestCallService, ISubmissionService { /// <param name="offset">RequestParam</param> /// <param name="limit">RequestParam</param> /// <returns></returns> - public async Task<SubmissionsForPickupDto> ListSubmissions(string? destinationId, + public SubmissionsForPickupDto ListSubmissions(string? destinationId, int offset = 0, int limit = 100) { _oAuthService.EnsureAuthenticated(); @@ -146,6 +145,7 @@ internal class SubmissionService : RestCallService, ISubmissionService { } public async Task<List<string>?> GetStatusForSubmissionAsync(string caseId, + string destinationId, bool skipTest = false) { _oAuthService.EnsureAuthenticated(); var events = await RestCall<EventLogDto>($"/cases/{caseId}/events", HttpMethod.Get); @@ -153,18 +153,17 @@ internal class SubmissionService : RestCallService, ISubmissionService { return null; // Download well known keys - var valid = await ValidateSignature(events); - // TODO: Check JSON Schema - // valid &= await ValidateSchema(events, false); + var valid = await ValidateSignature(events, destinationId); + // TODO Check JSON Schema + valid &= await ValidateSchema(events); if (!valid) { _logger?.LogError("Invalid SET, signature can not be verified"); if (events.EventLog != null) _logger?.LogTrace("Tested events: {Events}", events.EventLog.Aggregate((a, b) => a + "\n" + b)); - if (!skipTest) { + if (!skipTest) throw new InvalidOperationException("Invalid SET, signature can not be verified"); - } } return events.EventLog?.ToList(); @@ -193,14 +192,14 @@ internal class SubmissionService : RestCallService, ISubmissionService { var schemaRaw = await RestCallForString(new Uri(schema), HttpMethod.Get); // @formatter:off - schemaRaw = schemaRaw - .Replace("https://json-schema.org/draft/2020-12/schema", "https://json-schema.org/draft-07/schema") + var _schemaRaw = schemaRaw + // .Replace("https://json-schema.org/draft/2020-12/schema", "https://json-schema.org/draft-07/schema") .Replace("%3A", ":") .Replace("~1", "/"); // @formatter:on - var schemaData = await JsonSchema.FromJsonAsync(schemaRaw); + var schemaData = await JsonSchema.FromUrlAsync(schema); var schemaOk = schemaData.Validate(eventContent); if (schemaOk.Count == 0) continue; @@ -208,12 +207,11 @@ internal class SubmissionService : RestCallService, ISubmissionService { if (mandatory) valid = false; - foreach (var validationError in schemaOk) { + foreach (var validationError in schemaOk) if (mandatory) _logger?.Log(mandatory ? LogLevel.Error : LogLevel.Warning, "Error on validating JSON Schema {Error}", validationError.ToString()); - } } catch (Exception e) { _logger?.LogError(e, "Error validating event against schema {Exception}", e); @@ -226,12 +224,25 @@ internal class SubmissionService : RestCallService, ISubmissionService { return valid; } - private async Task<bool> ValidateSignature(EventLogDto events) { - var keys = await GetJsonWebKeysForEvent(events); + private async Task<bool> ValidateSignature(EventLogDto events, string destinationId) { + var keys = (await GetJsonWebKeysForEvent(events, destinationId)).ToList(); + + if (events.EventLog == null) + return true; + + var result = true; + foreach (var s in events.EventLog) { + var header = JsonConvert.DeserializeObject<Dictionary<string, object>>( + Base64UrlEncoder.Decode(s.Split('.')[0])); + + if (header == null) + throw new ArgumentException("Invalid event header"); + + var kid = (string)header["kid"]; + result &= FitEncryption.VerifyJwt(s, keys.Where(k => k.Kid == kid), _logger); + } - var valid = events.EventLog?.Aggregate(true, - (current, eventJson) => - current & FitEncryption.VerifyJwt(eventJson, keys, logger: _logger)) ?? true; + var valid = result; if (!valid) { _logger?.LogDebug("Signature is invalid"); @@ -242,16 +253,20 @@ internal class SubmissionService : RestCallService, ISubmissionService { return valid; } - private async Task<IEnumerable<JsonWebKey>> GetJsonWebKeysForEvent(EventLogDto events) { + private async Task<IEnumerable<JsonWebKey>> GetJsonWebKeysForEvent(EventLogDto events, + string destinationId) { var keySet = new JsonWebKeySet(await Router.GetSubmissionServiceValidationJwk(_baseUrl)); - var keys = _signatureValidationKey == null + var keys = (_signatureValidationKey == null ? keySet.Keys - : keySet.Keys.Append(_signatureValidationKey); + : keySet.Keys.Append(_signatureValidationKey)).ToList(); + - return (await GetKeyIdsFromEvent(events)).Union(keys); + return (await GetKeyIdsFromEvent(events, destinationId, keys.Select(k => k.Kid).ToList())) + .Union(keys); } - private async Task<IEnumerable<JsonWebKey>> GetKeyIdsFromEvent(EventLogDto events) { + private async Task<IEnumerable<JsonWebKey>> GetKeyIdsFromEvent(EventLogDto events, + string destinationId, ICollection<string> knownKeys) { if (events.EventLog == null) return new List<JsonWebKey>(); @@ -260,16 +275,12 @@ internal class SubmissionService : RestCallService, ISubmissionService { var result = new List<JsonWebKey>(); foreach (var (submission, keyId) in keyIds) { - try { - // TODO Get destinationId from submission - var destinationId = "aa3704d6-8bd7-4d40-a8af-501851f93934"; - var keyJson = await RestCallForString($"/destinations/{destinationId}/keys/{keyId}", - HttpMethod.Get); - result.Add(new JsonWebKey(keyJson)); - } - catch (Exception e) { - _logger?.LogWarning(e, "Error loading key {KeyId}", keyId); - } + if (knownKeys.Contains(keyId)) + continue; + + var keyJson = await RestCallForString($"/destinations/{destinationId}/keys/{keyId}", + HttpMethod.Get); + result.Add(new JsonWebKey(keyJson)); } return result; @@ -281,13 +292,12 @@ internal class SubmissionService : RestCallService, ISubmissionService { var header = JsonConvert.DeserializeObject<Dictionary<string, object>>(jwtParts[0]); var payload = JsonConvert.DeserializeObject<Dictionary<string, object>>(jwtParts[1]); + if (header == null || payload == null) + throw new NullReferenceException("Event string is invalid"); + var keyId = (string)header["kid"]; var submissionId = ((string)payload["sub"]).Split(':')[1]; return (submissionId, keyId); } - - public async Task GetValidationJwk() { - throw new NotImplementedException(); - } } diff --git a/FitConnect/Subscriber.cs b/FitConnect/Subscriber.cs index df196ed088f5153408fc5b2ebb6deec820f2610d..c02b90a36f1f66cdb9ac3510d00517b64b09f2a0 100644 --- a/FitConnect/Subscriber.cs +++ b/FitConnect/Subscriber.cs @@ -61,10 +61,10 @@ public class Subscriber : FitConnectClient, public IEnumerable<SubmissionForPickupDto> GetAvailableSubmissions(string? destinationId = null, int skip = 0, int take = 100) { - var submissionsResult = SubmissionService.ListSubmissions(destinationId, 0, 100).Result; + var submissionsResult = SubmissionService.ListSubmissions(destinationId, 0, 100); // Creating a dictionary of destinationId to submissionIds from the REST API result - return submissionsResult.Submissions; + return submissionsResult.Submissions ?? new List<SubmissionForPickupDto>(); } @@ -99,10 +99,10 @@ public class Subscriber : FitConnectClient, var (dataString, _, dataHash) = Encryption.Decrypt(submission.EncryptedData); submission.Data = dataString; - if (submission?.Metadata?.ContentStructure.Data.Hash.Content != + if (submission.Metadata?.ContentStructure.Data.Hash.Content != FitEncryption.CalculateHash(dataString)) { Logger?.LogWarning("Data hash mismatch: {DataHash} != {CalculatedHash}", - submission?.Metadata?.ContentStructure.Data.Hash.Content, + submission.Metadata?.ContentStructure.Data.Hash.Content, FitEncryption.CalculateHash(dataString)); throw new Exception("Data hash mismatch"); } @@ -114,12 +114,14 @@ public class Subscriber : FitConnectClient, public Submission? Submission { get; private set; } + /// <summary> /// Reading attachments for a submission. /// </summary> /// <returns></returns> public IEnumerable<Attachment> GetAttachments() { - // TODO add guard calls + if (Submission?.Id == null || Submission?.Metadata == null) + throw new Exception("No submission available"); var attachments = new List<Attachment>(); foreach (var id in Submission!.AttachmentIds) { @@ -168,12 +170,14 @@ public class Subscriber : FitConnectClient, var assembly = Assembly.GetExecutingAssembly(); var fullQualifiedName = $"{assembly.GetName().Name}.{resourceName}"; var resourceStream = assembly.GetManifestResourceStream(fullQualifiedName); + if (resourceStream == null) + throw new Exception($"Resource {fullQualifiedName} not found"); var reader = new StreamReader(resourceStream); return reader.ReadToEnd(); } - public void CompleteSubmission(SubmissionForPickupDto submission, + private void CompleteSubmission(SubmissionForPickupDto submission, FinishSubmissionStatus status, Problems[]? problems = null) { if (submission.SubmissionId == null || submission.CaseId == null || submission.DestinationId == null) @@ -231,6 +235,7 @@ public class Subscriber : FitConnectClient, public enum FinishSubmissionStatus { Accepted, - Rejected, + + Rejected // Forwarded } diff --git a/IntegrationTests/CallbackTest.cs b/IntegrationTests/CallbackTest.cs index 1b66144722ebcdbb26cb4cdd641e9b018621b1da..52a30c78eae27d8bebaab0a8b3afd50b25d93e67 100644 --- a/IntegrationTests/CallbackTest.cs +++ b/IntegrationTests/CallbackTest.cs @@ -61,7 +61,7 @@ public class CallbackTest { _callbackSecret = Container.Create().Resolve<MockSettings>().CallbackSecret; } - private HttpRequest Request; + private HttpRequest Request = null!; private string _callbackSecret = ""; [Test] @@ -94,7 +94,7 @@ public class CallbackTest { // Assert Assert.Throws<ArgumentException>(() => { FitConnect.Subscriber.VerifyCallback(_callbackSecret, Request); - }) + })! .Message.Should().Be("Request is too old"); } @@ -108,7 +108,7 @@ public class CallbackTest { // Assert Assert.Throws<ArgumentException>(() => { FitConnect.Subscriber.VerifyCallback(_callbackSecret, Request); - }) + })! .Message.Should().Be("Request is not authentic"); } } diff --git a/IntegrationTests/HelperMethods.cs b/IntegrationTests/HelperMethods.cs index e076c7014e78d6d0d189afeb49a04a98b34ce707..e5334dd76542651ce8e18296a04d32c5bdbf3b53 100644 --- a/IntegrationTests/HelperMethods.cs +++ b/IntegrationTests/HelperMethods.cs @@ -25,6 +25,6 @@ public static class HelperMethods { var jsonContent = File.ReadAllText(secretFile); var secret = JsonConvert.DeserializeObject<dynamic>(jsonContent); - return (secret.sender.id, secret.sender.secret); + return (secret!.sender.id, secret.sender.secret); } } diff --git a/IntegrationTests/OAuthServiceTest.cs b/IntegrationTests/OAuthServiceTest.cs index 6f1f29fd860b4e9b0231ddba5f56eced4f2c9144..5291ca1173bf30e5251105fae844208ad8be6afe 100644 --- a/IntegrationTests/OAuthServiceTest.cs +++ b/IntegrationTests/OAuthServiceTest.cs @@ -7,8 +7,8 @@ using NUnit.Framework; namespace IntegrationTests; public class OAuthServiceTest { - private string _clientId; - private string _clientSecret; + private string _clientId = null!; + private string _clientSecret = null!; private OAuthService _oAuthService = null!; [OneTimeSetUp] diff --git a/IntegrationTests/ProxyTest.cs b/IntegrationTests/ProxyTest.cs index 2024efe0d5098ffba29e1c07ba7e63581702ac1b..275d7cd9f0eb377d712922c8f815b9c9578a88a6 100644 --- a/IntegrationTests/ProxyTest.cs +++ b/IntegrationTests/ProxyTest.cs @@ -41,7 +41,9 @@ public class ProxyTest { _secret) .WithProxy("localhost", 3128); +#pragma warning disable SYSLIB0014 _webClient = new WebClient { +#pragma warning restore SYSLIB0014 Proxy = new WebProxy("http://localhost:3128") }; diff --git a/IntegrationTests/Routing/RoutingTests.cs b/IntegrationTests/Routing/RoutingTests.cs index 159ca068a1f0f597ae5c31425a8c17ff74e82000..34022333a92164b74e64661a6f20e043dcc6eb38 100644 --- a/IntegrationTests/Routing/RoutingTests.cs +++ b/IntegrationTests/Routing/RoutingTests.cs @@ -32,10 +32,10 @@ public class RoutingTests { @"{""encryptionKid"":""1e95f036-ccff-425c-a0de-3d89d5cc59fa"",""metadataVersions"":[""1.0.0""],""publicKeys"":{""keys"":[{""alg"":""RSA-OAEP-256"",""e"":""AQAB"",""key_ops"":[""wrapKey""],""kid"":""1e95f036-ccff-425c-a0de-3d89d5cc59fa"",""kty"":""RSA"",""n"":""2ch1Ir3_Lyb9_HxW9RqIodxi9fXhix6APKwqiSfi-JlRqVa1FoAFsW1nW0IbQjkW6sNkWUFWuA9AfVoKT9nnIcnLSjSQ84SI-if6qTbornyKvBjXg8BSecSUUPYyT0-4NmxrXMGHPYbJV7fQq6jPXzkWC5P5jqQ7ObraQp752BcE_JVQUmFk1ydhhbnroHpGUkA-8jG_kiVL-lAz7uUmZCh6i3ZJD5HN1JOE5LMyzUQOgOFUUPiviBywQAbPQuDLydZ2diO5wqm4mwBadAAzC27OllSkNXSgnd9MVajXmtBVpz2ksMaSCAbfB4rK9q5jXd5YMwu1ZlA-ZuWKYm_p1GjbZdx4xk9w23Zkgnrr3SvWnW98686fd03MG1ACAGatq5FcAvGp8BXCKwz5FpyYtOONx-tECYHcHhx_SafOe9siLYObLmBSsLF3TAjigZjpGOuEjBtKyv5OwJj-6YfIYYjlofuqv6GHUGDvv8iQsy6U4eHCoRpKJzmX6L22MUQgisYvQdGY2jbdEni3g_MpciMIbnZFLENVrqHXYcgHN-SbXl_GVR5b3F0ompES55xA7fuYlt4lp5j0IUo0OWM2_tYYHtASZicVAwnbzLQZEA0u-wXZr0ByMWE07Od_KaLUomlBPi1Ac_FU3KOx0APKJUm7D3__aiLZll3Sh9EnIvE"",""x5c"":[""MIIE6jCCAtKgAwIBAgIGAXo1pG0GMA0GCSqGSIb3DQEBCwUAMDYxNDAyBgNVBAMMK2FaV0ptMG1XRmFxRGFOTkJOaDNabVluVmw3eG52SVMxVEJ5SHhaVnNUNm8wHhcNMjEwNjIyMjEzMzI2WhcNMjIwNDE4MjEzMzI2WjA2MTQwMgYDVQQDDCthWldKbTBtV0ZhcURhTk5CTmgzWm1ZblZsN3hudklTMVRCeUh4WlZzVDZvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2ch1Ir3/Lyb9/HxW9RqIodxi9fXhix6APKwqiSfi+JlRqVa1FoAFsW1nW0IbQjkW6sNkWUFWuA9AfVoKT9nnIcnLSjSQ84SI+if6qTbornyKvBjXg8BSecSUUPYyT0+4NmxrXMGHPYbJV7fQq6jPXzkWC5P5jqQ7ObraQp752BcE/JVQUmFk1ydhhbnroHpGUkA+8jG/kiVL+lAz7uUmZCh6i3ZJD5HN1JOE5LMyzUQOgOFUUPiviBywQAbPQuDLydZ2diO5wqm4mwBadAAzC27OllSkNXSgnd9MVajXmtBVpz2ksMaSCAbfB4rK9q5jXd5YMwu1ZlA+ZuWKYm/p1GjbZdx4xk9w23Zkgnrr3SvWnW98686fd03MG1ACAGatq5FcAvGp8BXCKwz5FpyYtOONx+tECYHcHhx/SafOe9siLYObLmBSsLF3TAjigZjpGOuEjBtKyv5OwJj+6YfIYYjlofuqv6GHUGDvv8iQsy6U4eHCoRpKJzmX6L22MUQgisYvQdGY2jbdEni3g/MpciMIbnZFLENVrqHXYcgHN+SbXl/GVR5b3F0ompES55xA7fuYlt4lp5j0IUo0OWM2/tYYHtASZicVAwnbzLQZEA0u+wXZr0ByMWE07Od/KaLUomlBPi1Ac/FU3KOx0APKJUm7D3//aiLZll3Sh9EnIvECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAFi24JFEmYL2sOqaaQOMHCVm+WQ9QyN/rkvjHLG8th5TIF8NJ4uwxl+n6GBO2uOZOBixHbU5pK9slyt9Xryw7CbL/hhRGSs3GSFR2hkxoBHdzfFnAwegnd1H5XHQwSgh43jGOn9AxsyISpkvFo7+DObh9Cv8NsjpL57No4UJ62XggfYJW43u4+I/fHcDwnvIN0dvnYpQkCbTmrjLa5IkGim9BynfW1k6VuxLya1SsyjBHWw2YNQ4xBJI4OzXhL6OmBSrohF1RbIKOpjtqGfkXZpxufLNV2CnL36r/41c1nop6cmCIMDtnFEQdAmGe8m/8wvVpLnks59C02/WotlK3iORHCYB6G0pHMKFB4zOVANYtLFgqTgN4HNciV3FN0TvI19qzjkAdcB+m+L+LdseIzcQ/BToGyPvWkJ1mvJZIp0ejnlMWIl3VlNpMKeZ7lJbPpZvABO00lK+FynhITtb6N29toE+7JgHAlWmxw6PFFY1x+3xTHBTOU0oUR/TyKsEU0+bNSb/0S+ZyodmnIFbgYWarjK5pUwfTRyPyeVEukg1Gf30c/7f/5KZ/dpLFUNBb/YTNIzYEhGNUyLJ1mrSz33gr4MtvI4uSu0Jpr1NrwdMGvFhr5QOCULuoC9KlokusUpi0GTH0gK3K/TUi6qvU+Wztfa7mqah17BVVFT1wATs=""]}]},""replyChannels"":{""email"":{""usePgp"":true}},""status"":""active"",""submissionSchemas"":[{""mimeType"":""application/json"",""schemaUri"":""https://schema.fitko.de/fim/s06000178_0.4.schema.json""}],""submissionUrl"":""https://submission-api-testing.fit-connect.fitko.dev""}"; } - private IRouter _router; - private ILogger _logger; - private string _body; - private string _detached; + private IRouter _router = null!; + private ILogger _logger = null!; + private string _body = null!; + private string _detached = null!; [Test] @@ -186,7 +186,7 @@ public class RoutingTests { "https://submission-api-testing.fit-connect.fitko.dev").Result; var keySet = new JsonWebKeySet(secret); - var result = FitEncryption.VerifyJwt(combined, (JsonWebKey)keySet.Keys[0], _logger); + var result = FitEncryption.VerifyJwt(combined, keySet.Keys[0], _logger); Assert.IsTrue(result); } diff --git a/IntegrationTests/Sender/SenderTestBase.cs b/IntegrationTests/Sender/SenderTestBase.cs index 24fc1c7a1cf226a82fb21701ca4daae0c6297079..146d5921e12363e9227feca919d28d66820b98dc 100644 --- a/IntegrationTests/Sender/SenderTestBase.cs +++ b/IntegrationTests/Sender/SenderTestBase.cs @@ -36,9 +36,9 @@ public abstract class SenderTestBase { } var jsonContent = File.ReadAllText(secretFile); - var secret = JsonConvert.DeserializeObject<dynamic>(jsonContent); - _clientId = secret.sender.id; - _clientSecret = secret.sender.secret; + var secret = JsonConvert.DeserializeObject<dynamic>(jsonContent)!; + ClientId = secret.sender.id; + ClientSecret = secret.sender.secret; } [SetUp] @@ -46,21 +46,21 @@ public abstract class SenderTestBase { var logger = LoggerFactory.Create(b => b.AddConsole()).CreateLogger<FitConnect.Sender>(); Sender = new FitConnect.Sender( FitConnectEnvironment.Testing, - _clientId, _clientSecret, logger); + ClientId, ClientSecret, logger); } protected string LeikaKey = ""; - protected const string desitnationId = "aa3704d6-8bd7-4d40-a8af-501851f93934"; - protected string _clientId = "73a8ff88-076b-4263-9a80-8ebadac97b0d"; - protected string _clientSecret = "rdlXms-4ikO47AbTmmCTdzFoE4cTSt13JmSbcY5Dhsw"; - protected ISender Sender; + protected const string DestinationId = "aa3704d6-8bd7-4d40-a8af-501851f93934"; + protected string ClientId = "73a8ff88-076b-4263-9a80-8ebadac97b0d"; + protected string ClientSecret = "rdlXms-4ikO47AbTmmCTdzFoE4cTSt13JmSbcY5Dhsw"; + protected ISender Sender = null!; protected Submission GetSubmissionInfo(ISender sender) { var submission = sender.GetType().GetProperties() - .FirstOrDefault(p => p.Name == "Submission") + .FirstOrDefault(p => p.Name == "Submission")! .GetValue(Sender) as Submission; - return submission; + return submission!; } } diff --git a/IntegrationTests/Sender/SenderTestHappyPath.cs b/IntegrationTests/Sender/SenderTestHappyPath.cs index b72a98fecf1a1f3fbced091bbc5f8c37f6f6f436..89b33074301e82595b1b68adced0f28b72a11b04 100644 --- a/IntegrationTests/Sender/SenderTestHappyPath.cs +++ b/IntegrationTests/Sender/SenderTestHappyPath.cs @@ -12,8 +12,8 @@ namespace IntegrationTests.Sender; public class SenderTestHappyPath : SenderTestBase { [Test] public void CheckIfSecretsAreValid() { - _clientId.Should().NotBe("00000000-0000-0000-0000-000000000000"); - _clientSecret.Should().NotBe("0000000000000000000000000000000000000000000"); + ClientId.Should().NotBe("00000000-0000-0000-0000-000000000000"); + ClientSecret.Should().NotBe("0000000000000000000000000000000000000000000"); } [Test] @@ -21,7 +21,7 @@ public class SenderTestHappyPath : SenderTestBase { // Arrange // Act - Sender.WithDestination(desitnationId); + Sender.WithDestination(DestinationId); // Assert Sender.PublicKey.Should().NotBeNullOrEmpty(); @@ -31,7 +31,7 @@ public class SenderTestHappyPath : SenderTestBase { public void WithAttachments_IntroduceSubmission_ShouldGetIdFromServer() { // Arrange var dut = Sender - .WithDestination(desitnationId) + .WithDestination(DestinationId) .WithServiceType("ServiceName", "urn:de:fim:leika:leistung:99400048079000"); var attachments = new List<Attachment>(); @@ -61,7 +61,8 @@ public class SenderTestHappyPath : SenderTestBase { [TestCase("0b8e6fbd-62e2-4b6f-b333-5308d82e0a00")] [TestCase("d2be2027-9368-4c0c-a265-2fdbf7ecd4d9")] public void GetSubmissionStatus(string caseId) { - var status = Sender.GetStatusForSubmission(caseId, true); + var status = + Sender.GetStatusForSubmission(caseId, "aa3704d6-8bd7-4d40-a8af-501851f93934", true); status.ForEach(s => Console.WriteLine($"{s.EventTime} - {s.EventType}")); status.Count.Should().BeGreaterThan(0); } @@ -75,7 +76,7 @@ public class SenderTestHappyPath : SenderTestBase { var attachment = new Attachment("Test.pdf", "Just an attachment"); attachments.Add(attachment); var dut = Sender - .WithDestination(desitnationId) + .WithDestination(DestinationId) .WithServiceType("ServiceName", "urn:de:fim:leika:leistung:99400048079000") .WithAttachments(attachments); @@ -103,7 +104,7 @@ public class SenderTestHappyPath : SenderTestBase { }; attachments.Add(attachment); var dut = Sender - .WithDestination(desitnationId) + .WithDestination(DestinationId) .WithServiceType("ServiceName", "urn:de:fim:leika:leistung:99400048079000") .WithAttachments(attachments) .WithData(JsonConvert.SerializeObject(new { @@ -118,7 +119,7 @@ public class SenderTestHappyPath : SenderTestBase { // Assert var submission = GetSubmissionInfo(Sender); - var statusForSubmission = Sender.GetStatusForSubmission(submission.CaseId); + var statusForSubmission = Sender.GetStatusForSubmission(submission); foreach (var securityEventToken in statusForSubmission) Console.WriteLine(securityEventToken.Token.Subject); @@ -159,7 +160,7 @@ public class SenderTestHappyPath : SenderTestBase { // Assert var submission = GetSubmissionInfo(Sender); - var statusForSubmission = Sender.GetStatusForSubmission(submission.CaseId); + var statusForSubmission = Sender.GetStatusForSubmission(submission); foreach (var securityEventToken in statusForSubmission) Console.WriteLine(securityEventToken.Token.Subject); diff --git a/IntegrationTests/Sender/SenderTestUnhappyPath.cs b/IntegrationTests/Sender/SenderTestUnhappyPath.cs index f2de015cf55858ab1b1aeb18f9ab057dbc98dcf9..6e0f9c7e4bff7d67266d6c792b87971d00d00139 100644 --- a/IntegrationTests/Sender/SenderTestUnhappyPath.cs +++ b/IntegrationTests/Sender/SenderTestUnhappyPath.cs @@ -10,25 +10,25 @@ namespace IntegrationTests.Sender; public class SenderTestUnhappyPath : SenderTestBase { [Test] public void WithDestination_UnknownUUID_ShouldThrowAggregateWithInnerHttpRequestException() { - Assert.Throws<AggregateException>(() => { Sender.WithDestination(Guid.Empty.ToString()); }) + Assert.Throws<AggregateException>(() => { Sender.WithDestination(Guid.Empty.ToString()); })! .InnerExceptions.Should().ContainItemsAssignableTo<HttpRequestException>(); } [Test] public void WithDestination_CompletelyWrong_ShouldThrowArgumentException() { - Assert.Throws<ArgumentException>(() => { Sender.WithDestination("This is very wrong"); }) + Assert.Throws<ArgumentException>(() => { Sender.WithDestination("This is very wrong"); })! .Message.Should().Be("The destination must be a valid GUID"); } [Test] public void WithData_DataIsInvalidJson_ShouldThrowArgumentException() { Assert.Throws<ArgumentException>(() => { - Sender.WithDestination(desitnationId) + Sender.WithDestination(DestinationId) .WithServiceType("Name", "urn:de:fim:leika:leistung:00000000000000") .WithAttachments(new Attachment("Test.pdf", "Test PDF")) .WithData("This is very wrong"); - }) + })! .Message.Should().Be("The data must be valid JSON string"); } } diff --git a/IntegrationTests/Sender/ThreadTest.cs b/IntegrationTests/Sender/ThreadTest.cs index 4342d36af08a42eeeeb329ac83720470752b5ce5..c59372b206b870948ef118094a0edf47158f3e8e 100644 --- a/IntegrationTests/Sender/ThreadTest.cs +++ b/IntegrationTests/Sender/ThreadTest.cs @@ -29,9 +29,9 @@ public class ThreadTest { .CreateLogger<FitConnect.Sender>(); } - private IContainer _container; - private MockSettings _setting; - private ILogger _logger; + private IContainer _container = null!; + private MockSettings _setting = null!; + private ILogger _logger = null!; private const int NumberOfThreads = 34; [Test] @@ -42,11 +42,11 @@ public class ThreadTest { for (var i = 0; i < NumberOfThreads; i++) tasks.Add(Task.Run(() => { var counter = Thread.CurrentThread.ManagedThreadId; - var Sender = new FitConnect.Sender( + var sender = new FitConnect.Sender( FitConnectEnvironment.Testing, _setting.SenderClientId, _setting.SenderClientSecret, _logger); - var delayed = Sender.WithDestination(_setting.DestinationId) + var delayed = sender.WithDestination(_setting.DestinationId) .WithServiceType($"ThreadTest_{counter}", _setting.LeikaKey) .WithAttachments(new Attachment("Test.pdf", $"Attachment_{counter}")); diff --git a/IntegrationTests/Subscriber/SubscriberTestBase.cs b/IntegrationTests/Subscriber/SubscriberTestBase.cs index f4b2684a0c0b53d8279b4203f8f62d733332b7ae..ace3c93995db3181157ee2d589ded0aa9a3b68e8 100644 --- a/IntegrationTests/Subscriber/SubscriberTestBase.cs +++ b/IntegrationTests/Subscriber/SubscriberTestBase.cs @@ -8,12 +8,12 @@ using NUnit.Framework; namespace IntegrationTests.Subscriber; public abstract class SubscriberTestBase { - protected const string desitnationId = "aa3704d6-8bd7-4d40-a8af-501851f93934"; - protected string _clientId = "20175c2b-c4dd-4a01-99b1-3a08436881a1"; - protected string _clientSecret = "KV2qd7qc5n-xESB6dvfrTlMDx2BWHJd5hXJ6pKKnbEQ"; - private IContainer _container; - protected ILogger Logger; - protected ISubscriber subscriber { get; set; } + protected const string DestinationId = "aa3704d6-8bd7-4d40-a8af-501851f93934"; + private const string ClientId = "20175c2b-c4dd-4a01-99b1-3a08436881a1"; + private const string ClientSecret = "KV2qd7qc5n-xESB6dvfrTlMDx2BWHJd5hXJ6pKKnbEQ"; + private IContainer _container = null!; + protected ILogger Logger = null!; + protected ISubscriber Subscriber { get; set; } = null!; [OneTimeSetUp] public void OneTimeSetUp() { @@ -31,10 +31,10 @@ public abstract class SubscriberTestBase { b.SetMinimumLevel(LogLevel.Trace); }) .CreateLogger<FitConnect.Sender>(); - subscriber = new FitConnect.Subscriber( + Subscriber = new FitConnect.Subscriber( FitConnectEnvironment.Testing, - _clientId, - _clientSecret, + ClientId, + ClientSecret, _container.Resolve<MockSettings>().PrivateKeyDecryption, _container.Resolve<MockSettings>().PrivateKeySigning, _container.Resolve<MockSettings>().PublicKeyEncryption, diff --git a/IntegrationTests/Subscriber/SubscriberTestHappyPath.cs b/IntegrationTests/Subscriber/SubscriberTestHappyPath.cs index 77272bbeab07625fa17056e17ec83460d1a070c5..ee585fc6693fb33572091fb9dcfea7f372543d53 100644 --- a/IntegrationTests/Subscriber/SubscriberTestHappyPath.cs +++ b/IntegrationTests/Subscriber/SubscriberTestHappyPath.cs @@ -16,18 +16,18 @@ public class SubscriberTestHappyPath : SubscriberTestBase { [Test] public void GetAvailableSubmissions_WithDestinationId_ShouldReturnSubmissionsForPickupDto() { // Act - var submissions = subscriber.GetAvailableSubmissions(desitnationId).ToList(); + var submissions = Subscriber.GetAvailableSubmissions(DestinationId).ToList(); // Assert submissions.Count().Should().BeGreaterThanOrEqualTo(2); - submissions.All(s => s.DestinationId == desitnationId).Should().BeTrue(); + submissions.All(s => s.DestinationId == DestinationId).Should().BeTrue(); } [Order(202)] [Test] public void GetAvailableSubmissions_WithOutDestinationId_ShouldReturnSubmissionsForPickupDto() { // Act - var submissions = subscriber.GetAvailableSubmissions().ToList(); + var submissions = Subscriber.GetAvailableSubmissions().ToList(); // Assert submissions.Count().Should().BeGreaterThan(0); @@ -39,13 +39,13 @@ public class SubscriberTestHappyPath : SubscriberTestBase { public void GetAllSubmission_WithSubmissionId_ShouldReturnSubmissionsForPickupDto() { // Arrange var errorCounter = 0; - var submissions = subscriber.GetAvailableSubmissions().ToList(); + var submissions = Subscriber.GetAvailableSubmissions().ToList(); submissions.Count().Should().BeGreaterThan(0); var i = 0; foreach (var submissionId in submissions.Select(s => s.SubmissionId)) { // Act Console.WriteLine($"Getting submission {submissionId}"); - var dto = subscriber.RequestSubmission(submissionId); + var dto = Subscriber.RequestSubmission(submissionId); // Assert errorCounter.Should().BeLessThan(submissions.Count()); @@ -62,9 +62,9 @@ public class SubscriberTestHappyPath : SubscriberTestBase { public void GetAttachment_WithSubmissionId_ShouldReturnSubmissionsForPickupDto( string submissionId) { // Act - var attachments = subscriber.RequestSubmission(submissionId).GetAttachments(); + var attachments = Subscriber.RequestSubmission(submissionId).GetAttachments(); foreach (var attachment in attachments) { - attachment.Content.Length.Should().BeGreaterThan(0); + attachment.Content!.Length.Should().BeGreaterThan(0); File.WriteAllBytes("attachments/test.pdf", attachment.Content); } } @@ -72,11 +72,11 @@ public class SubscriberTestHappyPath : SubscriberTestBase { [Test] [Order(203)] public void GetStatus_ForAllPendingSubmissions() { - var submissions = subscriber.GetAvailableSubmissions().ToList(); + var submissions = Subscriber.GetAvailableSubmissions().ToList(); submissions.Count().Should().BeGreaterThan(0); foreach (var submission in submissions) - subscriber.GetStatusForSubmission(submission.CaseId).ForEach(s => + Subscriber.GetStatusForSubmission(submission).ForEach(s => Logger.LogInformation("{SubmissionCaseId} - {ObjEventTime} - {ObjEventType}", submission.CaseId, s.EventTime, s.EventType)); } @@ -86,7 +86,7 @@ public class SubscriberTestHappyPath : SubscriberTestBase { [Order(204)] public void GetAttachment_FromAllPendingSubmission_ShouldReturnAttachment() { // Arrange - var submissions = subscriber.GetAvailableSubmissions().ToList(); + var submissions = Subscriber.GetAvailableSubmissions().ToList(); submissions.Count().Should().BeGreaterThan(0); foreach (var submission in submissions) { @@ -96,10 +96,10 @@ public class SubscriberTestHappyPath : SubscriberTestBase { if (!Directory.Exists($"./attachments/{submissionId}/")) Directory.CreateDirectory($"./attachments/{submissionId}/"); - subscriber.GetStatusForSubmission(submission.CaseId).ForEach(s => + Subscriber.GetStatusForSubmission(submission).ForEach(s => Console.WriteLine($"{s.EventTime} - {s.EventType}")); - var subscriberWithSubmission = subscriber + var subscriberWithSubmission = Subscriber .RequestSubmission(submissionId, true); var attachments = subscriberWithSubmission .GetAttachments(); @@ -107,8 +107,8 @@ public class SubscriberTestHappyPath : SubscriberTestBase { if ((attachment?.Content?.Length ?? 0) > 0) File.WriteAllBytes( Path.Combine($"./attachments/{submissionId}/", - attachment.Filename), - attachment.Content); + attachment!.Filename!), + attachment.Content!); Console.WriteLine($"Json Fachdaten: \r\n{subscriberWithSubmission.GetDataJson()}"); Console.WriteLine($"Success {submissionId}"); @@ -123,14 +123,14 @@ public class SubscriberTestHappyPath : SubscriberTestBase { public void GetSingleSubmission_WithSubmissionId_ShouldReturnSubmissionsForPickupDto( string submissionId) { // Arrange - var submissions = subscriber.GetAvailableSubmissions().ToList(); + var submissions = Subscriber.GetAvailableSubmissions().ToList(); submissions.Count().Should().BeGreaterThan(0); Console.WriteLine($"Getting submission {submissionId}"); - var dto = subscriber.RequestSubmission(submissionId); + var dto = Subscriber.RequestSubmission(submissionId); // Assert - dto.Submission.Id.Should().Be(submissionId); + dto.Submission!.Id.Should().Be(submissionId); Console.WriteLine(dto.Submission.Data); diff --git a/MockContainer/MockContainer.cs b/MockContainer/MockContainer.cs index 9c9f009bd14f123aec88dbc00eaa7baff4d0a5a1..41927b145caf834014f6f0918854547fbbd38fa0 100644 --- a/MockContainer/MockContainer.cs +++ b/MockContainer/MockContainer.cs @@ -66,7 +66,7 @@ public static class Container { var credentials = JsonConvert.DeserializeObject<dynamic>( - File.ReadAllText("./encryptionKeys/credentials.json")); + File.ReadAllText("./encryptionKeys/credentials.json"))!; var senderClientId = (string)credentials.sender.clientId; var senderClientSecret = (string)credentials.sender.clientSecret; @@ -113,8 +113,9 @@ public static class Container { submissionService.Setup(s => s.ListSubmissions(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<int>())).Returns( () => - Task.Run(() => JsonConvert.DeserializeObject<SubmissionsForPickupDto>( - @"{""offset"":0,""count"":3,""totalCount"":3,""submissions"":[{""destinationId"":""879ee109-a690-4db8-ab32-424284184d7d"",""submissionId"":""ce75a6b8-d72f-4b94-b09e-af6be35bc2ae""},{""destinationId"":""19c8489b-29b8-422f-b7db-919852cfb04b"",""submissionId"":""e364430f-5a3b-4284-ba9a-f2867ba421e6""},{""destinationId"":""80a0aac3-148d-42bb-9366-516ce6355348"",""submissionId"":""530ba588-2db9-4899-ab0d-0c0b57689271""}]}")) + JsonConvert.DeserializeObject<SubmissionsForPickupDto>( + @"{""offset"":0,""count"":3,""totalCount"":3,""submissions"":[{""destinationId"":""879ee109-a690-4db8-ab32-424284184d7d"",""submissionId"":""ce75a6b8-d72f-4b94-b09e-af6be35bc2ae""},{""destinationId"":""19c8489b-29b8-422f-b7db-919852cfb04b"",""submissionId"":""e364430f-5a3b-4284-ba9a-f2867ba421e6""},{""destinationId"":""80a0aac3-148d-42bb-9366-516ce6355348"",""submissionId"":""530ba588-2db9-4899-ab0d-0c0b57689271""}]}") + ! ); submissionService.Setup(s => s.CreateSubmission(It.IsAny<CreateSubmissionDto>())).Returns( () => new SubmissionCreatedDto { @@ -135,7 +136,7 @@ public static class Container { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.Indented })}"); - return Task.Run(() => new SubmissionReducedDto()); + return Task.Run(() => new SubmissionReducedDto())!; }); var data = new Data();