Files
Omika/tests/Omika.Tests/Mobile/Local/RapportPhotoRoundtripTests.cs
2026-10-23 18:46:50 +02:00

227 lines
9.2 KiB
C#

using System.IO.Packaging;
using System.Text.Json;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using Omika.App.ViewModels;
using Omika.Core.Contracts.Documents;
using Omika.Core.Services;
namespace Omika.Tests.Mobile.Local;
/// <summary>
/// Trace E2E d'une photo de rapport : la valeur serveur (ListeDonnesBD avec
/// <c>cheminComplet</c> + <c>notes</c>) doit survivre au roundtrip mobile
/// (désérialisation → sélection → sérialisation ListeDonnesMob) puis être
/// exploitée par <see cref="RapportPdfService"/> (image + note dans le DOCX).
/// </summary>
public class RapportPhotoRoundtripTests
{
private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);
// ------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------
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;
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 string PhotoJson(string id, string? guid, string nom, string date, string? cheminComplet, string? notes)
=> JsonSerializer.Serialize(new[]
{
new
{
id,
guid,
nom,
date,
chemin = "SousDossier",
cheminComplet,
notes
}
}, Json);
private static RapportChampSousElementDto SousElement(string rubrique, string? membreMob, string? type = null)
=> new() { Rubrique = rubrique, MembreMob = membreMob, Type = type ?? "ChpSimpleChaine" };
private static RapportChampDto TableauPhoto(string listeDonnesBD, params RapportChampSousElementDto[] membres)
=> new()
{
Id = "1",
Nom = "Photos",
Rubrique = "$TABPHOTO$",
Type = "Tableau",
GroupeApp = "PHOTO_INTER",
ListeDonnesBD = listeDonnesBD,
ListeMembre = membres
};
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 (int ImageParts, int Drawings, string Texte) 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));
return (imageParts, drawings, texte);
}
// ------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------
[Fact]
public void Photo_Roundtrip_CheminCompletEtNotes_SontConserves()
{
string chemin = Path.Combine(Path.GetTempPath(), "omika-photo-e2e", Guid.NewGuid().ToString("N"), "photo.png");
Directory.CreateDirectory(Path.GetDirectoryName(chemin)!);
File.WriteAllBytes(chemin, FakePng(320, 240));
try
{
string json = PhotoJson("35", Guid.NewGuid().ToString(), "photo123.jpg", "2024-01-01", chemin, "Peinture mur nord");
var edition = new RapportChampEdition(TableauPhoto(
json,
SousElement("$TABPHOTO.IMAGE$", "cheminComplet", "ChpImage"),
SousElement("$TABPHOTO.DATEIMAGE$", "saDate"),
SousElement("$TABPHOTO.NOTEIMAGE$", "saNotes")));
// Le mobile sélectionne la photo.
edition.Options[0].EstSelectionne = true;
string mob = edition.SerialiserValeur()!;
using JsonDocument doc = JsonDocument.Parse(mob);
JsonElement item = doc.RootElement.EnumerateArray().Single();
// Le chemin physique ABSOLU et la note survivent au roundtrip.
Assert.Equal(chemin, item.GetProperty("cheminComplet").GetString());
Assert.Equal("Peinture mur nord", item.GetProperty("notes").GetString());
Assert.Equal("photo123.jpg", item.GetProperty("nom").GetString());
}
finally
{
if (Directory.Exists(Path.GetDirectoryName(chemin)!))
{
Directory.Delete(Path.GetDirectoryName(chemin)!, recursive: true);
}
}
}
[Fact]
public void Photo_E2E_DocxContientImageEtNote()
{
string chemin = Path.Combine(Path.GetTempPath(), "omika-photo-e2e", Guid.NewGuid().ToString("N"), "photo.png");
Directory.CreateDirectory(Path.GetDirectoryName(chemin)!);
File.WriteAllBytes(chemin, FakePng(320, 240));
try
{
string json = PhotoJson("35", Guid.NewGuid().ToString(), "photo123.jpg", "2024-01-01", chemin, "Peinture mur nord");
var edition = new RapportChampEdition(TableauPhoto(
json,
SousElement("$TABPHOTO.IMAGE$", "cheminComplet", "ChpImage"),
SousElement("$TABPHOTO.DATEIMAGE$", "saDate"),
SousElement("$TABPHOTO.NOTEIMAGE$", "saNotes")));
edition.Options[0].EstSelectionne = true;
// Ce que le mobile envoie au serveur (ListeDonnesMob).
var champMob = new RapportChampDto
{
Id = "1",
Rubrique = "$TABPHOTO$",
Type = "Tableau",
ListeDonnesMob = edition.SerialiserValeur(),
ListeMembre =
[
SousElement("$TABPHOTO.IMAGE$", "cheminComplet", "ChpImage"),
SousElement("$TABPHOTO.DATEIMAGE$", "saDate"),
SousElement("$TABPHOTO.NOTEIMAGE$", "saNotes")
]
};
byte[] docx = CreerDocxAvecTableau("$TABPHOTO.IMAGE$", "$TABPHOTO.DATEIMAGE$", "$TABPHOTO.NOTEIMAGE$");
byte[] resultat = RapportPdfService.RemplirRubriques(docx, new[] { champMob });
var (imageParts, drawings, texte) = Inspecter(resultat);
// L'image est réellement insérée (ImagePart + Drawing) et la note présente.
Assert.Equal(1, imageParts);
Assert.Equal(1, drawings);
Assert.Contains("2024-01-01", texte);
Assert.Contains("Peinture mur nord", texte);
Assert.DoesNotContain("$TABPHOTO", texte);
}
finally
{
if (Directory.Exists(Path.GetDirectoryName(chemin)!))
{
Directory.Delete(Path.GetDirectoryName(chemin)!, recursive: true);
}
}
}
[Fact]
public void Photo_NoteNomDifferents_MappingCorrect()
{
// Nom ≠ note : l'UI ne doit jamais utiliser le nom de fichier comme note.
string json = PhotoJson("35", Guid.NewGuid().ToString(), "photo123.jpg", "2024-01-01", null, "Peinture mur nord");
var edition = new RapportChampEdition(TableauPhoto(json, SousElement("$TABPHOTO.NOTEIMAGE$", "saNotes")));
TableauOptionEdition option = Assert.Single(edition.Options);
Assert.Equal("photo123.jpg", option.NomPhoto);
Assert.Equal("Peinture mur nord", option.NotePhoto);
}
[Fact]
public void Photo_NoteAbsente_NomJamaisUtiliseCommeNote()
{
// Aucune note : la note est vide (jamais le nom de fichier).
string json = PhotoJson("35", Guid.NewGuid().ToString(), "photo123.jpg", "2024-01-01", null, null);
var edition = new RapportChampEdition(TableauPhoto(json, SousElement("$TABPHOTO.NOTEIMAGE$", "saNotes")));
TableauOptionEdition option = Assert.Single(edition.Options);
Assert.Equal("photo123.jpg", option.NomPhoto);
Assert.Null(option.NotePhoto);
}
}