Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,409 @@
|
||||
using System.IO.Packaging;
|
||||
using System.Text.Json;
|
||||
using DocumentFormat.OpenXml;
|
||||
using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using Omika.Core.Contracts.Documents;
|
||||
using Omika.Core.Services;
|
||||
using WP = DocumentFormat.OpenXml.Drawing.Wordprocessing;
|
||||
|
||||
namespace Omika.Tests.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Tests de l'insertion d'images ChpImage (Base64 / fichier) dans le DOCX par
|
||||
/// <see cref="RapportPdfService"/> : Drawing OpenXML + ImagePart, suppression du
|
||||
/// placeholder, non-régression des champs texte, conservation du ratio.
|
||||
/// </summary>
|
||||
public class RapportPdfServiceImageTests
|
||||
{
|
||||
// ------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
/// <summary>Construit des octets « PNG-like » (magic bytes + dimensions) pour les tests DOCX.</summary>
|
||||
private static byte[] FakePng(int largeur, int hauteur)
|
||||
{
|
||||
byte[] png = new byte[24];
|
||||
png[0] = 0x89; png[1] = 0x50; png[2] = 0x4E; png[3] = 0x47;
|
||||
png[4] = 0x0D; png[5] = 0x0A; png[6] = 0x1A; png[7] = 0x0A;
|
||||
png[12] = 0x49; png[13] = 0x48; png[14] = 0x44; png[15] = 0x52; // "IHDR"
|
||||
png[16] = (byte)(largeur >> 24); png[17] = (byte)(largeur >> 16);
|
||||
png[18] = (byte)(largeur >> 8); png[19] = (byte)largeur;
|
||||
png[20] = (byte)(hauteur >> 24); png[21] = (byte)(hauteur >> 16);
|
||||
png[22] = (byte)(hauteur >> 8); png[23] = (byte)hauteur;
|
||||
return png;
|
||||
}
|
||||
|
||||
private static byte[] CreerDocx(params string[] paragraphes)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using (WordprocessingDocument doc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document))
|
||||
{
|
||||
MainDocumentPart main = doc.AddMainDocumentPart();
|
||||
main.Document = new Document();
|
||||
Body body = new Body();
|
||||
foreach (string texte in paragraphes)
|
||||
{
|
||||
body.Append(new Paragraph(new Run(new Text(texte))));
|
||||
}
|
||||
main.Document.Append(body);
|
||||
main.Document.Save();
|
||||
}
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static byte[] CreerDocxAvecTableau(params string[] cellules)
|
||||
{
|
||||
using var stream = new MemoryStream();
|
||||
using (WordprocessingDocument doc = WordprocessingDocument.Create(stream, WordprocessingDocumentType.Document))
|
||||
{
|
||||
MainDocumentPart main = doc.AddMainDocumentPart();
|
||||
main.Document = new Document();
|
||||
Body body = new Body();
|
||||
var table = new Table();
|
||||
var row = new TableRow();
|
||||
foreach (string cellule in cellules)
|
||||
{
|
||||
row.Append(new TableCell(new Paragraph(new Run(new Text(cellule)))));
|
||||
}
|
||||
table.Append(row);
|
||||
body.Append(table);
|
||||
main.Document.Append(body);
|
||||
main.Document.Save();
|
||||
}
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static RapportChampDto ChampImage(string rubrique, string? valeur, string? membreMob = null)
|
||||
=> new()
|
||||
{
|
||||
Id = "1",
|
||||
Rubrique = rubrique,
|
||||
Type = "ChpImage",
|
||||
TypeMembre = "ChpImage",
|
||||
MembreMob = membreMob,
|
||||
ListeDonnesBD = valeur
|
||||
};
|
||||
|
||||
private static (int ImageParts, int Drawings, string Texte, IReadOnlyList<string> Extents) Inspecter(byte[] docx)
|
||||
{
|
||||
int imageParts;
|
||||
using (var pkgStream = new MemoryStream(docx))
|
||||
using (Package package = Package.Open(pkgStream, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
imageParts = package.GetParts()
|
||||
.Count(p => p.Uri.OriginalString.StartsWith("/word/media/", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
using var stream = new MemoryStream(docx);
|
||||
using WordprocessingDocument doc = WordprocessingDocument.Open(stream, false);
|
||||
MainDocumentPart main = doc.MainDocumentPart!;
|
||||
int drawings = main.Document.Body!.Descendants<Drawing>().Count();
|
||||
string texte = string.Concat(main.Document.Body.Descendants<Text>().Select(t => t.Text));
|
||||
var extents = main.Document.Body.Descendants<WP.Extent>()
|
||||
.Select(e => $"{e.Cx!.Value}x{e.Cy!.Value}")
|
||||
.ToList();
|
||||
return (imageParts, drawings, texte, extents);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Tests
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ChpImageSimple_Base64Png_InsereDrawingEtImagePart()
|
||||
{
|
||||
string base64 = Convert.ToBase64String(FakePng(256, 100));
|
||||
byte[] docx = CreerDocx("Signature: $UNCR.IMAGESIGNATURE$");
|
||||
var champs = new[] { ChampImage("$UNCR.IMAGESIGNATURE$", base64, "CR_Signature") };
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(1, imageParts);
|
||||
Assert.Equal(1, drawings);
|
||||
Assert.Contains("Signature: ", texte);
|
||||
Assert.DoesNotContain("$UNCR.IMAGESIGNATURE$", texte);
|
||||
Assert.DoesNotContain(base64, texte); // aucun Base64 visible
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImageSimple_DeuxSignatures_DeuxImagesDistinctes()
|
||||
{
|
||||
string signature1 = Convert.ToBase64String(FakePng(256, 100));
|
||||
string signature2 = Convert.ToBase64String(FakePng(512, 200));
|
||||
byte[] docx = CreerDocx("S1: $UNCR.IMAGESIGNATURE$", "S2: $UNCR.IMAGESIGNATURETECH$");
|
||||
var champs = new[]
|
||||
{
|
||||
ChampImage("$UNCR.IMAGESIGNATURE$", signature1, "CR_Signature"),
|
||||
ChampImage("$UNCR.IMAGESIGNATURETECH$", signature2, "CR_SignatureTech")
|
||||
};
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(2, imageParts);
|
||||
Assert.Equal(2, drawings);
|
||||
Assert.DoesNotContain(signature1, texte);
|
||||
Assert.DoesNotContain(signature2, texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImageSimple_SignatureVide_PlaceholderSupprime()
|
||||
{
|
||||
byte[] docx = CreerDocx("Signature: $UNCR.IMAGESIGNATURE$");
|
||||
var champs = new[] { ChampImage("$UNCR.IMAGESIGNATURE$", null, "CR_Signature") };
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(0, imageParts);
|
||||
Assert.Equal(0, drawings);
|
||||
Assert.DoesNotContain("$UNCR.IMAGESIGNATURE$", texte);
|
||||
Assert.DoesNotContain("null", texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImageTableau_DeuxSignatures_DeuxImagesDansDeuxLignes()
|
||||
{
|
||||
string signature1 = Convert.ToBase64String(FakePng(256, 100));
|
||||
string signature2 = Convert.ToBase64String(FakePng(512, 200));
|
||||
byte[] docx = CreerDocxAvecTableau("$TABCR.IMAGESIGNATURE$");
|
||||
string json = JsonSerializer.Serialize(new[]
|
||||
{
|
||||
new { signature = signature1 },
|
||||
new { signature = signature2 }
|
||||
});
|
||||
|
||||
var tableau = new RapportChampDto
|
||||
{
|
||||
Id = "1",
|
||||
Rubrique = "$TABCOMPTERENDU$",
|
||||
Type = "Tableau",
|
||||
ListeDonnesBD = json,
|
||||
ListeMembre = new[]
|
||||
{
|
||||
new RapportChampSousElementDto { Rubrique = "$TABCR.IMAGESIGNATURE$", MembreMob = "CR_Signature", Type = "ChpImage" }
|
||||
}
|
||||
};
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, new[] { tableau });
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(2, imageParts);
|
||||
Assert.Equal(2, drawings);
|
||||
Assert.DoesNotContain("$TABCR.IMAGESIGNATURE$", texte);
|
||||
Assert.DoesNotContain(signature1, texte);
|
||||
Assert.DoesNotContain(signature2, texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImageSimple_Unphoto_InsereImage()
|
||||
{
|
||||
string base64 = Convert.ToBase64String(FakePng(320, 240));
|
||||
byte[] docx = CreerDocx("Photo: $UNPHOTO.IMAGE$");
|
||||
var champs = new[] { ChampImage("$UNPHOTO.IMAGE$", base64, "saNom") };
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(1, imageParts);
|
||||
Assert.Equal(1, drawings);
|
||||
Assert.DoesNotContain("$UNPHOTO.IMAGE$", texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImageTableau_DeuxPhotos_DeuxImages()
|
||||
{
|
||||
string photo1 = Convert.ToBase64String(FakePng(320, 240));
|
||||
string photo2 = Convert.ToBase64String(FakePng(640, 480));
|
||||
byte[] docx = CreerDocxAvecTableau("$TABPHOTO.IMAGE$");
|
||||
string json = JsonSerializer.Serialize(new[]
|
||||
{
|
||||
new { nom = photo1 },
|
||||
new { nom = photo2 }
|
||||
});
|
||||
|
||||
var tableau = new RapportChampDto
|
||||
{
|
||||
Id = "1",
|
||||
Rubrique = "$TABPHOTO$",
|
||||
Type = "Tableau",
|
||||
ListeDonnesBD = json,
|
||||
ListeMembre = new[]
|
||||
{
|
||||
new RapportChampSousElementDto { Rubrique = "$TABPHOTO.IMAGE$", MembreMob = "DC_Nom", Type = "ChpImage" }
|
||||
}
|
||||
};
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, new[] { tableau });
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(2, imageParts);
|
||||
Assert.Equal(2, drawings);
|
||||
Assert.DoesNotContain("$TABPHOTO.IMAGE$", texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImageTableau_ImageDateNote_SurBonneLigne()
|
||||
{
|
||||
string photo1 = Convert.ToBase64String(FakePng(320, 240));
|
||||
string photo2 = Convert.ToBase64String(FakePng(640, 480));
|
||||
byte[] docx = CreerDocxAvecTableau("$TABPHOTO.IMAGE$", "$TABPHOTO.DATEIMAGE$", "$TABPHOTO.NOTEIMAGE$");
|
||||
string json = JsonSerializer.Serialize(new[]
|
||||
{
|
||||
new { nom = photo1, date = "2024-01-01", notes = "Note A" },
|
||||
new { nom = photo2, date = "2024-01-02", notes = "Note B" }
|
||||
});
|
||||
|
||||
var tableau = new RapportChampDto
|
||||
{
|
||||
Id = "1",
|
||||
Rubrique = "$TABPHOTO$",
|
||||
Type = "Tableau",
|
||||
ListeDonnesBD = json,
|
||||
ListeMembre = new[]
|
||||
{
|
||||
new RapportChampSousElementDto { Rubrique = "$TABPHOTO.IMAGE$", MembreMob = "DC_Nom", Type = "ChpImage" },
|
||||
new RapportChampSousElementDto { Rubrique = "$TABPHOTO.DATEIMAGE$", MembreMob = "saDate" },
|
||||
new RapportChampSousElementDto { Rubrique = "$TABPHOTO.NOTEIMAGE$", MembreMob = "saNotes" }
|
||||
}
|
||||
};
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, new[] { tableau });
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(2, imageParts);
|
||||
Assert.Equal(2, drawings);
|
||||
Assert.Contains("2024-01-01", texte);
|
||||
Assert.Contains("2024-01-02", texte);
|
||||
Assert.Contains("Note A", texte);
|
||||
Assert.Contains("Note B", texte);
|
||||
Assert.DoesNotContain("$TABPHOTO", texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImage_Base64Invalide_PlaceholderSupprimeSansCorruption()
|
||||
{
|
||||
byte[] docx = CreerDocx("Signature: $UNCR.IMAGESIGNATURE$");
|
||||
var champs = new[] { ChampImage("$UNCR.IMAGESIGNATURE$", "###pas-du-base64###", "CR_Signature") };
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(0, imageParts);
|
||||
Assert.Equal(0, drawings);
|
||||
Assert.DoesNotContain("$UNCR.IMAGESIGNATURE$", texte);
|
||||
Assert.DoesNotContain("pas-du-base64", texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImage_RatioImage_Conserve()
|
||||
{
|
||||
string base64 = Convert.ToBase64String(FakePng(256, 100)); // ratio 2.56
|
||||
byte[] docx = CreerDocx("$UNCR.IMAGESIGNATURE$");
|
||||
var champs = new[] { ChampImage("$UNCR.IMAGESIGNATURE$", base64, "CR_Signature") };
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (_, _, _, extents) = Inspecter(resultat);
|
||||
|
||||
string extent = Assert.Single(extents);
|
||||
string[] parts = extent.Split('x');
|
||||
long cx = long.Parse(parts[0]);
|
||||
long cy = long.Parse(parts[1]);
|
||||
// Largeur bornée à 54,9 mm ; hauteur proportionnelle au ratio 100/256.
|
||||
Assert.Equal(1976400L, cx);
|
||||
Assert.Equal((long)(1976400L * 100.0 / 256.0), cy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImage_ChampTexte_NonRegression()
|
||||
{
|
||||
byte[] docx = CreerDocx("Réf: $REFINTERNE$");
|
||||
var champs = new[]
|
||||
{
|
||||
new RapportChampDto { Id = "1", Rubrique = "$REFINTERNE$", Type = "ChpSimpleChaine", ListeDonnesBD = "ABC-123" }
|
||||
};
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(0, imageParts);
|
||||
Assert.Equal(0, drawings);
|
||||
Assert.Contains("ABC-123", texte);
|
||||
Assert.DoesNotContain("$REFINTERNE$", texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChpImage_ImagePart_PlaceSousWordMedia()
|
||||
{
|
||||
// La photo doit être placée sous /word/media/ avec une cible RELATIVE :
|
||||
// un ImagePart à la racine (/media/…) avec une cible absolue (/media/…)
|
||||
// est invisible à l'export PDF de LibreOffice (photo absente du PDF).
|
||||
// Le SDK OpenXML 3.x (issue #1730) place les images à la racine ; le
|
||||
// moteur doit donc insérer l'image manuellement sous /word/media/.
|
||||
string base64 = Convert.ToBase64String(FakePng(256, 100));
|
||||
byte[] docx = CreerDocx("$UNPHOTO.IMAGE$");
|
||||
var champs = new[] { ChampImage("$UNPHOTO.IMAGE$", base64, "saNom") };
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
|
||||
using var pkgStream = new MemoryStream(resultat);
|
||||
using Package package = Package.Open(pkgStream, FileMode.Open, FileAccess.Read);
|
||||
|
||||
var mediaParts = package.GetParts()
|
||||
.Where(p => p.Uri.OriginalString.StartsWith("/word/media/", StringComparison.Ordinal))
|
||||
.ToList();
|
||||
Assert.Single(mediaParts);
|
||||
Assert.StartsWith("/word/media/", mediaParts[0].Uri.OriginalString, StringComparison.Ordinal);
|
||||
|
||||
// La cible de la relation image est RELATIVE (jamais /media/… absolue).
|
||||
var imageRelations = package
|
||||
.GetPart(new Uri("/word/document.xml", UriKind.Relative))
|
||||
.GetRelationshipsByType("http://schemas.openxmlformats.org/officeDocument/2006/relationships/image")
|
||||
.ToList();
|
||||
Assert.Single(imageRelations);
|
||||
Assert.False(imageRelations[0].TargetUri.OriginalString.StartsWith("/", StringComparison.Ordinal));
|
||||
Assert.StartsWith("media/", imageRelations[0].TargetUri.OriginalString, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemplirRubriques_DateHeureEtDate_FormatesSelonTypeEtMasque()
|
||||
{
|
||||
// §9/§22 : la même valeur source (31/08/2022 08:00) produit deux rendus.
|
||||
byte[] docx = CreerDocx("Début: $UNRDV.DATEDEBUT$ / Date: $UNRDV.DATEDEBUT_D$");
|
||||
var champs = new[]
|
||||
{
|
||||
new RapportChampDto { Id = "1", Rubrique = "$UNRDV.DATEDEBUT$", Type = "ChpSimpleDateHeure", MasqueSaisie = "JJ/MM/AAAA HH:mm", ListeDonnesBD = "31/08/2022 08:00:00" },
|
||||
new RapportChampDto { Id = "2", Rubrique = "$UNRDV.DATEDEBUT_D$", Type = "ChpSimpleDate", MasqueSaisie = "JJ/MM/AAAA", ListeDonnesBD = "31/08/2022 08:00:00" }
|
||||
};
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (_, _, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Contains("31/08/2022 08:00", texte);
|
||||
Assert.Contains("31/08/2022", texte);
|
||||
Assert.DoesNotContain("31/08/2022 08:00:00", texte); // jamais la valeur brute
|
||||
Assert.DoesNotContain("$UNRDV", texte);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Batik_MotClefTexte_NonRegression()
|
||||
{
|
||||
// Le moteur partagé doit continuer à substituer les mot-clefs Batik
|
||||
// (<CLIENT.NOM>) en texte, sans jamais les traiter comme des images.
|
||||
byte[] docx = CreerDocx("Client: <CLIENT.NOM>");
|
||||
var champs = new[]
|
||||
{
|
||||
new RapportChampDto { Id = "1", Rubrique = "<CLIENT.NOM>", Type = "ChpSimpleChaine", ListeDonnesBD = "Acme" }
|
||||
};
|
||||
|
||||
byte[] resultat = RapportPdfService.RemplirRubriques(docx, champs);
|
||||
var (imageParts, drawings, texte, _) = Inspecter(resultat);
|
||||
|
||||
Assert.Equal(0, imageParts);
|
||||
Assert.Equal(0, drawings);
|
||||
Assert.Contains("Acme", texte);
|
||||
Assert.DoesNotContain("<CLIENT.NOM>", texte);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user