diff --git a/src/DemaConsulting.FileAssert/Modeling/FileAssertFile.cs b/src/DemaConsulting.FileAssert/Modeling/FileAssertFile.cs index c474b89..aa78040 100644 --- a/src/DemaConsulting.FileAssert/Modeling/FileAssertFile.cs +++ b/src/DemaConsulting.FileAssert/Modeling/FileAssertFile.cs @@ -39,13 +39,7 @@ internal sealed class FileAssertFile /// The exact number of files that must match, or null for no constraint. /// The minimum file size in bytes, or null for no constraint. /// The maximum file size in bytes, or null for no constraint. - /// The text assert unit, or null when no text: block is declared. - /// The PDF assert unit, or null when no pdf: block is declared. - /// The XML assert unit, or null when no xml: block is declared. - /// The HTML assert unit, or null when no html: block is declared. - /// The YAML assert unit, or null when no yaml: block is declared. - /// The JSON assert unit, or null when no json: block is declared. - /// The zip assert unit, or null when no zip: block is declared. + /// The file-type assert units, each null when the corresponding block is not declared. private FileAssertFile( string pattern, int? min, @@ -53,13 +47,7 @@ private FileAssertFile( 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; @@ -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; } + /// + /// Groups the optional file-type assert units so they can be passed to the + /// constructor as a single parameter. + /// + /// The text assert unit, or null when no text: block is declared. + /// The PDF assert unit, or null when no pdf: block is declared. + /// The XML assert unit, or null when no xml: block is declared. + /// The HTML assert unit, or null when no html: block is declared. + /// The YAML assert unit, or null when no yaml: block is declared. + /// The JSON assert unit, or null when no json: block is declared. + /// The zip assert unit, or null when no zip: block is declared. + private readonly record struct FileTypeAsserts( + FileAssertTextAssert? TextAssert, + FileAssertPdfAssert? PdfAssert, + FileAssertXmlAssert? XmlAssert, + FileAssertHtmlAssert? HtmlAssert, + FileAssertYamlAssert? YamlAssert, + FileAssertJsonAssert? JsonAssert, + FileAssertZipAssert? ZipAssert); + /// /// Gets the glob pattern used to match files. /// @@ -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)); } /// diff --git a/src/DemaConsulting.FileAssert/Modeling/FileAssertPdfAssert.cs b/src/DemaConsulting.FileAssert/Modeling/FileAssertPdfAssert.cs index 113af30..21009c0 100644 --- a/src/DemaConsulting.FileAssert/Modeling/FileAssertPdfAssert.cs +++ b/src/DemaConsulting.FileAssert/Modeling/FileAssertPdfAssert.cs @@ -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()); } } @@ -326,7 +327,7 @@ private void RunDocumentAssertions(IContext context, string displayPath, PdfDocu /// /// The ordered list of pages from the PDF document. /// A single string containing all page text joined with newline separators. - private static string BuildPageText(IReadOnlyList pages) + private static string BuildPageText(List pages) { var sb = new StringBuilder(); for (var i = 0; i < pages.Count; i++) diff --git a/src/DemaConsulting.FileAssert/SelfTest/ValidationPdf.cs b/src/DemaConsulting.FileAssert/SelfTest/ValidationPdf.cs index 845bdfe..7f36047 100644 --- a/src/DemaConsulting.FileAssert/SelfTest/ValidationPdf.cs +++ b/src/DemaConsulting.FileAssert/SelfTest/ValidationPdf.cs @@ -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); diff --git a/test/DemaConsulting.FileAssert.Tests/Cli/ContextNewPropertiesTests.cs b/test/DemaConsulting.FileAssert.Tests/Cli/ContextNewPropertiesTests.cs index 38aa754..b8f1141 100644 --- a/test/DemaConsulting.FileAssert.Tests/Cli/ContextNewPropertiesTests.cs +++ b/test/DemaConsulting.FileAssert.Tests/Cli/ContextNewPropertiesTests.cs @@ -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); } /// diff --git a/test/DemaConsulting.FileAssert.Tests/Configuration/ConfigurationTests.cs b/test/DemaConsulting.FileAssert.Tests/Configuration/ConfigurationTests.cs index a840e2b..835e710 100644 --- a/test/DemaConsulting.FileAssert.Tests/Configuration/ConfigurationTests.cs +++ b/test/DemaConsulting.FileAssert.Tests/Configuration/ConfigurationTests.cs @@ -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); diff --git a/test/DemaConsulting.FileAssert.Tests/Configuration/FileAssertConfigTests.cs b/test/DemaConsulting.FileAssert.Tests/Configuration/FileAssertConfigTests.cs index 4d0c60c..855dcb8 100644 --- a/test/DemaConsulting.FileAssert.Tests/Configuration/FileAssertConfigTests.cs +++ b/test/DemaConsulting.FileAssert.Tests/Configuration/FileAssertConfigTests.cs @@ -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); } @@ -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); diff --git a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertHtmlAssertTests.cs b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertHtmlAssertTests.cs index a6e948a..0d06859 100644 --- a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertHtmlAssertTests.cs +++ b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertHtmlAssertTests.cs @@ -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); } /// diff --git a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertJsonAssertTests.cs b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertJsonAssertTests.cs index 4e74505..f088e47 100644 --- a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertJsonAssertTests.cs +++ b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertJsonAssertTests.cs @@ -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 { @@ -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); } /// diff --git a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertRuleTests.cs b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertRuleTests.cs index 6a31bf1..aa47cec 100644 --- a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertRuleTests.cs +++ b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertRuleTests.cs @@ -43,8 +43,8 @@ public void FileAssertRule_Create_WithContains_ReturnsContainsRule() var rule = FileAssertRule.Create(data); // Assert - Assert.IsType(rule); - Assert.Equal("expected text", ((FileAssertContainsRule)rule).Value); + var containsRule = Assert.IsType(rule); + Assert.Equal("expected text", containsRule.Value); } /// @@ -60,8 +60,8 @@ public void FileAssertRule_Create_WithMatches_ReturnsMatchesRule() var rule = FileAssertRule.Create(data); // Assert - Assert.IsType(rule); - Assert.Equal(@"\d+", ((FileAssertMatchesRule)rule).Pattern); + var matchesRule = Assert.IsType(rule); + Assert.Equal(@"\d+", matchesRule.Pattern); } /// @@ -172,8 +172,8 @@ public void FileAssertRule_Create_WithDoesNotContain_ReturnsDoesNotContainRule() var rule = FileAssertRule.Create(data); // Assert - Assert.IsType(rule); - Assert.Equal("forbidden text", ((FileAssertDoesNotContainRule)rule).Value); + var doesNotContainRule = Assert.IsType(rule); + Assert.Equal("forbidden text", doesNotContainRule.Value); } /// @@ -189,8 +189,8 @@ public void FileAssertRule_Create_WithDoesNotContainRegex_ReturnsDoesNotMatchRul var rule = FileAssertRule.Create(data); // Assert - Assert.IsType(rule); - Assert.Equal(@"FATAL|ERROR", ((FileAssertDoesNotMatchRule)rule).Pattern); + var doesNotMatchRule = Assert.IsType(rule); + Assert.Equal(@"FATAL|ERROR", doesNotMatchRule.Pattern); } /// diff --git a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertYamlAssertTests.cs b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertYamlAssertTests.cs index fd2deac..93c1d91 100644 --- a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertYamlAssertTests.cs +++ b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertYamlAssertTests.cs @@ -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 { diff --git a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertZipAssertTests.cs b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertZipAssertTests.cs index 883b0b6..8838afe 100644 --- a/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertZipAssertTests.cs +++ b/test/DemaConsulting.FileAssert.Tests/Modeling/FileAssertZipAssertTests.cs @@ -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; @@ -82,12 +83,13 @@ private static void CreateZipFile(string path, IEnumerable 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); + } } } @@ -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); } ///