Migration plan v1
This commit is contained in:
@@ -0,0 +1,696 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using SQLite;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Tests.Helpers;
|
||||
using Wino.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace Wino.Core.Tests.Services;
|
||||
|
||||
public sealed class LegacyLocalMigrationServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task DetectAsync_ReturnsPreviewCountsAndDuplicatesByProvider()
|
||||
{
|
||||
await using var context = await LegacyMigrationTestContext.CreateAsync();
|
||||
|
||||
await context.SeedCurrentAccountAsync(
|
||||
"gmail@example.com",
|
||||
MailProviderType.Gmail,
|
||||
"Existing Gmail");
|
||||
|
||||
await context.InsertLegacyAccountAsync(
|
||||
Guid.NewGuid(),
|
||||
"outlook@example.com",
|
||||
MailProviderType.Outlook,
|
||||
order: 0,
|
||||
name: "Outlook Legacy");
|
||||
|
||||
await context.InsertLegacyAccountAsync(
|
||||
Guid.NewGuid(),
|
||||
"gmail@example.com",
|
||||
MailProviderType.Gmail,
|
||||
order: 1,
|
||||
name: "Duplicate Gmail");
|
||||
|
||||
var imapId = Guid.NewGuid();
|
||||
await context.InsertLegacyAccountAsync(
|
||||
imapId,
|
||||
"imap@example.com",
|
||||
MailProviderType.IMAP4,
|
||||
order: 2,
|
||||
name: "Imported IMAP",
|
||||
specialImapProvider: SpecialImapProvider.Yahoo);
|
||||
|
||||
await context.InsertLegacyServerInformationAsync(
|
||||
imapId,
|
||||
address: "imap@example.com",
|
||||
incomingServer: "imap.mail.yahoo.com",
|
||||
incomingServerPort: "993",
|
||||
incomingServerUsername: "imap@example.com",
|
||||
outgoingServer: "smtp.mail.yahoo.com",
|
||||
outgoingServerPort: "587",
|
||||
outgoingServerUsername: "imap@example.com",
|
||||
calendarSupportMode: ImapCalendarSupportMode.CalDav,
|
||||
calDavServiceUrl: "https://caldav.calendar.yahoo.com/",
|
||||
calDavUsername: "imap@example.com");
|
||||
|
||||
var preview = await context.Service.DetectAsync();
|
||||
|
||||
preview.LegacyDatabaseExists.Should().BeTrue();
|
||||
preview.ShouldPrompt.Should().BeTrue();
|
||||
preview.LegacyAccountCount.Should().Be(3);
|
||||
preview.ImportableAccountCount.Should().Be(2);
|
||||
preview.DuplicateAccountCount.Should().Be(1);
|
||||
preview.Accounts.Select(a => a.Address).Should().ContainInOrder(
|
||||
"outlook@example.com",
|
||||
"gmail@example.com",
|
||||
"imap@example.com");
|
||||
|
||||
preview.ProviderCounts.Should().ContainSingle(a =>
|
||||
a.ProviderType == MailProviderType.Outlook &&
|
||||
a.ImportableAccountCount == 1 &&
|
||||
a.DuplicateAccountCount == 0);
|
||||
|
||||
preview.ProviderCounts.Should().ContainSingle(a =>
|
||||
a.ProviderType == MailProviderType.Gmail &&
|
||||
a.ImportableAccountCount == 0 &&
|
||||
a.DuplicateAccountCount == 1);
|
||||
|
||||
preview.ProviderCounts.Should().ContainSingle(a =>
|
||||
a.ProviderType == MailProviderType.IMAP4 &&
|
||||
a.ImportableAccountCount == 1 &&
|
||||
a.DuplicateAccountCount == 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ImportAsync_ImportsAccountsPreservesSafePreferencesAndRecreatesMergedInboxes()
|
||||
{
|
||||
await using var context = await LegacyMigrationTestContext.CreateAsync();
|
||||
|
||||
var mergedInboxId = Guid.NewGuid();
|
||||
await context.InsertLegacyMergedInboxAsync(mergedInboxId, "Legacy Linked");
|
||||
|
||||
var legacyOutlookId = Guid.NewGuid();
|
||||
var legacyGmailId = Guid.NewGuid();
|
||||
var legacyImapId = Guid.NewGuid();
|
||||
var legacySignatureId = Guid.NewGuid();
|
||||
|
||||
await context.InsertLegacyAccountAsync(
|
||||
legacyOutlookId,
|
||||
"outlook@example.com",
|
||||
MailProviderType.Outlook,
|
||||
order: 0,
|
||||
name: "Outlook Legacy",
|
||||
senderName: "Outlook Sender",
|
||||
mergedInboxId: mergedInboxId);
|
||||
|
||||
await context.InsertLegacyPreferencesAsync(
|
||||
legacyOutlookId,
|
||||
isNotificationsEnabled: false,
|
||||
isTaskbarBadgeEnabled: false,
|
||||
shouldAppendMessagesToSentFolder: true,
|
||||
isFocusedInboxEnabled: false,
|
||||
signatureIdForNewMessages: legacySignatureId,
|
||||
signatureIdForFollowingMessages: legacySignatureId);
|
||||
|
||||
await context.InsertLegacyAccountAsync(
|
||||
legacyGmailId,
|
||||
"gmail@example.com",
|
||||
MailProviderType.Gmail,
|
||||
order: 1,
|
||||
name: "Gmail Legacy",
|
||||
senderName: "Gmail Sender",
|
||||
mergedInboxId: mergedInboxId);
|
||||
|
||||
await context.InsertLegacyAccountAsync(
|
||||
legacyImapId,
|
||||
"imap@example.com",
|
||||
MailProviderType.IMAP4,
|
||||
order: 2,
|
||||
name: "iCloud Legacy",
|
||||
senderName: "iCloud Sender",
|
||||
specialImapProvider: SpecialImapProvider.iCloud);
|
||||
|
||||
await context.InsertLegacyServerInformationAsync(
|
||||
legacyImapId,
|
||||
address: "imap@example.com",
|
||||
incomingServer: "imap.mail.me.com",
|
||||
incomingServerPort: "993",
|
||||
incomingServerUsername: "imap-user",
|
||||
outgoingServer: "smtp.mail.me.com",
|
||||
outgoingServerPort: "587",
|
||||
outgoingServerUsername: "smtp-user",
|
||||
calendarSupportMode: ImapCalendarSupportMode.CalDav,
|
||||
calDavServiceUrl: "https://caldav.icloud.com/",
|
||||
calDavUsername: "imap@example.com",
|
||||
maxConcurrentClients: 7);
|
||||
|
||||
var result = await context.Service.ImportAsync();
|
||||
|
||||
result.ImportedAccountCount.Should().Be(3);
|
||||
result.SkippedDuplicateAccountCount.Should().Be(0);
|
||||
result.FailedAccountCount.Should().Be(0);
|
||||
result.ImportedMergedInboxCount.Should().Be(1);
|
||||
result.SkippedMergedInboxCount.Should().Be(0);
|
||||
|
||||
var accounts = await context.AccountService.GetAccountsAsync();
|
||||
accounts.Should().HaveCount(3);
|
||||
accounts.Select(a => a.Address).Should().ContainInOrder(
|
||||
"outlook@example.com",
|
||||
"gmail@example.com",
|
||||
"imap@example.com");
|
||||
|
||||
var outlookAccount = accounts.Single(a => a.Address == "outlook@example.com");
|
||||
outlookAccount.AttentionReason.Should().Be(AccountAttentionReason.InvalidCredentials);
|
||||
outlookAccount.IsMailAccessGranted.Should().BeTrue();
|
||||
outlookAccount.IsCalendarAccessGranted.Should().BeTrue();
|
||||
outlookAccount.Preferences.IsNotificationsEnabled.Should().BeFalse();
|
||||
outlookAccount.Preferences.IsTaskbarBadgeEnabled.Should().BeFalse();
|
||||
outlookAccount.Preferences.ShouldAppendMessagesToSentFolder.Should().BeTrue();
|
||||
outlookAccount.Preferences.IsFocusedInboxEnabled.Should().BeFalse();
|
||||
outlookAccount.Preferences.SignatureIdForNewMessages.Should().NotBe(legacySignatureId);
|
||||
outlookAccount.Preferences.SignatureIdForFollowingMessages.Should().NotBe(legacySignatureId);
|
||||
|
||||
var gmailAliases = await context.AccountService.GetAccountAliasesAsync(accounts.Single(a => a.Address == "gmail@example.com").Id);
|
||||
gmailAliases.Should().ContainSingle(a =>
|
||||
a.IsRootAlias &&
|
||||
a.IsPrimary &&
|
||||
a.AliasAddress == "gmail@example.com");
|
||||
|
||||
var imapAccount = accounts.Single(a => a.Address == "imap@example.com");
|
||||
imapAccount.AttentionReason.Should().Be(AccountAttentionReason.InvalidCredentials);
|
||||
imapAccount.IsCalendarAccessGranted.Should().BeTrue();
|
||||
|
||||
var serverInformation = await context.AccountService.GetAccountCustomServerInformationAsync(imapAccount.Id);
|
||||
serverInformation.Should().NotBeNull();
|
||||
serverInformation.IncomingServer.Should().Be("imap.mail.me.com");
|
||||
serverInformation.OutgoingServer.Should().Be("smtp.mail.me.com");
|
||||
serverInformation.IncomingServerPassword.Should().BeEmpty();
|
||||
serverInformation.OutgoingServerPassword.Should().BeEmpty();
|
||||
serverInformation.CalDavPassword.Should().BeEmpty();
|
||||
serverInformation.CalDavServiceUrl.Should().Be("https://caldav.icloud.com/");
|
||||
serverInformation.CalDavUsername.Should().Be("imap@example.com");
|
||||
serverInformation.MaxConcurrentClients.Should().Be(7);
|
||||
|
||||
var mergedInboxIds = accounts
|
||||
.Where(a => a.Address is "outlook@example.com" or "gmail@example.com")
|
||||
.Select(a => a.MergedInboxId)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
mergedInboxIds.Should().ContainSingle();
|
||||
mergedInboxIds[0].Should().NotBeNull();
|
||||
accounts.Single(a => a.Address == "outlook@example.com").MergedInbox.Name.Should().Be("Legacy Linked");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ImportAsync_WithMissingLegacySchemaColumns_DefaultsSafelyAndSkipsIncompleteMergedInboxes()
|
||||
{
|
||||
await using var context = await LegacyMigrationTestContext.CreateAsync(new LegacySchemaOptions(
|
||||
IncludeCalDavColumns: false,
|
||||
IncludeCalendarSupportMode: false,
|
||||
IncludeTaskbarBadgeColumn: false,
|
||||
IncludeFocusedInboxColumn: false));
|
||||
|
||||
await context.SeedCurrentAccountAsync(
|
||||
"duplicate@icloud.com",
|
||||
MailProviderType.IMAP4,
|
||||
"Existing iCloud",
|
||||
specialImapProvider: SpecialImapProvider.iCloud);
|
||||
|
||||
var mergedInboxId = Guid.NewGuid();
|
||||
var duplicateLegacyAccountId = Guid.NewGuid();
|
||||
var importableLegacyAccountId = Guid.NewGuid();
|
||||
|
||||
await context.InsertLegacyMergedInboxAsync(mergedInboxId, "Legacy Incomplete");
|
||||
|
||||
await context.InsertLegacyAccountAsync(
|
||||
duplicateLegacyAccountId,
|
||||
"duplicate@icloud.com",
|
||||
MailProviderType.IMAP4,
|
||||
order: 0,
|
||||
name: "Duplicate iCloud",
|
||||
specialImapProvider: SpecialImapProvider.iCloud,
|
||||
mergedInboxId: mergedInboxId);
|
||||
|
||||
await context.InsertLegacyAccountAsync(
|
||||
importableLegacyAccountId,
|
||||
"new@icloud.com",
|
||||
MailProviderType.IMAP4,
|
||||
order: 1,
|
||||
name: "Importable iCloud",
|
||||
specialImapProvider: SpecialImapProvider.iCloud,
|
||||
mergedInboxId: mergedInboxId);
|
||||
|
||||
await context.InsertLegacyServerInformationAsync(
|
||||
duplicateLegacyAccountId,
|
||||
address: "duplicate@icloud.com",
|
||||
incomingServer: "imap.mail.me.com",
|
||||
incomingServerPort: "993",
|
||||
incomingServerUsername: "duplicate",
|
||||
outgoingServer: "smtp.mail.me.com",
|
||||
outgoingServerPort: "587",
|
||||
outgoingServerUsername: "duplicate");
|
||||
|
||||
await context.InsertLegacyServerInformationAsync(
|
||||
importableLegacyAccountId,
|
||||
address: "new@icloud.com",
|
||||
incomingServer: "imap.mail.me.com",
|
||||
incomingServerPort: "993",
|
||||
incomingServerUsername: "new",
|
||||
outgoingServer: "smtp.mail.me.com",
|
||||
outgoingServerPort: "587",
|
||||
outgoingServerUsername: "new");
|
||||
|
||||
var result = await context.Service.ImportAsync();
|
||||
|
||||
result.ImportedAccountCount.Should().Be(1);
|
||||
result.SkippedDuplicateAccountCount.Should().Be(1);
|
||||
result.ImportedMergedInboxCount.Should().Be(0);
|
||||
result.SkippedMergedInboxCount.Should().Be(1);
|
||||
|
||||
var importedAccount = (await context.AccountService.GetAccountsAsync())
|
||||
.Single(a => a.Address == "new@icloud.com");
|
||||
|
||||
importedAccount.IsCalendarAccessGranted.Should().BeFalse();
|
||||
importedAccount.MergedInboxId.Should().BeNull();
|
||||
importedAccount.AttentionReason.Should().Be(AccountAttentionReason.InvalidCredentials);
|
||||
|
||||
var importedServerInfo = await context.AccountService.GetAccountCustomServerInformationAsync(importedAccount.Id);
|
||||
importedServerInfo.Should().NotBeNull();
|
||||
importedServerInfo.CalDavServiceUrl.Should().Be("https://caldav.icloud.com/");
|
||||
importedServerInfo.CalDavUsername.Should().Be("new@icloud.com");
|
||||
importedServerInfo.CalDavPassword.Should().BeEmpty();
|
||||
importedServerInfo.CalendarSupportMode.Should().Be(ImapCalendarSupportMode.Disabled);
|
||||
importedServerInfo.IncomingServerPassword.Should().BeEmpty();
|
||||
importedServerInfo.OutgoingServerPassword.Should().BeEmpty();
|
||||
}
|
||||
|
||||
private sealed class LegacyMigrationTestContext : IAsyncDisposable
|
||||
{
|
||||
private readonly SQLiteAsyncConnection _legacyConnection;
|
||||
private readonly InMemoryDatabaseService _databaseService;
|
||||
private readonly string _legacyFolderPath;
|
||||
|
||||
public LegacyLocalMigrationService Service { get; }
|
||||
public AccountService AccountService { get; }
|
||||
|
||||
private LegacyMigrationTestContext(string legacyFolderPath,
|
||||
SQLiteAsyncConnection legacyConnection,
|
||||
InMemoryDatabaseService databaseService,
|
||||
AccountService accountService,
|
||||
LegacyLocalMigrationService service)
|
||||
{
|
||||
_legacyFolderPath = legacyFolderPath;
|
||||
_legacyConnection = legacyConnection;
|
||||
_databaseService = databaseService;
|
||||
AccountService = accountService;
|
||||
Service = service;
|
||||
}
|
||||
|
||||
public static async Task<LegacyMigrationTestContext> CreateAsync(LegacySchemaOptions? schemaOptions = null)
|
||||
{
|
||||
var databaseService = new InMemoryDatabaseService();
|
||||
await databaseService.InitializeAsync();
|
||||
|
||||
var preferencesService = new Mock<IPreferencesService>();
|
||||
preferencesService.SetupProperty(a => a.StartupEntityId);
|
||||
|
||||
var accountService = CreateAccountService(databaseService, preferencesService.Object);
|
||||
|
||||
var legacyFolderPath = Path.Combine(Path.GetTempPath(), $"legacy-migration-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(legacyFolderPath);
|
||||
|
||||
var legacyDatabasePath = Path.Combine(legacyFolderPath, "Wino180.db");
|
||||
var legacyConnection = new SQLiteAsyncConnection(legacyDatabasePath);
|
||||
|
||||
await CreateLegacySchemaAsync(legacyConnection, schemaOptions ?? LegacySchemaOptions.Default);
|
||||
|
||||
var applicationConfiguration = new ApplicationConfiguration
|
||||
{
|
||||
ApplicationDataFolderPath = legacyFolderPath,
|
||||
ApplicationTempFolderPath = legacyFolderPath,
|
||||
PublisherSharedFolderPath = legacyFolderPath
|
||||
};
|
||||
|
||||
var service = new LegacyLocalMigrationService(
|
||||
applicationConfiguration,
|
||||
new InMemoryConfigurationService(),
|
||||
databaseService,
|
||||
accountService,
|
||||
new SpecialImapProviderConfigResolver());
|
||||
|
||||
return new LegacyMigrationTestContext(
|
||||
legacyFolderPath,
|
||||
legacyConnection,
|
||||
databaseService,
|
||||
accountService,
|
||||
service);
|
||||
}
|
||||
|
||||
public async Task SeedCurrentAccountAsync(string address,
|
||||
MailProviderType providerType,
|
||||
string name,
|
||||
SpecialImapProvider specialImapProvider = SpecialImapProvider.None)
|
||||
{
|
||||
var accountId = Guid.NewGuid();
|
||||
CustomServerInformation? serverInformation = null;
|
||||
|
||||
if (providerType == MailProviderType.IMAP4)
|
||||
{
|
||||
serverInformation = new CustomServerInformation
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
AccountId = accountId,
|
||||
Address = address,
|
||||
IncomingServer = "imap.current.test",
|
||||
IncomingServerPort = "993",
|
||||
IncomingServerUsername = address,
|
||||
IncomingServerPassword = "secret",
|
||||
IncomingServerSocketOption = ImapConnectionSecurity.Auto,
|
||||
IncomingAuthenticationMethod = ImapAuthenticationMethod.NormalPassword,
|
||||
OutgoingServer = "smtp.current.test",
|
||||
OutgoingServerPort = "587",
|
||||
OutgoingServerUsername = address,
|
||||
OutgoingServerPassword = "secret",
|
||||
OutgoingServerSocketOption = ImapConnectionSecurity.Auto,
|
||||
OutgoingAuthenticationMethod = ImapAuthenticationMethod.NormalPassword,
|
||||
CalDavServiceUrl = string.Empty,
|
||||
CalDavUsername = string.Empty,
|
||||
CalDavPassword = string.Empty,
|
||||
CalendarSupportMode = ImapCalendarSupportMode.Disabled,
|
||||
MaxConcurrentClients = 5
|
||||
};
|
||||
}
|
||||
|
||||
await AccountService.CreateAccountAsync(
|
||||
new MailAccount
|
||||
{
|
||||
Id = accountId,
|
||||
Name = name,
|
||||
SenderName = name,
|
||||
Address = address,
|
||||
ProviderType = providerType,
|
||||
SpecialImapProvider = specialImapProvider,
|
||||
IsMailAccessGranted = true,
|
||||
IsCalendarAccessGranted = providerType is MailProviderType.Outlook or MailProviderType.Gmail
|
||||
},
|
||||
serverInformation);
|
||||
}
|
||||
|
||||
public Task InsertLegacyAccountAsync(Guid accountId,
|
||||
string address,
|
||||
MailProviderType providerType,
|
||||
int order,
|
||||
string name,
|
||||
string? senderName = null,
|
||||
SpecialImapProvider specialImapProvider = SpecialImapProvider.None,
|
||||
Guid? mergedInboxId = null)
|
||||
=> InsertRowAsync(
|
||||
_legacyConnection,
|
||||
"MailAccount",
|
||||
("Id", accountId),
|
||||
("Address", address),
|
||||
("Name", name),
|
||||
("SenderName", senderName ?? name),
|
||||
("ProviderType", (int)providerType),
|
||||
("SpecialImapProvider", (int)specialImapProvider),
|
||||
("Order", order),
|
||||
("AccountColorHex", "#123456"),
|
||||
("MergedInboxId", mergedInboxId));
|
||||
|
||||
public Task InsertLegacyPreferencesAsync(Guid accountId,
|
||||
bool? isNotificationsEnabled = null,
|
||||
bool? isTaskbarBadgeEnabled = null,
|
||||
bool? shouldAppendMessagesToSentFolder = null,
|
||||
bool? isFocusedInboxEnabled = null,
|
||||
Guid? signatureIdForNewMessages = null,
|
||||
Guid? signatureIdForFollowingMessages = null)
|
||||
{
|
||||
var values = new List<(string Column, object? Value)>
|
||||
{
|
||||
("Id", Guid.NewGuid()),
|
||||
("AccountId", accountId)
|
||||
};
|
||||
|
||||
if (isNotificationsEnabled.HasValue)
|
||||
values.Add(("IsNotificationsEnabled", isNotificationsEnabled.Value));
|
||||
|
||||
if (isTaskbarBadgeEnabled.HasValue)
|
||||
values.Add(("IsTaskbarBadgeEnabled", isTaskbarBadgeEnabled.Value));
|
||||
|
||||
if (shouldAppendMessagesToSentFolder.HasValue)
|
||||
values.Add(("ShouldAppendMessagesToSentFolder", shouldAppendMessagesToSentFolder.Value));
|
||||
|
||||
if (isFocusedInboxEnabled.HasValue)
|
||||
values.Add(("IsFocusedInboxEnabled", isFocusedInboxEnabled.Value));
|
||||
|
||||
if (signatureIdForNewMessages.HasValue)
|
||||
values.Add(("SignatureIdForNewMessages", signatureIdForNewMessages.Value));
|
||||
|
||||
if (signatureIdForFollowingMessages.HasValue)
|
||||
values.Add(("SignatureIdForFollowingMessages", signatureIdForFollowingMessages.Value));
|
||||
|
||||
return InsertRowAsync(_legacyConnection, "MailAccountPreferences", values.ToArray());
|
||||
}
|
||||
|
||||
public Task InsertLegacyServerInformationAsync(Guid accountId,
|
||||
string address,
|
||||
string incomingServer,
|
||||
string incomingServerPort,
|
||||
string incomingServerUsername,
|
||||
string outgoingServer,
|
||||
string outgoingServerPort,
|
||||
string outgoingServerUsername,
|
||||
ImapCalendarSupportMode? calendarSupportMode = null,
|
||||
string? calDavServiceUrl = null,
|
||||
string? calDavUsername = null,
|
||||
int? maxConcurrentClients = null)
|
||||
{
|
||||
var values = new List<(string Column, object? Value)>
|
||||
{
|
||||
("Id", Guid.NewGuid()),
|
||||
("AccountId", accountId),
|
||||
("Address", address),
|
||||
("IncomingServer", incomingServer),
|
||||
("IncomingServerPort", incomingServerPort),
|
||||
("IncomingServerUsername", incomingServerUsername),
|
||||
("IncomingServerSocketOption", (int)ImapConnectionSecurity.Auto),
|
||||
("IncomingAuthenticationMethod", (int)ImapAuthenticationMethod.NormalPassword),
|
||||
("OutgoingServer", outgoingServer),
|
||||
("OutgoingServerPort", outgoingServerPort),
|
||||
("OutgoingServerUsername", outgoingServerUsername),
|
||||
("OutgoingServerSocketOption", (int)ImapConnectionSecurity.Auto),
|
||||
("OutgoingAuthenticationMethod", (int)ImapAuthenticationMethod.NormalPassword),
|
||||
("ProxyServer", "proxy.example.com"),
|
||||
("ProxyServerPort", "8080"),
|
||||
("MaxConcurrentClients", maxConcurrentClients ?? 5)
|
||||
};
|
||||
|
||||
if (calendarSupportMode.HasValue)
|
||||
values.Add(("CalendarSupportMode", (int)calendarSupportMode.Value));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(calDavServiceUrl))
|
||||
values.Add(("CalDavServiceUrl", calDavServiceUrl));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(calDavUsername))
|
||||
values.Add(("CalDavUsername", calDavUsername));
|
||||
|
||||
return InsertRowAsync(_legacyConnection, "CustomServerInformation", values.ToArray());
|
||||
}
|
||||
|
||||
public Task InsertLegacyMergedInboxAsync(Guid mergedInboxId, string name)
|
||||
=> InsertRowAsync(
|
||||
_legacyConnection,
|
||||
"MergedInbox",
|
||||
("Id", mergedInboxId),
|
||||
("Name", name));
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _legacyConnection.CloseAsync();
|
||||
|
||||
if (Directory.Exists(_legacyFolderPath))
|
||||
{
|
||||
Directory.Delete(_legacyFolderPath, recursive: true);
|
||||
}
|
||||
|
||||
await _databaseService.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class InMemoryConfigurationService : IConfigurationService
|
||||
{
|
||||
private readonly Dictionary<string, string?> _localValues = new(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, string?> _roamingValues = new(StringComparer.Ordinal);
|
||||
|
||||
public bool Contains(string key) => _localValues.ContainsKey(key);
|
||||
|
||||
public T Get<T>(string key, T defaultValue = default!)
|
||||
=> TryGetValue(_localValues, key, defaultValue);
|
||||
|
||||
public T GetRoaming<T>(string key, T defaultValue = default!)
|
||||
=> TryGetValue(_roamingValues, key, defaultValue);
|
||||
|
||||
public void Set(string key, object value)
|
||||
=> _localValues[key] = value?.ToString();
|
||||
|
||||
public void SetRoaming(string key, object value)
|
||||
=> _roamingValues[key] = value?.ToString();
|
||||
|
||||
private static T TryGetValue<T>(Dictionary<string, string?> values, string key, T defaultValue)
|
||||
{
|
||||
if (!values.TryGetValue(key, out var stringValue) || string.IsNullOrWhiteSpace(stringValue))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (typeof(T).IsEnum)
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), stringValue);
|
||||
}
|
||||
|
||||
if ((typeof(T) == typeof(Guid) || typeof(T) == typeof(Guid?)) && Guid.TryParse(stringValue, out var guid))
|
||||
{
|
||||
return (T)(object)guid;
|
||||
}
|
||||
|
||||
return (T)Convert.ChangeType(stringValue, typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record LegacySchemaOptions(
|
||||
bool IncludeCalDavColumns,
|
||||
bool IncludeCalendarSupportMode,
|
||||
bool IncludeTaskbarBadgeColumn,
|
||||
bool IncludeFocusedInboxColumn)
|
||||
{
|
||||
public static LegacySchemaOptions Default => new(true, true, true, true);
|
||||
}
|
||||
|
||||
private static async Task CreateLegacySchemaAsync(SQLiteAsyncConnection connection, LegacySchemaOptions options)
|
||||
{
|
||||
await connection.ExecuteAsync("""
|
||||
CREATE TABLE MailAccount (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Address TEXT NULL,
|
||||
Name TEXT NULL,
|
||||
SenderName TEXT NULL,
|
||||
ProviderType INTEGER NOT NULL,
|
||||
SpecialImapProvider INTEGER NOT NULL DEFAULT 0,
|
||||
[Order] INTEGER NOT NULL DEFAULT 0,
|
||||
AccountColorHex TEXT NULL,
|
||||
MergedInboxId TEXT NULL
|
||||
)
|
||||
""");
|
||||
|
||||
var preferenceColumns = new List<string>
|
||||
{
|
||||
"Id TEXT PRIMARY KEY",
|
||||
"AccountId TEXT NOT NULL",
|
||||
"IsNotificationsEnabled INTEGER NULL",
|
||||
"ShouldAppendMessagesToSentFolder INTEGER NULL",
|
||||
"SignatureIdForNewMessages TEXT NULL",
|
||||
"SignatureIdForFollowingMessages TEXT NULL"
|
||||
};
|
||||
|
||||
if (options.IncludeTaskbarBadgeColumn)
|
||||
preferenceColumns.Add("IsTaskbarBadgeEnabled INTEGER NULL");
|
||||
|
||||
if (options.IncludeFocusedInboxColumn)
|
||||
preferenceColumns.Add("IsFocusedInboxEnabled INTEGER NULL");
|
||||
|
||||
await connection.ExecuteAsync($"CREATE TABLE MailAccountPreferences ({string.Join(", ", preferenceColumns)})");
|
||||
|
||||
var serverColumns = new List<string>
|
||||
{
|
||||
"Id TEXT PRIMARY KEY",
|
||||
"AccountId TEXT NOT NULL",
|
||||
"Address TEXT NULL",
|
||||
"IncomingServer TEXT NULL",
|
||||
"IncomingServerPort TEXT NULL",
|
||||
"IncomingServerUsername TEXT NULL",
|
||||
"IncomingServerSocketOption INTEGER NULL",
|
||||
"IncomingAuthenticationMethod INTEGER NULL",
|
||||
"OutgoingServer TEXT NULL",
|
||||
"OutgoingServerPort TEXT NULL",
|
||||
"OutgoingServerUsername TEXT NULL",
|
||||
"OutgoingServerSocketOption INTEGER NULL",
|
||||
"OutgoingAuthenticationMethod INTEGER NULL",
|
||||
"ProxyServer TEXT NULL",
|
||||
"ProxyServerPort TEXT NULL",
|
||||
"MaxConcurrentClients INTEGER NULL"
|
||||
};
|
||||
|
||||
if (options.IncludeCalDavColumns)
|
||||
{
|
||||
serverColumns.Add("CalDavServiceUrl TEXT NULL");
|
||||
serverColumns.Add("CalDavUsername TEXT NULL");
|
||||
}
|
||||
|
||||
if (options.IncludeCalendarSupportMode)
|
||||
{
|
||||
serverColumns.Add("CalendarSupportMode INTEGER NULL");
|
||||
}
|
||||
|
||||
await connection.ExecuteAsync($"CREATE TABLE CustomServerInformation ({string.Join(", ", serverColumns)})");
|
||||
await connection.ExecuteAsync("""
|
||||
CREATE TABLE MergedInbox (
|
||||
Id TEXT PRIMARY KEY,
|
||||
Name TEXT NULL
|
||||
)
|
||||
""");
|
||||
}
|
||||
|
||||
private static Task InsertRowAsync(SQLiteAsyncConnection connection, string tableName, params (string Column, object? Value)[] values)
|
||||
{
|
||||
var columns = string.Join(", ", values.Select(a => $"[{a.Column}]"));
|
||||
var placeholders = string.Join(", ", values.Select(_ => "?"));
|
||||
var arguments = values.Select(a => ConvertLegacyValue(a.Value)).ToArray();
|
||||
|
||||
return connection.ExecuteAsync($"INSERT INTO [{tableName}] ({columns}) VALUES ({placeholders})", arguments);
|
||||
}
|
||||
|
||||
private static object? ConvertLegacyValue(object? value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
null => null,
|
||||
bool boolValue => boolValue ? 1 : 0,
|
||||
Guid guidValue => guidValue.ToString(),
|
||||
_ => value
|
||||
};
|
||||
}
|
||||
|
||||
private static AccountService CreateAccountService(InMemoryDatabaseService databaseService, IPreferencesService preferencesService)
|
||||
{
|
||||
var signatureService = new Mock<ISignatureService>();
|
||||
signatureService
|
||||
.Setup(a => a.CreateDefaultSignatureAsync(It.IsAny<Guid>()))
|
||||
.ReturnsAsync((Guid accountId) => new AccountSignature
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
MailAccountId = accountId,
|
||||
Name = "Default",
|
||||
HtmlBody = string.Empty
|
||||
});
|
||||
|
||||
return new AccountService(
|
||||
databaseService,
|
||||
signatureService.Object,
|
||||
Mock.Of<IAuthenticationProvider>(),
|
||||
Mock.Of<IMimeFileService>(),
|
||||
preferencesService,
|
||||
Mock.Of<IContactPictureFileService>());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user