using System.Security.Cryptography; using MimeKit; using Omika.Core.Interfaces; using Omika.Core.Services; namespace Omika.Tests.Services; /// /// 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. /// 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 = $""; (int imgCount, int dataUriCount, IReadOnlyList 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 = $"" + $"
30/01/2026
31/01/2026
"; // Étape 1 : APERÇU (serveur → mobile). (_, _, IReadOnlyList apercu) = EmailInlineDiagnostic.Analyser(preview); // Étape 2 : REQUÊTE (le mobile renvoie le HTML inchangé). string request = preview; (_, _, IReadOnlyList requete) = EmailInlineDiagnostic.Analyser(request); // Étape 3 : SMTP (data URI → ressource CID). (string htmlSmtp, IReadOnlyList 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 imagesMime = mime.BodyParts.OfType() .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 = $""; // Le mobile ne renvoie qu'UNE image (ex. troncature) → la seconde disparaît. string request = $""; (_, _, IReadOnlyList apercu) = EmailInlineDiagnostic.Analyser(preview); (_, _, IReadOnlyList 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); } }