227 lines
9.2 KiB
C#
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);
|
|
}
|
|
}
|