225 lines
8.3 KiB
C#
225 lines
8.3 KiB
C#
using System.IO.Compression;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using DocumentFormat.OpenXml.Packaging;
|
|
using DocumentFormat.OpenXml.Validation;
|
|
using GerbilManagerWebAPI.Contracts;
|
|
|
|
namespace GerbilManager.Tests;
|
|
|
|
/// <summary>
|
|
/// FEAT-13: Tests des Abgabevertrag-Generators. Die erzeugte .docx wird als
|
|
/// Zip geöffnet und auf word/document.xml geprüft — kein Word nötig.
|
|
/// </summary>
|
|
public class ContractGeneratorTests
|
|
{
|
|
private static BreederProfile Seller() => new()
|
|
{
|
|
ZuchtName = "Zucht Testhausen",
|
|
Name = "Frau Erika Muster",
|
|
Address = "Musterweg 1, 12345 Testhausen",
|
|
Phone = "0123 456789",
|
|
Email = "zucht@example.org",
|
|
Homepage = "https://zucht.example.org/",
|
|
City = "Testhausen",
|
|
};
|
|
|
|
private static ContractAnimal Krümel() => new(
|
|
Name: "Krümel",
|
|
Geschlecht: "Weiblich",
|
|
Geburtsdatum: new DateOnly(2025, 3, 9),
|
|
Farbschlag: "Agouti");
|
|
|
|
private static ContractData OneAnimal() => new(
|
|
Seller: Seller(),
|
|
Buyer: new ContractBuyer(
|
|
Name: "Herr Max Beispiel",
|
|
Address: "Beispielallee 7, 54321 Beispielstadt",
|
|
Phone: "0987 654321",
|
|
Email: "max@example.com"),
|
|
Animals: [Krümel()],
|
|
Price: 72m,
|
|
HandoverDate: new DateOnly(2026, 6, 5));
|
|
|
|
/// <summary>
|
|
/// Sichtbarer Text aus word/document.xml der erzeugten Datei. Tags werden
|
|
/// entfernt, damit Aussagen über Run-Grenzen hinweg möglich sind (Werte
|
|
/// und Fließtext liegen in getrennten w:t-Runs).
|
|
/// </summary>
|
|
private static string DocumentText(byte[] docx)
|
|
{
|
|
using var zip = new ZipArchive(new MemoryStream(docx), ZipArchiveMode.Read);
|
|
var entry = Assert.Single(zip.Entries, e => e.FullName == "word/document.xml");
|
|
using var reader = new StreamReader(entry.Open(), Encoding.UTF8);
|
|
return Regex.Replace(reader.ReadToEnd(), "<[^>]+>", "");
|
|
}
|
|
|
|
[Fact]
|
|
public void EinTier_EnthaeltAlleVertragswerte()
|
|
{
|
|
var text = DocumentText(ContractGenerator.Generate(OneAnimal()));
|
|
|
|
// Verkäufer-Block (aus dem Zuchtprofil)
|
|
Assert.Contains("Zucht Testhausen", text);
|
|
Assert.Contains("Frau Erika Muster", text);
|
|
Assert.Contains("Musterweg 1, 12345 Testhausen", text);
|
|
Assert.Contains("0123 456789", text);
|
|
Assert.Contains("zucht@example.org", text);
|
|
Assert.Contains("https://zucht.example.org/", text);
|
|
|
|
// Käufer-Block
|
|
Assert.Contains("Herr Max Beispiel", text);
|
|
Assert.Contains("Beispielallee 7, 54321 Beispielstadt", text);
|
|
Assert.Contains("0987 654321", text);
|
|
Assert.Contains("max@example.com", text);
|
|
|
|
// Tier
|
|
Assert.Contains("Krümel", text);
|
|
Assert.Contains("Weiblich", text);
|
|
Assert.Contains("09.03.2025", text);
|
|
Assert.Contains("Agouti", text);
|
|
|
|
// Kaufpreis (de-DE) + Übergabedatum (TT.MM.JJJJ)
|
|
Assert.Contains("Kaufpreis von 72,00 €", text);
|
|
Assert.Contains("am 05.06.2026 dem Käufer", text);
|
|
|
|
// Unterschriftszeile: Ort des Züchters + Vertragsdatum (= Übergabedatum)
|
|
Assert.Contains("Testhausen, den 05.06.2026", text);
|
|
}
|
|
|
|
[Fact]
|
|
public void KeineUnersetztenTokens()
|
|
{
|
|
var text = DocumentText(ContractGenerator.Generate(OneAnimal()));
|
|
Assert.DoesNotContain("{{", text);
|
|
Assert.DoesNotContain("}}", text);
|
|
}
|
|
|
|
[Fact]
|
|
public void DreiTiere_KlontDieTierTabelleProTier()
|
|
{
|
|
var data = OneAnimal() with
|
|
{
|
|
Animals =
|
|
[
|
|
Krümel(),
|
|
new ContractAnimal("Fridolin", "Männlich", new DateOnly(2023, 5, 1), "Schwarz"),
|
|
new ContractAnimal("Luna", "Weiblich", new DateOnly(2023, 8, 15), "Gold"),
|
|
],
|
|
};
|
|
|
|
var text = DocumentText(ContractGenerator.Generate(data));
|
|
|
|
Assert.Contains("Krümel", text);
|
|
Assert.Contains("Fridolin", text);
|
|
Assert.Contains("Luna", text);
|
|
Assert.Contains("01.05.2023", text);
|
|
Assert.Contains("15.08.2023", text);
|
|
|
|
// Pro Tier eine Tabelle: „Tierart:“ ist das fixe Label jeder Tier-Tabelle.
|
|
Assert.Equal(3, Regex.Matches(text, "Tierart:").Count);
|
|
Assert.DoesNotContain("{{", text);
|
|
}
|
|
|
|
[Fact]
|
|
public void PreisUndDatum_DeutscheFormate()
|
|
{
|
|
var data = OneAnimal() with { Price = 1234.5m, HandoverDate = new DateOnly(2026, 1, 3) };
|
|
var text = DocumentText(ContractGenerator.Generate(data));
|
|
|
|
Assert.Contains("Kaufpreis von 1.234,50 €", text);
|
|
Assert.Contains("am 03.01.2026 dem Käufer", text);
|
|
}
|
|
|
|
[Fact]
|
|
public void VertragsDatum_UeberschreibtUebergabedatumInDerUnterschriftszeile()
|
|
{
|
|
var data = OneAnimal() with { ContractDate = new DateOnly(2026, 6, 7) };
|
|
var text = DocumentText(ContractGenerator.Generate(data));
|
|
|
|
Assert.Contains("Testhausen, den 07.06.2026", text);
|
|
Assert.Contains("am 05.06.2026 dem Käufer", text); // Übergabedatum bleibt eigenständig
|
|
}
|
|
|
|
[Fact]
|
|
public void OptionaleFelder_LeerStattPlatzhalter()
|
|
{
|
|
var data = OneAnimal() with
|
|
{
|
|
Buyer = new ContractBuyer("Frau Lisa Test", "Testgasse 2, 11111 Teststadt"),
|
|
Animals = [new ContractAnimal("Momo", "Unbekannt")],
|
|
};
|
|
|
|
var text = DocumentText(ContractGenerator.Generate(data));
|
|
|
|
Assert.Contains("Frau Lisa Test", text);
|
|
Assert.Contains("Momo", text);
|
|
Assert.DoesNotContain("{{", text);
|
|
Assert.DoesNotContain("null", text, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
[Fact]
|
|
public void OhneTiere_WirftArgumentException()
|
|
{
|
|
var data = OneAnimal() with { Animals = [] };
|
|
var ex = Assert.Throws<ArgumentException>(() => ContractGenerator.Generate(data));
|
|
Assert.Contains("mindestens ein Tier", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public void ErzeugteDatei_IstEinGueltigesDocxPaket()
|
|
{
|
|
var docx = ContractGenerator.Generate(OneAnimal());
|
|
|
|
using var zip = new ZipArchive(new MemoryStream(docx), ZipArchiveMode.Read);
|
|
Assert.Contains(zip.Entries, e => e.FullName == "[Content_Types].xml");
|
|
Assert.Contains(zip.Entries, e => e.FullName == "word/document.xml");
|
|
// Tierfotos wurden beim Vorlagenbau entfernt, das Zucht-Logo bleibt.
|
|
Assert.Contains(zip.Entries, e => e.FullName == "word/media/image1.jpeg");
|
|
Assert.DoesNotContain(zip.Entries, e => e.FullName == "word/media/image2.jpeg");
|
|
}
|
|
|
|
[Fact]
|
|
public void ErzeugtesDokument_ValidiertGegenOpenXmlSchema()
|
|
{
|
|
// Office-2013-Schema: die Vorlage nutzt w:tblLook-Attribute der
|
|
// Word-2010-Form, die der 2007-Default des Validators nicht kennt.
|
|
var validator = new OpenXmlValidator(DocumentFormat.OpenXml.FileFormatVersions.Office2013);
|
|
|
|
var data = OneAnimal() with
|
|
{
|
|
Animals = [Krümel(), new ContractAnimal("Fridolin", "Männlich"), new ContractAnimal("Luna", "Weiblich")],
|
|
};
|
|
using var generatedDoc = WordprocessingDocument.Open(
|
|
new MemoryStream(ContractGenerator.Generate(data)), isEditable: false);
|
|
var errors = validator.Validate(generatedDoc)
|
|
.Select(e => $"{e.ErrorType}: {e.Description} [{e.Path?.XPath}]")
|
|
.ToList();
|
|
|
|
Assert.True(errors.Count == 0, "OpenXml-Validierungsfehler:\n" + string.Join("\n", errors));
|
|
}
|
|
|
|
[Fact]
|
|
public void VorlageSelbst_EnthaeltKeineEchtdaten()
|
|
{
|
|
// Die Vorlage wird hier über den Generator-Pfad geladen: ein Vertrag
|
|
// mit leeren Werten darf keinerlei Daten des Mustervertrags enthalten.
|
|
var data = new ContractData(
|
|
new BreederProfile(),
|
|
new ContractBuyer("", ""),
|
|
[new ContractAnimal("", "")],
|
|
0m,
|
|
new DateOnly(2026, 1, 1));
|
|
|
|
var text = DocumentText(ContractGenerator.Generate(data));
|
|
|
|
// Stichproben der Original-PII (Name/Ort/Mail des Mustervertrags).
|
|
Assert.DoesNotContain("Nießner", text);
|
|
Assert.DoesNotContain("Hartengrund", text);
|
|
Assert.DoesNotContain("Ronneburg", text);
|
|
Assert.DoesNotContain("Schädtler", text);
|
|
Assert.DoesNotContain("gmx.de", text);
|
|
Assert.DoesNotContain("jimdofree", text);
|
|
}
|
|
}
|