mirror of
https://github.com/MAKS-IT-COM/maksit-postclient.git
synced 2026-09-29 19:08:09 +02:00
368 lines
13 KiB
C#
368 lines
13 KiB
C#
using MimeKit;
|
|
|
|
|
|
namespace MaksIT.PostClient.Tests.Envelope;
|
|
|
|
|
|
public class EnvelopeClassifierTests {
|
|
[Fact]
|
|
public void TransportHeader_IsItalianPec() {
|
|
var info = EnvelopeClassifier.Classify(
|
|
[new KeyValuePair<string, string>("X-Trasporto", "posta-certificata")],
|
|
["daticert.xml", "postacert.eml"]);
|
|
Assert.Equal(EnvelopeKind.PecTransport, info.Kind);
|
|
Assert.Equal(EnvelopeRegion.Italy, info.Region);
|
|
Assert.Equal("PEC", info.Badge);
|
|
Assert.True(info.HasInnerMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void RicevutaHeader_IsItalianReceipt() {
|
|
var info = EnvelopeClassifier.Classify(
|
|
[new KeyValuePair<string, string>("X-Ricevuta", "accettazione")],
|
|
["daticert.xml"]);
|
|
Assert.Equal(EnvelopeKind.PecReceipt, info.Kind);
|
|
Assert.Equal(EnvelopeRegion.Italy, info.Region);
|
|
Assert.Equal("RIC", info.Badge);
|
|
Assert.False(info.HasInnerMessage);
|
|
Assert.Equal(ReceiptStatus.Accepted, info.EventStatus);
|
|
}
|
|
|
|
[Fact]
|
|
public void RiferimentoHeader_IsOriginalMessageId() {
|
|
var info = EnvelopeClassifier.Classify(
|
|
[
|
|
new KeyValuePair<string, string>("X-Ricevuta", "avvenuta-consegna"),
|
|
new KeyValuePair<string, string>("X-Riferimento-Message-ID", "<orig@studio.it>")
|
|
],
|
|
["daticert.xml"]);
|
|
Assert.Equal(EnvelopeKind.PecReceipt, info.Kind);
|
|
Assert.Equal("<orig@studio.it>", info.Msgid);
|
|
Assert.Equal(ReceiptStatus.Delivered, info.EventStatus);
|
|
}
|
|
|
|
[Fact]
|
|
public void EtsiXml_IsEuropeanRem() {
|
|
var info = EnvelopeClassifier.Classify(
|
|
[],
|
|
["evidence.xml"],
|
|
"<REMEvidence xmlns=\"http://uri.etsi.org/02640/v2#\"></REMEvidence>");
|
|
Assert.Equal(EnvelopeKind.EidasRem, info.Kind);
|
|
Assert.Equal(EnvelopeRegion.Europe, info.Region);
|
|
Assert.Equal("REM", info.Badge);
|
|
}
|
|
|
|
[Fact]
|
|
public void Ordinary_HasNoBadge() {
|
|
var info = EnvelopeClassifier.Classify([], ["invoice.pdf"]);
|
|
Assert.Equal(EnvelopeKind.Ordinary, info.Kind);
|
|
Assert.Equal("", info.Badge);
|
|
}
|
|
|
|
[Fact]
|
|
public void Label_ExplainsKind() {
|
|
Assert.Equal("Certified mail (PEC)", EnvelopeKind.Label(EnvelopeKind.PecTransport));
|
|
Assert.Equal("PEC receipt", EnvelopeKind.Label(EnvelopeKind.PecReceipt));
|
|
Assert.Equal("Certified mail (REM)", EnvelopeKind.Label(EnvelopeKind.EidasRem));
|
|
Assert.Equal("Digitally signed", EnvelopeKind.Label(EnvelopeKind.SmimeSigned));
|
|
Assert.Equal("Ordinary mail", EnvelopeKind.Label(EnvelopeKind.Ordinary));
|
|
}
|
|
}
|
|
|
|
|
|
public class DaticertParserTests {
|
|
[Fact]
|
|
public void Parse_ReadsTipoGestoreId() {
|
|
var xml = """
|
|
<postacert tipo="posta-certificata" errore="nessuno">
|
|
<intestazione>
|
|
<mittente>a@pec.example.it</mittente>
|
|
<oggetto>Fattura</oggetto>
|
|
</intestazione>
|
|
<dati>
|
|
<gestore-emittente>Example PEC</gestore-emittente>
|
|
<identificativo>id-1</identificativo>
|
|
<msgid><orig@studio.it></msgid>
|
|
</dati>
|
|
</postacert>
|
|
""";
|
|
var data = DaticertParser.Parse(xml);
|
|
Assert.NotNull(data);
|
|
Assert.Equal("posta-certificata", data.Tipo);
|
|
Assert.Equal("Example PEC", data.Gestore);
|
|
Assert.Equal("id-1", data.Identificativo);
|
|
Assert.Equal("a@pec.example.it", data.Mittente);
|
|
Assert.Equal("<orig@studio.it>", data.Msgid);
|
|
}
|
|
}
|
|
|
|
|
|
public class EnvelopeUnwrapperTests {
|
|
[Fact]
|
|
public void Inspect_UnwrapsPostacert() {
|
|
var xml = """
|
|
<postacert tipo="posta-certificata" errore="nessuno">
|
|
<dati>
|
|
<gestore-emittente>Example PEC</gestore-emittente>
|
|
<identificativo>id-1</identificativo>
|
|
</dati>
|
|
</postacert>
|
|
""";
|
|
var inner = new MimeMessage();
|
|
inner.From.Add(new MailboxAddress("Studio", "studio@example.it"));
|
|
inner.Subject = "Inner letter";
|
|
inner.Body = new TextPart("plain") { Text = "hello inner" };
|
|
|
|
var mixed = new Multipart("mixed");
|
|
mixed.Add(new TextPart("plain") { Text = "wrapper text from gestore" });
|
|
mixed.Add(XmlPart("daticert.xml", xml));
|
|
mixed.Add(Rfc822Part(inner, "postacert.eml"));
|
|
|
|
var outer = new MimeMessage();
|
|
outer.Headers.Add("X-Trasporto", "posta-certificata");
|
|
outer.Subject = "POSTA CERTIFICATA: Inner letter";
|
|
outer.From.Add(new MailboxAddress("", "posta-certificata@pec.example.it"));
|
|
outer.Body = mixed;
|
|
|
|
var parsed = EnvelopeUnwrapper.Inspect(outer);
|
|
Assert.Equal(EnvelopeKind.PecTransport, parsed.Info.Kind);
|
|
Assert.Equal(EnvelopeRegion.Italy, parsed.Info.Region);
|
|
Assert.Equal("Example PEC", parsed.Info.Gestore);
|
|
Assert.True(parsed.Info.HasInnerMessage);
|
|
Assert.Equal("Inner letter", parsed.Inner?.Subject);
|
|
Assert.Equal("hello inner", parsed.Inner?.TextBody);
|
|
}
|
|
|
|
[Fact]
|
|
public void Inspect_ReceiptWithoutInner() {
|
|
var xml = """
|
|
<postacert tipo="accettazione" errore="nessuno">
|
|
<dati>
|
|
<gestore-emittente>Example PEC</gestore-emittente>
|
|
<identificativo>id-2</identificativo>
|
|
</dati>
|
|
</postacert>
|
|
""";
|
|
var mixed = new Multipart("mixed");
|
|
mixed.Add(new TextPart("plain") { Text = "accettazione" });
|
|
mixed.Add(XmlPart("daticert.xml", xml));
|
|
var outer = new MimeMessage();
|
|
outer.Headers.Add("X-Ricevuta", "accettazione");
|
|
outer.Body = mixed;
|
|
var parsed = EnvelopeUnwrapper.Inspect(outer);
|
|
Assert.Equal(EnvelopeKind.PecReceipt, parsed.Info.Kind);
|
|
Assert.False(parsed.Info.HasInnerMessage);
|
|
Assert.Null(parsed.Inner);
|
|
}
|
|
|
|
[Fact]
|
|
public void Inspect_SavedPostclientObjects_IfPresent() {
|
|
var dir = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
"MaksIT",
|
|
"Postclient",
|
|
"objects");
|
|
if (!Directory.Exists(dir)) {
|
|
dir = Path.Combine(
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
"Postclient",
|
|
"objects");
|
|
}
|
|
if (!Directory.Exists(dir))
|
|
return;
|
|
var files = Directory.GetFiles(dir, "*.eml");
|
|
if (files.Length == 0)
|
|
return;
|
|
foreach (var file in files) {
|
|
MimeMessage mime;
|
|
try {
|
|
mime = MimeMessage.Load(file, TestContext.Current.CancellationToken);
|
|
}
|
|
catch (IOException) {
|
|
continue;
|
|
}
|
|
var parsed = EnvelopeUnwrapper.Inspect(mime);
|
|
if (parsed.Info.Kind == EnvelopeKind.Ordinary)
|
|
continue;
|
|
if (parsed.Info.Kind is EnvelopeKind.PecTransport or EnvelopeKind.PecReceipt)
|
|
Assert.Equal(EnvelopeRegion.Italy, parsed.Info.Region);
|
|
if (parsed.Info.Kind == EnvelopeKind.EidasRem)
|
|
Assert.Equal(EnvelopeRegion.Europe, parsed.Info.Region);
|
|
}
|
|
}
|
|
|
|
private static MimePart XmlPart(string name, string xml) {
|
|
var part = new MimePart("application", "xml") {
|
|
Content = new MimeContent(new MemoryStream(System.Text.Encoding.UTF8.GetBytes(xml))),
|
|
FileName = name
|
|
};
|
|
part.ContentDisposition = new ContentDisposition(ContentDisposition.Attachment) { FileName = name };
|
|
part.ContentType.Name = name;
|
|
return part;
|
|
}
|
|
|
|
private static MessagePart Rfc822Part(MimeMessage inner, string name) {
|
|
var part = new MessagePart { Message = inner };
|
|
part.ContentDisposition = new ContentDisposition(ContentDisposition.Attachment) { FileName = name };
|
|
part.ContentType.Name = name;
|
|
return part;
|
|
}
|
|
}
|
|
|
|
|
|
public class ReceiptStatusTests {
|
|
[Fact]
|
|
public void FromTipo_PecDeliveryIsNotRead() {
|
|
Assert.Equal(ReceiptStatus.Accepted, ReceiptStatus.FromTipo("accettazione"));
|
|
Assert.Equal(ReceiptStatus.Delivered, ReceiptStatus.FromTipo("avvenuta-consegna"));
|
|
Assert.Equal(ReceiptStatus.Failed, ReceiptStatus.FromTipo("mancata-consegna"));
|
|
Assert.Equal(
|
|
"Delivered to the recipient certified mailbox (not a read receipt).",
|
|
ReceiptStatus.Explain("avvenuta-consegna"));
|
|
}
|
|
|
|
[Fact]
|
|
public void FromTipo_RemRetrievalIsReadLike() {
|
|
Assert.Equal(ReceiptStatus.Accepted, ReceiptStatus.FromTipo("SubmissionAcceptance"));
|
|
Assert.Equal(ReceiptStatus.Delivered, ReceiptStatus.FromTipo("Delivery"));
|
|
Assert.Equal(ReceiptStatus.Retrieved, ReceiptStatus.FromTipo("ContentConsignment"));
|
|
Assert.Equal(ReceiptStatus.Retrieved, ReceiptStatus.FromTipo("Abholbestätigung"));
|
|
Assert.Equal(ReceiptStatus.Delivered, ReceiptStatus.FromTipo("avis-de-reception"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Combine_DoesNotDowngradeDeliveredToAccepted() {
|
|
var status = ReceiptStatus.Combine(ReceiptStatus.Submitted, ReceiptStatus.Accepted);
|
|
status = ReceiptStatus.Combine(status, ReceiptStatus.Delivered);
|
|
Assert.Equal(ReceiptStatus.Delivered, ReceiptStatus.Combine(status, ReceiptStatus.Accepted));
|
|
}
|
|
}
|
|
|
|
|
|
public class RemEvidenceParserTests {
|
|
[Fact]
|
|
public void Parse_ReadsEventAndRelatedId() {
|
|
var xml = """
|
|
<REMEvidence xmlns="http://uri.etsi.org/02640/v2#">
|
|
<EventCode>ContentConsignment</EventCode>
|
|
<RelatesTo>orig@studio.it</RelatesTo>
|
|
<EvidenceIdentifier>ev-1</EvidenceIdentifier>
|
|
</REMEvidence>
|
|
""";
|
|
var data = RemEvidenceParser.Parse(xml);
|
|
Assert.NotNull(data);
|
|
Assert.Equal("ContentConsignment", data.Event);
|
|
Assert.Equal("orig@studio.it", data.RelatedMessageId);
|
|
Assert.Equal("ev-1", data.EvidenceId);
|
|
}
|
|
}
|
|
|
|
|
|
public class SentReceiptStoreTests {
|
|
[Fact]
|
|
public void RememberAndApply_MatchesMsgidAndSubject() {
|
|
var path = Path.Combine(Path.GetTempPath(), "postclient-receipts-" + Guid.NewGuid().ToString("N") + ".json");
|
|
try {
|
|
var store = new FileSentReceiptStore(path);
|
|
store.Remember(new SentDispatch {
|
|
MailboxId = "box-1",
|
|
MessageId = "<orig@studio.it>",
|
|
Subject = "Fattura studio",
|
|
Status = ReceiptStatus.Submitted
|
|
});
|
|
Assert.Equal(ReceiptStatus.Submitted, store.StatusFor("box-1", "orig@studio.it"));
|
|
store.ApplyEvidence("box-1", "<orig@studio.it>", "id-9", "ACCETTAZIONE: Fattura studio", "accettazione");
|
|
Assert.Equal(ReceiptStatus.Accepted, store.StatusFor("box-1", "orig@studio.it"));
|
|
store.ApplyEvidence("box-1", "orig@studio.it", null, "CONSEGNA: Fattura studio", "avvenuta-consegna");
|
|
Assert.Equal(ReceiptStatus.Delivered, store.StatusFor("box-1", "<orig@studio.it>", "Fattura studio"));
|
|
store.ApplyEvidence("box-1", null, null, "ACCETTAZIONE: Fattura studio", "accettazione");
|
|
Assert.Equal(ReceiptStatus.Delivered, store.StatusFor("box-1", "orig@studio.it"));
|
|
}
|
|
finally {
|
|
if (File.Exists(path))
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ForgetMailbox_DropsOnlyThatAccount() {
|
|
var path = Path.Combine(Path.GetTempPath(), "postclient-receipts-" + Guid.NewGuid().ToString("N") + ".json");
|
|
try {
|
|
var store = new FileSentReceiptStore(path);
|
|
store.Remember(new SentDispatch {
|
|
MailboxId = "gmail-1",
|
|
MessageId = "gmail@msg",
|
|
Subject = "Hello from gmail",
|
|
Status = ReceiptStatus.Submitted
|
|
});
|
|
store.Remember(new SentDispatch {
|
|
MailboxId = "pec-1",
|
|
MessageId = "pec@msg",
|
|
Subject = "Certified",
|
|
Status = ReceiptStatus.Submitted
|
|
});
|
|
store.ForgetMailbox("gmail-1");
|
|
Assert.Equal("", store.StatusFor("gmail-1", "gmail@msg", "Hello from gmail"));
|
|
Assert.Equal(ReceiptStatus.Submitted, store.StatusFor("pec-1", "pec@msg"));
|
|
}
|
|
finally {
|
|
if (File.Exists(path))
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public class MimeBodyTests {
|
|
[Fact]
|
|
public async Task FromMimeAsync_LeavesMessageUnread() {
|
|
var mime = new MimeMessage();
|
|
mime.From.Add(new MailboxAddress("A", "a@b.c"));
|
|
mime.To.Add(new MailboxAddress("B", "b@c.d"));
|
|
mime.Subject = "Hello";
|
|
mime.Body = new TextPart("plain") { Text = "Hi" };
|
|
var body = await MimeBody.FromMimeAsync("INBOX", 8, mime, CancellationToken.None);
|
|
Assert.False(body.Header.IsSeen);
|
|
Assert.False(body.Header.IsFlagged);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FromMimeAsync_NamedInlinePartCountsAsAttachment() {
|
|
var mime = new MimeMessage();
|
|
mime.From.Add(new MailboxAddress("A", "a@b.c"));
|
|
mime.To.Add(new MailboxAddress("B", "b@c.d"));
|
|
mime.Subject = "Invoice";
|
|
var pdf = new MimePart("application", "pdf") {
|
|
Content = new MimeContent(new MemoryStream("pdf"u8.ToArray())),
|
|
ContentDisposition = new ContentDisposition(ContentDisposition.Inline),
|
|
FileName = "invoice.pdf"
|
|
};
|
|
mime.Body = new Multipart("mixed") {
|
|
new TextPart("plain") { Text = "See attached." },
|
|
pdf
|
|
};
|
|
var body = await MimeBody.FromMimeAsync("INBOX", 9, mime, CancellationToken.None);
|
|
Assert.True(body.Header.HasAttachments);
|
|
Assert.Contains(body.AttachmentFiles, f => f.Name == "invoice.pdf");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task FromMimeAsync_CidImageInHtmlIsNotAttachment() {
|
|
var mime = new MimeMessage();
|
|
mime.From.Add(new MailboxAddress("A", "a@b.c"));
|
|
mime.To.Add(new MailboxAddress("B", "b@c.d"));
|
|
mime.Subject = "Logo";
|
|
var image = new MimePart("image", "png") {
|
|
Content = new MimeContent(new MemoryStream([0x89, 0x50])),
|
|
ContentId = "logo@mail",
|
|
ContentDisposition = new ContentDisposition(ContentDisposition.Inline)
|
|
};
|
|
mime.Body = new Multipart("related") {
|
|
new TextPart("html") { Text = "<img src=\"cid:logo@mail\">" },
|
|
image
|
|
};
|
|
var body = await MimeBody.FromMimeAsync("INBOX", 10, mime, CancellationToken.None);
|
|
Assert.False(body.Header.HasAttachments);
|
|
}
|
|
}
|