This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
using System.IO.Compression;
|
||||
using LehrerApp.Templating;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Templating.Tests;
|
||||
|
||||
public sealed class TemplatingTests : IDisposable
|
||||
{
|
||||
private readonly string _directory = Path.Combine(Path.GetTempPath(), $"lavorlage-{Guid.NewGuid():N}");
|
||||
public TemplatingTests() => Directory.CreateDirectory(_directory);
|
||||
|
||||
[Fact]
|
||||
public void LayoutParser_LiestAlleElementtypenUndAttribute()
|
||||
{
|
||||
var layout = new LayoutParser().Parse("""
|
||||
# Beispiel
|
||||
PAGE 210 297 mm
|
||||
BG background.png
|
||||
IMG logo.png 15 15 30 12 scale=50%
|
||||
TEXT 20 45 $Datum|dd.MM.yyyy size=10
|
||||
TEXTBOX 20 90 170 120 $Brieftext wrap=true
|
||||
TABLE 20 215 170 40 $Zeilen size=9
|
||||
CHART 20 260 170 25 $Werte type=line
|
||||
""");
|
||||
|
||||
Assert.Equal(6, layout.Elements.Count);
|
||||
Assert.Equal("50%", Assert.IsType<ImageElement>(layout.Elements[1]).Attributes["scale"]);
|
||||
Assert.Equal("dd.MM.yyyy", Assert.IsType<TextElement>(layout.Elements[2]).Format);
|
||||
Assert.Equal("line", Assert.IsType<ChartElement>(layout.Elements[5]).ChartType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("scale=0%")]
|
||||
[InlineData("scale=-10%")]
|
||||
[InlineData("scale=1001%")]
|
||||
[InlineData("scale=abc")]
|
||||
public void LayoutParser_LehntUngueltigeBildskalierungAb(string attribute)
|
||||
{
|
||||
var exception = Assert.Throws<TemplateValidationException>(() =>
|
||||
new LayoutParser().Parse($"PAGE 210 297 mm\nIMG logo.png 10 10 20 20 {attribute}"));
|
||||
|
||||
Assert.Contains(exception.Result.Issues, issue => issue.Message.Contains("scale", StringComparison.OrdinalIgnoreCase)
|
||||
|| issue.Message.Contains("keine Zahl", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LayoutParser_MeldetAlleSyntaxfehlerMitZeilen()
|
||||
{
|
||||
var exception = Assert.Throws<TemplateValidationException>(() => new LayoutParser().Parse("""
|
||||
TEXT 1 2 "vor PAGE"
|
||||
PAGE x 297 mm
|
||||
UNBEKANNT 1
|
||||
"""));
|
||||
|
||||
Assert.True(exception.Result.Issues.Count >= 3);
|
||||
Assert.Equal(3, exception.Result.Issues.Count(issue => issue.Line is not null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Loader_BlockiertPathTraversal()
|
||||
{
|
||||
var path = Path.Combine(_directory, "unsafe.lavorlage");
|
||||
using (var archive = ZipFile.Open(path, ZipArchiveMode.Create))
|
||||
{
|
||||
Write(archive, "../manifest.json", "{}");
|
||||
Write(archive, "manifest.json", ManifestJson());
|
||||
Write(archive, "layout.tpl", "PAGE 210 297 mm");
|
||||
}
|
||||
|
||||
var exception = Assert.Throws<TemplateValidationException>(() => new TemplateLoader().LoadFromPackage(path));
|
||||
Assert.Contains(exception.Result.Issues, x => x.Message.Contains("Unsicherer Paketpfad"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Loader_SammeltUnbekanntenPlatzhalterUndFehlendesBild()
|
||||
{
|
||||
var path = Path.Combine(_directory, "invalid.lavorlage");
|
||||
using (var archive = ZipFile.Open(path, ZipArchiveMode.Create))
|
||||
{
|
||||
Write(archive, "manifest.json", ManifestJson());
|
||||
Write(archive, "layout.tpl", "PAGE 210 297 mm\nBG fehlt.png\nTEXT 10 10 $Unbekannt");
|
||||
}
|
||||
|
||||
var exception = Assert.Throws<TemplateValidationException>(() => new TemplateLoader().LoadFromPackage(path));
|
||||
Assert.Equal(2, exception.Result.Issues.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_SammeltFehlendeUndFalscheDatentypen()
|
||||
{
|
||||
var loaded = new LoadedTemplate(new TemplateManifest
|
||||
{
|
||||
Placeholders = [new("Datum", PlaceholderType.Date, true), new("Text", PlaceholderType.Text, true)],
|
||||
}, new(210, 297, "mm", []), new Dictionary<string, byte[]>());
|
||||
|
||||
var result = new TemplateLoader().Validate(loaded,
|
||||
new Dictionary<string, PlaceholderType> { ["Datum"] = PlaceholderType.Text });
|
||||
|
||||
Assert.False(result.IsValid);
|
||||
Assert.Equal(2, result.Issues.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Paket_RoundtripUndPdfSindGueltig()
|
||||
{
|
||||
var path = Path.Combine(_directory, "brief.lavorlage");
|
||||
var manifest = new TemplateManifest
|
||||
{
|
||||
Id = "brief", Name = "Brief",
|
||||
Placeholders = [new("Datum", PlaceholderType.Date, true), new("Text", PlaceholderType.Multiline, true)],
|
||||
};
|
||||
TemplatePackage.Create(path, manifest, """
|
||||
PAGE 210 297 mm
|
||||
TEXT 20 20 "Elternbrief" size=18 bold=true color=#1D4ED8
|
||||
TEXT 20 35 $Datum|dd.MM.yyyy size=10
|
||||
TEXTBOX 20 50 170 200 $Text size=11
|
||||
""", new Dictionary<string, byte[]>());
|
||||
var loaded = new TemplateLoader().LoadFromPackage(path);
|
||||
var pdf = new QuestTemplateRenderer().RenderToPdf(loaded, new DictionaryProvider(new Dictionary<string, PlaceholderValue>
|
||||
{
|
||||
["Datum"] = new DateValue(new DateOnly(2026, 8, 30)),
|
||||
["Text"] = new MultilineValue("Sehr geehrte Eltern,\n\ndies ist ein Testbrief."),
|
||||
}));
|
||||
|
||||
Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(pdf, 0, 4));
|
||||
Assert.True(pdf.Length > 1_000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Paket_RoundtripErhaeltAuchNochNichtVerwendeteBildAssets()
|
||||
{
|
||||
var path = Path.Combine(_directory, "assets.lavorlage");
|
||||
var png = Convert.FromBase64String(
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=");
|
||||
TemplatePackage.Create(path, new TemplateManifest { Id = "assets", Name = "Assets" },
|
||||
"PAGE 210 297 mm\nIMG logo.png 10 10 20 20",
|
||||
new Dictionary<string, byte[]> { ["logo.png"] = png, ["assets/siegel.png"] = png });
|
||||
|
||||
var loaded = new TemplateLoader().LoadFromPackage(path);
|
||||
|
||||
Assert.Equal(2, loaded.Assets.Count);
|
||||
Assert.Contains("assets/siegel.png", loaded.Assets.Keys);
|
||||
}
|
||||
|
||||
private static string ManifestJson() => """
|
||||
{ "schemaVersion": 1, "id": "test", "name": "Test",
|
||||
"pageSize": { "width": 210, "height": 297, "unit": "mm" },
|
||||
"layoutFile": "layout.tpl", "placeholders": [] }
|
||||
""";
|
||||
private static void Write(ZipArchive archive, string path, string content)
|
||||
{ using var writer = new StreamWriter(archive.CreateEntry(path).Open()); writer.Write(content); }
|
||||
public void Dispose() { if (Directory.Exists(_directory)) Directory.Delete(_directory, true); }
|
||||
}
|
||||
|
||||
internal sealed class DictionaryProvider(IReadOnlyDictionary<string, PlaceholderValue> values) : ITemplateDataProvider
|
||||
{
|
||||
public IReadOnlyDictionary<string, PlaceholderValue> GetValues() => values;
|
||||
}
|
||||
Reference in New Issue
Block a user