Add manual live ImapSynchronizer coverage tests (#818)
* Add manual live IMAP synchronizer tests * Fixing build errors and testing.
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
using FluentAssertions;
|
||||
using MailKit;
|
||||
using MailKit.Net.Imap;
|
||||
using MailKit.Search;
|
||||
using Moq;
|
||||
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.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Integration.Processors;
|
||||
using Wino.Core.Requests.Mail;
|
||||
using Wino.Core.Synchronizers.ImapSync;
|
||||
using Wino.Core.Synchronizers.Mail;
|
||||
using Wino.Services.Extensions;
|
||||
using Xunit;
|
||||
using IMailService = Wino.Core.Domain.Interfaces.IMailService;
|
||||
|
||||
namespace Wino.Core.Tests.Synchronizers;
|
||||
|
||||
public sealed class ImapSynchronizerLiveTests
|
||||
{
|
||||
private const string ManualSkipMessage = "Manual live IMAP test. Fill Server/Port/Username/Password placeholders and remove Skip to run.";
|
||||
|
||||
// Replace placeholders with your own credentials when running these live tests.
|
||||
private const string Server = "imap.example.com";
|
||||
private const int Port = 993;
|
||||
private const string Username = "REPLACE_WITH_USERNAME";
|
||||
private const string Password = "REPLACE_WITH_PASSWORD";
|
||||
|
||||
[Fact(Skip = ManualSkipMessage)]
|
||||
[Trait("Category", "Live")]
|
||||
public async Task InitialSynchronization_DownloadsInboxMetadata()
|
||||
{
|
||||
using var context = await CreateContextAsync();
|
||||
|
||||
var result = await context.Synchronizer.SynchronizeMailsAsync(CreateCustomFolderSyncOptions(context.Account.Id, context.InboxFolder.Id));
|
||||
|
||||
result.CompletedState.Should().Be(SynchronizationCompletedState.Success);
|
||||
result.FolderResults.Should().ContainSingle();
|
||||
result.FolderResults[0].Success.Should().BeTrue();
|
||||
result.FolderResults[0].DownloadedCount.Should().BeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact(Skip = ManualSkipMessage)]
|
||||
[Trait("Category", "Live")]
|
||||
public async Task DeltaSynchronization_SecondRunDownloadsNoAdditionalMessages()
|
||||
{
|
||||
using var context = await CreateContextAsync();
|
||||
|
||||
var initialResult = await context.Synchronizer.SynchronizeMailsAsync(CreateCustomFolderSyncOptions(context.Account.Id, context.InboxFolder.Id));
|
||||
initialResult.CompletedState.Should().Be(SynchronizationCompletedState.Success);
|
||||
|
||||
var deltaResult = await context.Synchronizer.SynchronizeMailsAsync(CreateCustomFolderSyncOptions(context.Account.Id, context.InboxFolder.Id));
|
||||
|
||||
deltaResult.CompletedState.Should().Be(SynchronizationCompletedState.Success);
|
||||
deltaResult.FolderResults.Should().ContainSingle();
|
||||
deltaResult.FolderResults[0].DownloadedCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact(Skip = ManualSkipMessage)]
|
||||
[Trait("Category", "Live")]
|
||||
public async Task MarkFirstInboxMailUnreadThenRead_ValidatesReadFlagChanges()
|
||||
{
|
||||
using var context = await CreateContextAsync();
|
||||
|
||||
var firstUid = await GetFirstInboxMessageUidAsync(context.Account);
|
||||
var mailCopy = CreateInboxMailCopy(context.InboxFolder, firstUid);
|
||||
|
||||
await ExecuteMarkReadAsync(context.Synchronizer, mailCopy, isRead: false);
|
||||
(await GetIsSeenAsync(context.Account, firstUid)).Should().BeFalse();
|
||||
|
||||
await ExecuteMarkReadAsync(context.Synchronizer, mailCopy, isRead: true);
|
||||
(await GetIsSeenAsync(context.Account, firstUid)).Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact(Skip = ManualSkipMessage)]
|
||||
[Trait("Category", "Live")]
|
||||
public async Task MarkFirstInboxMailReadThenUnread_ValidatesUnreadFlagChanges()
|
||||
{
|
||||
using var context = await CreateContextAsync();
|
||||
|
||||
var firstUid = await GetFirstInboxMessageUidAsync(context.Account);
|
||||
var mailCopy = CreateInboxMailCopy(context.InboxFolder, firstUid);
|
||||
|
||||
await ExecuteMarkReadAsync(context.Synchronizer, mailCopy, isRead: true);
|
||||
(await GetIsSeenAsync(context.Account, firstUid)).Should().BeTrue();
|
||||
|
||||
await ExecuteMarkReadAsync(context.Synchronizer, mailCopy, isRead: false);
|
||||
(await GetIsSeenAsync(context.Account, firstUid)).Should().BeFalse();
|
||||
}
|
||||
|
||||
private static MailSynchronizationOptions CreateCustomFolderSyncOptions(Guid accountId, Guid folderId)
|
||||
=> new()
|
||||
{
|
||||
AccountId = accountId,
|
||||
Type = MailSynchronizationType.CustomFolders,
|
||||
SynchronizationFolderIds = [folderId]
|
||||
};
|
||||
|
||||
private static async Task ExecuteMarkReadAsync(ImapSynchronizer synchronizer, MailCopy mailCopy, bool isRead)
|
||||
{
|
||||
var requests = synchronizer.MarkRead(new BatchMarkReadRequest([new MarkReadRequest(mailCopy, isRead)]));
|
||||
await synchronizer.ExecuteNativeRequestsAsync(requests);
|
||||
}
|
||||
|
||||
private static MailCopy CreateInboxMailCopy(MailItemFolder folder, UniqueId uid)
|
||||
=> new()
|
||||
{
|
||||
Id = MailkitClientExtensions.CreateUid(folder.Id, uid.Id),
|
||||
AssignedFolder = folder,
|
||||
IsRead = false,
|
||||
Subject = "Live test placeholder"
|
||||
};
|
||||
|
||||
private static async Task<UniqueId> GetFirstInboxMessageUidAsync(MailAccount account)
|
||||
{
|
||||
using var client = await CreateConnectedClientAsync(account);
|
||||
var inbox = client.Inbox;
|
||||
|
||||
await inbox.OpenAsync(FolderAccess.ReadWrite);
|
||||
|
||||
var allUids = await inbox.SearchAsync(SearchQuery.All);
|
||||
allUids.Should().NotBeEmpty("Inbox must contain at least one message for mark-read live tests.");
|
||||
|
||||
var firstUid = allUids.First();
|
||||
|
||||
await inbox.CloseAsync();
|
||||
await client.DisconnectAsync(true);
|
||||
|
||||
return firstUid;
|
||||
}
|
||||
|
||||
private static async Task<bool> GetIsSeenAsync(MailAccount account, UniqueId uid)
|
||||
{
|
||||
using var client = await CreateConnectedClientAsync(account);
|
||||
var inbox = client.Inbox;
|
||||
|
||||
await inbox.OpenAsync(FolderAccess.ReadOnly);
|
||||
var summary = await inbox.FetchAsync([uid], MessageSummaryItems.Flags);
|
||||
|
||||
await inbox.CloseAsync();
|
||||
await client.DisconnectAsync(true);
|
||||
|
||||
summary.Should().ContainSingle();
|
||||
|
||||
var flags = summary[0].Flags;
|
||||
flags.Should().NotBeNull();
|
||||
|
||||
return flags!.Value.HasFlag(MessageFlags.Seen);
|
||||
}
|
||||
|
||||
private static async Task<ImapClient> CreateConnectedClientAsync(MailAccount account)
|
||||
{
|
||||
var client = new ImapClient();
|
||||
|
||||
await client.ConnectAsync(account.ServerInformation.IncomingServer, int.Parse(account.ServerInformation.IncomingServerPort), MailKit.Security.SecureSocketOptions.Auto);
|
||||
await client.AuthenticateAsync(account.ServerInformation.IncomingServerUsername, account.ServerInformation.IncomingServerPassword);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
private static async Task<LiveTestContext> CreateContextAsync()
|
||||
{
|
||||
var tempDirectory = Path.Combine(Path.GetTempPath(), "wino-imap-live-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(tempDirectory);
|
||||
|
||||
var account = new MailAccount
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Name = "IMAP Live Test",
|
||||
Address = Username,
|
||||
ProviderType = MailProviderType.IMAP4,
|
||||
ServerInformation = new CustomServerInformation
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
IncomingServer = Server,
|
||||
IncomingServerPort = Port.ToString(),
|
||||
IncomingServerUsername = Username,
|
||||
IncomingServerPassword = Password,
|
||||
IncomingServerSocketOption = ImapConnectionSecurity.Auto,
|
||||
MaxConcurrentClients = 2
|
||||
}
|
||||
};
|
||||
|
||||
var inboxFolder = new MailItemFolder
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
MailAccountId = account.Id,
|
||||
FolderName = "Inbox",
|
||||
RemoteFolderId = "INBOX",
|
||||
SpecialFolderType = SpecialFolderType.Inbox,
|
||||
IsSynchronizationEnabled = true,
|
||||
ShowUnreadCount = true,
|
||||
UidValidity = 0,
|
||||
HighestModeSeq = 0,
|
||||
HighestKnownUid = 0
|
||||
};
|
||||
|
||||
var storedMails = new Dictionary<string, MailCopy>();
|
||||
|
||||
var folderService = new Mock<IFolderService>();
|
||||
folderService.Setup(x => x.GetKnownUidsForFolderAsync(inboxFolder.Id))
|
||||
.ReturnsAsync(() => storedMails.Values.Select(m => MailkitClientExtensions.ResolveUid(m.Id)).ToList());
|
||||
folderService.Setup(x => x.UpdateFolderAsync(It.IsAny<MailItemFolder>())).Returns(Task.CompletedTask);
|
||||
folderService.Setup(x => x.UpdateFolderHighestModeSeqAsync(It.IsAny<Guid>(), It.IsAny<long>())).Returns(Task.CompletedTask);
|
||||
folderService.Setup(x => x.DeleteFolderAsync(It.IsAny<Guid>(), It.IsAny<string>())).Returns(Task.CompletedTask);
|
||||
|
||||
var mailService = new Mock<IMailService>();
|
||||
mailService.Setup(x => x.GetMailsByFolderIdAsync(inboxFolder.Id)).ReturnsAsync(() => storedMails.Values.ToList());
|
||||
mailService.Setup(x => x.GetExistingMailsAsync(inboxFolder.Id, It.IsAny<IEnumerable<UniqueId>>()))
|
||||
.ReturnsAsync((Guid _, IEnumerable<UniqueId> ids) =>
|
||||
ids.Select(uid => MailkitClientExtensions.CreateUid(inboxFolder.Id, uid.Id))
|
||||
.Where(storedMails.ContainsKey)
|
||||
.Select(id => storedMails[id])
|
||||
.ToList());
|
||||
mailService.Setup(x => x.CreateMailAsync(account.Id, It.IsAny<NewMailItemPackage>()))
|
||||
.ReturnsAsync((Guid _, NewMailItemPackage package) =>
|
||||
{
|
||||
storedMails[package.Copy.Id] = package.Copy;
|
||||
return true;
|
||||
});
|
||||
mailService.Setup(x => x.ChangeReadStatusAsync(It.IsAny<string>(), It.IsAny<bool>()))
|
||||
.Returns((string mailCopyId, bool isRead) =>
|
||||
{
|
||||
if (storedMails.TryGetValue(mailCopyId, out var copy))
|
||||
{
|
||||
copy.IsRead = isRead;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
mailService.Setup(x => x.ChangeFlagStatusAsync(It.IsAny<string>(), It.IsAny<bool>())).Returns(Task.CompletedTask);
|
||||
mailService.Setup(x => x.DeleteMailAsync(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.Returns((Guid _, string mailCopyId) =>
|
||||
{
|
||||
storedMails.Remove(mailCopyId);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
var changeProcessor = new Mock<IImapChangeProcessor>();
|
||||
changeProcessor.Setup(x => x.GetSynchronizationFoldersAsync(It.IsAny<MailSynchronizationOptions>()))
|
||||
.ReturnsAsync([inboxFolder]);
|
||||
changeProcessor.Setup(x => x.GetRecentMailIdsForFolderAsync(inboxFolder.Id, It.IsAny<int>()))
|
||||
.ReturnsAsync((Guid _, int count) => storedMails.Keys.Take(count).ToList());
|
||||
changeProcessor.Setup(x => x.GetDownloadedUnreadMailsAsync(account.Id, It.IsAny<IEnumerable<string>>()))
|
||||
.ReturnsAsync(new List<MailCopy>());
|
||||
|
||||
var appConfiguration = new Mock<IApplicationConfiguration>();
|
||||
appConfiguration.SetupProperty(x => x.ApplicationDataFolderPath, tempDirectory);
|
||||
appConfiguration.SetupProperty(x => x.PublisherSharedFolderPath, tempDirectory);
|
||||
appConfiguration.SetupProperty(x => x.ApplicationTempFolderPath, tempDirectory);
|
||||
appConfiguration.SetupGet(x => x.SentryDNS).Returns(string.Empty);
|
||||
|
||||
var unifiedSynchronizer = new UnifiedImapSynchronizer(
|
||||
folderService.Object,
|
||||
mailService.Object,
|
||||
Mock.Of<IImapSynchronizerErrorHandlerFactory>());
|
||||
|
||||
var synchronizer = new ImapSynchronizer(
|
||||
account,
|
||||
changeProcessor.Object,
|
||||
appConfiguration.Object,
|
||||
unifiedSynchronizer,
|
||||
Mock.Of<IImapSynchronizerErrorHandlerFactory>(),
|
||||
Mock.Of<ICalDavClient>(),
|
||||
Mock.Of<IAutoDiscoveryService>(),
|
||||
Mock.Of<ICalendarService>());
|
||||
|
||||
return await Task.FromResult(new LiveTestContext(account, inboxFolder, synchronizer, tempDirectory));
|
||||
}
|
||||
|
||||
private sealed class LiveTestContext(MailAccount account, MailItemFolder inboxFolder, ImapSynchronizer synchronizer, string tempDirectory) : IDisposable
|
||||
{
|
||||
public MailAccount Account { get; } = account;
|
||||
public MailItemFolder InboxFolder { get; } = inboxFolder;
|
||||
public ImapSynchronizer Synchronizer { get; } = synchronizer;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Synchronizer.KillSynchronizerAsync().GetAwaiter().GetResult();
|
||||
|
||||
if (Directory.Exists(tempDirectory))
|
||||
{
|
||||
Directory.Delete(tempDirectory, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user