using Books.Api.Invoicing.Pdf; using AwesomeAssertions; using QuestPDF.Infrastructure; namespace Books.Api.Tests.Invoicing; /// /// Unit tests for the InvoicePdfGenerator. /// [Trait("Category", "Unit")] public class InvoicePdfGeneratorTests { private readonly InvoicePdfGenerator _generator; public InvoicePdfGeneratorTests() { // Ensure QuestPDF license is set for tests QuestPDF.Settings.License = LicenseType.Community; _generator = new InvoicePdfGenerator(); } [Fact] public void Generate_WithValidData_ProducesPdfBytes() { // Arrange var data = CreateSampleInvoiceData(); // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); pdfBytes.Should().StartWith([0x25, 0x50, 0x44, 0x46]); // PDF magic bytes "%PDF" } [Fact] public void Generate_WithMultipleLines_ProducesPdf() { // Arrange var data = CreateSampleInvoiceData(lineCount: 10); // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); pdfBytes.Length.Should().BeGreaterThan(1000); // PDF should be substantial } [Fact] public void Generate_WithSingleLine_ProducesPdf() { // Arrange var data = CreateSampleInvoiceData(lineCount: 1); // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithDiscount_ProducesPdf() { // Arrange var data = CreateSampleInvoiceData() with { Lines = [ new InvoicePdfLineItem { LineNumber = 1, Description = "Produkt med rabat", Quantity = 10, Unit = "stk", UnitPrice = 100m, DiscountPercent = 15m, VatCode = "U25", AmountExVat = 850m, AmountVat = 212.50m, AmountTotal = 1062.50m } ], AmountExVat = 850m, AmountVat = 212.50m, AmountTotal = 1062.50m, VatSummary = [ new VatSummaryLine { VatCode = "U25", VatRate = 0.25m, BasisAmount = 850m, VatAmount = 212.50m } ] }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithMultipleVatCodes_ShowsVatSummary() { // Arrange var data = CreateDataWithMixedVatCodes(); // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithDanishCharacters_HandlesEncoding() { // Arrange var data = CreateSampleInvoiceData() with { CompanyName = "Møller & Sønner ApS", CustomerName = "Børge Åkeson", Notes = "Tak for ordren! Vi ses igen. Æbler, øl og åben dør." }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithLongDescription_HandlesWrapping() { // Arrange var data = CreateSampleInvoiceData() with { Lines = [ new InvoicePdfLineItem { LineNumber = 1, Description = "Dette er en meget lang beskrivelse af en ydelse der inkluderer mange detaljer om hvad der er leveret, hvornår det er leveret, og hvordan det er udført. Beskrivelsen fortsætter for at teste tekst-wrapping funktionaliteten i PDF generatoren.", Quantity = 1, Unit = "stk", UnitPrice = 5000m, DiscountPercent = 0, VatCode = "U25", AmountExVat = 5000m, AmountVat = 1250m, AmountTotal = 6250m } ], AmountExVat = 5000m, AmountVat = 1250m, AmountTotal = 6250m, VatSummary = [ new VatSummaryLine { VatCode = "U25", VatRate = 0.25m, BasisAmount = 5000m, VatAmount = 1250m } ] }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithNotes_IncludesNotesSection() { // Arrange var data = CreateSampleInvoiceData() with { Notes = "Betalingsfrist overholdes venligst. Ved forsinket betaling beregnes morarenter." }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithReference_IncludesReference() { // Arrange var data = CreateSampleInvoiceData() with { Reference = "PO-2024-001234" }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithZeroVatLines_HandlesCorrectly() { // Arrange var data = CreateSampleInvoiceData() with { Lines = [ new InvoicePdfLineItem { LineNumber = 1, Description = "EU-ydelse (momsfri)", Quantity = 1, Unit = "stk", UnitPrice = 10000m, DiscountPercent = 0, VatCode = "UEU", AmountExVat = 10000m, AmountVat = 0m, AmountTotal = 10000m } ], AmountExVat = 10000m, AmountVat = 0m, AmountTotal = 10000m, VatSummary = [ new VatSummaryLine { VatCode = "UEU", VatRate = 0m, BasisAmount = 10000m, VatAmount = 0m } ] }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithDecimalQuantity_FormatsCorrectly() { // Arrange var data = CreateSampleInvoiceData() with { Lines = [ new InvoicePdfLineItem { LineNumber = 1, Description = "Konsulentydelse", Quantity = 7.5m, Unit = "timer", UnitPrice = 850m, DiscountPercent = 0, VatCode = "U25", AmountExVat = 6375m, AmountVat = 1593.75m, AmountTotal = 7968.75m } ], AmountExVat = 6375m, AmountVat = 1593.75m, AmountTotal = 7968.75m, VatSummary = [ new VatSummaryLine { VatCode = "U25", VatRate = 0.25m, BasisAmount = 6375m, VatAmount = 1593.75m } ] }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithBankDetails_IncludesPaymentInfo() { // Arrange var data = CreateSampleInvoiceData() with { CompanyBankName = "Nordea", CompanyBankRegNo = "2191", CompanyBankAccountNo = "0012345678", CompanyIban = "DK8920000012345678", CompanyBic = "NDEADKKK" }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); pdfBytes.Should().StartWith([0x25, 0x50, 0x44, 0x46]); // PDF magic bytes } [Fact] public void Generate_WithPartialBankDetails_ProducesPdf() { // Arrange - Only Danish bank details, no IBAN/BIC var data = CreateSampleInvoiceData() with { CompanyBankName = "Jyske Bank", CompanyBankRegNo = "5064", CompanyBankAccountNo = "1234567", CompanyIban = null, CompanyBic = null }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } [Fact] public void Generate_WithNoBankDetails_ProducesPdf() { // Arrange - No bank details at all var data = CreateSampleInvoiceData() with { CompanyBankName = null, CompanyBankRegNo = null, CompanyBankAccountNo = null, CompanyIban = null, CompanyBic = null }; // Act var pdfBytes = _generator.Generate(data); // Assert pdfBytes.Should().NotBeNullOrEmpty(); } #region Test Data Helpers private static InvoicePdfData CreateSampleInvoiceData(int lineCount = 3) { var lines = Enumerable.Range(1, lineCount) .Select(i => new InvoicePdfLineItem { LineNumber = i, Description = $"Konsulentydelse - uge {i}", Quantity = 40, Unit = "timer", UnitPrice = 850m, DiscountPercent = 0, VatCode = "U25", AmountExVat = 34000m, AmountVat = 8500m, AmountTotal = 42500m }) .ToList(); var totalExVat = lines.Sum(l => l.AmountExVat); var totalVat = lines.Sum(l => l.AmountVat); var total = lines.Sum(l => l.AmountTotal); return new InvoicePdfData { CompanyName = "Test Firma ApS", CompanyCvr = "12345678", CompanyAddress = "Testvej 123", CompanyPostalCode = "2100", CompanyCity = "København Ø", CompanyCountry = "DK", // Bank details CompanyBankName = "Danske Bank", CompanyBankRegNo = "3409", CompanyBankAccountNo = "1234567890", CompanyIban = "DK5000400440116243", CompanyBic = "DABADKKK", CustomerName = "Kunde ApS", CustomerCvr = "87654321", CustomerAddress = "Kundevej 456", CustomerPostalCode = "8000", CustomerCity = "Aarhus C", CustomerCountry = "DK", CustomerNumber = "K-001", InvoiceNumber = "2024-0001", InvoiceDate = new DateOnly(2024, 1, 15), DueDate = new DateOnly(2024, 2, 14), PaymentTermsDays = 30, Currency = "DKK", Lines = lines, AmountExVat = totalExVat, AmountVat = totalVat, AmountTotal = total, VatSummary = [ new VatSummaryLine { VatCode = "U25", VatRate = 0.25m, BasisAmount = totalExVat, VatAmount = totalVat } ] }; } private static InvoicePdfData CreateDataWithMixedVatCodes() { var lines = new List { new() { LineNumber = 1, Description = "Dansk ydelse (25% moms)", Quantity = 1, Unit = "stk", UnitPrice = 10000m, DiscountPercent = 0, VatCode = "U25", AmountExVat = 10000m, AmountVat = 2500m, AmountTotal = 12500m }, new() { LineNumber = 2, Description = "EU-ydelse (0% moms)", Quantity = 1, Unit = "stk", UnitPrice = 5000m, DiscountPercent = 0, VatCode = "UEU", AmountExVat = 5000m, AmountVat = 0m, AmountTotal = 5000m } }; return new InvoicePdfData { CompanyName = "Mixed VAT Company ApS", CompanyCvr = "11111111", CompanyAddress = "Momsvej 1", CompanyPostalCode = "1000", CompanyCity = "København K", CompanyCountry = "DK", CustomerName = "EU Customer GmbH", CustomerCvr = "DE123456789", CustomerAddress = "Hauptstraße 1", CustomerPostalCode = "10115", CustomerCity = "Berlin", CustomerCountry = "DE", CustomerNumber = "K-EU-001", InvoiceNumber = "2024-0002", InvoiceDate = new DateOnly(2024, 2, 1), DueDate = new DateOnly(2024, 3, 1), PaymentTermsDays = 30, Currency = "DKK", Lines = lines, AmountExVat = 15000m, AmountVat = 2500m, AmountTotal = 17500m, VatSummary = [ new VatSummaryLine { VatCode = "U25", VatRate = 0.25m, BasisAmount = 10000m, VatAmount = 2500m }, new VatSummaryLine { VatCode = "UEU", VatRate = 0m, BasisAmount = 5000m, VatAmount = 0m } ] }; } #endregion }