Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 30 additions & 22 deletions src/DemaConsulting.FileAssert/Modeling/FileAssertFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,27 +39,15 @@ internal sealed class FileAssertFile
/// <param name="count">The exact number of files that must match, or null for no constraint.</param>
/// <param name="minSize">The minimum file size in bytes, or null for no constraint.</param>
/// <param name="maxSize">The maximum file size in bytes, or null for no constraint.</param>
/// <param name="textAssert">The text assert unit, or null when no text: block is declared.</param>
/// <param name="pdfAssert">The PDF assert unit, or null when no pdf: block is declared.</param>
/// <param name="xmlAssert">The XML assert unit, or null when no xml: block is declared.</param>
/// <param name="htmlAssert">The HTML assert unit, or null when no html: block is declared.</param>
/// <param name="yamlAssert">The YAML assert unit, or null when no yaml: block is declared.</param>
/// <param name="jsonAssert">The JSON assert unit, or null when no json: block is declared.</param>
/// <param name="zipAssert">The zip assert unit, or null when no zip: block is declared.</param>
/// <param name="asserts">The file-type assert units, each null when the corresponding block is not declared.</param>
private FileAssertFile(
string pattern,
int? min,
int? max,
int? count,
long? minSize,
long? maxSize,
FileAssertTextAssert? textAssert,
FileAssertPdfAssert? pdfAssert,
FileAssertXmlAssert? xmlAssert,
FileAssertHtmlAssert? htmlAssert,
FileAssertYamlAssert? yamlAssert,
FileAssertJsonAssert? jsonAssert,
FileAssertZipAssert? zipAssert)
FileTypeAsserts asserts)
{
// Store all validated properties for use during execution
Pattern = pattern;
Expand All @@ -68,15 +56,35 @@ private FileAssertFile(
Count = count;
MinSize = minSize;
MaxSize = maxSize;
TextAssert = textAssert;
PdfAssert = pdfAssert;
XmlAssert = xmlAssert;
HtmlAssert = htmlAssert;
YamlAssert = yamlAssert;
JsonAssert = jsonAssert;
ZipAssert = zipAssert;
TextAssert = asserts.TextAssert;
PdfAssert = asserts.PdfAssert;
XmlAssert = asserts.XmlAssert;
HtmlAssert = asserts.HtmlAssert;
YamlAssert = asserts.YamlAssert;
JsonAssert = asserts.JsonAssert;
ZipAssert = asserts.ZipAssert;
}

/// <summary>
/// Groups the optional file-type assert units so they can be passed to the
/// <see cref="FileAssertFile"/> constructor as a single parameter.
/// </summary>
/// <param name="TextAssert">The text assert unit, or null when no text: block is declared.</param>
/// <param name="PdfAssert">The PDF assert unit, or null when no pdf: block is declared.</param>
/// <param name="XmlAssert">The XML assert unit, or null when no xml: block is declared.</param>
/// <param name="HtmlAssert">The HTML assert unit, or null when no html: block is declared.</param>
/// <param name="YamlAssert">The YAML assert unit, or null when no yaml: block is declared.</param>
/// <param name="JsonAssert">The JSON assert unit, or null when no json: block is declared.</param>
/// <param name="ZipAssert">The zip assert unit, or null when no zip: block is declared.</param>
private readonly record struct FileTypeAsserts(
FileAssertTextAssert? TextAssert,
FileAssertPdfAssert? PdfAssert,
FileAssertXmlAssert? XmlAssert,
FileAssertHtmlAssert? HtmlAssert,
FileAssertYamlAssert? YamlAssert,
FileAssertJsonAssert? JsonAssert,
FileAssertZipAssert? ZipAssert);

/// <summary>
/// Gets the glob pattern used to match files.
/// </summary>
Expand Down Expand Up @@ -172,7 +180,7 @@ internal static FileAssertFile Create(FileAssertFileData data)
// Return the fully constructed file assertion
return new FileAssertFile(
data.Pattern, data.Min, data.Max, data.Count, data.MinSize, data.MaxSize,
textAssert, pdfAssert, xmlAssert, htmlAssert, yamlAssert, jsonAssert, zipAssert);
new FileTypeAsserts(textAssert, pdfAssert, xmlAssert, htmlAssert, yamlAssert, jsonAssert, zipAssert));
}

/// <summary>
Expand Down
5 changes: 3 additions & 2 deletions src/DemaConsulting.FileAssert/Modeling/FileAssertPdfAssert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ private void RunDocumentAssertions(IContext context, string displayPath, PdfDocu
}
else
{
_pages?.Apply(context, displayPath, document.GetPages().Count());
// When we reach here _text is empty, so the guard above guarantees _pages is non-null
_pages!.Apply(context, displayPath, document.GetPages().Count());
}
}

Expand All @@ -326,7 +327,7 @@ private void RunDocumentAssertions(IContext context, string displayPath, PdfDocu
/// </summary>
/// <param name="pages">The ordered list of pages from the PDF document.</param>
/// <returns>A single string containing all page text joined with newline separators.</returns>
private static string BuildPageText(IReadOnlyList<Page> pages)
private static string BuildPageText(List<Page> pages)
{
var sb = new StringBuilder();
for (var i = 0; i < pages.Count; i++)
Expand Down
2 changes: 1 addition & 1 deletion src/DemaConsulting.FileAssert/SelfTest/ValidationPdf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private static void RunPdfTest(Context context, DemaConsulting.TestResults.TestR
var configFile = tempDir.GetFilePath(".fileassert.yaml");

// Build a minimal PDF in memory using PdfPig's writer API
var builder = new PdfDocumentBuilder();
using var builder = new PdfDocumentBuilder();
builder.DocumentInformation.Title = "Test PDF";
var font = builder.AddStandard14Font(Standard14Font.Helvetica);
var page = builder.AddPage(PageSize.A4);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ public void Context_Create_MixedArguments_ParsesCorrectly()
// Assert
Assert.True(context.Silent);
Assert.Equal("cfg.yaml", context.ConfigFile);
Assert.Single(context.Filters);
Assert.Equal("my-filter", context.Filters[0]);
var filter = Assert.Single(context.Filters);
Assert.Equal("my-filter", filter);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,11 @@ public void Configuration_LoadYaml_BuildsCompleteTestHierarchy()
var config = FileAssertConfig.ReadFromFile(configPath);

// Assert - the full hierarchy is correctly constructed
Assert.Single(config.Tests);
var test = config.Tests[0];
var test = Assert.Single(config.Tests);
Assert.Equal("License Check", test.Name);
Assert.Single(test.Tags);
Assert.Equal("license", test.Tags[0]);
Assert.Single(test.Files);
var file = test.Files[0];
var tag = Assert.Single(test.Tags);
Assert.Equal("license", tag);
var file = Assert.Single(test.Files);
Assert.Equal("**/*.txt", file.Pattern);
Assert.Equal(1, file.Min);
Assert.Single(file.TextAssert!.Rules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ public void FileAssertConfig_ReadFromFile_ValidFile_ReturnsConfig()
var config = FileAssertConfig.ReadFromFile(configPath);

// Assert
Assert.Single(config.Tests);
Assert.Equal("Sample Test", config.Tests[0].Name);
var test = Assert.Single(config.Tests);
Assert.Equal("Sample Test", test.Name);

}

Expand Down Expand Up @@ -285,11 +285,9 @@ public void FileAssertConfig_ReadFromFile_PdfAssertConfig_ParsesCorrectly()
var config = FileAssertConfig.ReadFromFile(configPath);

// Assert - one test was parsed with one file assertion and populated PDF settings
Assert.Single(config.Tests);
Assert.Equal("PDF Check", config.Tests[0].Name);
Assert.Single(config.Tests[0].Files);

var fileAssertion = config.Tests[0].Files[0];
var test = Assert.Single(config.Tests);
Assert.Equal("PDF Check", test.Name);
var fileAssertion = Assert.Single(test.Files);
Assert.Equal("report.pdf", fileAssertion.Pattern);
Assert.NotNull(fileAssertion.PdfAssert);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,8 @@ public void FileAssertHtmlAssert_Run_UnauthorizedAccess_WritesError()
htmlAssert.Run(context, container, "page.html");

// Assert: the IO failure is reported
Assert.Single(context.Errors);
Assert.Contains("could not be parsed as an HTML document", context.Errors[0]);
var error = Assert.Single(context.Errors);
Assert.Contains("could not be parsed as an HTML document", error);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,8 +364,8 @@ public void FileAssertJsonAssert_Run_InvalidJson_WritesParseError()
jsonAssert.Run(context, container, fileName);

// Assert: the error identifies a parse failure, not an IO failure
Assert.Single(context.Errors);
Assert.Contains("could not be parsed as a JSON document", context.Errors[0]);
var error = Assert.Single(context.Errors);
Assert.Contains("could not be parsed as a JSON document", error);
}
finally
{
Expand All @@ -389,8 +389,8 @@ public void FileAssertJsonAssert_Run_IOError_WritesReadError()
jsonAssert.Run(context, container, "data.json");

// Assert: the error identifies an IO failure, not a parse failure
Assert.Single(context.Errors);
Assert.Contains("could not be read", context.Errors[0]);
var error = Assert.Single(context.Errors);
Assert.Contains("could not be read", error);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ public void FileAssertRule_Create_WithContains_ReturnsContainsRule()
var rule = FileAssertRule.Create(data);

// Assert
Assert.IsType<FileAssertContainsRule>(rule);
Assert.Equal("expected text", ((FileAssertContainsRule)rule).Value);
var containsRule = Assert.IsType<FileAssertContainsRule>(rule);
Assert.Equal("expected text", containsRule.Value);
}

/// <summary>
Expand All @@ -60,8 +60,8 @@ public void FileAssertRule_Create_WithMatches_ReturnsMatchesRule()
var rule = FileAssertRule.Create(data);

// Assert
Assert.IsType<FileAssertMatchesRule>(rule);
Assert.Equal(@"\d+", ((FileAssertMatchesRule)rule).Pattern);
var matchesRule = Assert.IsType<FileAssertMatchesRule>(rule);
Assert.Equal(@"\d+", matchesRule.Pattern);
}

/// <summary>
Expand Down Expand Up @@ -172,8 +172,8 @@ public void FileAssertRule_Create_WithDoesNotContain_ReturnsDoesNotContainRule()
var rule = FileAssertRule.Create(data);

// Assert
Assert.IsType<FileAssertDoesNotContainRule>(rule);
Assert.Equal("forbidden text", ((FileAssertDoesNotContainRule)rule).Value);
var doesNotContainRule = Assert.IsType<FileAssertDoesNotContainRule>(rule);
Assert.Equal("forbidden text", doesNotContainRule.Value);
}

/// <summary>
Expand All @@ -189,8 +189,8 @@ public void FileAssertRule_Create_WithDoesNotContainRegex_ReturnsDoesNotMatchRul
var rule = FileAssertRule.Create(data);

// Assert
Assert.IsType<FileAssertDoesNotMatchRule>(rule);
Assert.Equal(@"FATAL|ERROR", ((FileAssertDoesNotMatchRule)rule).Pattern);
var doesNotMatchRule = Assert.IsType<FileAssertDoesNotMatchRule>(rule);
Assert.Equal(@"FATAL|ERROR", doesNotMatchRule.Pattern);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,8 +438,8 @@ public void FileAssertYamlAssert_Run_InvalidFile_RemainingAssertionsSkipped()
yamlAssert.Run(context, container, fileName);

// Assert - exactly one error (the parse failure); the queries are not evaluated
Assert.Single(context.Errors);
Assert.Contains("could not be parsed", context.Errors[0]);
var error = Assert.Single(context.Errors);
Assert.Contains("could not be parsed", error);
}
finally
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

using System.Collections.ObjectModel;
using System.IO.Compression;
using System.Linq;
using DemaConsulting.FileAssert.Cli;
using DemaConsulting.FileAssert.Configuration;
using DemaConsulting.FileAssert.Modeling;
Expand Down Expand Up @@ -82,12 +83,13 @@ private static void CreateZipFile(string path, IEnumerable<string> entries)
File.Delete(path);

using var archive = ZipFile.Open(path, ZipArchiveMode.Create);
foreach (var entry in entries)
foreach (var stream in entries.Select(entry => archive.CreateEntry(entry).Open()))
{
using var stream = archive.CreateEntry(entry).Open();

// Write a single placeholder byte so the entry is not an empty-stream edge case
stream.WriteByte(0x00);
using (stream)
{
// Write a single placeholder byte so the entry is not an empty-stream edge case
stream.WriteByte(0x00);
}
}
}

Expand Down Expand Up @@ -238,10 +240,10 @@ public void FileAssertZipAssert_Create_ValidData_CreatesZipAssert()

// Assert
Assert.NotNull(zipAssert);
Assert.Single(zipAssert.Files);
Assert.Equal("lib/**/*.dll", zipAssert.Files[0].Pattern);
Assert.Equal(1, zipAssert.Files[0].Min);
Assert.Null(zipAssert.Files[0].Max);
var file = Assert.Single(zipAssert.Files);
Assert.Equal("lib/**/*.dll", file.Pattern);
Assert.Equal(1, file.Min);
Assert.Null(file.Max);
}

/// <summary>
Expand Down