mirror of
https://github.com/MAKS-IT-COM/maksit-postclient.git
synced 2026-11-13 21:56:11 +01:00
357 lines
12 KiB
C#
357 lines
12 KiB
C#
using System.Text;
|
|
using MaksIT.PostClient.Client;
|
|
using MaksIT.PostClient.Shared;
|
|
|
|
|
|
namespace MaksIT.PostClient.Tests;
|
|
|
|
|
|
public class MailRuleEngineTests {
|
|
[Fact]
|
|
public void Matches_FromAndSubject() {
|
|
var rule = new MailRule {
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Archive",
|
|
Logic = "and",
|
|
Conditions = [
|
|
new() { Field = "from", Op = "contains", Value = "comune.it" },
|
|
new() { Field = "subject", Op = "contains", Value = "IMU" }
|
|
]
|
|
};
|
|
Assert.True(MailRuleEngine.Matches(rule, "sportello@comune.it", "me@studio.it", "Avviso IMU 2026", "", false));
|
|
Assert.False(MailRuleEngine.Matches(rule, "sportello@comune.it", "me@studio.it", "TARI", "", false));
|
|
}
|
|
|
|
[Fact]
|
|
public void ResolveFolder_MapsOutlookDeletedItems() {
|
|
var folders = new List<(string Name, string FullName)> {
|
|
("INBOX", "INBOX"),
|
|
("Cestino", "Cestino")
|
|
};
|
|
Assert.Equal("Cestino", MailRuleEngine.ResolveFolder("Deleted Items", folders));
|
|
}
|
|
|
|
[Fact]
|
|
public void Merge_ReplacesSameName() {
|
|
var into = new List<MailRule> {
|
|
new() {
|
|
Name = "IMU",
|
|
Action = MailRuleAction.Flag,
|
|
Conditions = [new() { Field = "subject", Value = "old" }]
|
|
}
|
|
};
|
|
var added = MailRuleEngine.Merge(into, [
|
|
new MailRule {
|
|
Name = "IMU",
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Pratiche",
|
|
Conditions = [new() { Field = "subject", Value = "IMU" }]
|
|
}
|
|
]);
|
|
Assert.Equal(1, added);
|
|
Assert.Single(into);
|
|
Assert.Equal(MailRuleAction.Move, into[0].Action);
|
|
Assert.Equal("Pratiche", into[0].Folder);
|
|
}
|
|
|
|
[Fact]
|
|
public void ExactFolder_RequiresNameOrFullName() {
|
|
var folders = new List<(string Name, string FullName)> {
|
|
("Inbox", "INBOX"),
|
|
("Temu", "Temu"),
|
|
("Cestino", "Cestino")
|
|
};
|
|
Assert.Equal("Temu", MailRuleEngine.ExactFolder("Temu", folders));
|
|
Assert.Equal("INBOX", MailRuleEngine.ExactFolder("Inbox", folders));
|
|
Assert.Equal("Projects/Temu", MailRuleEngine.ExactFolder("Temu", [("Temu", "Projects/Temu")]));
|
|
Assert.Equal("Temu", MailRuleEngine.ExactFolder("Inbox/Temu", [("Temu", "Temu")]));
|
|
Assert.Equal("Temu", MailRuleEngine.ExactFolder(@"Personal Folders\Temu", [("Temu", "Temu")]));
|
|
Assert.Null(MailRuleEngine.ExactFolder("Deleted Items", folders));
|
|
Assert.Null(MailRuleEngine.ExactFolder("emu", folders));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanApply_NeedsMailboxAndExactFolder() {
|
|
var folders = new List<(string Name, string FullName)> { ("Temu", "Temu") };
|
|
var rule = new MailRule {
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Temu",
|
|
MailboxId = "box-1",
|
|
Conditions = [new() { Field = "from", Value = "temuemail.com" }]
|
|
};
|
|
Assert.True(MailRuleEngine.CanApply(rule, "box-1", folders));
|
|
Assert.False(MailRuleEngine.CanApply(rule, "box-2", folders));
|
|
rule.MailboxId = "";
|
|
Assert.False(MailRuleEngine.CanApply(rule, "box-1", folders));
|
|
rule.MailboxId = "box-1";
|
|
rule.Folder = "Amazon";
|
|
Assert.False(MailRuleEngine.CanApply(rule, "box-1", folders));
|
|
}
|
|
|
|
[Fact]
|
|
public void FolderMailbox_FallsBackToRuleMailbox() {
|
|
var rule = new MailRule { MailboxId = "imap-1", Folder = "Archive" };
|
|
Assert.Equal("imap-1", MailRuleEngine.FolderMailbox(rule));
|
|
rule.FolderMailboxId = "pst-1";
|
|
Assert.Equal("pst-1", MailRuleEngine.FolderMailbox(rule));
|
|
}
|
|
|
|
[Fact]
|
|
public void CanApply_MoveFolderOnAnotherMailbox() {
|
|
var dest = new List<(string Name, string FullName)> { ("Archive", "Archive") };
|
|
var rule = new MailRule {
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Archive",
|
|
MailboxId = "imap-1",
|
|
FolderMailboxId = "pst-1",
|
|
Conditions = [new() { Field = "from", Value = "comune.it" }]
|
|
};
|
|
Assert.True(MailRuleEngine.CanApply(rule, "imap-1", dest));
|
|
Assert.False(MailRuleEngine.CanApply(rule, "pst-1", dest));
|
|
}
|
|
|
|
[Fact]
|
|
public void RetargetFolders_RewritesPathAndMailbox() {
|
|
var rules = new List<MailRule> {
|
|
new() {
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Project",
|
|
MailboxId = "imap-1",
|
|
FolderMailboxId = "imap-1",
|
|
Conditions = [new() { Field = "from", Value = "a" }]
|
|
}
|
|
};
|
|
Assert.Equal(1, MailRuleEngine.RetargetFolders(rules, "imap-1", "Project", "Archive/Project", "pst-1"));
|
|
Assert.Equal("Archive/Project", rules[0].Folder);
|
|
Assert.Equal("pst-1", rules[0].FolderMailboxId);
|
|
}
|
|
|
|
[Fact]
|
|
public void Merge_KeepsMailboxId() {
|
|
var into = new List<MailRule> {
|
|
new() {
|
|
Name = "Temu",
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Temu",
|
|
Conditions = [new() { Field = "from", Value = "temu" }]
|
|
}
|
|
};
|
|
MailRuleEngine.Merge(into, [
|
|
new MailRule {
|
|
Name = "Temu",
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Temu",
|
|
MailboxId = "box-9",
|
|
Conditions = [new() { Field = "from", Value = "temuemail.com" }]
|
|
}
|
|
]);
|
|
Assert.Equal("box-9", into[0].MailboxId);
|
|
}
|
|
|
|
[Fact]
|
|
public void Merge_KeepsFolderMailboxId() {
|
|
var into = new List<MailRule> {
|
|
new() {
|
|
Name = "Temu",
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Temu",
|
|
MailboxId = "imap-1",
|
|
Conditions = [new() { Field = "from", Value = "temu" }]
|
|
}
|
|
};
|
|
MailRuleEngine.Merge(into, [
|
|
new MailRule {
|
|
Name = "Temu",
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Temu",
|
|
MailboxId = "imap-1",
|
|
FolderMailboxId = "pst-1",
|
|
Conditions = [new() { Field = "from", Value = "temuemail.com" }]
|
|
}
|
|
]);
|
|
Assert.Equal("pst-1", into[0].FolderMailboxId);
|
|
}
|
|
}
|
|
|
|
|
|
public class MailRetentionTests {
|
|
[Fact]
|
|
public void Jobs_SkipsZeroDays() {
|
|
var jobs = MailRetention.Jobs(
|
|
[
|
|
new FolderRetention { MailboxId = "box", Folder = "INBOX", Days = 0 },
|
|
new FolderRetention { MailboxId = "box", Folder = "Pratiche", Days = 30 }
|
|
]);
|
|
var job = Assert.Single(jobs);
|
|
Assert.Equal("box", job.MailboxId);
|
|
Assert.Equal("Pratiche", job.Folder);
|
|
Assert.Equal(30, job.Days);
|
|
}
|
|
|
|
[Fact]
|
|
public void ResolveTrash_PrefersExistingTrashFolder() {
|
|
Assert.Equal("Cestino", MailRetention.ResolveTrash(["INBOX", "Cestino"]));
|
|
Assert.Equal(MailRetention.TrashFolder, MailRetention.ResolveTrash(["INBOX"]));
|
|
Assert.True(MailRetention.IsTrash("Deleted Items"));
|
|
}
|
|
}
|
|
|
|
|
|
public class MapiRestrictionTests {
|
|
[Fact]
|
|
public void Parse_SubjectContainsAndMarkRead() {
|
|
var subject = Encoding.Unicode.GetBytes("fattura\0");
|
|
var condition = new byte[] { 0x03, 0x01, 0x00, 0x00, 0x00, 0x37, 0x00, 0x1F, 0x00 }
|
|
.Concat(subject)
|
|
.ToArray();
|
|
var actions = new byte[] { 0x01, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
var rule = MapiRestriction.Parse("Fatture", condition, actions);
|
|
Assert.NotNull(rule);
|
|
Assert.Equal("Fatture", rule!.Name);
|
|
Assert.Equal(MailRuleAction.MarkRead, rule.Action);
|
|
Assert.Equal("subject", rule.Conditions[0].Field);
|
|
Assert.Equal("fattura", rule.Conditions[0].Value);
|
|
Assert.True(MailRuleEngine.Matches(rule, "a@b.it", "", "Fattura PA", "", false));
|
|
}
|
|
}
|
|
|
|
|
|
public class OutlookRulesTests {
|
|
[Fact]
|
|
public void FromBytes_ReadsJsonRules() {
|
|
var json = """
|
|
[
|
|
{
|
|
"name": "Comune",
|
|
"action": "move",
|
|
"folder": "Archive",
|
|
"mailboxId": "box-1",
|
|
"conditions": [{ "field": "from", "op": "contains", "value": "comune.it" }]
|
|
}
|
|
]
|
|
"""u8.ToArray();
|
|
var rules = OutlookRules.FromBytes(json, "rules.json");
|
|
Assert.Single(rules);
|
|
Assert.Equal("Comune", rules[0].Name);
|
|
Assert.Equal(MailRuleAction.Move, rules[0].Action);
|
|
Assert.Equal("Archive", rules[0].Folder);
|
|
Assert.Equal("box-1", rules[0].MailboxId);
|
|
}
|
|
|
|
[Fact]
|
|
public void FromBytes_ReadsModernRwz() {
|
|
var bytes = ModernRwz(
|
|
("temuemail.com", "temuemail.com", "Temu"),
|
|
("iliad", "iliad.it", "Iliad"),
|
|
("'amazon.it' or 'amazon.com'", "amazon.it", "Amazon"));
|
|
var rules = OutlookRules.FromBytes(bytes, "rules.rwz");
|
|
Assert.Equal(3, rules.Count);
|
|
Assert.Equal("temuemail.com", rules[0].Name);
|
|
Assert.Equal("Temu", rules[0].Folder);
|
|
Assert.Equal("temuemail.com", rules[0].Conditions[0].Value);
|
|
Assert.Equal("iliad", rules[1].Name);
|
|
Assert.Equal("iliad.it", rules[1].Conditions[0].Value);
|
|
Assert.Equal("Iliad", rules[1].Folder);
|
|
Assert.Equal("or", rules[2].Logic);
|
|
Assert.Contains(rules[2].Conditions, c => c.Value == "amazon.it");
|
|
Assert.Contains(rules[2].Conditions, c => c.Value == "amazon.com");
|
|
Assert.Equal("Amazon", rules[2].Folder);
|
|
Assert.True(MailRuleEngine.Matches(rules[2], "order@amazon.com", "", "Order", "", false));
|
|
}
|
|
|
|
[Fact]
|
|
public void FromBytes_ReadsOutlookExportIfPresent() {
|
|
var path = SampleRwz();
|
|
Assert.SkipWhen(path is null, "Untitled.rwz is a local Outlook export, not in CI.");
|
|
var rules = OutlookRules.FromFile(path!);
|
|
Assert.True(rules.Count >= 20, "expected the exported Outlook rules, got " + rules.Count);
|
|
Assert.Contains(rules, r => r.Folder == "Temu" && r.Conditions.Any(c => c.Value.Contains("temuemail.com")));
|
|
Assert.Contains(rules, r => r.Folder == "Iliad" && r.Conditions.Any(c => c.Value.Contains("iliad.it")));
|
|
Assert.Contains(rules, r => r.Folder == "Amazon" && r.Logic == "or");
|
|
Assert.Contains(rules, r => r.Folder == "Studio Giovannini");
|
|
Assert.DoesNotContain(rules, r => r.Name.Contains('@'));
|
|
}
|
|
|
|
[Fact]
|
|
public void ToJson_RoundTrips() {
|
|
var json = OutlookRules.ToJson([
|
|
new MailRule {
|
|
Name = "Comune",
|
|
Action = MailRuleAction.Move,
|
|
Folder = "Archive",
|
|
MailboxId = "box-1",
|
|
Conditions = [new() { Field = "from", Value = "comune.it" }]
|
|
}
|
|
]);
|
|
var rules = OutlookRules.FromBytes(json, "rules.json");
|
|
Assert.Single(rules);
|
|
Assert.Equal("Comune", rules[0].Name);
|
|
Assert.Equal("box-1", rules[0].MailboxId);
|
|
}
|
|
|
|
private static byte[] ModernRwz(params (string Name, string From, string Folder)[] rules) {
|
|
using var buffer = new MemoryStream();
|
|
var header = new byte[32];
|
|
header[2] = 0x14;
|
|
header[6] = 0x14;
|
|
header[7] = 0x06;
|
|
buffer.Write(header);
|
|
WriteInt(buffer, 1);
|
|
WriteInt(buffer, 1);
|
|
WriteInt(buffer, 0);
|
|
WriteInt(buffer, rules.Length);
|
|
WriteName(buffer, "user@example.com");
|
|
WritePath(buffer, @"C:\mail.ost");
|
|
WriteFolder(buffer, "Inbox");
|
|
foreach (var rule in rules) {
|
|
WriteName(buffer, rule.Name);
|
|
WriteFrom(buffer, rule.From);
|
|
WritePath(buffer, @"C:\mail.ost");
|
|
WriteFolder(buffer, rule.Folder);
|
|
}
|
|
|
|
return buffer.ToArray();
|
|
}
|
|
|
|
private static void WriteName(Stream stream, string text) {
|
|
stream.WriteByte(0x14);
|
|
stream.WriteByte(0);
|
|
WriteCounted(stream, text);
|
|
}
|
|
|
|
private static void WriteFrom(Stream stream, string text) {
|
|
stream.Write([0x01, 0x80, 0xE6, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
|
WriteCounted(stream, text);
|
|
}
|
|
|
|
private static void WritePath(Stream stream, string path) {
|
|
stream.Write(Encoding.Unicode.GetBytes(path));
|
|
stream.WriteByte(0);
|
|
stream.WriteByte(0);
|
|
}
|
|
|
|
private static void WriteFolder(Stream stream, string text) =>
|
|
WriteCounted(stream, text);
|
|
|
|
private static void WriteCounted(Stream stream, string text) {
|
|
var chars = Encoding.Unicode.GetBytes(text);
|
|
stream.WriteByte((byte)(chars.Length / 2));
|
|
stream.Write(chars);
|
|
}
|
|
|
|
private static void WriteInt(Stream stream, int value) =>
|
|
stream.Write(BitConverter.GetBytes(value));
|
|
|
|
private static string? SampleRwz() {
|
|
var dir = AppContext.BaseDirectory;
|
|
for (var i = 0; i < 10 && !string.IsNullOrEmpty(dir); i++) {
|
|
var path = Path.Combine(dir, "Untitled.rwz");
|
|
if (File.Exists(path))
|
|
return path;
|
|
dir = Path.GetDirectoryName(dir) ?? "";
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|