mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-06 06:49:40 +00:00
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Verify (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
145 lines
7.6 KiB
C#
145 lines
7.6 KiB
C#
using System.Net.Sockets;
|
|
|
|
using AIStudio.Settings.DataModel;
|
|
using AIStudio.Tools.Mail;
|
|
|
|
using MailKit;
|
|
using MailKit.Net.Imap;
|
|
using MailKit.Security;
|
|
|
|
namespace AIStudio.Tests.Tools.Mail;
|
|
|
|
/// <summary>
|
|
/// Checks the parts of the IMAP connection which decide something without a server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Which failure an exception stands for matters more than it seems: only a rejected sign-in may
|
|
/// pause the mailbox, and a sign-in counted as a network failure would be tried again every
|
|
/// synchronization, until the directory locks the account.
|
|
/// </remarks>
|
|
[TestFixture]
|
|
public sealed class ImapMailboxConnectorTests
|
|
{
|
|
private const string PASSWORD = "correct horse battery staple";
|
|
|
|
private static readonly DataSourceMailbox MAILBOX = new()
|
|
{
|
|
Host = "imap.example.org",
|
|
Port = 993,
|
|
TransportSecurity = MailboxTransportSecurity.SSL_ON_CONNECT,
|
|
AuthMethod = MailboxAuthMethod.PASSWORD,
|
|
Username = "someone@example.org",
|
|
};
|
|
|
|
private static IEnumerable<TestCaseData> ExceptionsWithTheirFailure()
|
|
{
|
|
yield return Case(new AuthenticationException("The server rejected the credentials."), MailboxConnectionFailure.AUTHENTICATION_FAILED);
|
|
yield return Case(new SslHandshakeException("The certificate is not trusted."), MailboxConnectionFailure.TLS_FAILED);
|
|
yield return Case(new SocketException((int)SocketError.HostNotFound), MailboxConnectionFailure.NETWORK_UNAVAILABLE);
|
|
yield return Case(new IOException("The connection was reset.", new SocketException((int)SocketError.ConnectionReset)), MailboxConnectionFailure.NETWORK_UNAVAILABLE);
|
|
yield return Case(new TimeoutException(), MailboxConnectionFailure.NETWORK_UNAVAILABLE);
|
|
yield return Case(new OperationCanceledException(), MailboxConnectionFailure.NETWORK_UNAVAILABLE);
|
|
yield return Case(new ImapProtocolException("The server sent garbage."), MailboxConnectionFailure.SERVER_ERROR);
|
|
yield return Case(new FolderNotFoundException("Archive"), MailboxConnectionFailure.SERVER_ERROR);
|
|
yield break;
|
|
|
|
static TestCaseData Case(Exception exception, MailboxConnectionFailure failure) => new TestCaseData(exception, failure).SetArgDisplayNames(exception.GetType().Name, failure.ToString());
|
|
}
|
|
|
|
private static IEnumerable<TestCaseData> InvalidSettings()
|
|
{
|
|
yield return Case("unknown transport security", MAILBOX with { TransportSecurity = MailboxTransportSecurity.UNKNOWN }, PASSWORD);
|
|
yield return Case("unknown auth method", MAILBOX with { AuthMethod = MailboxAuthMethod.UNKNOWN }, PASSWORD);
|
|
yield return Case("no host", MAILBOX with { Host = " " }, PASSWORD);
|
|
yield return Case("a URL as host", MAILBOX with { Host = "imaps://imap.example.org" }, PASSWORD);
|
|
yield return Case("port 0", MAILBOX with { Port = 0 }, PASSWORD);
|
|
yield return Case("port 65536", MAILBOX with { Port = 65536 }, PASSWORD);
|
|
yield return Case("no username", MAILBOX with { Username = string.Empty }, PASSWORD);
|
|
yield return Case("no password", MAILBOX, string.Empty);
|
|
yield break;
|
|
|
|
static TestCaseData Case(string name, DataSourceMailbox mailbox, string password) => new TestCaseData(mailbox, password).SetArgDisplayNames(name);
|
|
}
|
|
|
|
[TestCaseSource(nameof(ExceptionsWithTheirFailure))]
|
|
public void AnExceptionOfTheClientStandsForItsFailure(Exception exception, MailboxConnectionFailure failure)
|
|
{
|
|
Assert.That(ImapMailboxConnector.Classify(exception, CancellationToken.None), Is.EqualTo(failure));
|
|
}
|
|
|
|
[Test]
|
|
public void ACancellationByTheUserIsNoFailure()
|
|
{
|
|
using var cancellation = new CancellationTokenSource();
|
|
cancellation.Cancel();
|
|
|
|
Assert.That(ImapMailboxConnector.Classify(new OperationCanceledException(cancellation.Token), cancellation.Token), Is.Null);
|
|
}
|
|
|
|
[Test]
|
|
public void AnExceptionOfAnotherKindIsLeftAlone()
|
|
{
|
|
Assert.That(ImapMailboxConnector.Classify(new InvalidOperationException(), CancellationToken.None), Is.Null);
|
|
}
|
|
|
|
[TestCaseSource(nameof(InvalidSettings))]
|
|
public async Task InvalidSettingsNeverReachTheServer(DataSourceMailbox mailbox, string password)
|
|
{
|
|
//
|
|
// The host of the mailbox does not exist. Were anything sent, the failure would be a network
|
|
// failure instead of this one.
|
|
//
|
|
await using var connector = new ImapMailboxConnector();
|
|
var exception = Assert.ThrowsAsync<MailboxConnectionException>(() => connector.ConnectAsync(mailbox, password, MailServerPolicy.ANY_SERVER, CancellationToken.None));
|
|
Assert.That(exception?.Failure, Is.EqualTo(MailboxConnectionFailure.INVALID_SETTINGS));
|
|
}
|
|
|
|
[Test]
|
|
public async Task AServerTheOrganizationDoesNotAllowIsNeverContacted()
|
|
{
|
|
//
|
|
// As above, the host of the mailbox does not exist. Were anything sent, the failure would be
|
|
// a network failure instead of this one.
|
|
//
|
|
await using var connector = new ImapMailboxConnector();
|
|
var policy = new MailServerPolicy(true, ["imap.intra.example.org"]);
|
|
var exception = Assert.ThrowsAsync<MailboxConnectionException>(() => connector.ConnectAsync(MAILBOX, PASSWORD, policy, CancellationToken.None));
|
|
Assert.That(exception?.Failure, Is.EqualTo(MailboxConnectionFailure.SERVER_NOT_ALLOWED));
|
|
}
|
|
|
|
[TestCase("Projects", '/', ExpectedResult = true)]
|
|
[TestCase("Projekte 2026", '.', ExpectedResult = true)]
|
|
[TestCase("Ablage/Alt", '.', ExpectedResult = true)]
|
|
[TestCase("Ablage/Alt", '/', ExpectedResult = false)]
|
|
[TestCase("Ablage.Alt", '.', ExpectedResult = false)]
|
|
[TestCase("Flat", '\0', ExpectedResult = true)]
|
|
[TestCase("", '/', ExpectedResult = false)]
|
|
[TestCase(" ", '/', ExpectedResult = false)]
|
|
[TestCase(" Projects", '/', ExpectedResult = false)]
|
|
[TestCase("All*", '/', ExpectedResult = false)]
|
|
[TestCase("100%", '/', ExpectedResult = false)]
|
|
[TestCase("Line\nBreak", '/', ExpectedResult = false)]
|
|
public bool AFolderNameNeedsNoReservedCharacter(string name, char directorySeparator) => ImapMailboxConnector.IsValidFolderName(name, directorySeparator);
|
|
|
|
[Test]
|
|
public void AFolderNameHasAMaximumLength()
|
|
{
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(ImapMailboxConnector.IsValidFolderName(new string('a', ImapMailboxConnector.MAX_FOLDER_NAME_LENGTH), '/'), Is.True);
|
|
Assert.That(ImapMailboxConnector.IsValidFolderName(new string('a', ImapMailboxConnector.MAX_FOLDER_NAME_LENGTH + 1), '/'), Is.False);
|
|
});
|
|
}
|
|
|
|
[TestCase(FolderAttributes.None, ExpectedResult = MailFolderSpecialUse.NONE)]
|
|
[TestCase(FolderAttributes.HasChildren | FolderAttributes.Subscribed, ExpectedResult = MailFolderSpecialUse.NONE)]
|
|
[TestCase(FolderAttributes.Sent, ExpectedResult = MailFolderSpecialUse.SENT)]
|
|
[TestCase(FolderAttributes.Drafts, ExpectedResult = MailFolderSpecialUse.DRAFTS)]
|
|
[TestCase(FolderAttributes.Archive, ExpectedResult = MailFolderSpecialUse.ARCHIVE)]
|
|
[TestCase(FolderAttributes.All, ExpectedResult = MailFolderSpecialUse.ALL)]
|
|
[TestCase(FolderAttributes.Flagged, ExpectedResult = MailFolderSpecialUse.FLAGGED)]
|
|
[TestCase(FolderAttributes.Important, ExpectedResult = MailFolderSpecialUse.IMPORTANT)]
|
|
[TestCase(FolderAttributes.Trash | FolderAttributes.HasNoChildren, ExpectedResult = MailFolderSpecialUse.TRASH)]
|
|
[TestCase(FolderAttributes.Junk | FolderAttributes.Important, ExpectedResult = MailFolderSpecialUse.JUNK)]
|
|
public MailFolderSpecialUse AFolderIsForWhatTheServerAnnounces(FolderAttributes attributes) => ImapMailboxConnector.ToSpecialUse(attributes);
|
|
} |