Refactored impa synchronization.
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
using FluentAssertions;
|
||||
using MailKit;
|
||||
using MailKit.Net.Imap;
|
||||
using Moq;
|
||||
using System.Reflection;
|
||||
using Wino.Core.Domain.Entities.Mail;
|
||||
using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Synchronizers.ImapSync;
|
||||
using Xunit;
|
||||
using IMailService = Wino.Core.Domain.Interfaces.IMailService;
|
||||
|
||||
namespace Wino.Core.Tests.Synchronizers;
|
||||
|
||||
public class UnifiedImapSynchronizerTests
|
||||
{
|
||||
private static UnifiedImapSynchronizer CreateSut()
|
||||
{
|
||||
return new UnifiedImapSynchronizer(
|
||||
Mock.Of<IFolderService>(),
|
||||
Mock.Of<IMailService>(),
|
||||
Mock.Of<IImapSynchronizerErrorHandlerFactory>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineSyncStrategy_ShouldPrioritizeQResync_WhenEnabledAndSupported()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
var strategy = sut.DetermineSyncStrategy(
|
||||
ImapCapabilities.QuickResync | ImapCapabilities.CondStore,
|
||||
isQResyncEnabled: true,
|
||||
serverHost: "imap.example.com");
|
||||
|
||||
strategy.Should().Be(ImapSyncStrategy.QResync);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineSyncStrategy_ShouldFallbackToCondstore_WhenQResyncNotEnabled()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
var strategy = sut.DetermineSyncStrategy(
|
||||
ImapCapabilities.QuickResync | ImapCapabilities.CondStore,
|
||||
isQResyncEnabled: false,
|
||||
serverHost: "imap.example.com");
|
||||
|
||||
strategy.Should().Be(ImapSyncStrategy.Condstore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineSyncStrategy_ShouldUseUidFallback_WhenNoAdvancedCapability()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
var strategy = sut.DetermineSyncStrategy(
|
||||
ImapCapabilities.None,
|
||||
isQResyncEnabled: false,
|
||||
serverHost: "imap.example.com");
|
||||
|
||||
strategy.Should().Be(ImapSyncStrategy.UidBased);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineSyncStrategy_ShouldRespectQuirkOverride_ForStrictProviders()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
var strategy = sut.DetermineSyncStrategy(
|
||||
ImapCapabilities.QuickResync | ImapCapabilities.CondStore,
|
||||
isQResyncEnabled: true,
|
||||
serverHost: "imap.qq.com");
|
||||
|
||||
strategy.Should().Be(ImapSyncStrategy.Condstore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineSyncStrategy_ShouldFallbackToUid_WhenCondstoreIsUnavailable()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
var strategy = sut.DetermineSyncStrategy(
|
||||
ImapCapabilities.QuickResync,
|
||||
isQResyncEnabled: false,
|
||||
serverHost: "imap.example.com");
|
||||
|
||||
strategy.Should().Be(ImapSyncStrategy.UidBased);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetermineSyncStrategy_ShouldFallbackToUid_WhenQuirkDisablesQresyncAndNoCondstore()
|
||||
{
|
||||
var sut = CreateSut();
|
||||
|
||||
var strategy = sut.DetermineSyncStrategy(
|
||||
ImapCapabilities.QuickResync,
|
||||
isQResyncEnabled: true,
|
||||
serverHost: "imap.163.com");
|
||||
|
||||
strategy.Should().Be(ImapSyncStrategy.UidBased);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateHighestKnownUid_ShouldUseMaxOfCurrentObservedAndUidNext()
|
||||
{
|
||||
var result = UnifiedImapSynchronizer.CalculateHighestKnownUid(
|
||||
currentHighestKnownUid: 100,
|
||||
uidNext: new MailKit.UniqueId(151),
|
||||
observedUids: new uint[] { 120, 140, 130 });
|
||||
|
||||
result.Should().Be(150);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateHighestKnownUid_ShouldNotRegress_WhenObservedUidsAreLower()
|
||||
{
|
||||
var result = UnifiedImapSynchronizer.CalculateHighestKnownUid(
|
||||
currentHighestKnownUid: 500,
|
||||
uidNext: null,
|
||||
observedUids: new uint[] { 110, 120, 130 });
|
||||
|
||||
result.Should().Be(500);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateHighestKnownUid_ShouldUseUidNextMinusOne_WhenNoObservedUids()
|
||||
{
|
||||
var result = UnifiedImapSynchronizer.CalculateHighestKnownUid(
|
||||
currentHighestKnownUid: 0,
|
||||
uidNext: new MailKit.UniqueId(901),
|
||||
observedUids: null);
|
||||
|
||||
result.Should().Be(900);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRunUidReconcile_ShouldReturnTrue_WhenNeverReconciled()
|
||||
{
|
||||
var shouldRun = UnifiedImapSynchronizer.ShouldRunUidReconcile(
|
||||
lastUidReconcileUtc: null,
|
||||
utcNow: DateTime.UtcNow,
|
||||
reconcileInterval: TimeSpan.FromHours(12));
|
||||
|
||||
shouldRun.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRunUidReconcile_ShouldReturnFalse_WhenWithinInterval()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var shouldRun = UnifiedImapSynchronizer.ShouldRunUidReconcile(
|
||||
lastUidReconcileUtc: now.AddHours(-1),
|
||||
utcNow: now,
|
||||
reconcileInterval: TimeSpan.FromHours(12));
|
||||
|
||||
shouldRun.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldRunUidReconcile_ShouldReturnTrue_WhenIntervalElapsed()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
var shouldRun = UnifiedImapSynchronizer.ShouldRunUidReconcile(
|
||||
lastUidReconcileUtc: now.AddHours(-13),
|
||||
utcNow: now,
|
||||
reconcileInterval: TimeSpan.FromHours(12));
|
||||
|
||||
shouldRun.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessSummariesAsync_ShouldUseMetadataOnlyPackage()
|
||||
{
|
||||
var localFolder = new MailItemFolder
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
MailAccountId = Guid.NewGuid(),
|
||||
FolderName = "Inbox",
|
||||
RemoteFolderId = "INBOX"
|
||||
};
|
||||
|
||||
var summaryMock = new Mock<IMessageSummary>();
|
||||
summaryMock.SetupGet(x => x.UniqueId).Returns(new UniqueId(42));
|
||||
summaryMock.SetupGet(x => x.Flags).Returns(MessageFlags.None);
|
||||
|
||||
var mailServiceMock = new Mock<IMailService>();
|
||||
mailServiceMock
|
||||
.Setup(x => x.GetExistingMailsAsync(localFolder.Id, It.IsAny<IEnumerable<UniqueId>>()))
|
||||
.ReturnsAsync(new List<MailCopy>());
|
||||
mailServiceMock
|
||||
.Setup(x => x.CreateMailAsync(localFolder.MailAccountId, It.IsAny<NewMailItemPackage>()))
|
||||
.ReturnsAsync(true);
|
||||
|
||||
var sut = new UnifiedImapSynchronizer(
|
||||
Mock.Of<IFolderService>(),
|
||||
mailServiceMock.Object,
|
||||
Mock.Of<IImapSynchronizerErrorHandlerFactory>());
|
||||
|
||||
ImapMessageCreationPackage? capturedPackage = null;
|
||||
|
||||
var imapSynchronizerMock = new Mock<IImapSynchronizer>();
|
||||
imapSynchronizerMock
|
||||
.Setup(x => x.CreateNewMailPackagesAsync(It.IsAny<ImapMessageCreationPackage>(), localFolder, It.IsAny<CancellationToken>()))
|
||||
.Callback<ImapMessageCreationPackage, MailItemFolder, CancellationToken>((package, _, _) => capturedPackage = package)
|
||||
.ReturnsAsync(new List<NewMailItemPackage>
|
||||
{
|
||||
new(new MailCopy { Id = "mail-id" }, null, localFolder.RemoteFolderId, Array.Empty<AccountContact>())
|
||||
});
|
||||
|
||||
var processMethod = typeof(UnifiedImapSynchronizer).GetMethod("ProcessSummariesAsync", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
processMethod.Should().NotBeNull();
|
||||
|
||||
var task = (Task<List<string>>)processMethod!.Invoke(
|
||||
sut,
|
||||
[imapSynchronizerMock.Object, localFolder, new List<IMessageSummary> { summaryMock.Object }, CancellationToken.None])!;
|
||||
|
||||
var result = await task;
|
||||
|
||||
result.Should().ContainSingle().Which.Should().Be("mail-id");
|
||||
capturedPackage.Should().NotBeNull();
|
||||
capturedPackage!.MimeMessage.Should().BeNull();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user