113 lines
5.1 KiB
C#
113 lines
5.1 KiB
C#
using System.Security.Cryptography;
|
|
using MimeKit;
|
|
using Omika.Core.Interfaces;
|
|
using Omika.Core.Services;
|
|
|
|
namespace Omika.Tests.Services;
|
|
|
|
/// <summary>
|
|
/// Diagnostic roundtrip : suit la même image (par SHA256) à travers
|
|
/// APERÇU (data URI) → REQUÊTE mobile (écho) → SMTP (ressource CID), pour
|
|
/// identifier précisément l'étape où une image disparaîtrait. Aucun contenu
|
|
/// n'est journalisé — uniquement des empreintes.
|
|
/// </summary>
|
|
public class EmailRoundtripDiagnosticTests
|
|
{
|
|
private static byte[] JpegA() => [0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46];
|
|
private static byte[] JpegB() => [0xFF, 0xD8, 0xFF, 0xE1, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
|
|
|
|
private static string DataUri(byte[] bytes) => $"data:image/jpeg;base64,{Convert.ToBase64String(bytes)}";
|
|
|
|
private static byte[] Contenu(MimePart part)
|
|
{
|
|
using var ms = new MemoryStream();
|
|
part.Content.DecodeTo(ms);
|
|
return ms.ToArray();
|
|
}
|
|
|
|
private static string Sha256(byte[] data)
|
|
=> Convert.ToHexString(SHA256.HashData(data));
|
|
|
|
// ------------------------------------------------------------------
|
|
// L'analyseur produit des empreintes déterministes (sans contenu)
|
|
// ------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void Analyser_CompteEtEmpreintes_Deterministes()
|
|
{
|
|
string html = $"<img src=\"{DataUri(JpegA())}\"><img src=\"{DataUri(JpegB())}\">";
|
|
|
|
(int imgCount, int dataUriCount, IReadOnlyList<EmailInlineDiagnostic.ImageTrace> images) =
|
|
EmailInlineDiagnostic.Analyser(html);
|
|
|
|
Assert.Equal(2, imgCount);
|
|
Assert.Equal(2, dataUriCount);
|
|
Assert.Equal(2, images.Count);
|
|
Assert.All(images, i => Assert.Equal("image/jpeg", i.Mime));
|
|
Assert.Equal(Sha256(JpegA()), images[0].Sha256);
|
|
Assert.Equal(Sha256(JpegB()), images[1].Sha256);
|
|
Assert.DoesNotContain("base64", images[0].Sha256);
|
|
Assert.DoesNotContain(Convert.ToBase64String(JpegA()), images[0].Sha256);
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Roundtrip : APERÇU → REQUÊTE → SMTP (mêmes empreintes)
|
|
// ------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void Roundtrip_HashApercu_Request_Smtp_Identiques()
|
|
{
|
|
string preview = $"<table><tr><td><img src=\"{DataUri(JpegA())}\"></td><td>30/01/2026</td></tr>" +
|
|
$"<tr><td><img src=\"{DataUri(JpegB())}\"></td><td>31/01/2026</td></tr></table>";
|
|
|
|
// Étape 1 : APERÇU (serveur → mobile).
|
|
(_, _, IReadOnlyList<EmailInlineDiagnostic.ImageTrace> apercu) = EmailInlineDiagnostic.Analyser(preview);
|
|
|
|
// Étape 2 : REQUÊTE (le mobile renvoie le HTML inchangé).
|
|
string request = preview;
|
|
(_, _, IReadOnlyList<EmailInlineDiagnostic.ImageTrace> requete) = EmailInlineDiagnostic.Analyser(request);
|
|
|
|
// Étape 3 : SMTP (data URI → ressource CID).
|
|
(string htmlSmtp, IReadOnlyList<EmailSmtpInline.Ressource> ressources) = EmailSmtpInline.Convertir(request);
|
|
Assert.Equal(2, ressources.Count);
|
|
Assert.DoesNotContain("data:", htmlSmtp);
|
|
|
|
// Étape 4 : MimeMessage réel (LinkedResources).
|
|
MimeMessage mime = MailKitEmailSender.ConstruireMime(
|
|
new EmailMessage { Sujet = "S", CorpsHtml = request, Destinataires = ["d@x.fr"] },
|
|
"exp@x.fr");
|
|
List<MimePart> imagesMime = mime.BodyParts.OfType<MimePart>()
|
|
.Where(p => p.ContentType.MediaType == "image").ToList();
|
|
Assert.Equal(2, imagesMime.Count);
|
|
|
|
// Correspondance par SHA256, photo par photo.
|
|
Assert.Equal(apercu[0].Sha256, requete[0].Sha256);
|
|
Assert.Equal(apercu[1].Sha256, requete[1].Sha256);
|
|
Assert.Equal(apercu[0].Sha256, Sha256(ressources[0].Data));
|
|
Assert.Equal(apercu[1].Sha256, Sha256(ressources[1].Data));
|
|
Assert.Equal(apercu[0].Sha256, Sha256(Contenu(imagesMime[0])));
|
|
Assert.Equal(apercu[1].Sha256, Sha256(Contenu(imagesMime[1])));
|
|
}
|
|
|
|
// ------------------------------------------------------------------
|
|
// Perte simulée : une image absente de la requête → détectée par hash
|
|
// ------------------------------------------------------------------
|
|
|
|
[Fact]
|
|
public void Roundtrip_ImageManquante_DetecteeParHash()
|
|
{
|
|
string preview = $"<img src=\"{DataUri(JpegA())}\"><img src=\"{DataUri(JpegB())}\">";
|
|
// Le mobile ne renvoie qu'UNE image (ex. troncature) → la seconde disparaît.
|
|
string request = $"<img src=\"{DataUri(JpegA())}\">";
|
|
|
|
(_, _, IReadOnlyList<EmailInlineDiagnostic.ImageTrace> apercu) = EmailInlineDiagnostic.Analyser(preview);
|
|
(_, _, IReadOnlyList<EmailInlineDiagnostic.ImageTrace> requete) = EmailInlineDiagnostic.Analyser(request);
|
|
|
|
Assert.Equal(2, apercu.Count);
|
|
Assert.Single(requete);
|
|
Assert.Equal(apercu[0].Sha256, requete[0].Sha256);
|
|
// La photo B est perdue au roundtrip : détectable par absence d'empreinte.
|
|
Assert.DoesNotContain(requete, i => i.Sha256 == apercu[1].Sha256);
|
|
}
|
|
}
|