using Common; using FluentAssertions; using NUnit.Framework; using Serilog; using Serilog.Sinks.TestCorrelator; using TestLibrary; using TrashLib.Sonarr.Config; using TrashLib.Sonarr.ReleaseProfile; namespace TrashLib.Tests.Sonarr.ReleaseProfile; [TestFixture] [Parallelizable(ParallelScope.All)] public class ReleaseProfileParserTest { [OneTimeSetUp] public void Setup() { // Formatter.AddFormatter(new ProfileDataValueFormatter()); } private class Context { public Context() { var logger = new LoggerConfiguration() .WriteTo.TestCorrelator() .MinimumLevel.Debug() .CreateLogger(); Config = new SonarrConfiguration { ReleaseProfiles = new[] {new ReleaseProfileConfig()} }; GuideParser = new ReleaseProfileGuideParser(logger); } public SonarrConfiguration Config { get; } public ReleaseProfileGuideParser GuideParser { get; } public ResourceDataReader TestData { get; } = new(typeof(ReleaseProfileParserTest), "Data"); public IDictionary ParseWithDefaults(string markdown) { return GuideParser.ParseMarkdown(Config.ReleaseProfiles.First(), markdown); } } [Test] public void Parse_CodeBlockScopedCategories_CategoriesSwitch() { var markdown = StringUtils.TrimmedString(@" # Test Release Profile Add this to must not contain (ignored) ``` abc ``` Add this to must contain (required) ``` xyz ``` "); var context = new Context(); var results = context.ParseWithDefaults(markdown); results.Should().ContainKey("Test Release Profile") .WhoseValue.Should().BeEquivalentTo(new { Ignored = new List {"abc"}, Required = new List {"xyz"} }); } [Test] public void Parse_HeaderCategoryFollowedByCodeBlockCategories_CodeBlockChangesCurrentCategory() { var markdown = StringUtils.TrimmedString(@" # Test Release Profile ## Must Not Contain Add this one ``` abc ``` Add this to must contain (required) ``` xyz ``` One more ``` 123 ``` "); var context = new Context(); var results = context.ParseWithDefaults(markdown); results.Should().ContainKey("Test Release Profile") .WhoseValue.Should().BeEquivalentTo(new { Ignored = new List {"abc"}, Required = new List {"xyz", "123"} }); } [Test] public void Parse_IgnoredRequiredPreferredScores() { var context = new Context(); var markdown = context.TestData.ReadData("test_parse_markdown_complete_doc.md"); var results = context.GuideParser.ParseMarkdown(context.Config.ReleaseProfiles.First(), markdown); results.Count.Should().Be(1); var profile = results.First().Value; profile.Ignored.Should().BeEquivalentTo("term2", "term3"); profile.Required.Should().BeEquivalentTo("term4"); profile.Preferred.Should().ContainKey(100).WhoseValue.Should().BeEquivalentTo(new List {"term1"}); } [Test] public void Parse_IncludePreferredWhenRenaming() { var context = new Context(); var markdown = context.TestData.ReadData("include_preferred_when_renaming.md"); var results = context.ParseWithDefaults(markdown); results.Should() .ContainKey("First Release Profile") .WhoseValue.IncludePreferredWhenRenaming.Should().Be(true); results.Should() .ContainKey("Second Release Profile") .WhoseValue.IncludePreferredWhenRenaming.Should().Be(false); } [Test] public void Parse_IndentedIncludePreferred_ShouldBeParsed() { var markdown = StringUtils.TrimmedString(@" # Release Profile 1 !!! Warning Do not check include preferred must contain ``` test1 ``` # Release Profile 2 !!! Warning Check include preferred must contain ``` test2 ``` "); var context = new Context(); var results = context.ParseWithDefaults(markdown); var expectedResults = new Dictionary { { "Release Profile 1", new ProfileData { IncludePreferredWhenRenaming = false, Required = new List {"test1"} } }, { "Release Profile 2", new ProfileData { IncludePreferredWhenRenaming = true, Required = new List {"test2"} } } }; results.Should().BeEquivalentTo(expectedResults); } [Test] public void Parse_OptionalTerms_AreCapturedProperly() { var markdown = StringUtils.TrimmedString(@" # Optional Release Profile ``` skipped1 ``` ## Must Not Contain ``` optional1 ``` ## Preferred score [10] ``` optional2 ``` One more must contain: ``` optional3 ``` # Second Release Profile This must not contain: ``` not-optional1 ``` "); var context = new Context(); var results = context.ParseWithDefaults(markdown); var expectedResults = new Dictionary { { "Optional Release Profile", new ProfileData { Optional = new ProfileDataOptional { Ignored = new List {"optional1"}, Required = new List {"optional3"}, Preferred = new Dictionary> { {10, new List {"optional2"}} } } } }, { "Second Release Profile", new ProfileData { Ignored = new List {"not-optional1"} } } }; results.Should().BeEquivalentTo(expectedResults); } [Test] public void Parse_PotentialScore_WarningLogged() { var markdown = StringUtils.TrimmedString(@" # First Release Profile The below line should be a score but isn't because it's missing the word 'score'. Use this number [100] ``` abc ``` "); var context = new Context(); var results = context.ParseWithDefaults(markdown); results.Should().BeEmpty(); const string expectedLog = "Found a potential score on line #5 that will be ignored because the " + "word 'score' is missing (This is probably a bug in the guide itself): \"[100]\""; TestCorrelator.GetLogEventsFromCurrentContext() .Should().ContainSingle(evt => evt.RenderMessage(default) == expectedLog); } [Test] public void Parse_ScoreWithoutCategory_ImplicitlyPreferred() { var markdown = StringUtils.TrimmedString(@" # Test Release Profile score is [100] ``` abc ``` "); var context = new Context(); var results = context.ParseWithDefaults(markdown); results.Should() .ContainKey("Test Release Profile") .WhoseValue.Preferred.Should() .BeEquivalentTo(new Dictionary> { {100, new List {"abc"}} }); } [Test] public void Parse_SkippableLines_AreSkippedWithLog() { var markdown = StringUtils.TrimmedString(@" # First Release Profile !!! Admonition lines are skipped Indented lines are skipped "); // List of substrings of logs that should appear in the resulting list of logs after parsing is done. // We are only looking for logs relevant to the skipped lines we're testing for. var expectedLogs = new List { "Skip Admonition", "Skip Indented Line" }; var context = new Context(); var results = context.ParseWithDefaults(markdown); results.Should().BeEmpty(); var ctx = TestCorrelator.GetLogEventsFromCurrentContext().ToList(); foreach (var log in expectedLogs) { ctx.Should().Contain(evt => evt.MessageTemplate.Text.Contains(log)); } } [Test] public void Parse_StrictNegativeScores() { var context = new Context(); context.Config.ReleaseProfiles = new List { new() {StrictNegativeScores = true} }; var markdown = context.TestData.ReadData("strict_negative_scores.md"); var results = context.ParseWithDefaults(markdown); results.Should() .ContainKey("Test Release Profile").WhoseValue.Should() .BeEquivalentTo(new { Required = new { }, Ignored = new List {"abc"}, Preferred = new Dictionary> {{0, new List {"xyz"}}} }); } [Test] public void Parse_TermsWithoutCategory_AreSkipped() { var markdown = StringUtils.TrimmedString(@" # Test Release Profile ``` skipped1 ``` ## Must Not Contain ``` added1 ``` ## Preferred score [10] ``` added2 ``` One more ``` added3 ``` # Second Release Profile ``` skipped2 ``` "); var context = new Context(); var results = context.ParseWithDefaults(markdown); var expectedResults = new Dictionary { { "Test Release Profile", new ProfileData { Ignored = new List {"added1"}, Preferred = new Dictionary> { {10, new List {"added2", "added3"}} } } } }; results.Should().BeEquivalentTo(expectedResults); } }