Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(IMAP): Support basic SEARCH command and handle unsupported others correctly with a 'NO'. #1448

Merged
merged 2 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Rnwood.Smtp4dev.Tests/E2E/E2ETests_Imap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using MailKit;
using MailKit.Net.Imap;
using MailKit.Net.Smtp;
using MailKit.Search;
using MailKit.Security;
using MimeKit;
using System;
Expand Down
299 changes: 299 additions & 0 deletions Rnwood.Smtp4dev.Tests/ImapSearchTranslatorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
using MimeKit;
using Rnwood.Smtp4dev.Data;
using Rnwood.Smtp4dev.Server.Settings;
using Rnwood.Smtp4dev.Server;
using Rnwood.Smtp4dev.Tests.DBMigrations.Helpers;
using Rnwood.SmtpServer;
using System.Threading.Tasks;
using System;
using Xunit;
using Rnwood.Smtp4dev.Server.Imap;
using LumiSoft.Net.IMAP;
using System.Linq.Dynamic.Core;
using FluentAssertions;
using Rnwood.Smtp4dev.Migrations;
using Org.BouncyCastle.Bcpg;

namespace Rnwood.Smtp4dev.Tests
{

public class ImapSearchTranslatorTests
{
[Fact]
public async Task Unsupported()
{

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

Assert.Throws<ImapSearchCriteriaNotSupportedException>(() =>
{
imapSearchTranslator.Translate(new IMAP_Search_Key_Undeleted());
});


}

[Fact]
public async Task Subject()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1");
DbModel.Message testMessage2 = await GetTestMessage("Message subject2");
DbModel.Message testMessage3 = await GetTestMessage("Message subject3");
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_Subject("notfound"));

var results = context.Messages.Where(criteria);
Assert.Empty(results);

criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_Subject("subject2"));

results = context.Messages.Where(criteria);
results.Should().Contain([testMessage2]);

}

[Fact]
public async Task To()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1", to: "to1@to.com");
DbModel.Message testMessage2 = await GetTestMessage("Message subject2", to: "to2@to.com");
DbModel.Message testMessage3 = await GetTestMessage("Message subject3", to: "to3@to.com");
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_To("notfound"));

var results = context.Messages.Where(criteria);
Assert.Empty(results);

criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_To("to2"));

results = context.Messages.Where(criteria);
results.Should().Contain([testMessage2]);

}


[Fact]
public async Task From()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1", from: "to1@to.com");
DbModel.Message testMessage2 = await GetTestMessage("Message subject2", from: "to2@to.com");
DbModel.Message testMessage3 = await GetTestMessage("Message subject3", from: "to3@to.com");
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_From("notfound"));

var results = context.Messages.Where(criteria);
Assert.Empty(results);

criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_From("to2"));

results = context.Messages.Where(criteria);
results.Should().Contain([testMessage2]);

}

[Fact]
public async Task Unseen()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1", unread: false);
DbModel.Message testMessage2 = await GetTestMessage("Message subject2", unread: true);
DbModel.Message testMessage3 = await GetTestMessage("Message subject3", unread: false);
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_Unseen());
var results = context.Messages.Where(criteria);
results.Should().Contain([testMessage2]);

}

[Fact]
public async Task Seen()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1", unread: false);
DbModel.Message testMessage2 = await GetTestMessage("Message subject2", unread: true);
DbModel.Message testMessage3 = await GetTestMessage("Message subject3", unread: false);
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_Seen());
var results = context.Messages.Where(criteria);
results.Should().Contain([testMessage1, testMessage3]);

}

[Fact]
public async Task Not()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1");
DbModel.Message testMessage2 = await GetTestMessage("Message subject2");
DbModel.Message testMessage3 = await GetTestMessage("Message subject3");
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_Not(new IMAP_Search_Key_Subject("subject2")));

var results = context.Messages.Where(criteria);
results.Should().Contain([testMessage1, testMessage3]);
}

[Fact]
public async Task Or()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1");
DbModel.Message testMessage2 = await GetTestMessage("Message subject2");
DbModel.Message testMessage3 = await GetTestMessage("Message subject3");
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_Or(new IMAP_Search_Key_Subject("subject1"), new IMAP_Search_Key_Subject("subject2")));

var results = context.Messages.Where(criteria);
results.Should().Contain([testMessage1, testMessage2]);

criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_Or(new IMAP_Search_Key_Subject("subject1"), new IMAP_Search_Key_Subject("notfound")));

results = context.Messages.Where(criteria);
results.Should().Contain([testMessage1]);
}

[Fact]
public async Task And()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1");
DbModel.Message testMessage2 = await GetTestMessage("Message subject2");
DbModel.Message testMessage3 = await GetTestMessage("Message subject3");
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var group = new IMAP_Search_Key_Group();
group.Keys.AddRange([new IMAP_Search_Key_Subject("subject"), new IMAP_Search_Key_Subject("1")]);
var criteria = imapSearchTranslator.Translate(group);
var results = context.Messages.Where(criteria);
results.Should().Contain([testMessage1]);

group = new IMAP_Search_Key_Group();
group.Keys.AddRange([new IMAP_Search_Key_Subject("subject"), new IMAP_Search_Key_Subject("notfound")]);
criteria = imapSearchTranslator.Translate(group);
results = context.Messages.Where(criteria);
Assert.Empty(results);
}


[Fact]
public async Task All()
{
SqliteInMemory m = new SqliteInMemory();

DbModel.Message testMessage1 = await GetTestMessage("Message subject1");
DbModel.Message testMessage2 = await GetTestMessage("Message subject2");
DbModel.Message testMessage3 = await GetTestMessage("Message subject3");
var sqlLiteForTesting = new SqliteInMemory();
var context = new Smtp4devDbContext(sqlLiteForTesting.ContextOptions);

context.AddRange(testMessage1, testMessage2, testMessage3);
context.SaveChanges();

ImapSearchTranslator imapSearchTranslator = new ImapSearchTranslator();

var criteria = imapSearchTranslator.Translate(new IMAP_Search_Key_All());

var results = context.Messages.Where(criteria);
results.Should().Contain([testMessage1, testMessage2, testMessage3]);


}

private static async Task<DbModel.Message> GetTestMessage(string subject, string from = "from@from.com", string to = "to@to.com", Boolean unread = true)
{
MimeMessage mimeMessage = new MimeMessage();
mimeMessage.From.Add(InternetAddress.Parse(from));
mimeMessage.To.Add(InternetAddress.Parse(to));

mimeMessage.Subject = subject;
BodyBuilder bodyBuilder = new BodyBuilder();
bodyBuilder.HtmlBody = "<html>Hi</html>";
bodyBuilder.TextBody = "Hi";

mimeMessage.Body = bodyBuilder.ToMessageBody();

MemoryMessageBuilder memoryMessageBuilder = new MemoryMessageBuilder();
memoryMessageBuilder.Recipients.Add(to);
memoryMessageBuilder.From = from;
memoryMessageBuilder.ReceivedDate = DateTime.Now;
using (var messageData = await memoryMessageBuilder.WriteData())
{
mimeMessage.WriteTo(messageData);
}

IMessage message = await memoryMessageBuilder.ToMessage();

var dbMessage = await new MessageConverter().ConvertAsync(message);
dbMessage.Mailbox = new DbModel.Mailbox { Name = MailboxOptions.DEFAULTNAME };
dbMessage.IsUnread = unread;

return dbMessage;
}
}
}
3 changes: 2 additions & 1 deletion Rnwood.Smtp4dev/Rnwood.Smtp4dev.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.60" />
<PackageReference Include="Jint" Version="3.0.1" />
<PackageReference Include="LinqKit" Version="1.2.5" />
<PackageReference Include="MailKit" Version="4.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Hosting.WindowsServices" Version="8.0.4" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.9.2" />
Expand All @@ -52,7 +53,7 @@
<PackageReference Include="MimeKit" Version="4.5.0" />
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
<PackageReference Include="NSwag.AspNetCore" Version="14.0.7" />
<PackageReference Include="Rnwood.LumiSoft.Net" Version="1.0.1" />
<PackageReference Include="Rnwood.LumiSoft.Net" Version="1.0.2" />


<PackageReference Include="Serilog" Version="3.1.1" />
Expand Down
Loading