public void UploadKeysInMultipleBatches_shouldSendOnlyDkKeys(int batchSize)
        {
            // .: Setup
            // key data must be unique for verification methods
            var denmarkKeys_ApiV1 = TestTemporaryExposureKeyBuilder.CreateDefault(_denmark)
                                    .SetVisitedCountries(new[] { _germany, _poland })
                                    .SetKeySource(KeySource.SmitteStopApiVersion2)
                                    .SetRollingStartNumber(DateTime.UtcNow.AddDays(-1))
                                    .Build(new[] { "Dk_V1_1", "Dk_V1_2", "Dk_V1_3", "Dk_V1_4" });

            var denmarkKeys_ApiV2 = TestTemporaryExposureKeyBuilder.CreateDefault(_denmark)
                                    .SetVisitedCountries(new[] { _germany, _poland })
                                    .SetKeySource(KeySource.SmitteStopApiVersion3)
                                    .SetRollingStartNumber(DateTime.UtcNow.AddDays(-1))
                                    .SetDaysSinceOnsetOfSymptoms(10)
                                    .Build(new[] { "Dk_V2_1", "Dk_V2_2", "Dk_V2_3", "Dk_V2_4" });

            var denmarkKeys_ApiV2_InvalidDaysSinceOnsetOfSymptoms = TestTemporaryExposureKeyBuilder.CreateDefault(_denmark)
                                                                    .SetVisitedCountries(new[] { _germany, _poland })
                                                                    .SetKeySource(KeySource.SmitteStopApiVersion3)
                                                                    .SetRollingStartNumber(DateTime.UtcNow.AddDays(-1))
                                                                    .SetDaysSinceOnsetOfSymptoms(100)
                                                                    .Build(new[] { "Dk_V2_5", "Dk_V2_6", "Dk_V2_7", "Dk_V2_8" });

            var denmarkKeys_ApiV2_Old = TestTemporaryExposureKeyBuilder.CreateDefault(_denmark)
                                        .SetVisitedCountries(new[] { _germany, _poland })
                                        .SetKeySource(KeySource.SmitteStopApiVersion3)
                                        .SetRollingStartNumber(DateTime.UtcNow.AddDays(-16))
                                        .Build(new[] { "Dk_V2_Old_1", "Dk_V2_Old_2", "Dk_V2_Old_3", "Dk_V2_Old_4" });

            var germanKeys_FromGateway = TestTemporaryExposureKeyBuilder.CreateDefault(_germany)
                                         .SetVisitedCountries(new[] { _poland })
                                         .SetKeySource(KeySource.Gateway)
                                         .Build(new[] { "De_Gate_1", "De_Gate_2", "De_Gate_3", "De_Gate_4", "De_Gate_5", "De_Gate_6" });

            var latviaKeys_FromGateway = TestTemporaryExposureKeyBuilder.CreateDefault(_latvia)
                                         .SetKeySource(KeySource.Gateway)
                                         .Build(new[] { "Lv_Gate_1", "Lv_Gate_2", "Lv_Gate_3", "Lv_Gate_4", "Lv_Gate_5", "Lv_Gate_6" });

            _dbContext.TemporaryExposureKey.AddRange(denmarkKeys_ApiV1);
            _dbContext.TemporaryExposureKey.AddRange(denmarkKeys_ApiV2);
            _dbContext.TemporaryExposureKey.AddRange(denmarkKeys_ApiV2_Old);
            _dbContext.TemporaryExposureKey.AddRange(germanKeys_FromGateway);
            _dbContext.TemporaryExposureKey.AddRange(latviaKeys_FromGateway);
            _dbContext.TemporaryExposureKey.AddRange(denmarkKeys_ApiV2_InvalidDaysSinceOnsetOfSymptoms);
            _dbContext.SaveChanges();

            // setup mock
            var gatewayHttpClientMock = new Mock <IGatewayHttpClient>();

            var expectedResponse = new HttpResponseMessage {
                StatusCode = HttpStatusCode.Created, Content = new StringContent("")
            };

            gatewayHttpClientMock.Setup(client => client.PostAsync(It.IsAny <string>(), It.IsAny <HttpContent>()))
            .ReturnsAsync(expectedResponse);

            var service = CreateGatewayServiceAndDependencies(gatewayHttpClientMock.Object);

            // .: Act
            service.UploadKeysToTheGateway(10, batchSize);

            // .: Verify
            var requestArgInterceptor = new ArgumentInterceptor <HttpContent>();

            gatewayHttpClientMock.Verify(c => c.PostAsync(It.IsAny <string>(), requestArgInterceptor.CreateCaptureAction()));

            var allBatchesPassedToHttpClient = ParseRequestBodiesBatches(requestArgInterceptor);
            var keysFromAllSentBatches       = allBatchesPassedToHttpClient.SelectMany(bath => bath.Keys).ToList();

            keysFromAllSentBatches.Should()
            .OnlyContain(key => key.Origin == _denmark.Code, because: "Only DK keys from APIV2 can be send to the UE Gateway.")
            .And.OnlyHaveUniqueItems(key => key.KeyData, because: "Service cannot send duplicates.")
            .And.OnlyContain(key => denmarkKeys_ApiV2.Any(keyApiv2 => EqualsKeyData(key.KeyData.ToByteArray(), keyApiv2.KeyData)));

            int expectedNumberOfBatches = (int)Math.Ceiling(denmarkKeys_ApiV2.Count / (decimal)batchSize);

            allBatchesPassedToHttpClient.Should()
            .NotBeNull()
            .And.HaveCount(expectedNumberOfBatches, because: "Keys needed to be send in batches");

            allBatchesPassedToHttpClient.ToList().ForEach(
                batch =>
            {
                batch.Keys.Should()
                .HaveCountGreaterThan(0, because: "Service cannot send empty requests.")
                .And.HaveCountLessOrEqualTo(batchSize, because: "Service cannot send batch with wrong size.")
                .And.OnlyHaveUniqueItems(key => key.KeyData, because: "Service cannot send duplicates.");
            });
        }
        public void UploadKeysInMultipleBatches_connectionErrrorOccured_shouldSendAllDkKeys()
        {
            int batchSize            = 2;
            int keysNotOlderThenDays = 14;
            // .: Setup - key data must be unique for verification methods
            var keysThatShouldBeSent = TestTemporaryExposureKeyBuilder.CreateDefault(_denmark)
                                       .SetVisitedCountries(new[] { _germany, _poland })
                                       .SetKeySource(KeySource.SmitteStopApiVersion3)
                                       .SetRollingStartNumber(DateTime.UtcNow.AddDays(-1))
                                       .Build(new[] { "Dk_V2_1", "Dk_V2_2", "Dk_V2_3", "Dk_V2_4" });

            var denmarkKeys_ApiV2_Old = TestTemporaryExposureKeyBuilder.CreateDefault(_denmark)
                                        .SetVisitedCountries(new[] { _germany, _poland })
                                        .SetKeySource(KeySource.SmitteStopApiVersion3)
                                        .SetRollingStartNumber(DateTime.UtcNow.AddDays(-16))
                                        .Build(new[] { "Dk_V2_Old_1", "Dk_V2_Old_2", "Dk_V2_Old_3", "Dk_V2_Old_4" });

            var germanKeys_FromGateway = TestTemporaryExposureKeyBuilder.CreateDefault(_germany)
                                         .SetVisitedCountries(new[] { _poland })
                                         .SetKeySource(KeySource.Gateway)
                                         .Build(new[] { "De_Gate_1", "De_Gate_2", "De_Gate_3", "De_Gate_4", "De_Gate_5", "De_Gate_6" });

            _dbContext.TemporaryExposureKey.AddRange(keysThatShouldBeSent);
            _dbContext.TemporaryExposureKey.AddRange(denmarkKeys_ApiV2_Old);
            _dbContext.TemporaryExposureKey.AddRange(germanKeys_FromGateway);
            _dbContext.SaveChanges();

            // setup mock
            var gatewayHttpClientMock = new Mock <IGatewayHttpClient>();

            var successfulResponse = new HttpResponseMessage {
                StatusCode = HttpStatusCode.Created, Content = new StringContent("")
            };
            var errorResponse = new HttpResponseMessage {
                StatusCode = HttpStatusCode.BadRequest, Content = new StringContent("")
            };

            gatewayHttpClientMock.SetupSequence(client => client.PostAsync(It.IsAny <string>(), It.IsAny <HttpContent>()))
            .ReturnsAsync(successfulResponse)
            .ReturnsAsync(errorResponse)
            .ReturnsAsync(errorResponse)
            .ReturnsAsync(successfulResponse);

            var service = CreateGatewayServiceAndDependencies(gatewayHttpClientMock.Object);

            // .: Act
            try
            {
                service.UploadKeysToTheGateway(keysNotOlderThenDays, batchSize); // partial success - one batch sent, second fail
            }
            catch (Exception) { }

            // emulate Job retry
            try
            {
                service.UploadKeysToTheGateway(keysNotOlderThenDays, batchSize);  // first batch fails
            }
            catch (Exception) { }


            // emulate Job retry
            service.UploadKeysToTheGateway(keysNotOlderThenDays, batchSize); // success
            // .: Verify
            var requestArgInterceptor = new ArgumentInterceptor <HttpContent>();

            gatewayHttpClientMock.Verify(c => c.PostAsync(It.IsAny <string>(), requestArgInterceptor.CreateCaptureAction()));

            var allBatchesPassedToHttpClient = ParseRequestBodiesBatches(requestArgInterceptor);

            var firstSucessfulCall         = allBatchesPassedToHttpClient[0].Keys;
            var keysFromRetry1             = allBatchesPassedToHttpClient[1].Keys;
            var keysFromRetry2             = allBatchesPassedToHttpClient[2].Keys;
            var lastSuccessfulCall         = allBatchesPassedToHttpClient[3].Keys;
            var keysFromAllSuccessfulCalls = firstSucessfulCall.Concat(lastSuccessfulCall);

            // all  keys has been sent
            keysFromAllSuccessfulCalls.Should()
            .OnlyContain(key => key.Origin == _denmark.Code, because: "Only DK keys from APIV2 can be send to the UE Gateway.")
            .And.OnlyHaveUniqueItems(key => key.KeyData, because: "Service cannot send duplicates.")
            .And.OnlyContain(key => keysThatShouldBeSent.Any(keyApiv2 => EqualsKeyData(key.KeyData.ToByteArray(), keyApiv2.KeyData)))
            .And.HaveCount(keysThatShouldBeSent.Count);

            // Service was trying to send  keys again
            keysFromRetry1.Should()
            .OnlyHaveUniqueItems(key => key.KeyData, because: "Service cannot send duplicates.")
            .And.OnlyContain(key2Check => keysThatShouldBeSent.Any(keyFromCollection => EqualsKeyData(key2Check.KeyData.ToByteArray(), keyFromCollection.KeyData)))
            .And.NotContain(key2Check => firstSucessfulCall.Any(keyFromCollection => EqualsKeyData(key2Check.KeyData, keyFromCollection.KeyData)));

            keysFromRetry2.Should()
            .OnlyHaveUniqueItems(key => key.KeyData, because: "Service cannot send duplicates.")
            .And.OnlyContain(key2Check => keysFromRetry1.Any(denmarkKey => EqualsKeyData(key2Check.KeyData, denmarkKey.KeyData)))
            .And.HaveCount(keysFromRetry1.Count);

            lastSuccessfulCall.Should()
            .OnlyHaveUniqueItems(key => key.KeyData, because: "Service cannot send duplicates.")
            .And.OnlyContain(key => keysThatShouldBeSent.Any(keyApiv2 => EqualsKeyData(key.KeyData.ToByteArray(), keyApiv2.KeyData)), because: "Can contain only keys that should be sent.")
            .And.OnlyContain(key2Check => keysFromRetry1.Any(keyFromCollection => EqualsKeyData(key2Check.KeyData, keyFromCollection.KeyData)), because: "Service should try to sent the same keys again.")
            .And.OnlyContain(key2Check => keysFromRetry2.Any(keyFromCollection => EqualsKeyData(key2Check.KeyData, keyFromCollection.KeyData)), because: "Service should try to sent the same keys again.")
            .And.NotContain(key2Check => firstSucessfulCall.Any(keyFromCollection => EqualsKeyData(key2Check.KeyData, keyFromCollection.KeyData)), because: "Service cannot send duplicates.")
            .And.HaveCount(keysFromRetry1.Count)
            .And.HaveCount(keysFromRetry2.Count);

            allBatchesPassedToHttpClient.ToList().ForEach(
                batch =>
            {
                batch.Keys.Should()
                .HaveCountGreaterThan(0, because: "Service cannot send empty requests.")
                .And.HaveCountLessOrEqualTo(batchSize, because: "Service cannot send batch with wrong size.");
            });
        }
        public void UploadKeysInOneBatch_shouldSendDkKeysOnlyWithConsent(int batchSize)
        {
            int keysNotOlderThenDays = 14;
            // .: Setup - key data must be unique for verification methods
            var dkApiV2DefaultBuilder = TestTemporaryExposureKeyBuilder.CreateDefault(_denmark)
                                        .SetKeySource(KeySource.SmitteStopApiVersion3)
                                        .SetRollingStartNumber(DateTime.UtcNow.AddDays(-1));

            var otherKeysDefaultBuilder = TestTemporaryExposureKeyBuilder.CreateDefault(_poland)
                                          .SetKeySource(KeySource.Gateway)
                                          .SetRollingStartNumber(DateTime.UtcNow.AddDays(-3));

            // data for upload 1
            // it is not possible to have creation date exactly the same as before for data that appear in the db later
            dkApiV2DefaultBuilder.SetCreatedOn(DateTime.Now);
            var denmarkKeys_upload1 = dkApiV2DefaultBuilder.Copy()
                                      .SetVisitedCountries(new[] { _germany, _poland })
                                      .Build(new[] { "Dk_U1_1", "Dk_U1_2", "Dk_U1_3", "Dk_U1_4" });

            for (int i = 0; i < denmarkKeys_upload1.Count; i++)
            {
                denmarkKeys_upload1[i].SharingConsentGiven = i % 2 == 0;//Set evenkly placed keys to not shared
            }
            var otherKeys_upload1 = otherKeysDefaultBuilder.Copy()
                                    .SetOrigin(_poland)
                                    .Build(new[] { "Other_U1_1", "Other_U1_2", "Other_U1_2", "Other_U1_3" });
            // setup mock
            var gatewayHttpClientMock = new Mock <IGatewayHttpClient>();

            var expectedResponse = new HttpResponseMessage {
                StatusCode = HttpStatusCode.Created, Content = new StringContent("")
            };

            gatewayHttpClientMock.Setup(client => client.PostAsync(It.IsAny <string>(), It.IsAny <HttpContent>()))
            .ReturnsAsync(expectedResponse);

            var service = CreateGatewayServiceAndDependencies(gatewayHttpClientMock.Object);

            // .: Act
            _dbContext.TemporaryExposureKey.AddRange(denmarkKeys_upload1);
            _dbContext.TemporaryExposureKey.AddRange(otherKeys_upload1);
            _dbContext.SaveChanges();

            service.UploadKeysToTheGateway(keysNotOlderThenDays, batchSize);

            // .: Verify
            // get data
            var requestArgInterceptor = new ArgumentInterceptor <HttpContent>();

            gatewayHttpClientMock.Verify(c => c.PostAsync(It.IsAny <string>(), requestArgInterceptor.CreateCaptureAction()));

            var allBatchesPassedToHttpClient = ParseRequestBodiesBatches(requestArgInterceptor);
            var keysFromAllSentBatches       = allBatchesPassedToHttpClient.SelectMany(batch => batch.Keys).ToList();
            // assert
            var expected_AllSentKeys             = denmarkKeys_upload1.Where(key => key.SharingConsentGiven == true).ToList();
            int expectedNumberOfBatchesInUpload1 = (int)Math.Ceiling(denmarkKeys_upload1.Where(key => key.SharingConsentGiven).Count() / (decimal)batchSize);

            keysFromAllSentBatches.Should()
            .OnlyContain(key => key.Origin == _denmark.Code, because: "Only DK keys from APIV2 can be send to the UE Gateway.")
            .And.HaveCount(expected_AllSentKeys.Count(), "Service need to send all keys valid for the sending.")
            .And.HaveCountLessThan(denmarkKeys_upload1.Count, because: "Service should only send keys with consent.")
            .And.OnlyHaveUniqueItems(key => key.KeyData.ToBase64(), because: "Service cannot send duplicates.")
            .And.OnlyContain(key => expected_AllSentKeys.Any(keyApiv2 => EqualsKeyData(key.KeyData.ToByteArray(), keyApiv2.KeyData)));

            allBatchesPassedToHttpClient.Should()
            .NotBeNull()
            .And.HaveCount(expectedNumberOfBatchesInUpload1, because: "Keys needed to be send in batches");

            allBatchesPassedToHttpClient.ToList().ForEach(
                batch =>
            {
                batch.Keys.Should()
                .HaveCountGreaterThan(0, because: "Service cannot send empty requests.")
                .And.HaveCountLessOrEqualTo(batchSize, because: "Service cannot send batch with wrong size.")
                .And.OnlyHaveUniqueItems(key => key.KeyData, because: "Service cannot send duplicates.");
            });
        }
Example #4
0
        public void DownloadKeysFromMultipleCountries_ShouldSaveOnlyValidKeysFromEnabledOrigins()
        {
            int maximumNumberOfDaysBack = 3;

            var today       = DateTime.UtcNow.Date;
            var day1DateStr = today.AddDays(-2).ToString(DateFormat);
            var day2DateStr = today.AddDays(-1).ToString(DateFormat);
            var day3DateStr = today.ToString(DateFormat);

            var germanDefaultBuilder = TestTemporaryExposureKeyBuilder.CreateDefault(_germany)
                                       .SetReportType(ReportType.CONFIRMED_TEST)
                                       .SetKeySource(KeySource.Gateway);

            // has DK in VisitedCountries - should be accepted
            var germanValidKeysBuilder1 = germanDefaultBuilder.Copy().SetVisitedCountries(new[] { _poland, _denmark });
            // has empty VisitedCountries = no filtering. Should be accepted
            var germanValidKeysBuilder2 = germanDefaultBuilder.Copy().SetVisitedCountries(new Country[] { });
            // doesn't have DK in VisitedCountries - should be accepted because of one world policy
            var germanValidKeysBuilder3 = germanDefaultBuilder.Copy().SetVisitedCountries(new Country[] { _poland });
            // invalid RollingStartNumber - should be rejected
            var germanInvalidKeysBuilder1 = germanDefaultBuilder.Copy()
                                            .SetVisitedCountries(new Country[] { _denmark })
                                            .SetRollingStartNumber(DateTime.UnixEpoch);
            // pulling from Latvia is disabled - should be rejected
            var latviaKeysBuilder = TestTemporaryExposureKeyBuilder.CreateDefault(_latviaDisabledDownload)
                                    .SetKeySource(KeySource.Gateway);

            // BE AWARE THAT KeyData CANNOT BE LONGER THAN 16 BYTES - longer keys will be rejected by GatewayService
            // day 1 - batch 1
            var daysOneBatchTag1      = day1DateStr;
            var dayOneValidKeysBatch1 = new List <TemporaryExposureKey>()
                                        .Concat(germanValidKeysBuilder1.BuildNormalized(new[] { "DE1.1_V1", "DE1_V2", "DE1.1_V3" }))
                                        .Concat(germanValidKeysBuilder2.BuildNormalized(new[] { "DE1.1_V4", "DE1.1_V5", "DE1.1_V6" }));
            var dayOneValidKeysBatch4 = new List <TemporaryExposureKey>()
                                        .Concat(germanValidKeysBuilder3.BuildNormalized(new[] { "DE1.1_INV1", "DE1.1_INV2" }));
            // day #1 - batch 2
            var daysOneBatchTag2      = day1DateStr + "-2";
            var dayOneValidKeysBatch2 = new List <TemporaryExposureKey>()
                                        .Concat(germanValidKeysBuilder1.BuildNormalized(new[] { "DE1.2_V1", "DE1.2_V2", "DE1.2_V3" }));
            var dayOneInvalidKeysBatch2 = new List <TemporaryExposureKey>()
                                          .Concat(latviaKeysBuilder.BuildNormalized(new[] { "LT1.2_INV1", "LT1.2_INV2" }));
            // day #1 - batch 3
            var daysOneBatchTag3      = day1DateStr + "-3";
            var dayOneValidKeysBatch3 = new List <TemporaryExposureKey>()
                                        .Concat(germanValidKeysBuilder1.BuildNormalized(new[] { "DE1.3_V1", "DE1.3_V2", "DE1.3_V3" }));
            // day #2 - batch 1
            var dayTwoBatchTag1         = day2DateStr;
            var dayTwoInvalidKeysBatch1 = new List <TemporaryExposureKey>()
                                          .Concat(latviaKeysBuilder.BuildNormalized(new[] { "LT2.1_INV1", "LT2.1_INV2" }));
            // day #2 - batch 2
            var dayTwoBatchTag2         = day2DateStr + "-2";
            var dayTwoInvalidKeysBatch2 = new List <TemporaryExposureKey>()
                                          .Concat(latviaKeysBuilder.BuildNormalized(new[] { "LT2.2_INV1", "LT2.2_INV2" }))
                                          .Concat(germanInvalidKeysBuilder1.BuildNormalized(new[] { "DE2.2_INV3" }));
            // day #3 - batch 1
            var dayThreeBatchTag1       = day3DateStr;
            var dayThreeValidKeysBatch1 = new List <TemporaryExposureKey>()
                                          .Concat(germanValidKeysBuilder1.BuildNormalized(new[] { "DE3.1_V1", "DE3_V2", "DE3.1_V3" }))
                                          .Concat(germanValidKeysBuilder2.BuildNormalized(new[] { "DE3.1_V4", "DE3.1_V5", "DE3.1_V6" }));

            var keyBatchesDict = new Dictionary <string, IEnumerable <TemporaryExposureKey> >();

            keyBatchesDict.Add(key: daysOneBatchTag1, value: dayOneValidKeysBatch1.Concat(dayOneValidKeysBatch4));
            keyBatchesDict.Add(key: daysOneBatchTag2, value: dayOneValidKeysBatch2.Concat(dayOneInvalidKeysBatch2));
            keyBatchesDict.Add(key: daysOneBatchTag3, value: dayOneValidKeysBatch3);

            keyBatchesDict.Add(key: dayTwoBatchTag1, value: dayTwoInvalidKeysBatch1);
            keyBatchesDict.Add(key: dayTwoBatchTag2, value: dayTwoInvalidKeysBatch2);

            keyBatchesDict.Add(key: dayThreeBatchTag1, value: dayThreeValidKeysBatch1);

            // setup mock
            var gatewayHttpClientMock = new Mock <IGatewayHttpClient>(MockBehavior.Strict);

            gatewayHttpClientMock
            .Setup(client => client.SendAsync(It.IsAny <HttpRequestMessage>()))
            .ReturnsAsync((HttpRequestMessage request) =>
            {
                string dictKey;
                // request for the first batch doesn't contain BatchTag header
                if (!request.Headers.Contains(BatchTagHeaderName))
                {
                    dictKey = ExtractDateFromUrl(request.RequestUri.ToString());
                }
                else
                {
                    dictKey = request.Headers.GetValues(BatchTagHeaderName).First();
                }

                if (keyBatchesDict.ContainsKey(dictKey))
                {
                    return(CreateGatewayResponseFromBatch(keyBatchesDict[dictKey]));
                }
                else
                {
                    throw new InvalidOperationException($"Invalid batchTag has been requested: {dictKey}");
                }
            });
            var service = CreateGatewayServiceAndDependencies(gatewayHttpClientMock.Object);

            // Act
            service.DownloadKeysFromGateway(maximumNumberOfDaysBack);

            // .: Verify
            // get data
            var allSavedKeys = _keysRepository.GetAll().Result;

            // assert
            var allValidKeys = new List <TemporaryExposureKey>()
                               .Concat(dayOneValidKeysBatch1)
                               .Concat(dayOneValidKeysBatch2)
                               .Concat(dayOneValidKeysBatch3)
                               .Concat(dayOneValidKeysBatch4)
                               .Concat(dayThreeValidKeysBatch1)
                               .ToList();

            var allInvalidKeys = new List <TemporaryExposureKey>()
                                 .Concat(dayOneInvalidKeysBatch2)
                                 .Concat(dayOneInvalidKeysBatch2)
                                 .Concat(dayTwoInvalidKeysBatch1)
                                 .Concat(dayTwoInvalidKeysBatch2)
                                 .ToList();

            allSavedKeys.Should()
            .OnlyHaveUniqueItems(key => key.KeyData, because: "Service cannot send duplicates.")
            .And.OnlyContain(key2Check => IsInCollection(key2Check.KeyData, allValidKeys), because: "Only valid keys should be save in the database.");
        }