From 442bd60b74f81eafeeb395fcefe747e9f7938415 Mon Sep 17 00:00:00 2001
From: cocoa-dev004 <66989461+cocoa-dev004@users.noreply.github.com>
Date: Wed, 8 Jun 2022 14:04:31 +0900
Subject: [PATCH 1/5] Add event log function
---
.../Covid19Radar.Android.csproj | 1 +
.../Covid19Radar.Android/MainApplication.cs | 19 +-
.../EventLogSubmissionBackgroundService.cs | 107 +++++++
Covid19Radar/Covid19Radar.iOS/AppDelegate.cs | 21 +-
.../Covid19Radar.iOS/Covid19Radar.iOS.csproj | 1 +
Covid19Radar/Covid19Radar.iOS/Info.plist | 1 +
.../EventLogSubmissionBackgroundService.cs | 105 +++++++
.../Covid19Radar/Common/AppConstants.cs | 10 +
.../Common/DeviceVerifierUtils.cs | 6 +-
.../Covid19Radar/Model/V1EventLogRequest.cs | 42 +--
.../Repository/EventLogRepository.cs | 292 ++++++++++++++++++
.../Repository/SendEventLogStateRepository.cs | 158 ++++++++++
.../Repository/UserDataRepository.cs | 36 ---
.../AbsEventLogSubmissionBackgroundService.cs | 11 +
.../Covid19Radar/Services/EventLogService.cs | 218 ++++++-------
.../Services/EventLogServiceNop.cs | 35 +--
.../Services/ExposureDetectionService.cs | 131 ++------
.../Covid19Radar/Services/HttpDataService.cs | 23 ++
.../Services/HttpDataServiceMock.cs | 8 +
.../Covid19Radar/Services/IHttpDataService.cs | 2 +
.../Services/ILocalPathService.cs | 4 +
...entlog_submission_parameter1-cleartext.txt | 2 +-
.../Services/ExposureDetectionServiceTests.cs | 14 +-
.../Services/HttpDataServiceTests.cs | 87 +++++-
24 files changed, 989 insertions(+), 345 deletions(-)
create mode 100644 Covid19Radar/Covid19Radar.Android/Services/EventLogSubmissionBackgroundService.cs
create mode 100644 Covid19Radar/Covid19Radar.iOS/Services/EventLogSubmissionBackgroundService.cs
create mode 100644 Covid19Radar/Covid19Radar/Repository/EventLogRepository.cs
create mode 100644 Covid19Radar/Covid19Radar/Repository/SendEventLogStateRepository.cs
create mode 100644 Covid19Radar/Covid19Radar/Services/AbsEventLogSubmissionBackgroundService.cs
diff --git a/Covid19Radar/Covid19Radar.Android/Covid19Radar.Android.csproj b/Covid19Radar/Covid19Radar.Android/Covid19Radar.Android.csproj
index 8c2e65477..2cba60edc 100644
--- a/Covid19Radar/Covid19Radar.Android/Covid19Radar.Android.csproj
+++ b/Covid19Radar/Covid19Radar.Android/Covid19Radar.Android.csproj
@@ -191,6 +191,7 @@
+
diff --git a/Covid19Radar/Covid19Radar.Android/MainApplication.cs b/Covid19Radar/Covid19Radar.Android/MainApplication.cs
index 32202a76b..0c357e822 100644
--- a/Covid19Radar/Covid19Radar.Android/MainApplication.cs
+++ b/Covid19Radar/Covid19Radar.Android/MainApplication.cs
@@ -45,6 +45,9 @@ private Lazy _exposureDetectionService
private Lazy _exposureDetectionBackgroundService
= new Lazy(() => ContainerLocator.Current.Resolve());
+ private readonly Lazy _eventLogSubmissionBackgroundService
+ = new Lazy(() => ContainerLocator.Current.Resolve());
+
private Lazy _loggerService
= new Lazy(() => ContainerLocator.Current.Resolve());
@@ -79,13 +82,26 @@ public override void OnCreate()
SetupENClient(exposureNotificationApiService.Client);
}
+ ScheduleBackgroundTasks();
+ }
+
+ private void ScheduleBackgroundTasks()
+ {
try
{
_exposureDetectionBackgroundService.Value.Schedule();
}
catch (Exception exception)
{
- _loggerService.Value.Exception("failed to Scheduling", exception);
+ _loggerService.Value.Exception("failed to Scheduling ExposureDetectionBackgroundService", exception);
+ }
+ try
+ {
+ _eventLogSubmissionBackgroundService.Value.Schedule();
+ }
+ catch (Exception exception)
+ {
+ _loggerService.Value.Exception("failed to Scheduling EventLogSubmissionBackgroundService", exception);
}
}
@@ -121,6 +137,7 @@ private void RegisterPlatformTypes(IContainer container)
container.Register(Reuse.Singleton);
container.Register(Reuse.Singleton);
+ container.Register(Reuse.Singleton);
}
public Task GetExposureConfigurationAsync()
diff --git a/Covid19Radar/Covid19Radar.Android/Services/EventLogSubmissionBackgroundService.cs b/Covid19Radar/Covid19Radar.Android/Services/EventLogSubmissionBackgroundService.cs
new file mode 100644
index 000000000..1c5172226
--- /dev/null
+++ b/Covid19Radar/Covid19Radar.Android/Services/EventLogSubmissionBackgroundService.cs
@@ -0,0 +1,107 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+using System;
+using Android.Content;
+using Android.Runtime;
+using AndroidX.Work;
+using Covid19Radar.Common;
+using Covid19Radar.Services;
+using Covid19Radar.Services.Logs;
+using Java.Util.Concurrent;
+using Prism.Ioc;
+using Xamarin.Essentials;
+
+namespace Covid19Radar.Droid.Services
+{
+ public class EventLogSubmissionBackgroundService : AbsEventLogSubmissionBackgroundService
+ {
+ private const string CURRENT_WORK_NAME = "eventlog_submission_worker_20220112";
+
+ private const long INTERVAL_IN_HOURS = 24;
+ private const long BACKOFF_DELAY_IN_MINUTES = 60;
+
+ private readonly ILoggerService _loggerService;
+
+ public EventLogSubmissionBackgroundService(
+ ILoggerService loggerService
+ ) : base()
+ {
+ _loggerService = loggerService;
+ }
+
+ public override void Schedule()
+ {
+ _loggerService.StartMethod();
+
+ WorkManager workManager = WorkManager.GetInstance(Platform.AppContext);
+
+ PeriodicWorkRequest periodicWorkRequest = CreatePeriodicWorkRequest();
+ workManager.EnqueueUniquePeriodicWork(
+ CURRENT_WORK_NAME,
+ ExistingPeriodicWorkPolicy.Replace,
+ periodicWorkRequest
+ );
+
+ _loggerService.EndMethod();
+ }
+
+ private static PeriodicWorkRequest CreatePeriodicWorkRequest()
+ {
+ var workRequestBuilder = new PeriodicWorkRequest.Builder(
+ typeof(BackgroundWorker),
+ INTERVAL_IN_HOURS, TimeUnit.Hours
+ )
+ .SetConstraints(new Constraints.Builder()
+ .SetRequiresBatteryNotLow(true)
+ .SetRequiredNetworkType(NetworkType.Connected)
+ .Build())
+ .SetBackoffCriteria(BackoffPolicy.Linear, BACKOFF_DELAY_IN_MINUTES, TimeUnit.Minutes);
+ return workRequestBuilder.Build();
+ }
+
+ [Preserve]
+ public class BackgroundWorker : Worker
+ {
+ private Lazy _loggerService => new Lazy(() => ContainerLocator.Current.Resolve());
+ private Lazy _eventLogService => new Lazy(() => ContainerLocator.Current.Resolve());
+
+ public BackgroundWorker(Context context, WorkerParameters workerParams) : base(context, workerParams)
+ {
+ // do nothing
+ }
+
+ public override Result DoWork()
+ {
+ var loggerService = _loggerService.Value;
+ var eventLogService = _eventLogService.Value;
+
+ loggerService.StartMethod();
+
+ try
+ {
+ eventLogService.SendAllAsync(AppConstants.EventLogMaxRequestSizeInBytes, AppConstants.EventLogMaxRetry);
+ return Result.InvokeSuccess();
+ }
+ catch (Exception exception)
+ {
+ loggerService.Exception("Exception occurred, SendAllAsync", exception);
+ return Result.InvokeFailure();
+ }
+ finally
+ {
+ loggerService.EndMethod();
+ }
+ }
+
+ public override void OnStopped()
+ {
+ base.OnStopped();
+
+ _loggerService.Value.Warning("OnStopped");
+ }
+ }
+ }
+}
+
diff --git a/Covid19Radar/Covid19Radar.iOS/AppDelegate.cs b/Covid19Radar/Covid19Radar.iOS/AppDelegate.cs
index 62c0f7ba7..c4fabdbb5 100644
--- a/Covid19Radar/Covid19Radar.iOS/AppDelegate.cs
+++ b/Covid19Radar/Covid19Radar.iOS/AppDelegate.cs
@@ -41,6 +41,9 @@ private Lazy _exposureNotificationClient
private Lazy _exposureDetectionBackgroundService
= new Lazy(() => ContainerLocator.Current.Resolve());
+ private readonly Lazy _eventLogSubmissionBackgroundService
+ = new Lazy(() => ContainerLocator.Current.Resolve());
+
private Lazy _exposureDetectionService
= new Lazy(() => ContainerLocator.Current.Resolve());
@@ -109,6 +112,13 @@ public override bool FinishedLaunching(UIApplication app, NSDictionary launchOpt
UIApplication.SharedApplication.SetMinimumBackgroundFetchInterval(UIApplication.BackgroundFetchIntervalMinimum);
+ ScheduleBackgroundTask();
+
+ return base.FinishedLaunching(app, launchOptions);
+ }
+
+ private void ScheduleBackgroundTask()
+ {
try
{
_exposureDetectionBackgroundService.Value.Schedule();
@@ -117,8 +127,14 @@ public override bool FinishedLaunching(UIApplication app, NSDictionary launchOpt
{
_loggerService.Value.Exception("failed to Scheduling", exception);
}
-
- return base.FinishedLaunching(app, launchOptions);
+ try
+ {
+ _eventLogSubmissionBackgroundService.Value.Schedule();
+ }
+ catch (Exception exception)
+ {
+ _loggerService.Value.Exception("failed to Scheduling EventLogSubmissionBackgroundService", exception);
+ }
}
private bool IsUniversalLinks(NSDictionary launchOptions)
@@ -257,6 +273,7 @@ private void RegisterPlatformTypes(IContainer container)
container.Register(Reuse.Singleton);
#endif
container.Register(Reuse.Singleton);
+ container.Register(Reuse.Singleton);
}
public Task GetExposureConfigurationAsync()
diff --git a/Covid19Radar/Covid19Radar.iOS/Covid19Radar.iOS.csproj b/Covid19Radar/Covid19Radar.iOS/Covid19Radar.iOS.csproj
index e3ac0a6c3..013036399 100644
--- a/Covid19Radar/Covid19Radar.iOS/Covid19Radar.iOS.csproj
+++ b/Covid19Radar/Covid19Radar.iOS/Covid19Radar.iOS.csproj
@@ -270,6 +270,7 @@
+
diff --git a/Covid19Radar/Covid19Radar.iOS/Info.plist b/Covid19Radar/Covid19Radar.iOS/Info.plist
index cfd01d1dc..751350d06 100644
--- a/Covid19Radar/Covid19Radar.iOS/Info.plist
+++ b/Covid19Radar/Covid19Radar.iOS/Info.plist
@@ -55,6 +55,7 @@
APP_PACKAGE_NAME.exposure-notification
APP_PACKAGE_NAME.delete-old-logs
+ APP_PACKAGE_NAME.eventlog-submission
UIAppFonts
diff --git a/Covid19Radar/Covid19Radar.iOS/Services/EventLogSubmissionBackgroundService.cs b/Covid19Radar/Covid19Radar.iOS/Services/EventLogSubmissionBackgroundService.cs
new file mode 100644
index 000000000..b0c15bbe5
--- /dev/null
+++ b/Covid19Radar/Covid19Radar.iOS/Services/EventLogSubmissionBackgroundService.cs
@@ -0,0 +1,105 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using BackgroundTasks;
+using Covid19Radar.Common;
+using Covid19Radar.Services;
+using Covid19Radar.Services.Logs;
+using Foundation;
+using Xamarin.Essentials;
+
+namespace Covid19Radar.iOS.Services
+{
+ public class EventLogSubmissionBackgroundService : AbsEventLogSubmissionBackgroundService
+ {
+ private static readonly string IDENTIFIER = AppInfo.PackageName + ".eventlog-submission";
+
+ private const double ONE_DAY_IN_SECONDS = 1 * 24 * 60 * 60;
+
+ private readonly IEventLogService _eventLogService;
+ private readonly ILoggerService _loggerService;
+
+ public EventLogSubmissionBackgroundService(
+ IEventLogService eventLogService,
+ ILoggerService loggerService
+ ) : base()
+ {
+ _eventLogService = eventLogService;
+ _loggerService = loggerService;
+ }
+
+ public override void Schedule()
+ {
+ _ = BGTaskScheduler.Shared.Register(IDENTIFIER, null, task =>
+ {
+ HandleSendLogAsync((BGAppRefreshTask)task);
+ });
+
+ ScheduleSendEventLog();
+
+ _loggerService.EndMethod();
+ }
+
+ private void HandleSendLogAsync(BGAppRefreshTask task)
+ {
+ _loggerService.StartMethod();
+
+ ScheduleSendEventLog();
+
+ var cancellationTokenSource = new CancellationTokenSource();
+ task.ExpirationHandler = cancellationTokenSource.Cancel;
+
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ await _eventLogService.SendAllAsync(AppConstants.EventLogMaxRequestSizeInBytes, AppConstants.EventLogMaxRetry);
+ task.SetTaskCompleted(true);
+ }
+ catch (OperationCanceledException exception)
+ {
+ _loggerService.Exception($"Background task canceled.", exception);
+ task.SetTaskCompleted(false);
+ }
+ catch (Exception exception)
+ {
+ _loggerService.Exception($"Exception", exception);
+ task.SetTaskCompleted(false);
+ }
+ finally
+ {
+ cancellationTokenSource.Dispose();
+ _loggerService.EndMethod();
+ }
+ });
+ }
+
+ private void ScheduleSendEventLog()
+ {
+ _loggerService.StartMethod();
+
+ var bgTaskRequest = new BGProcessingTaskRequest(IDENTIFIER)
+ {
+ EarliestBeginDate = NSDate.FromTimeIntervalSinceNow(ONE_DAY_IN_SECONDS),
+ RequiresNetworkConnectivity = true
+ };
+
+ _loggerService.Info($"request.EarliestBeginDate: {bgTaskRequest.EarliestBeginDate}");
+
+ _ = BGTaskScheduler.Shared.Submit(bgTaskRequest, out var error);
+ if (error != null)
+ {
+ NSErrorException exception = new NSErrorException(error);
+ _loggerService.Exception("BGTaskScheduler submit failed.", exception);
+ throw exception;
+ }
+
+ _loggerService.EndMethod();
+ }
+ }
+}
+
diff --git a/Covid19Radar/Covid19Radar/Common/AppConstants.cs b/Covid19Radar/Covid19Radar/Common/AppConstants.cs
index 467ac036b..abd34d71d 100644
--- a/Covid19Radar/Covid19Radar/Common/AppConstants.cs
+++ b/Covid19Radar/Covid19Radar/Common/AppConstants.cs
@@ -81,6 +81,16 @@ public static readonly DateTime COCOA_FIRST_RELEASE_DATE
///
public const int DelayForRegistrationErrorMillis = 5000;
+ ///
+ /// Maximum size of event log content to be sent.
+ ///
+ public const long EventLogMaxRequestSizeInBytes = 8 * 1024 * 1024; // 8 MiB
+
+ ///
+ /// Number of retries to send event log.
+ ///
+ public const int EventLogMaxRetry = 4;
+
#region Other Private Methods
private static TimeZoneInfo JstTimeZoneInfo()
diff --git a/Covid19Radar/Covid19Radar/Common/DeviceVerifierUtils.cs b/Covid19Radar/Covid19Radar/Common/DeviceVerifierUtils.cs
index 94259db64..bbea05756 100644
--- a/Covid19Radar/Covid19Radar/Common/DeviceVerifierUtils.cs
+++ b/Covid19Radar/Covid19Radar/Common/DeviceVerifierUtils.cs
@@ -44,12 +44,12 @@ static string GetRegionString(IEnumerable regions) =>
public static string GetNonceClearTextV3(V1EventLogRequest eventLogRequest)
{
- return string.Join("|", eventLogRequest.AppPackageName, GetListClearText(eventLogRequest.EventLogs));
+ return string.Join("|", eventLogRequest.IdempotencyKey, eventLogRequest.AppPackageName, GetListClearText(eventLogRequest.EventLogs));
- static string GetListClearText(V1EventLogRequest.EventLog[] eventLogs)
+ static string GetListClearText(IList eventLogs)
=> string.Join(",", eventLogs.Select(log => GetClearText(log)));
- static string GetClearText(V1EventLogRequest.EventLog log)
+ static string GetClearText(EventLog log)
=> string.Join(".", log.HasConsent, log.Epoch, log.Type, log.Subtype, log.Content);
}
diff --git a/Covid19Radar/Covid19Radar/Model/V1EventLogRequest.cs b/Covid19Radar/Covid19Radar/Model/V1EventLogRequest.cs
index d319a258d..09becf837 100644
--- a/Covid19Radar/Covid19Radar/Model/V1EventLogRequest.cs
+++ b/Covid19Radar/Covid19Radar/Model/V1EventLogRequest.cs
@@ -1,7 +1,9 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
using System;
+using System.Collections.Generic;
using Covid19Radar.Common;
using Newtonsoft.Json;
@@ -22,32 +24,32 @@ public class V1EventLogRequest
public string DeviceVerificationPayload;
[JsonProperty("event_logs")]
- public EventLog[] EventLogs;
-
- public string ToJsonString() => JsonConvert.SerializeObject(this);
+ public List EventLogs;
+ }
- public class EventLog
- {
- [JsonProperty("has_consent")]
- public bool HasConsent;
+ public class EventLog
+ {
+ [JsonProperty("has_consent")]
+ public bool HasConsent;
- [JsonProperty("epoch")]
- public long Epoch;
+ [JsonProperty("epoch")]
+ public long Epoch;
- [JsonProperty("type")]
- public string Type;
+ [JsonProperty("type")]
+ public string Type;
- [JsonProperty("subtype")]
- public string Subtype;
+ [JsonProperty("subtype")]
+ public string Subtype;
- [JsonProperty("content")]
- public string Content;
+ [JsonProperty("content")]
+ public string Content;
- [JsonIgnore]
- public string Timestamp
- {
- get => DateTime.UnixEpoch.AddSeconds(Epoch).ToString(AppConstants.FORMAT_TIMESTAMP);
- }
+ [JsonIgnore]
+ public string Timestamp
+ {
+ get => DateTime.UnixEpoch.AddSeconds(Epoch).ToString(AppConstants.FORMAT_TIMESTAMP);
}
+
+ internal string GetEventType() => $"{Type}-{Subtype}";
}
}
diff --git a/Covid19Radar/Covid19Radar/Repository/EventLogRepository.cs b/Covid19Radar/Covid19Radar/Repository/EventLogRepository.cs
new file mode 100644
index 000000000..2fec2688f
--- /dev/null
+++ b/Covid19Radar/Covid19Radar/Repository/EventLogRepository.cs
@@ -0,0 +1,292 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Covid19Radar.Common;
+using Covid19Radar.Model;
+using Covid19Radar.Services;
+using Covid19Radar.Services.Logs;
+using Newtonsoft.Json;
+
+namespace Covid19Radar.Repository
+{
+ public interface IEventLogRepository
+ {
+ public Task AddAsync(
+ EventLog eventLog,
+ long maxSize = AppConstants.EventLogMaxRequestSizeInBytes
+ );
+
+ public Task> GetLogsAsync(
+ long maxSize = AppConstants.EventLogMaxRequestSizeInBytes
+ );
+
+ public Task RemoveAsync(EventLog eventLog);
+
+ public Task AddEventNotifiedAsync(
+ long maxSize = AppConstants.EventLogMaxRequestSizeInBytes
+ );
+ }
+
+ public class EventLogRepository : IEventLogRepository
+ {
+ private const string LOG_EXTENSION = ".log";
+
+ private readonly ISendEventLogStateRepository _sendEventLogStateRepository;
+ private readonly IDateTimeUtility _dateTimeUtility;
+ private readonly ILoggerService _loggerService;
+
+ private readonly string _basePath;
+
+ public EventLogRepository(
+ ISendEventLogStateRepository sendEventLogStateRepository,
+ IDateTimeUtility dateTimeUtility,
+ ILocalPathService localPathService,
+ ILoggerService loggerService
+ )
+ {
+ _sendEventLogStateRepository = sendEventLogStateRepository;
+ _dateTimeUtility = dateTimeUtility;
+ _basePath = localPathService.EventLogDirPath;
+ _loggerService = loggerService;
+ }
+
+ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
+
+ private string GetFileName(EventLog eventLog)
+ {
+ var clearText = string.Join(".", eventLog.Epoch, eventLog.Type, eventLog.Subtype, eventLog.Content);
+
+ using var sha = SHA256.Create();
+ var textBytes = Encoding.UTF8.GetBytes(clearText);
+ string hash = Convert.ToBase64String(sha.ComputeHash(textBytes));
+
+ return $"{hash}{LOG_EXTENSION}";
+ }
+
+ public async Task AddAsync(EventLog eventLog, long maxSize = AppConstants.EventLogMaxRequestSizeInBytes)
+ {
+ _loggerService.StartMethod();
+
+ await _semaphore.WaitAsync();
+
+ try
+ {
+ return await AddAsyncInternal(eventLog, maxSize);
+ }
+ finally
+ {
+ _semaphore.Release();
+
+ _loggerService.EndMethod();
+ }
+ }
+
+ private async Task AddAsyncInternal(EventLog eventLog, long maxSize)
+ {
+ _loggerService.StartMethod();
+
+ try
+ {
+ string fileName = GetFileName(eventLog);
+ string filePath = Path.Combine(_basePath, fileName);
+
+ if (File.Exists(filePath))
+ {
+ _loggerService.Info($"{filePath} already exist.");
+ return false;
+ }
+
+ var serializedJson = JsonConvert.SerializeObject(eventLog);
+
+ // Check log size.
+ long size = Encoding.UTF8.GetByteCount(serializedJson);
+ if (size > maxSize)
+ {
+ _loggerService.Info($"Log size {size} exceed maxSize( {maxSize} bytes.");
+ return false;
+ }
+
+ await File.WriteAllTextAsync(filePath, serializedJson);
+
+ return true;
+ }
+ finally
+ {
+ _loggerService.EndMethod();
+ }
+ }
+
+ public async Task> GetLogsAsync(long maxSize = AppConstants.EventLogMaxRequestSizeInBytes)
+ {
+ _loggerService.StartMethod();
+
+ await _semaphore.WaitAsync();
+
+ try
+ {
+ return await GetLogsAsyncInternal(maxSize);
+ }
+ finally
+ {
+ _semaphore.Release();
+
+ _loggerService.EndMethod();
+ }
+ }
+
+ private async Task> GetLogsAsyncInternal(long maxSize)
+ {
+ _loggerService.StartMethod();
+
+ try
+ {
+ long currentSize = 0;
+ var resultList = new List();
+
+ string[] files = Directory.GetFiles(_basePath)
+ .Where(file => file.EndsWith(LOG_EXTENSION))
+ .ToArray();
+ if (files.Length == 0)
+ {
+ _loggerService.Info("No log found.");
+ return resultList;
+ }
+
+ List filePathList = files.Select(file => Path.Combine(_basePath, file)).ToList();
+ foreach (var path in filePathList)
+ {
+ string content = await File.ReadAllTextAsync(path);
+
+ // Check result size.
+ long size = Encoding.UTF8.GetByteCount(content);
+ long expectSize = currentSize + size;
+ if (expectSize > maxSize)
+ {
+ _loggerService.Info($"Log {path} size will exceed maxSize( {maxSize} bytes.");
+ continue;
+ }
+
+ try
+ {
+ EventLog eventLog = JsonConvert.DeserializeObject(content);
+ resultList.Add(eventLog);
+ }
+ catch (JsonReaderException exception)
+ {
+ _loggerService.Exception($"Serialize failed {path} will be removed.", exception);
+ File.Delete(path);
+ }
+ }
+
+ return resultList;
+ }
+ finally
+ {
+ _loggerService.EndMethod();
+ }
+
+ }
+
+ public async Task RemoveAsync(EventLog eventLog)
+ {
+ _loggerService.StartMethod();
+
+ await _semaphore.WaitAsync();
+
+ try
+ {
+ return RemoveAsyncInternal(eventLog);
+ }
+ finally
+ {
+ _semaphore.Release();
+
+ _loggerService.EndMethod();
+ }
+ }
+
+ private bool RemoveAsyncInternal(EventLog eventLog)
+ {
+ _loggerService.StartMethod();
+
+ try
+ {
+ string fileName = GetFileName(eventLog);
+ string filePath = Path.Combine(_basePath, fileName);
+
+ if (!File.Exists(filePath))
+ {
+ _loggerService.Info($"{filePath} not found.");
+ return false;
+ }
+
+ File.Delete(filePath);
+
+ _loggerService.Info($"{filePath} is deleted.");
+
+ return true;
+ }
+ finally
+ {
+ _loggerService.EndMethod();
+ }
+ }
+
+ public async Task AddEventNotifiedAsync(long maxSize = AppConstants.EventLogMaxRequestSizeInBytes)
+ {
+ _loggerService.StartMethod();
+
+ await _semaphore.WaitAsync();
+
+ try
+ {
+ await AddEventNotifiedAsyncInternal(maxSize);
+ }
+ finally
+ {
+ _semaphore.Release();
+
+ _loggerService.EndMethod();
+ }
+ }
+
+ private async Task AddEventNotifiedAsyncInternal(long maxSize)
+ {
+ bool hasConsent = _sendEventLogStateRepository.GetSendEventLogState(
+ EventType.ExposureNotified
+ ) == SendEventLogState.Enable;
+
+ var content = new EventContentExposureNotified()
+ {
+ NotifiedTimeInMillis = _dateTimeUtility.UtcNow.Ticks
+ };
+
+ var eventLog = new EventLog()
+ {
+ HasConsent = hasConsent,
+ Epoch = _dateTimeUtility.UtcNow.ToUnixEpoch(),
+ Type = EventType.ExposureNotified.Type,
+ Subtype = EventType.ExposureNotified.SubType,
+ Content = content.ToJsonString(),
+ };
+ await AddAsync(eventLog, maxSize);
+ }
+ }
+
+ public class EventContentExposureNotified
+ {
+ [JsonProperty("notified_time_in_millis")]
+ public long NotifiedTimeInMillis;
+
+ public string ToJsonString() => JsonConvert.SerializeObject(this, Formatting.Indented);
+ }
+}
\ No newline at end of file
diff --git a/Covid19Radar/Covid19Radar/Repository/SendEventLogStateRepository.cs b/Covid19Radar/Covid19Radar/Repository/SendEventLogStateRepository.cs
new file mode 100644
index 000000000..a6550ec60
--- /dev/null
+++ b/Covid19Radar/Covid19Radar/Repository/SendEventLogStateRepository.cs
@@ -0,0 +1,158 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Covid19Radar.Common;
+using Covid19Radar.Services;
+using Covid19Radar.Services.Logs;
+using Newtonsoft.Json;
+
+namespace Covid19Radar.Repository
+{
+ public enum SendEventLogState
+ {
+ NotSet = 0,
+ Disable = -1,
+ Enable = 1
+ }
+
+ public class EventType
+ {
+ public static readonly EventType ExposureNotified = new EventType("ExposureNotification", "ExposureNotified");
+ public static readonly EventType ExposureData = new EventType("ExposureNotification", "ExposureData");
+
+ public static readonly EventType[] All = new EventType[] {
+ ExposureNotified,
+ ExposureData,
+ };
+
+ public string Type { get; }
+ public string SubType { get; }
+
+ public EventType(string type, string subType)
+ {
+ Type = type;
+ SubType = subType;
+ }
+
+ public override string ToString()
+ {
+ return $"{Type}-{SubType}";
+ }
+ }
+
+ public interface ISendEventLogStateRepository
+ {
+ void SetSendEventLogState(EventType eventType, SendEventLogState state);
+
+ SendEventLogState GetSendEventLogState(EventType eventType);
+
+ public static bool IsExistNotSetEventType(ISendEventLogStateRepository sendEventLogStateRepository)
+ => EventType.All
+ .Select(eventType => sendEventLogStateRepository.GetSendEventLogState(eventType))
+ .Any(state => state == SendEventLogState.NotSet);
+ }
+
+ public class SendEventLogStateRepository : ISendEventLogStateRepository
+ {
+ private const string EMPTY_DICT = "{}";
+
+ private readonly IPreferencesService _preferencesService;
+ private readonly ILoggerService _loggerService;
+
+ public SendEventLogStateRepository(
+ IPreferencesService preferencesService,
+ ILoggerService loggerService
+ )
+ {
+ _preferencesService = preferencesService;
+ _loggerService = loggerService;
+ }
+
+ public SendEventLogState GetSendEventLogState(EventType eventType)
+ {
+ string stateString = EMPTY_DICT;
+
+ try
+ {
+ _loggerService.StartMethod();
+
+ if (!_preferencesService.ContainsKey(PreferenceKey.SendEventLogState))
+ {
+ return SendEventLogState.NotSet;
+ }
+
+ stateString = _preferencesService.GetStringValue(
+ PreferenceKey.SendEventLogState,
+ EMPTY_DICT
+ );
+
+ IDictionary stateDict
+ = JsonConvert.DeserializeObject>(stateString);
+
+ if (!stateDict.ContainsKey(eventType.ToString()))
+ {
+ return SendEventLogState.NotSet;
+ }
+
+ int value = stateDict[eventType.ToString()];
+ return (SendEventLogState)Enum.ToObject(typeof(SendEventLogState), value);
+ }
+ catch (JsonReaderException exception)
+ {
+ _preferencesService.SetStringValue(PreferenceKey.SendEventLogState, EMPTY_DICT);
+
+ _loggerService.Exception($"JsonSerializationException {stateString}", exception);
+ _loggerService.Warning($"Preference-key {PreferenceKey.SendEventLogState} has been initialized.");
+ }
+ finally
+ {
+ _loggerService.EndMethod();
+ }
+
+ return SendEventLogState.NotSet;
+ }
+
+ public void SetSendEventLogState(EventType eventType, SendEventLogState state)
+ {
+ try
+ {
+ _loggerService.StartMethod();
+
+ string stateString = EMPTY_DICT;
+ IDictionary stateDict = new Dictionary();
+
+ try
+ {
+ if (_preferencesService.ContainsKey(PreferenceKey.SendEventLogState))
+ {
+ stateString = _preferencesService.GetStringValue(
+ PreferenceKey.SendEventLogState,
+ EMPTY_DICT
+ );
+
+ stateDict = JsonConvert.DeserializeObject>(stateString);
+ }
+ }
+ catch (JsonReaderException exception)
+ {
+ _loggerService.Exception($"JsonSerializationException {stateString}", exception);
+ }
+
+ stateDict[eventType.ToString()] = (int)state;
+
+ string newStateString = JsonConvert.SerializeObject(stateDict);
+ _preferencesService.SetStringValue(PreferenceKey.SendEventLogState, newStateString);
+
+ }
+ finally
+ {
+ _loggerService.EndMethod();
+
+ }
+ }
+ }
+}
diff --git a/Covid19Radar/Covid19Radar/Repository/UserDataRepository.cs b/Covid19Radar/Covid19Radar/Repository/UserDataRepository.cs
index 5727de4eb..a34c3d0c8 100644
--- a/Covid19Radar/Covid19Radar/Repository/UserDataRepository.cs
+++ b/Covid19Radar/Covid19Radar/Repository/UserDataRepository.cs
@@ -43,16 +43,6 @@ public interface IUserDataRepository
Task GetLastProcessDiagnosisKeyTimestampAsync(string region);
Task SetLastProcessDiagnosisKeyTimestampAsync(string region, long timestamp);
Task RemoveLastProcessDiagnosisKeyTimestampAsync();
-
- void SetSendEventLogState(SendEventLogState sendEventLogState);
- SendEventLogState GetSendEventLogState();
- }
-
- public enum SendEventLogState
- {
- NotSet = 0,
- Disable = -1,
- Enable = 1
}
public class UserDataRepository : IUserDataRepository
@@ -271,31 +261,5 @@ public void RemoveAllExposureNotificationStatus()
_preferencesService.RemoveValue(PreferenceKey.LastConfirmedDateTimeEpoch);
_loggerService.EndMethod();
}
-
- public void SetSendEventLogState(SendEventLogState sendEventLogState)
- {
- _loggerService.StartMethod();
-
- _preferencesService.SetIntValue(PreferenceKey.SendEventLogState, (int)sendEventLogState);
-
- _loggerService.EndMethod();
- }
-
- public SendEventLogState GetSendEventLogState()
- {
- _loggerService.StartMethod();
- try
- {
- int state = _preferencesService.GetIntValue(
- PreferenceKey.SendEventLogState,
- (int)SendEventLogState.NotSet
- );
- return (SendEventLogState)state;
- }
- finally
- {
- _loggerService.EndMethod();
- }
- }
}
}
diff --git a/Covid19Radar/Covid19Radar/Services/AbsEventLogSubmissionBackgroundService.cs b/Covid19Radar/Covid19Radar/Services/AbsEventLogSubmissionBackgroundService.cs
new file mode 100644
index 000000000..07a31c74b
--- /dev/null
+++ b/Covid19Radar/Covid19Radar/Services/AbsEventLogSubmissionBackgroundService.cs
@@ -0,0 +1,11 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+namespace Covid19Radar.Services
+{
+ public abstract class AbsEventLogSubmissionBackgroundService : IBackgroundService
+ {
+ public abstract void Schedule();
+ }
+}
diff --git a/Covid19Radar/Covid19Radar/Services/EventLogService.cs b/Covid19Radar/Covid19Radar/Services/EventLogService.cs
index 3ffc88874..6ee0beefe 100644
--- a/Covid19Radar/Covid19Radar/Services/EventLogService.cs
+++ b/Covid19Radar/Covid19Radar/Services/EventLogService.cs
@@ -4,199 +4,160 @@
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Net;
using System.Net.Http;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
-using Chino;
-using Covid19Radar.Common;
using Covid19Radar.Model;
using Covid19Radar.Repository;
using Covid19Radar.Services.Logs;
-using Newtonsoft.Json;
namespace Covid19Radar.Services
{
public interface IEventLogService
{
- public Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel,
- string enVersion,
- ExposureSummary exposureSummary,
- IList exposureInformation
- );
-
- public Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel,
- string enVersion,
- IList dailySummaries,
- IList exposureWindows
- );
-
- public Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel,
- string enVersion
- );
+ public Task> SendAllAsync(long maxSize, int maxRetry);
}
-#if EVENT_LOG_ENABLED
public class EventLogService : IEventLogService
{
- private readonly IUserDataRepository _userDataRepository;
+ private readonly ISendEventLogStateRepository _sendEventLogStateRepository;
+ private readonly IEventLogRepository _eventLogRepository;
private readonly IServerConfigurationRepository _serverConfigurationRepository;
private readonly IEssentialsService _essentialsService;
private readonly IDeviceVerifier _deviceVerifier;
- private readonly IDateTimeUtility _dateTimeUtility;
-
+ private readonly IHttpDataService _httpDataService;
private readonly ILoggerService _loggerService;
- private readonly HttpClient _httpClient;
+ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public EventLogService(
- IUserDataRepository userDataRepository,
+ ISendEventLogStateRepository sendEventLogStateRepository,
+ IEventLogRepository eventLogRepository,
IServerConfigurationRepository serverConfigurationRepository,
IEssentialsService essentialsService,
IDeviceVerifier deviceVerifier,
- IDateTimeUtility dateTimeUtility,
- IHttpClientService httpClientService,
+ IHttpDataService httpDataService,
ILoggerService loggerService
)
{
- _userDataRepository = userDataRepository;
+ _sendEventLogStateRepository = sendEventLogStateRepository;
+ _eventLogRepository = eventLogRepository;
_serverConfigurationRepository = serverConfigurationRepository;
_essentialsService = essentialsService;
_deviceVerifier = deviceVerifier;
- _dateTimeUtility = dateTimeUtility;
+ _httpDataService = httpDataService;
_loggerService = loggerService;
-
- _httpClient = httpClientService.Create();
}
- public async Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel,
- string enVersion,
- ExposureSummary exposureSummary,
- IList exposureInformation
- )
+ public async Task> SendAllAsync(long maxSize, int maxRetry)
{
- var data = new ExposureData(exposureConfiguration,
- exposureSummary, exposureInformation
- )
- {
- Device = deviceModel,
- EnVersion = enVersion,
- };
-
- await SendExposureDataAsync(idempotencyKey, data);
- }
+ await _semaphore.WaitAsync();
- public async Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel,
- string enVersion,
- IList dailySummaries,
- IList exposureWindows
- )
- {
- var data = new ExposureData(exposureConfiguration,
- dailySummaries, exposureWindows
- )
+ try
{
- Device = deviceModel,
- EnVersion = enVersion,
- };
-
- await SendExposureDataAsync(idempotencyKey, data);
+ return await SendAllInternalAsync(maxSize, maxRetry);
+ }
+ finally
+ {
+ _semaphore.Release();
+ }
}
- public async Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel,
- string enVersion
- )
+ private async Task> SendAllInternalAsync(long maxSize, int maxRetry)
{
- var data = new ExposureData(
- exposureConfiguration
- )
+ _loggerService.StartMethod();
+
+ try
{
- Device = deviceModel,
- EnVersion = enVersion,
- };
+ List eventLogList = await _eventLogRepository.GetLogsAsync(maxSize);
+ if (eventLogList.Count == 0)
+ {
+ _loggerService.Info($"No Event-logs found.");
+ return new List();
+ }
- await SendExposureDataAsync(idempotencyKey, data);
- }
+ IDictionary eventStateDict = new Dictionary();
+ foreach (var eventType in EventType.All)
+ {
+ eventStateDict[eventType.ToString()] = _sendEventLogStateRepository.GetSendEventLogState(eventType);
+ }
+ foreach (var eventLog in eventLogList)
+ {
+ eventLog.HasConsent = eventStateDict[eventLog.GetEventType()] == SendEventLogState.Enable;
+ }
- private async Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureData exposureData
- )
- {
- _loggerService.StartMethod();
+ string idempotencyKey = Guid.NewGuid().ToString();
- SendEventLogState sendEventLogState = _userDataRepository.GetSendEventLogState();
- bool isEnabled = sendEventLogState == SendEventLogState.Enable;
+ for (var retryCount = 0; retryCount < maxRetry; retryCount++)
+ {
+ try
+ {
+ List sentEventLogList = await SendAsync(
+ idempotencyKey,
+ eventLogList
+ .Where(eventLog => eventLog.HasConsent)
+ .ToList()
+ );
+ _loggerService.Info($"Send complete.");
+
+ // TODO Error handling??
+
+ _loggerService.Info($"Clean up...");
+ foreach (var eventLog in eventLogList)
+ {
+ await _eventLogRepository.RemoveAsync(eventLog);
+ }
+ _loggerService.Info($"Done.");
+
+ return sentEventLogList;
+ }
+ catch (Exception exception)
+ {
+ _loggerService.Exception("Exception occurred, SendAsync", exception);
+ }
+ }
- if (!isEnabled)
+ return new List();
+ }
+ finally
{
- _loggerService.Debug($"Send event-log function is not enabled.");
_loggerService.EndMethod();
- return;
}
+ }
- await _serverConfigurationRepository.LoadAsync();
-
- string exposureDataCollectServerEndpoint = _serverConfigurationRepository.EventLogApiEndpoint;
- _loggerService.Debug($"exposureDataCollectServerEndpoint: {exposureDataCollectServerEndpoint}");
+ private async Task> SendAsync(string idempotencyKey, List eventLogList)
+ {
+ _loggerService.StartMethod();
try
{
- var contentJson = exposureData.ToJsonString();
-
- var eventLog = new V1EventLogRequest.EventLog() {
- HasConsent = isEnabled,
- Epoch = _dateTimeUtility.UtcNow.ToUnixEpoch(),
- Type = "ExposureData",
- Subtype = "Debug",
- Content = contentJson,
- };
- var eventLogs = new[] { eventLog };
-
var request = new V1EventLogRequest()
{
IdempotencyKey = idempotencyKey,
Platform = _essentialsService.Platform,
AppPackageName = _essentialsService.AppPackageName,
- EventLogs = eventLogs,
+ EventLogs = eventLogList,
};
request.DeviceVerificationPayload = await _deviceVerifier.VerifyAsync(request);
- var requestJson = request.ToJsonString();
-
- var httpContent = new StringContent(requestJson, Encoding.UTF8, "application/json");
+ ApiResponse response = await _httpDataService.PutEventLog(request);
+ _loggerService.Info($"PutEventLog() StatusCode:{response.StatusCode}");
- Uri uri = new Uri(exposureDataCollectServerEndpoint);
-
- HttpResponseMessage response = await _httpClient.PutAsync(uri, httpContent);
- if (response.IsSuccessStatusCode)
- {
- var responseJson = await response.Content.ReadAsStringAsync();
- _loggerService.Debug($"{responseJson}");
- }
- else
+ if (response.StatusCode == (int)HttpStatusCode.Created)
{
- _loggerService.Info($"UploadExposureDataAsync {response.StatusCode}");
+ _loggerService.Info("Send event log succeeded");
+ _loggerService.Debug($"response: {response.Result}");
+ return eventLogList;
}
+
+ // TODO Error Handling??
+
+ return new List();
}
finally
{
@@ -204,5 +165,4 @@ ExposureData exposureData
}
}
}
-#endif
}
diff --git a/Covid19Radar/Covid19Radar/Services/EventLogServiceNop.cs b/Covid19Radar/Covid19Radar/Services/EventLogServiceNop.cs
index eccf1b9d7..6a3dac8df 100644
--- a/Covid19Radar/Covid19Radar/Services/EventLogServiceNop.cs
+++ b/Covid19Radar/Covid19Radar/Services/EventLogServiceNop.cs
@@ -4,44 +4,19 @@
using System.Collections.Generic;
using System.Threading.Tasks;
-using Chino;
+using Covid19Radar.Model;
namespace Covid19Radar.Services
{
public class EventLogServiceNop : IEventLogService
{
- public Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel, string enVersion,
- ExposureSummary exposureSummary,
- IList exposureInformation
+ public Task> SendAllAsync(
+ long maxSize,
+ int maxRetry
)
{
// do nothing
- return Task.CompletedTask;
- }
-
- public Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel, string enVersion,
- IList dailySummaries, IList exposureWindows
- )
- {
- // do nothing
- return Task.CompletedTask;
- }
-
- public Task SendExposureDataAsync(
- string idempotencyKey,
- ExposureConfiguration exposureConfiguration,
- string deviceModel,
- string enVersion
- )
- {
- // do nothing
- return Task.CompletedTask;
+ return Task.FromResult(new List());
}
}
}
diff --git a/Covid19Radar/Covid19Radar/Services/ExposureDetectionService.cs b/Covid19Radar/Covid19Radar/Services/ExposureDetectionService.cs
index 64ba9ffaa..4a6bfbbc8 100644
--- a/Covid19Radar/Covid19Radar/Services/ExposureDetectionService.cs
+++ b/Covid19Radar/Covid19Radar/Services/ExposureDetectionService.cs
@@ -29,7 +29,8 @@ public interface IExposureDetectionService
public class ExposureDetectionService : IExposureDetectionService
{
private readonly ILoggerService _loggerService;
- private readonly IUserDataRepository _userDataRepository;
+ private readonly ISendEventLogStateRepository _sendEventLogStateRepository;
+
private readonly IExposureDataRepository _exposureDataRepository;
private readonly ILocalNotificationService _localNotificationService;
@@ -38,38 +39,31 @@ public class ExposureDetectionService : IExposureDetectionService
private readonly IExposureConfigurationRepository _exposureConfigurationRepository;
- private readonly IEventLogService _eventLogService;
-
- private readonly IDebugExposureDataCollectServer _exposureDataCollectServer;
+ private readonly IEventLogRepository _eventLogRepository;
private readonly IDateTimeUtility _dateTimeUtility;
- private readonly IDeviceInfoUtility _deviceInfoUtility;
public ExposureDetectionService
(
ILoggerService loggerService,
- IUserDataRepository userDataRepository,
+ ISendEventLogStateRepository sendEventLogStateRepository,
IExposureDataRepository exposureDataRepository,
ILocalNotificationService localNotificationService,
IExposureRiskCalculationConfigurationRepository exposureRiskCalculationConfigurationRepository,
IExposureRiskCalculationService exposureRiskCalculationService,
IExposureConfigurationRepository exposureConfigurationRepository,
- IEventLogService eventLogService,
- IDebugExposureDataCollectServer exposureDataCollectServer,
- IDateTimeUtility dateTimeUtility,
- IDeviceInfoUtility deviceInfoUtility
+ IEventLogRepository eventLogRepository,
+ IDateTimeUtility dateTimeUtility
)
{
_loggerService = loggerService;
- _userDataRepository = userDataRepository;
+ _sendEventLogStateRepository = sendEventLogStateRepository;
_exposureDataRepository = exposureDataRepository;
_localNotificationService = localNotificationService;
_exposureRiskCalculationService = exposureRiskCalculationService;
_exposureRiskCalculationConfigurationRepository = exposureRiskCalculationConfigurationRepository;
_exposureConfigurationRepository = exposureConfigurationRepository;
- _eventLogService = eventLogService;
- _exposureDataCollectServer = exposureDataCollectServer;
+ _eventLogRepository = eventLogRepository;
_dateTimeUtility = dateTimeUtility;
- _deviceInfoUtility = deviceInfoUtility;
}
public void DiagnosisKeysDataMappingApplied()
@@ -128,40 +122,18 @@ long expectOldestDateMillisSinceEpoch
if (isHighRiskExposureDetected)
{
_ = _localNotificationService.ShowExposureNotificationAsync();
- }
- else
- {
- _loggerService.Info($"DailySummary: {dailySummaries.Count}, but no high-risk exposure detected");
- }
- try
- {
- await _exposureDataCollectServer.UploadExposureDataAsync(
- exposureConfiguration,
- _deviceInfoUtility.Model,
- enVersionStr,
- newDailySummaries, newExposureWindows
- );
- }
- catch (Exception e)
- {
- _loggerService.Exception("UploadExposureDataAsync", e);
- }
+ bool enableSendEventExposureNotificationNotified = _sendEventLogStateRepository
+ .GetSendEventLogState(EventType.ExposureNotified) == SendEventLogState.Enable;
- string idempotencyKey = Guid.NewGuid().ToString();
- try
- {
- await _eventLogService.SendExposureDataAsync(
- idempotencyKey,
- exposureConfiguration,
- _deviceInfoUtility.Model,
- enVersionStr,
- newDailySummaries, newExposureWindows
- );
+ if (enableSendEventExposureNotificationNotified)
+ {
+ await _eventLogRepository.AddEventNotifiedAsync();
+ }
}
- catch (Exception e)
+ else
{
- _loggerService.Exception("SendExposureDataAsync", e);
+ _loggerService.Info($"DailySummary: {dailySummaries.Count}, but no high-risk exposure detected");
}
}
@@ -182,76 +154,25 @@ public async Task ExposureDetectedAsync(ExposureConfiguration exposureConfigurat
if (isNewExposureDetected)
{
_ = _localNotificationService.ShowExposureNotificationAsync();
- }
- else
- {
- _loggerService.Info($"MatchedKeyCount: {exposureSummary.MatchedKeyCount}, but no new exposure detected");
- }
- try
- {
- await _exposureDataCollectServer.UploadExposureDataAsync(
- exposureConfiguration,
- _deviceInfoUtility.Model,
- enVersionStr,
- exposureSummary, exposureInformations
- );
- }
- catch (Exception e)
- {
- _loggerService.Exception("UploadExposureDataAsync", e);
- }
+ bool enableSendEventExposureNotificationNotified = _sendEventLogStateRepository
+ .GetSendEventLogState(EventType.ExposureNotified) == SendEventLogState.Enable;
- string idempotencyKey = Guid.NewGuid().ToString();
- try
- {
- await _eventLogService.SendExposureDataAsync(
- idempotencyKey,
- exposureConfiguration,
- _deviceInfoUtility.Model,
- enVersionStr,
- exposureSummary, exposureInformations
- );
+ if (enableSendEventExposureNotificationNotified)
+ {
+ await _eventLogRepository.AddEventNotifiedAsync();
+ }
}
- catch (Exception e)
+ else
{
- _loggerService.Exception("SendExposureDataAsync", e);
+ _loggerService.Info($"MatchedKeyCount: {exposureSummary.MatchedKeyCount}, but no new exposure detected");
}
}
- public async Task ExposureNotDetectedAsync(ExposureConfiguration exposureConfiguration, long enVersion)
+ public Task ExposureNotDetectedAsync(ExposureConfiguration exposureConfiguration, long enVersion)
{
_loggerService.Info("ExposureNotDetected");
-
- var enVersionStr = enVersion.ToString();
-
- try
- {
- await _exposureDataCollectServer.UploadExposureDataAsync(
- exposureConfiguration,
- _deviceInfoUtility.Model,
- enVersionStr
- );
- }
- catch (Exception e)
- {
- _loggerService.Exception("UploadExposureDataAsync", e);
- }
-
- string idempotencyKey = Guid.NewGuid().ToString();
- try
- {
- await _eventLogService.SendExposureDataAsync(
- idempotencyKey,
- exposureConfiguration,
- _deviceInfoUtility.Model,
- enVersionStr
- );
- }
- catch (Exception e)
- {
- _loggerService.Exception("SendExposureDataAsync", e);
- }
+ return Task.CompletedTask;
}
}
}
diff --git a/Covid19Radar/Covid19Radar/Services/HttpDataService.cs b/Covid19Radar/Covid19Radar/Services/HttpDataService.cs
index ce5308f1c..b2915769a 100644
--- a/Covid19Radar/Covid19Radar/Services/HttpDataService.cs
+++ b/Covid19Radar/Covid19Radar/Services/HttpDataService.cs
@@ -145,6 +145,29 @@ public async Task> GetLogStorageSas()
return new ApiResponse(statusCode, logStorageSas);
}
+ // PUT /api/v*/event_log
+ public async Task> PutEventLog(V1EventLogRequest request)
+ {
+ loggerService.StartMethod();
+
+ try
+ {
+ await serverConfigurationRepository.LoadAsync();
+
+ string url = serverConfigurationRepository.EventLogApiEndpoint;
+ var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
+
+ HttpResponseMessage response = await apiClient.PutAsync(url, content);
+ string responseContent = await response.Content.ReadAsStringAsync();
+
+ return new ApiResponse((int)response.StatusCode, responseContent);
+ }
+ finally
+ {
+ loggerService.EndMethod();
+ }
+ }
+
private async Task PutAsync(string url, HttpContent body)
{
var result = await httpClient.PutAsync(url, body);
diff --git a/Covid19Radar/Covid19Radar/Services/HttpDataServiceMock.cs b/Covid19Radar/Covid19Radar/Services/HttpDataServiceMock.cs
index cacb6abfc..1bc7a61c7 100644
--- a/Covid19Radar/Covid19Radar/Services/HttpDataServiceMock.cs
+++ b/Covid19Radar/Covid19Radar/Services/HttpDataServiceMock.cs
@@ -144,6 +144,14 @@ public Task> GetLogStorageSas()
return new ApiResponse((int)HttpStatusCode.OK, new LogStorageSas { SasToken = "sv=2012-02-12&se=2015-07-08T00%3A12%3A08Z&sr=c&sp=wl&sig=t%2BbzU9%2B7ry4okULN9S0wst%2F8MCUhTjrHyV9rDNLSe8g%3Dsss" });
});
}
+
+ public Task> PutEventLog(V1EventLogRequest request)
+ {
+ return Task.Factory.StartNew(() =>
+ {
+ return new ApiResponse((int)HttpStatusCode.Created, "");
+ });
+ }
}
public class MockCommonUtils
diff --git a/Covid19Radar/Covid19Radar/Services/IHttpDataService.cs b/Covid19Radar/Covid19Radar/Services/IHttpDataService.cs
index 4a6d72205..369de292f 100644
--- a/Covid19Radar/Covid19Radar/Services/IHttpDataService.cs
+++ b/Covid19Radar/Covid19Radar/Services/IHttpDataService.cs
@@ -15,5 +15,7 @@ public interface IHttpDataService
Task PutSelfExposureKeysAsync(DiagnosisSubmissionParameter request);
Task> GetLogStorageSas();
+
+ Task> PutEventLog(V1EventLogRequest request);
}
}
diff --git a/Covid19Radar/Covid19Radar/Services/ILocalPathService.cs b/Covid19Radar/Covid19Radar/Services/ILocalPathService.cs
index 487c67588..9d4ec567e 100644
--- a/Covid19Radar/Covid19Radar/Services/ILocalPathService.cs
+++ b/Covid19Radar/Covid19Radar/Services/ILocalPathService.cs
@@ -14,6 +14,7 @@ public interface ILocalPathService
private const string CURRENT_EXPOSURE_RISK_CALCULATION_CONFIGURATION_FILENAME = "exposure_risk_calculation_configuration_current.json";
private const string EXPOSURE_DATA_FILENAME = "exposure_data.json";
+ private const string EVENT_LOG_QUEUE_DIR = "event_log_queue";
string ExposureConfigurationDirPath =>
Path.Combine(FileSystem.AppDataDirectory, EXPOSURE_CONFIGURATION_DIR);
@@ -32,5 +33,8 @@ public interface ILocalPathService
string LogsDirPath { get; }
string LogUploadingTmpPath { get; }
string LogUploadingPublicPath { get; }
+
+ string EventLogDirPath =>
+ Path.Combine(FileSystem.AppDataDirectory, EVENT_LOG_QUEUE_DIR);
}
}
diff --git a/Covid19Radar/Tests/Covid19Radar.UnitTests/Files/eventlog_submission_parameter1-cleartext.txt b/Covid19Radar/Tests/Covid19Radar.UnitTests/Files/eventlog_submission_parameter1-cleartext.txt
index 4a6f145e0..3745f79cd 100644
--- a/Covid19Radar/Tests/Covid19Radar.UnitTests/Files/eventlog_submission_parameter1-cleartext.txt
+++ b/Covid19Radar/Tests/Covid19Radar.UnitTests/Files/eventlog_submission_parameter1-cleartext.txt
@@ -1 +1 @@
-jp.go.mhlw.covid19radar|True.1640256881.ExposureNotification.ExposureData.Lorem ipsum dolor sit amet, consectetur adipisci elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrum exercitationem ullam,True.1640257311.ExposureNotification.ExposureData.Aenean sit amet dui blandit, faucibus neque sollicitudin, sollicitudin nisi. Vivamus mattis felis vitae lorem ultrices interdum. Nam massa orci, hendrerit in mauris at, ultricies eleifend tortor. Etiam neque dolor, tincidunt a scelerisque id, commodo at purus. Mauris ac ultrices quam. Aliquam elementum diam venenatis orci tempor finibus. Donec ultricies interdum tortor, vitae imperdiet orci porta ac.,True.1640258212.ExposureNotification.ExposureData.Etiam venenatis condimentum maximus. Morbi eu consectetur erat. Nam in tellus velit. Cras accumsan mattis lectus quis vulputate. Fusce bibendum odio risus, eget hendrerit enim volutpat nec. Praesent sagittis, metus et ullamcorper interdum, lacus dolor consectetur quam, ut euismod elit erat quis nisi. Phasellus ac condimentum lorem. Etiam suscipit, odio at elementum malesuada, nisl metus posuere lorem, non sollicitudin quam justo ac enim. Vestibulum finibus, sapien et dignissim commodo, felis quam tincidunt odio, ac luctus tortor nibh ut nunc. Phasellus lobortis posuere rutrum. Donec dictum nec quam ac cursus. Aliquam erat volutpat. In vitae ullamcorper ipsum. Maecenas vestibulum nibh purus. Aliquam erat volutpat.
\ No newline at end of file
+E09585E4-A6A9-4E4A-84C3-0FB8B23B2498|jp.go.mhlw.covid19radar|True.1640256881.ExposureNotification.ExposureData.Lorem ipsum dolor sit amet, consectetur adipisci elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrum exercitationem ullam,True.1640257311.ExposureNotification.ExposureData.Aenean sit amet dui blandit, faucibus neque sollicitudin, sollicitudin nisi. Vivamus mattis felis vitae lorem ultrices interdum. Nam massa orci, hendrerit in mauris at, ultricies eleifend tortor. Etiam neque dolor, tincidunt a scelerisque id, commodo at purus. Mauris ac ultrices quam. Aliquam elementum diam venenatis orci tempor finibus. Donec ultricies interdum tortor, vitae imperdiet orci porta ac.,True.1640258212.ExposureNotification.ExposureData.Etiam venenatis condimentum maximus. Morbi eu consectetur erat. Nam in tellus velit. Cras accumsan mattis lectus quis vulputate. Fusce bibendum odio risus, eget hendrerit enim volutpat nec. Praesent sagittis, metus et ullamcorper interdum, lacus dolor consectetur quam, ut euismod elit erat quis nisi. Phasellus ac condimentum lorem. Etiam suscipit, odio at elementum malesuada, nisl metus posuere lorem, non sollicitudin quam justo ac enim. Vestibulum finibus, sapien et dignissim commodo, felis quam tincidunt odio, ac luctus tortor nibh ut nunc. Phasellus lobortis posuere rutrum. Donec dictum nec quam ac cursus. Aliquam erat volutpat. In vitae ullamcorper ipsum. Maecenas vestibulum nibh purus. Aliquam erat volutpat.
\ No newline at end of file
diff --git a/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/ExposureDetectionServiceTests.cs b/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/ExposureDetectionServiceTests.cs
index e25fb5b7e..3d49c3dc4 100644
--- a/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/ExposureDetectionServiceTests.cs
+++ b/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/ExposureDetectionServiceTests.cs
@@ -23,15 +23,16 @@ public class ExposureDetectionServiceTests: IDisposable
private readonly MockRepository mockRepository;
private readonly Mock loggerService;
+ private readonly Mock sendEventLogStateRepository;
private readonly Mock localNotificationService;
private readonly Mock exposureDataCollectServer;
private readonly Mock exposureRiskCalculationConfigurationRepository;
private readonly Mock exposureRiskCalculationService;
private readonly Mock eventLogService;
private readonly Mock dateTimeUtility;
+ private readonly Mock eventLogRepository;
private readonly Mock deviceInfoUtility;
-
private readonly Mock clientService;
private readonly Mock localPathService;
private readonly Mock preferencesService;
@@ -45,6 +46,7 @@ public ExposureDetectionServiceTests()
{
mockRepository = new MockRepository(MockBehavior.Default);
loggerService = mockRepository.Create();
+ sendEventLogStateRepository = mockRepository.Create();
localNotificationService = mockRepository.Create();
exposureDataCollectServer = mockRepository.Create();
exposureRiskCalculationConfigurationRepository = mockRepository.Create();
@@ -57,9 +59,9 @@ public ExposureDetectionServiceTests()
secureStorageService = mockRepository.Create();
serverConfigurationRepository = mockRepository.Create();
dateTimeUtility = mockRepository.Create();
+ eventLogRepository = mockRepository.Create();
deviceInfoUtility = mockRepository.Create();
-
localPathService.Setup(x => x.ExposureConfigurationDirPath).Returns($"{Path.GetTempPath()}/cocoa/");
}
@@ -101,16 +103,14 @@ private ExposureDetectionService CreateService()
return new ExposureDetectionService(
loggerService.Object,
- userDataRepository,
+ sendEventLogStateRepository.Object,
exposureDataRepository,
localNotificationService.Object,
exposureRiskCalculationConfigurationRepository.Object,
exposureRiskCalculationService.Object,
exposureConfigurationRepository,
- eventLogService.Object,
- exposureDataCollectServer.Object,
- dateTimeUtility.Object,
- deviceInfoUtility.Object
+ eventLogRepository.Object,
+ dateTimeUtility.Object
);
}
#endregion
diff --git a/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/HttpDataServiceTests.cs b/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/HttpDataServiceTests.cs
index 03ac6a495..fa6afee51 100644
--- a/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/HttpDataServiceTests.cs
+++ b/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/HttpDataServiceTests.cs
@@ -26,7 +26,8 @@ public class HttpDataServiceTests
private readonly MockRepository mockRepository;
private readonly Mock mockLoggerService;
private readonly Mock mockHttpClientService;
- private readonly Mock mockServerConfigurationRepository;
+
+ private readonly ReleaseServerConfigurationRepository _serverConfigurationRepository;
#endregion
@@ -37,7 +38,7 @@ public HttpDataServiceTests()
mockRepository = new MockRepository(MockBehavior.Default);
mockLoggerService = mockRepository.Create();
mockHttpClientService = mockRepository.Create();
- mockServerConfigurationRepository = mockRepository.Create();
+ _serverConfigurationRepository = new ReleaseServerConfigurationRepository();
}
#endregion
@@ -49,7 +50,7 @@ private IHttpDataService CreateService()
return new HttpDataService(
mockLoggerService.Object,
mockHttpClientService.Object,
- mockServerConfigurationRepository.Object
+ _serverConfigurationRepository
);
}
@@ -77,8 +78,6 @@ public async Task PostRegisterUserAsyncTestsAsync_Success()
}));
mockHttpClientService.Setup(x => x.Create()).Returns(mockHttpClient);
- mockServerConfigurationRepository.Setup(x => x.UserRegisterApiEndpoint)
- .Returns(IServerConfigurationRepository.CombineAsUrl(AppSettings.Instance.ApiUrlBase, "api/register"));
var unitUnderTest = CreateService();
@@ -106,8 +105,6 @@ public async Task PostRegisterUserAsyncTestsAsync_Failure()
}));
mockHttpClientService.Setup(x => x.Create()).Returns(mockHttpClient);
- mockServerConfigurationRepository.Setup(x => x.UserRegisterApiEndpoint)
- .Returns(IServerConfigurationRepository.CombineAsUrl(AppSettings.Instance.ApiUrlBase, "api/register"));
var unitUnderTest = CreateService();
@@ -135,8 +132,6 @@ public void PostRegisterUserAsyncTestsAsync_Exception()
}));
mockHttpClientService.Setup(x => x.Create()).Returns(mockHttpClient);
- mockServerConfigurationRepository.Setup(x => x.UserRegisterApiEndpoint)
- .Returns(IServerConfigurationRepository.CombineAsUrl(AppSettings.Instance.ApiUrlBase, "api/register"));
var unitUnderTest = CreateService();
@@ -173,8 +168,6 @@ public async Task PutSelfExposureKeysAsync_Success()
}));
mockHttpClientService.Setup(x => x.Create()).Returns(mockHttpClient);
- mockServerConfigurationRepository.Setup(x => x.DiagnosisKeyRegisterApiUrls)
- .Returns(new List() { IServerConfigurationRepository.CombineAsUrl(AppSettings.Instance.ApiUrlBase, "api/v3/diagnosis") });
var unitUnderTest = CreateService();
@@ -231,6 +224,78 @@ public async Task PutSelfExposureKeysAsync_Success()
#endregion
+ #region PutEventLogTests
+
+ [Fact]
+ public async Task PutEventLogTests_Success()
+ {
+ HttpContent requestContent = null;
+ var mockHttpClient = new HttpClient(new MockHttpHandler((r, c) =>
+ {
+ var absoluteUri = r.RequestUri.AbsoluteUri;
+ if (absoluteUri.EndsWith("/api/v1/event_log"))
+ {
+ requestContent = r.Content;
+ var response = new HttpResponseMessage(HttpStatusCode.Created);
+ response.Content = new StringContent("");
+ return response;
+ }
+ return new HttpResponseMessage(HttpStatusCode.NotFound);
+ }));
+
+ mockHttpClientService.Setup(x => x.Create()).Returns(mockHttpClient);
+
+ var unitUnderTest = CreateService();
+
+ var request = new V1EventLogRequest()
+ {
+ IdempotencyKey = "05A6A158-E216-4599-B99E-3708D360FF2F",
+ Platform = "platform",
+ AppPackageName = "app-package-name",
+ DeviceVerificationPayload = "device-verification-payload",
+ EventLogs = new List()
+ {
+ new EventLog()
+ {
+ HasConsent = true,
+ Epoch = 1000,
+ Type = "type",
+ Subtype = "subtype",
+ Content = "content"
+ }
+ }
+ };
+
+ var result = await unitUnderTest.PutEventLog(request);
+
+ Assert.Equal((int)HttpStatusCode.Created, result.StatusCode);
+ Assert.NotNull(requestContent);
+
+ var stringContent = await requestContent.ReadAsStringAsync();
+ Assert.NotEmpty(stringContent);
+
+ var jsonContent = JsonConvert.DeserializeObject(stringContent) as JObject;
+ Assert.NotNull(jsonContent);
+
+ Assert.Equal("05A6A158-E216-4599-B99E-3708D360FF2F", jsonContent["idempotency_key"].Value());
+ Assert.Equal("platform", jsonContent["platform"].Value());
+ Assert.Equal("app-package-name", jsonContent["appPackageName"].Value());
+ Assert.Equal("device-verification-payload", jsonContent["deviceVerificationPayload"].Value());
+
+ var eventLogs = jsonContent["event_logs"] as JArray;
+ Assert.Single(eventLogs);
+
+ Assert.True(eventLogs[0]["has_consent"].Value());
+ Assert.Equal(1000, eventLogs[0]["epoch"].Value());
+ Assert.Equal("type", eventLogs[0]["type"].Value());
+ Assert.Equal("subtype", eventLogs[0]["subtype"].Value());
+ Assert.Equal("content", eventLogs[0]["content"].Value());
+
+ Assert.Null(mockHttpClient.DefaultRequestHeaders.Authorization);
+ }
+
+ #endregion
+
#endregion
}
}
From 4839cab7a1a7318d503f485b009a7d6bf380773b Mon Sep 17 00:00:00 2001
From: cocoa-dev004 <66989461+cocoa-dev004@users.noreply.github.com>
Date: Tue, 14 Jun 2022 11:19:48 +0900
Subject: [PATCH 2/5] Fixed error handling etc
---
.../EventLogSubmissionBackgroundService.cs | 5 +-
.../EventLogSubmissionBackgroundService.cs | 10 +-
Covid19Radar/Covid19Radar/App.xaml.cs | 6 +-
.../Covid19Radar/Common/AppConstants.cs | 5 +
.../Repository/EventLogRepository.cs | 39 +--
.../Covid19Radar/Services/EventLogService.cs | 78 +++--
.../Services/EventLogServiceNop.cs | 5 +-
.../SendEventLogStateRepositoryTests.cs | 196 +++++++++++
.../Services/EventLogServiceTests.cs | 325 ++++++++++++++++++
9 files changed, 598 insertions(+), 71 deletions(-)
create mode 100644 Covid19Radar/Tests/Covid19Radar.UnitTests/Repository/SendEventLogStateRepositoryTests.cs
create mode 100644 Covid19Radar/Tests/Covid19Radar.UnitTests/Services/EventLogServiceTests.cs
diff --git a/Covid19Radar/Covid19Radar.Android/Services/EventLogSubmissionBackgroundService.cs b/Covid19Radar/Covid19Radar.Android/Services/EventLogSubmissionBackgroundService.cs
index 1c5172226..2c276e8fa 100644
--- a/Covid19Radar/Covid19Radar.Android/Services/EventLogSubmissionBackgroundService.cs
+++ b/Covid19Radar/Covid19Radar.Android/Services/EventLogSubmissionBackgroundService.cs
@@ -81,7 +81,10 @@ public override Result DoWork()
try
{
- eventLogService.SendAllAsync(AppConstants.EventLogMaxRequestSizeInBytes, AppConstants.EventLogMaxRetry);
+ eventLogService.SendAllAsync(
+ AppConstants.EventLogMaxRequestSizeInBytes,
+ AppConstants.EventLogMaxRetry,
+ AppConstants.EventLogRetryInternval);
return Result.InvokeSuccess();
}
catch (Exception exception)
diff --git a/Covid19Radar/Covid19Radar.iOS/Services/EventLogSubmissionBackgroundService.cs b/Covid19Radar/Covid19Radar.iOS/Services/EventLogSubmissionBackgroundService.cs
index b0c15bbe5..3b77a5fec 100644
--- a/Covid19Radar/Covid19Radar.iOS/Services/EventLogSubmissionBackgroundService.cs
+++ b/Covid19Radar/Covid19Radar.iOS/Services/EventLogSubmissionBackgroundService.cs
@@ -55,9 +55,13 @@ private void HandleSendLogAsync(BGAppRefreshTask task)
_ = Task.Run(async () =>
{
+ _loggerService.Info("HandleSendLogAsync() Task.Run() start");
try
{
- await _eventLogService.SendAllAsync(AppConstants.EventLogMaxRequestSizeInBytes, AppConstants.EventLogMaxRetry);
+ await _eventLogService.SendAllAsync(
+ AppConstants.EventLogMaxRequestSizeInBytes,
+ AppConstants.EventLogMaxRetry,
+ AppConstants.EventLogRetryInternval);
task.SetTaskCompleted(true);
}
catch (OperationCanceledException exception)
@@ -73,9 +77,11 @@ private void HandleSendLogAsync(BGAppRefreshTask task)
finally
{
cancellationTokenSource.Dispose();
- _loggerService.EndMethod();
+ _loggerService.Info("HandleSendLogAsync() Task.Run() end");
}
});
+
+ _loggerService.EndMethod();
}
private void ScheduleSendEventLog()
diff --git a/Covid19Radar/Covid19Radar/App.xaml.cs b/Covid19Radar/Covid19Radar/App.xaml.cs
index 2d11bdd2d..5653063f3 100644
--- a/Covid19Radar/Covid19Radar/App.xaml.cs
+++ b/Covid19Radar/Covid19Radar/App.xaml.cs
@@ -220,11 +220,9 @@ private static void RegisterCommonTypes(IContainer container)
container.Register(Reuse.Singleton);
container.Register(Reuse.Singleton);
-#if EVENT_LOG_ENABLED
+ container.Register(Reuse.Singleton);
+ container.Register(Reuse.Singleton);
container.Register(Reuse.Singleton);
-#else
- container.Register(Reuse.Singleton);
-#endif
// Utilities
container.Register(Reuse.Singleton);
diff --git a/Covid19Radar/Covid19Radar/Common/AppConstants.cs b/Covid19Radar/Covid19Radar/Common/AppConstants.cs
index abd34d71d..c8f094ffc 100644
--- a/Covid19Radar/Covid19Radar/Common/AppConstants.cs
+++ b/Covid19Radar/Covid19Radar/Common/AppConstants.cs
@@ -91,6 +91,11 @@ public static readonly DateTime COCOA_FIRST_RELEASE_DATE
///
public const int EventLogMaxRetry = 4;
+ ///
+ /// Retry interval for sending event logs.
+ ///
+ public const int EventLogRetryInternval = 1000;
+
#region Other Private Methods
private static TimeZoneInfo JstTimeZoneInfo()
diff --git a/Covid19Radar/Covid19Radar/Repository/EventLogRepository.cs b/Covid19Radar/Covid19Radar/Repository/EventLogRepository.cs
index 2fec2688f..21b4f5ebe 100644
--- a/Covid19Radar/Covid19Radar/Repository/EventLogRepository.cs
+++ b/Covid19Radar/Covid19Radar/Repository/EventLogRepository.cs
@@ -20,11 +20,6 @@ namespace Covid19Radar.Repository
{
public interface IEventLogRepository
{
- public Task AddAsync(
- EventLog eventLog,
- long maxSize = AppConstants.EventLogMaxRequestSizeInBytes
- );
-
public Task> GetLogsAsync(
long maxSize = AppConstants.EventLogMaxRequestSizeInBytes
);
@@ -72,24 +67,6 @@ private string GetFileName(EventLog eventLog)
return $"{hash}{LOG_EXTENSION}";
}
- public async Task AddAsync(EventLog eventLog, long maxSize = AppConstants.EventLogMaxRequestSizeInBytes)
- {
- _loggerService.StartMethod();
-
- await _semaphore.WaitAsync();
-
- try
- {
- return await AddAsyncInternal(eventLog, maxSize);
- }
- finally
- {
- _semaphore.Release();
-
- _loggerService.EndMethod();
- }
- }
-
private async Task AddAsyncInternal(EventLog eventLog, long maxSize)
{
_loggerService.StartMethod();
@@ -111,18 +88,30 @@ private async Task AddAsyncInternal(EventLog eventLog, long maxSize)
long size = Encoding.UTF8.GetByteCount(serializedJson);
if (size > maxSize)
{
- _loggerService.Info($"Log size {size} exceed maxSize( {maxSize} bytes.");
+ _loggerService.Info($"Log size {size} exceed maxSize {maxSize} bytes.");
return false;
}
+ string directoryName = Path.GetDirectoryName(filePath);
+ if (!Directory.Exists(directoryName))
+ {
+ Directory.CreateDirectory(directoryName);
+ _loggerService.Info($"Directory created. directoryName:{directoryName}");
+ }
+
await File.WriteAllTextAsync(filePath, serializedJson);
return true;
}
+ catch (Exception ex)
+ {
+ _loggerService.Exception("Write event log failure.", ex);
+ }
finally
{
_loggerService.EndMethod();
}
+ return false;
}
public async Task> GetLogsAsync(long maxSize = AppConstants.EventLogMaxRequestSizeInBytes)
@@ -278,7 +267,7 @@ private async Task AddEventNotifiedAsyncInternal(long maxSize)
Subtype = EventType.ExposureNotified.SubType,
Content = content.ToJsonString(),
};
- await AddAsync(eventLog, maxSize);
+ await AddAsyncInternal(eventLog, maxSize);
}
}
diff --git a/Covid19Radar/Covid19Radar/Services/EventLogService.cs b/Covid19Radar/Covid19Radar/Services/EventLogService.cs
index 6ee0beefe..2f34bd355 100644
--- a/Covid19Radar/Covid19Radar/Services/EventLogService.cs
+++ b/Covid19Radar/Covid19Radar/Services/EventLogService.cs
@@ -6,8 +6,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Net;
-using System.Net.Http;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Covid19Radar.Model;
@@ -18,14 +16,13 @@ namespace Covid19Radar.Services
{
public interface IEventLogService
{
- public Task> SendAllAsync(long maxSize, int maxRetry);
+ public Task SendAllAsync(long maxSize, int maxRetry, int retryInterval);
}
public class EventLogService : IEventLogService
{
private readonly ISendEventLogStateRepository _sendEventLogStateRepository;
private readonly IEventLogRepository _eventLogRepository;
- private readonly IServerConfigurationRepository _serverConfigurationRepository;
private readonly IEssentialsService _essentialsService;
private readonly IDeviceVerifier _deviceVerifier;
private readonly IHttpDataService _httpDataService;
@@ -36,7 +33,6 @@ public class EventLogService : IEventLogService
public EventLogService(
ISendEventLogStateRepository sendEventLogStateRepository,
IEventLogRepository eventLogRepository,
- IServerConfigurationRepository serverConfigurationRepository,
IEssentialsService essentialsService,
IDeviceVerifier deviceVerifier,
IHttpDataService httpDataService,
@@ -45,20 +41,19 @@ ILoggerService loggerService
{
_sendEventLogStateRepository = sendEventLogStateRepository;
_eventLogRepository = eventLogRepository;
- _serverConfigurationRepository = serverConfigurationRepository;
_essentialsService = essentialsService;
_deviceVerifier = deviceVerifier;
_httpDataService = httpDataService;
_loggerService = loggerService;
}
- public async Task> SendAllAsync(long maxSize, int maxRetry)
+ public async Task SendAllAsync(long maxSize, int maxRetry, int retryInterval)
{
await _semaphore.WaitAsync();
try
{
- return await SendAllInternalAsync(maxSize, maxRetry);
+ await SendAllInternalAsync(maxSize, maxRetry, retryInterval);
}
finally
{
@@ -66,7 +61,7 @@ public async Task> SendAllAsync(long maxSize, int maxRetry)
}
}
- private async Task> SendAllInternalAsync(long maxSize, int maxRetry)
+ private async Task SendAllInternalAsync(long maxSize, int maxRetry, int retryInterval)
{
_loggerService.StartMethod();
@@ -75,8 +70,8 @@ private async Task> SendAllInternalAsync(long maxSize, int maxRet
List eventLogList = await _eventLogRepository.GetLogsAsync(maxSize);
if (eventLogList.Count == 0)
{
- _loggerService.Info($"No Event-logs found.");
- return new List();
+ _loggerService.Info($"Event-logs not found.");
+ return;
}
IDictionary eventStateDict = new Dictionary();
@@ -87,41 +82,52 @@ private async Task> SendAllInternalAsync(long maxSize, int maxRet
foreach (var eventLog in eventLogList)
{
- eventLog.HasConsent = eventStateDict[eventLog.GetEventType()] == SendEventLogState.Enable;
+ if (eventStateDict[eventLog.GetEventType()] != SendEventLogState.Enable)
+ {
+ eventLog.HasConsent = false;
+ }
}
string idempotencyKey = Guid.NewGuid().ToString();
+ List agreedEventLogList
+ = eventLogList.Where(eventLog => eventLog.HasConsent).ToList();
- for (var retryCount = 0; retryCount < maxRetry; retryCount++)
+ if (agreedEventLogList.Count == 0)
{
- try
- {
- List sentEventLogList = await SendAsync(
- idempotencyKey,
- eventLogList
- .Where(eventLog => eventLog.HasConsent)
- .ToList()
- );
- _loggerService.Info($"Send complete.");
+ _loggerService.Info($"Agreed event-logs not found.");
+ return;
+ }
- // TODO Error handling??
+ int tries = 0;
+ while (true)
+ {
+ bool isSuccess = await SendAsync(idempotencyKey, agreedEventLogList);
+
+ if (isSuccess)
+ {
+ _loggerService.Info($"Event log send successful.");
_loggerService.Info($"Clean up...");
foreach (var eventLog in eventLogList)
{
await _eventLogRepository.RemoveAsync(eventLog);
}
- _loggerService.Info($"Done.");
- return sentEventLogList;
+ break;
}
- catch (Exception exception)
+ else if (tries >= maxRetry)
{
- _loggerService.Exception("Exception occurred, SendAsync", exception);
+ _loggerService.Error("Event log send failed all.");
+ break;
}
+
+ _loggerService.Warning($"Event log send failed. tries:{tries + 1}");
+ await Task.Delay(retryInterval);
+
+ tries++;
}
- return new List();
+ _loggerService.Info($"Done.");
}
finally
{
@@ -129,7 +135,7 @@ private async Task> SendAllInternalAsync(long maxSize, int maxRet
}
}
- private async Task> SendAsync(string idempotencyKey, List eventLogList)
+ private async Task SendAsync(string idempotencyKey, List eventLogList)
{
_loggerService.StartMethod();
@@ -151,18 +157,20 @@ private async Task> SendAsync(string idempotencyKey, List();
+ }
+ catch (Exception ex)
+ {
+ _loggerService.Exception("Exception occurred, SendAsync", ex);
}
finally
{
_loggerService.EndMethod();
}
+
+ _loggerService.Error("Send event log failure");
+ return false;
}
}
}
diff --git a/Covid19Radar/Covid19Radar/Services/EventLogServiceNop.cs b/Covid19Radar/Covid19Radar/Services/EventLogServiceNop.cs
index 6a3dac8df..8fea52571 100644
--- a/Covid19Radar/Covid19Radar/Services/EventLogServiceNop.cs
+++ b/Covid19Radar/Covid19Radar/Services/EventLogServiceNop.cs
@@ -10,10 +10,7 @@ namespace Covid19Radar.Services
{
public class EventLogServiceNop : IEventLogService
{
- public Task> SendAllAsync(
- long maxSize,
- int maxRetry
- )
+ public Task SendAllAsync(long maxSize, int maxRetry, int retryInterval)
{
// do nothing
return Task.FromResult(new List());
diff --git a/Covid19Radar/Tests/Covid19Radar.UnitTests/Repository/SendEventLogStateRepositoryTests.cs b/Covid19Radar/Tests/Covid19Radar.UnitTests/Repository/SendEventLogStateRepositoryTests.cs
new file mode 100644
index 000000000..6c1e1803d
--- /dev/null
+++ b/Covid19Radar/Tests/Covid19Radar.UnitTests/Repository/SendEventLogStateRepositoryTests.cs
@@ -0,0 +1,196 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+using Covid19Radar.Common;
+using Covid19Radar.Repository;
+using Covid19Radar.Services;
+using Covid19Radar.Services.Logs;
+using Moq;
+using Xunit;
+
+namespace Covid19Radar.UnitTests.Repository
+{
+ public class SendEventLogStateRepositoryTests
+ {
+ private static EventType DUMMY_EVENT1 = new EventType("DummyEventType1", "DummyEventSubtype1");
+ private static EventType DUMMY_EVENT2 = new EventType("DummyEventType2", "DummyEventSubtype2");
+
+ private readonly MockRepository mockRepository;
+ private readonly Mock mockLoggerService;
+ private readonly Mock mockPreferencesService;
+
+ public SendEventLogStateRepositoryTests()
+ {
+ mockRepository = new MockRepository(MockBehavior.Default);
+ mockLoggerService = mockRepository.Create();
+ mockPreferencesService = mockRepository.Create();
+ }
+
+ private ISendEventLogStateRepository CreateRepository()
+ => new SendEventLogStateRepository(
+ mockPreferencesService.Object,
+ mockLoggerService.Object
+ );
+
+ [Fact]
+ public void GetSendEventLogStateTest_NotExists()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(false);
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ var exposureNotificationNotified = sendEventLogStateRepository
+ .GetSendEventLogState(DUMMY_EVENT1);
+
+ Assert.Equal(SendEventLogState.NotSet, exposureNotificationNotified);
+ }
+
+ [Fact]
+ public void GetSendEventLogStateTest_InvalidJson()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{]");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ var exposureNotificationNotified = sendEventLogStateRepository
+ .GetSendEventLogState(DUMMY_EVENT1);
+
+ Assert.Equal(SendEventLogState.NotSet, exposureNotificationNotified);
+ }
+
+ [Fact]
+ public void GetSendEventLogStateTest_Blank()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{ }");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ var exposureNotificationNotified = sendEventLogStateRepository
+ .GetSendEventLogState(DUMMY_EVENT1);
+
+ Assert.Equal(SendEventLogState.NotSet, exposureNotificationNotified);
+ }
+
+ [Fact]
+ public void GetSendEventLogStateTest_NotSet()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{ \"DummyEventType\": 0 }");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ var exposureNotificationNotified = sendEventLogStateRepository
+ .GetSendEventLogState(DUMMY_EVENT1);
+
+ Assert.Equal(SendEventLogState.NotSet, exposureNotificationNotified);
+ }
+
+ [Fact]
+ public void GetSendEventLogStateTest_Disable()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{ \"DummyEventType1-DummyEventSubtype1\": -1 }");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ var exposureNotificationNotified = sendEventLogStateRepository
+ .GetSendEventLogState(DUMMY_EVENT1);
+
+ Assert.Equal(SendEventLogState.Disable, exposureNotificationNotified);
+ }
+
+ [Fact]
+ public void GetSendEventLogStateTest_Enable()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{ \"DummyEventType1-DummyEventSubtype1\": 1, \"DummyEventType2-DummyEventSubtype2\": -11 }");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ var exposureNotificationNotified = sendEventLogStateRepository
+ .GetSendEventLogState(DUMMY_EVENT1);
+
+ Assert.Equal(SendEventLogState.Enable, exposureNotificationNotified);
+ }
+
+ [Fact]
+ public void SetSendEventLogStateTest_Create()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{}");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ sendEventLogStateRepository
+ .SetSendEventLogState(DUMMY_EVENT1, SendEventLogState.Enable);
+
+ mockPreferencesService.Verify(s => s.SetStringValue(
+ PreferenceKey.SendEventLogState,
+ "{\"DummyEventType1-DummyEventSubtype1\":1}"
+ ), Times.Once());
+ }
+
+ [Fact]
+ public void SetSendEventLogStateTest_Create_InvalidJson()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{]");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ sendEventLogStateRepository
+ .SetSendEventLogState(DUMMY_EVENT1, SendEventLogState.Enable);
+
+ mockPreferencesService.Verify(s => s.SetStringValue(
+ PreferenceKey.SendEventLogState,
+ "{\"DummyEventType1-DummyEventSubtype1\":1}"
+ ), Times.Once());
+ }
+
+ [Fact]
+ public void SetSendEventLogStateTest_Update()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{\"DummyEventType1-DummyEventSubtype1\": -1}");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ sendEventLogStateRepository
+ .SetSendEventLogState(DUMMY_EVENT1, SendEventLogState.Enable);
+
+ mockPreferencesService.Verify(s => s.SetStringValue(
+ PreferenceKey.SendEventLogState,
+ "{\"DummyEventType1-DummyEventSubtype1\":1}"
+ ), Times.Once());
+ }
+
+ [Fact]
+ public void SetSendEventLogStateTest_Append()
+ {
+ mockPreferencesService.Setup(s => s.ContainsKey(PreferenceKey.SendEventLogState)).Returns(true);
+ mockPreferencesService.Setup(s => s.GetStringValue(PreferenceKey.SendEventLogState, It.IsAny()))
+ .Returns("{ \"DummyEventType0-DummyEventSubtype0\": -1 }");
+
+ var sendEventLogStateRepository = CreateRepository();
+
+ sendEventLogStateRepository
+ .SetSendEventLogState(DUMMY_EVENT1, SendEventLogState.Enable);
+
+ mockPreferencesService.Verify(s => s.SetStringValue(
+ PreferenceKey.SendEventLogState,
+ "{\"DummyEventType0-DummyEventSubtype0\":-1,\"DummyEventType1-DummyEventSubtype1\":1}"
+ ), Times.Once());
+ }
+ }
+}
diff --git a/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/EventLogServiceTests.cs b/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/EventLogServiceTests.cs
new file mode 100644
index 000000000..9a9d5e5e0
--- /dev/null
+++ b/Covid19Radar/Tests/Covid19Radar.UnitTests/Services/EventLogServiceTests.cs
@@ -0,0 +1,325 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Threading.Tasks;
+using Covid19Radar.Model;
+using Covid19Radar.Repository;
+using Covid19Radar.Services;
+using Covid19Radar.Services.Logs;
+using Moq;
+using Xunit;
+
+namespace Covid19Radar.UnitTests.Services
+{
+ public class EventLogServiceTests
+ {
+ private readonly MockRepository mockRepository;
+
+ private readonly Mock _sendEventLogStateRepository;
+ private readonly Mock _eventLogRepository;
+ private readonly Mock _essentialsService;
+ private readonly Mock _deviceVerifier;
+ private readonly Mock _httpDataService;
+
+ private readonly Mock _loggerService;
+
+ public EventLogServiceTests()
+ {
+ mockRepository = new MockRepository(MockBehavior.Default);
+
+ _sendEventLogStateRepository = mockRepository.Create();
+ _eventLogRepository = mockRepository.Create();
+ _essentialsService = mockRepository.Create();
+ _deviceVerifier = mockRepository.Create();
+ _httpDataService = mockRepository.Create();
+ _loggerService = mockRepository.Create();
+ }
+
+ private EventLogService CreateService()
+ {
+ return new EventLogService(
+ _sendEventLogStateRepository.Object,
+ _eventLogRepository.Object,
+ _essentialsService.Object,
+ _deviceVerifier.Object,
+ _httpDataService.Object,
+ _loggerService.Object);
+ }
+
+ private List CreateDummyEventLogList()
+ {
+ return new List()
+ {
+ new EventLog()
+ {
+ HasConsent = true,
+ Epoch = 1,
+ Type = EventType.ExposureNotified.Type,
+ Subtype = EventType.ExposureNotified.SubType,
+ Content = "{\"key\" : \"value2\"}",
+ },
+ new EventLog()
+ {
+ HasConsent = true,
+ Epoch = 2,
+ Type = EventType.ExposureData.Type,
+ Subtype = EventType.ExposureData.SubType,
+ Content = "{\"key\" : \"value3\"}",
+ },
+ new EventLog()
+ {
+ HasConsent = false,
+ Epoch = 1,
+ Type = EventType.ExposureNotified.Type,
+ Subtype = EventType.ExposureNotified.SubType,
+ Content = "{\"key\" : \"value1\"}",
+ }
+ };
+ }
+
+ [Fact]
+ public async Task SendAllAsyncTest1()
+ {
+ // Dummy data
+ List dummyEventLogList = CreateDummyEventLogList();
+
+ _httpDataService.Setup(x => x.PutEventLog(It.IsAny()))
+ .ReturnsAsync(new ApiResponse((int)HttpStatusCode.Created, ""));
+
+ // Mock Setup
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureNotified))
+ .Returns(SendEventLogState.Enable);
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureData))
+ .Returns(SendEventLogState.Enable);
+ _eventLogRepository.Setup(x => x.GetLogsAsync(long.MaxValue))
+ .ReturnsAsync(dummyEventLogList);
+
+ // Test Case
+ var unitUnderTest = CreateService();
+ await unitUnderTest.SendAllAsync(
+ long.MaxValue,
+ 1,
+ 100
+ );
+
+ _loggerService.Verify(x => x.Warning(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+ _loggerService.Verify(x => x.Info("Event log send successful.", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Error(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+
+ _httpDataService.Verify(x => x.PutEventLog(It.Is(x => x.EventLogs.Count == 2)), Times.Once());
+
+ _deviceVerifier.Verify(x => x.VerifyAsync(It.IsAny()), Times.Once());
+
+ Assert.True(dummyEventLogList[0].HasConsent);
+ _eventLogRepository.Verify(x => x.RemoveAsync(dummyEventLogList[0]), Times.Once());
+
+ Assert.True(dummyEventLogList[1].HasConsent);
+ _eventLogRepository.Verify(x => x.RemoveAsync(dummyEventLogList[1]), Times.Once());
+ }
+
+ [Fact]
+ public async Task SendAllAsyncTest_withdrawn_consent()
+ {
+ // Dummy data
+ List dummyEventLogList = CreateDummyEventLogList();
+
+ // Mock Setup
+ _httpDataService.Setup(x => x.PutEventLog(It.IsAny()))
+ .ReturnsAsync(new ApiResponse((int)HttpStatusCode.Created, ""));
+
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureNotified))
+ .Returns(SendEventLogState.Enable);
+
+ // Consent withdrawn
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureData))
+ .Returns(SendEventLogState.Disable);
+
+ _eventLogRepository.Setup(x => x.GetLogsAsync(long.MaxValue))
+ .ReturnsAsync(dummyEventLogList);
+
+ // Test Case
+ var unitUnderTest = CreateService();
+ await unitUnderTest.SendAllAsync(
+ long.MaxValue,
+ 1,
+ 100
+ );
+
+ _loggerService.Verify(x => x.Warning(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+ _loggerService.Verify(x => x.Info("Event log send successful.", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Error("Event log send failed all.", It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+
+ _httpDataService.Verify(x => x.PutEventLog(It.Is(x => x.EventLogs.Count == 1 && !x.EventLogs.Exists(x => x.Type != EventType.ExposureNotified.Type))), Times.Once());
+
+ _deviceVerifier.Verify(x => x.VerifyAsync(It.IsAny()), Times.Once());
+
+ Assert.True(dummyEventLogList[0].HasConsent);
+ _eventLogRepository.Verify(x => x.RemoveAsync(dummyEventLogList[0]), Times.Once());
+
+ // Consent withdrawn
+ Assert.False(dummyEventLogList[1].HasConsent);
+ _eventLogRepository.Verify(x => x.RemoveAsync(dummyEventLogList[1]), Times.Once());
+ }
+
+ [Fact]
+ public async Task SendAllAsyncTest_RetrySuccess()
+ {
+ // Dummy data
+ List dummyEventLogList = CreateDummyEventLogList();
+
+ // Mock Setup
+ _httpDataService.SetupSequence(x => x.PutEventLog(It.IsAny()))
+ .ReturnsAsync(new ApiResponse((int)HttpStatusCode.BadGateway, ""))
+ .ReturnsAsync(new ApiResponse((int)HttpStatusCode.BadGateway, ""))
+ .ReturnsAsync(new ApiResponse((int)HttpStatusCode.Created, ""));
+
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureNotified))
+ .Returns(SendEventLogState.Enable);
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureData))
+ .Returns(SendEventLogState.Disable);
+
+ _eventLogRepository.Setup(x => x.GetLogsAsync(long.MaxValue))
+ .ReturnsAsync(dummyEventLogList);
+
+ // Test Case
+ var unitUnderTest = CreateService();
+ await unitUnderTest.SendAllAsync(
+ long.MaxValue,
+ 3,
+ 100
+ );
+
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:1", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:2", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:3", It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+ _loggerService.Verify(x => x.Info("Event log send successful.", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Error("Event log send failed all.", It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+
+ _httpDataService.Verify(x => x.PutEventLog(It.IsAny()), Times.Exactly(3));
+ _deviceVerifier.Verify(x => x.VerifyAsync(It.IsAny()), Times.Exactly(3));
+ _eventLogRepository.Verify(x => x.RemoveAsync(It.IsAny()), Times.Exactly(dummyEventLogList.Count));
+ }
+
+ [Fact]
+ public async Task SendAllAsyncTest_RetryFailure()
+ {
+ // Dummy data
+ List dummyEventLogList = CreateDummyEventLogList();
+
+ // Mock Setup
+ _httpDataService.Setup(x => x.PutEventLog(It.IsAny()))
+ .ReturnsAsync(new ApiResponse((int)HttpStatusCode.BadRequest, ""));
+
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureNotified))
+ .Returns(SendEventLogState.Enable);
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureData))
+ .Returns(SendEventLogState.Disable);
+
+ _eventLogRepository.Setup(x => x.GetLogsAsync(long.MaxValue))
+ .ReturnsAsync(dummyEventLogList);
+
+ // Test Case
+ var unitUnderTest = CreateService();
+ await unitUnderTest.SendAllAsync(
+ long.MaxValue,
+ 3,
+ 100
+ );
+
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:1", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:2", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:3", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:4", It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+ _loggerService.Verify(x => x.Info("Event log send successful.", It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+ _loggerService.Verify(x => x.Error("Event log send failed all.", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+
+ _httpDataService.Verify(x => x.PutEventLog(It.IsAny()), Times.Exactly(4));
+ _deviceVerifier.Verify(x => x.VerifyAsync(It.IsAny()), Times.Exactly(4));
+ _eventLogRepository.Verify(x => x.RemoveAsync(It.IsAny()), Times.Never());
+ }
+
+ [Fact]
+ public async Task SendAllAsyncTest_RetryException()
+ {
+ // Dummy data
+ List dummyEventLogList = CreateDummyEventLogList();
+
+ // Mock Setup
+ _httpDataService.Setup(x => x.PutEventLog(It.IsAny()))
+ .Throws(new Exception());
+
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureNotified))
+ .Returns(SendEventLogState.Enable);
+ _sendEventLogStateRepository
+ .Setup(x => x.GetSendEventLogState(EventType.ExposureData))
+ .Returns(SendEventLogState.Disable);
+
+ _eventLogRepository.Setup(x => x.GetLogsAsync(long.MaxValue))
+ .ReturnsAsync(dummyEventLogList);
+
+ // Test Case
+ var unitUnderTest = CreateService();
+ await unitUnderTest.SendAllAsync(
+ long.MaxValue,
+ 3,
+ 100
+ );
+
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:1", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:2", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:3", It.IsAny(), It.IsAny(), It.IsAny()), Times.Once());
+ _loggerService.Verify(x => x.Warning("Event log send failed. tries:4", It.IsAny(), It.IsAny(), It.IsAny()), Times.Never());
+ _loggerService.Verify(x => x.Info("Event log send successful.", It.IsAny(), It.IsAny