test: strengthen DevExpressPdfProcessor tests (IsEncrypted + DetectEncryption)
- Rename ValidateAsync_ValidPdf_ReturnsPdfMetadata to ReturnsPdfValidationResult and add FileSizeMB assertion - Add Encryption Detection region with 4 tests: - NormalPdf_IsEncryptedFalse (real PDF via ValidateAsync) - ValidatePdfAAsync_NormalPdf_EncryptedFalse (real PDF via ValidatePdfAAsync) - DetectEncryption_TrailerWithEncryptEntry_ReturnsTrue (reflection, byte scan) - DetectEncryption_NoEncryptEntry_ReturnsFalse (reflection, byte scan) - DetectEncryptionKeyword_NotStandaloneToken_ReturnsFalse (token boundary check) - Add IsEncrypted.Should().BeFalse() assertion to attachment count test
This commit is contained in:
@@ -68,19 +68,20 @@ public class DevExpressPdfProcessorTests
|
|||||||
#region ValidateAsync Tests
|
#region ValidateAsync Tests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ValidateAsync_ValidPdf_ReturnsPdfMetadata()
|
public async Task ValidateAsync_ValidPdf_ReturnsPdfValidationResult()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = await _sut.ValidateAsync(ToStream(pdfBytes));
|
var result = await _sut.ValidateAsync(ToStream(pdfBytes));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
metadata.Should().NotBeNull("a valid PDF should return metadata");
|
result.Should().NotBeNull("a valid PDF should return a result");
|
||||||
metadata.PageCount.Should().BeGreaterThan(0, "PDF must have at least one page");
|
result.PageCount.Should().BeGreaterThan(0, "PDF must have at least one page");
|
||||||
metadata.FileSizeBytes.Should().Be(pdfBytes.Length, "file size should match input");
|
result.FileSizeBytes.Should().Be(pdfBytes.Length, "file size must match input");
|
||||||
metadata.PdfVersion.Should().NotBeNullOrEmpty("PDF version should be detected");
|
result.PdfVersion.Should().NotBeNullOrEmpty("PDF version should be detected");
|
||||||
|
result.FileSizeMB.Should().BeApproximately(pdfBytes.Length / 1024.0 / 1024.0, 0.01, "FileSizeMB should be derived from FileSizeBytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -90,11 +91,10 @@ public class DevExpressPdfProcessorTests
|
|||||||
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = await _sut.ValidateAsync(ToStream(pdfBytes));
|
var result = await _sut.ValidateAsync(ToStream(pdfBytes));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// Deine valid.pdf hat wahrscheinlich 1-5 Seiten - passe an!
|
result.PageCount.Should().BeInRange(1, 100, "test PDF should have reasonable page count");
|
||||||
metadata.PageCount.Should().BeInRange(1, 100, "test PDF should have reasonable page count");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -150,16 +150,112 @@ public class DevExpressPdfProcessorTests
|
|||||||
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = await _sut.ValidateAsync(ToStream(pdfBytes));
|
var result = await _sut.ValidateAsync(ToStream(pdfBytes));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
double expectedSizeMB = pdfBytes.Length / 1024.0 / 1024.0;
|
double expectedSizeMB = pdfBytes.Length / 1024.0 / 1024.0;
|
||||||
metadata.FileSizeMB.Should().BeApproximately(expectedSizeMB, 0.01,
|
result.FileSizeMB.Should().BeApproximately(expectedSizeMB, 0.01,
|
||||||
"FileSizeMB should be calculated correctly from bytes");
|
"FileSizeMB should be calculated correctly from bytes");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Encryption Detection Tests
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidateAsync_NormalPdf_IsEncryptedFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _sut.ValidateAsync(ToStream(pdfBytes));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.IsEncrypted.Should().BeFalse("valid.pdf is not password-protected");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidatePdfAAsync_NormalPdf_EncryptedFalse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _sut.ValidatePdfAAsync(ToStream(pdfBytes));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Encrypted.Should().BeFalse("valid.pdf is not password-protected");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DetectEncryption_TrailerWithEncryptEntry_ReturnsTrueViaRawBytes()
|
||||||
|
{
|
||||||
|
// Validates the encryption detection logic directly by crafting PDF bytes
|
||||||
|
// that contain /Encrypt in the trailer section (last 2KB).
|
||||||
|
// The byte-level detection must identify this as encrypted.
|
||||||
|
|
||||||
|
// Build a minimal fake PDF where the trailer contains /Encrypt
|
||||||
|
// (we are not loading it with DevExpress — just testing the byte scanner)
|
||||||
|
var trailerBytes = System.Text.Encoding.Latin1.GetBytes(
|
||||||
|
"\ntrailer\n<< /Size 5 /Root 1 0 R /Encrypt 4 0 R >>\nstartxref\n370\n%%EOF\n");
|
||||||
|
|
||||||
|
// Pad with enough bytes so the trailer is within last 2KB
|
||||||
|
var padding = new byte[100];
|
||||||
|
Array.Fill(padding, (byte)0x20);
|
||||||
|
var pdfBytes = padding.Concat(trailerBytes).ToArray();
|
||||||
|
|
||||||
|
// Access private method via reflection to test it in isolation
|
||||||
|
var processorType = typeof(DocumentService.Infrastructure.Services.PdfProcessing.DevExpressPdfProcessor);
|
||||||
|
var detectMethod = processorType.GetMethod("DetectEncryption",
|
||||||
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||||
|
|
||||||
|
detectMethod.Should().NotBeNull("DetectEncryption method should exist");
|
||||||
|
var detected = (bool)detectMethod!.Invoke(null, [pdfBytes])!;
|
||||||
|
detected.Should().BeTrue("trailer with /Encrypt 4 0 R should be detected as encrypted");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DetectEncryption_NoEncryptEntry_ReturnsFalseViaRawBytes()
|
||||||
|
{
|
||||||
|
// A PDF without /Encrypt in trailer should return false
|
||||||
|
var pdfBytes = System.Text.Encoding.Latin1.GetBytes(
|
||||||
|
"%PDF-1.4\n1 0 obj\n<< /Type /Catalog >>\nendobj\nxref\n0 2\ntrailer\n<< /Size 2 /Root 1 0 R >>\nstartxref\n10\n%%EOF\n");
|
||||||
|
|
||||||
|
var processorType = typeof(DocumentService.Infrastructure.Services.PdfProcessing.DevExpressPdfProcessor);
|
||||||
|
var detectMethod = processorType.GetMethod("DetectEncryption",
|
||||||
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||||
|
|
||||||
|
var detected = (bool)detectMethod!.Invoke(null, [pdfBytes])!;
|
||||||
|
detected.Should().BeFalse("PDF without /Encrypt entry should not be detected as encrypted");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task DetectEncryption_EncryptedKeywordNotAsStandaloneToken_ReturnsFalse()
|
||||||
|
{
|
||||||
|
// "/Encrypt" is NOT standalone if the next byte is a letter (e.g. "/Encryption", "/EncryptedData").
|
||||||
|
// Strategy 3 (full-file) checks token terminators.
|
||||||
|
// To isolate Strategy 3, we put the keyword well inside the file
|
||||||
|
// (not in first or last 2KB) so Strategies 1 and 2 don't fire.
|
||||||
|
|
||||||
|
// Build: 3KB prefix + "/Encryption more" + 3KB suffix + minimal trailer (no /Encrypt)
|
||||||
|
var prefix = new string('A', 3000);
|
||||||
|
var fakeContent = " /Encryption some data "; // /Encrypt followed by 'i' — not a terminator
|
||||||
|
var suffix = new string('B', 3000);
|
||||||
|
var normalTrailer = "\ntrailer\n<< /Size 2 /Root 1 0 R >>\nstartxref\n10\n%%EOF\n";
|
||||||
|
var pdfBytes = System.Text.Encoding.Latin1.GetBytes(prefix + fakeContent + suffix + normalTrailer);
|
||||||
|
|
||||||
|
var processorType = typeof(DocumentService.Infrastructure.Services.PdfProcessing.DevExpressPdfProcessor);
|
||||||
|
var detectMethod = processorType.GetMethod("DetectEncryption",
|
||||||
|
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
|
||||||
|
|
||||||
|
var detected = (bool)detectMethod!.Invoke(null, [pdfBytes])!;
|
||||||
|
// /Encryption has 'i' after /Encrypt — 'i' is not a PDF token terminator → should NOT match
|
||||||
|
detected.Should().BeFalse("'/Encryption' (with letter suffix) should not match the '/Encrypt ' token");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Attachment Detection Tests
|
#region Attachment Detection Tests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -169,13 +265,10 @@ public class DevExpressPdfProcessorTests
|
|||||||
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
byte[] pdfBytes = LoadTestPdf("valid.pdf");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = await _sut.ValidateAsync(ToStream(pdfBytes));
|
var result = await _sut.ValidateAsync(ToStream(pdfBytes));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// Note: valid.pdf actually contains /EmbeddedFiles reference (15 0 R)
|
result.Should().NotBeNull();
|
||||||
// This test just verifies that attachment detection doesn't crash
|
|
||||||
// The exact count depends on the PDF content
|
|
||||||
metadata.Should().NotBeNull();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -185,18 +278,14 @@ public class DevExpressPdfProcessorTests
|
|||||||
byte[] pdfBytes = LoadTestPdf("pdfWithMoreThanOneAttachment.pdf");
|
byte[] pdfBytes = LoadTestPdf("pdfWithMoreThanOneAttachment.pdf");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var metadata = await _sut.ValidateAsync(ToStream(pdfBytes));
|
var result = await _sut.ValidateAsync(ToStream(pdfBytes));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
metadata.HasAttachments.Should().BeTrue("PDF has multiple attachments");
|
result.HasAttachments.Should().BeTrue("PDF has multiple attachments");
|
||||||
metadata.AttachmentCount.Should().BeGreaterThan(1, "PDF has more than 1 attachment");
|
result.AttachmentCount.Should().BeGreaterThan(1, "PDF has more than 1 attachment");
|
||||||
|
result.IsEncrypted.Should().BeFalse("this PDF is not encrypted");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Testing PDF with attachments requires a real ZUGFeRD PDF file
|
|
||||||
// as DevExpress PdfDocumentProcessor doesn't expose a simple API to create attachments.
|
|
||||||
// This test will be added when a ZUGFeRD test PDF is available.
|
|
||||||
// For now, we verify that attachment detection works (returns false for PDFs without attachments).
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region CheckAttachmentsAsync Tests
|
#region CheckAttachmentsAsync Tests
|
||||||
|
|||||||
Reference in New Issue
Block a user