fix: Log warning if a "potential" score is found

A potential score is one where a number between brackets is found (e.g.
[100]) but the word "score" is not before it. A warning is logged to
notify the user of a potential issue with the guide itself.

Other changes:

- `Serilog.Sinks.TestCorrelator` package added to test log output
  messages.
- New StringUtils class in TestLibrary
- Refactored `Regex.Match()` calls to use new extension method that
  allows the match to be directly used as an expression in an `if`
  condition while also providing the match object as output.
pull/5/head
Robert Dailey 3 years ago
parent 9065932eaa
commit b9db8b7ef3

@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- A warning is now logged when we find a number in brackets (such as `[100]`) without the word
`score` before it. This represents a potential score and bug in the guide itself.
## [1.0.0] - 2021-04-14
See the [Python Migration Guide][py-mig] for details on how to update your YAML configuration.

@ -24,6 +24,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="AutofacContrib.NSubstitute" />
<PackageReference Include="GitHubActionsTestLogger" />
<PackageReference Include="Serilog.Sinks.TestCorrelator" />
</ItemGroup>
<ItemGroup Condition="$(ProjectName.EndsWith('.Tests'))">
<EmbeddedResource Include="**\Data\*" />

@ -10,6 +10,7 @@
<PackageReference Update="NUnit.Analyzers" Version="3.*" />
<PackageReference Update="NUnit" Version="3.*" />
<PackageReference Update="NUnit3TestAdapter" Version="3.*" />
<PackageReference Update="Serilog.Sinks.TestCorrelator" Version="3.*" />
<!-- Non-Test Packages -->
<PackageReference Update="Autofac.Extensions.DependencyInjection" Version="7.*" />

@ -0,0 +1,7 @@
namespace TestLibrary
{
public static class StringUtils
{
public static string TrimmedString(string value) => value.Trim('\r', '\n');
}
}

@ -4,11 +4,12 @@ using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using Serilog;
using Serilog.Sinks.TestCorrelator;
using TestLibrary;
using Trash.Sonarr;
using Trash.Sonarr.ReleaseProfile;
namespace Trash.Tests.Sonarr.Guide
namespace Trash.Tests.Sonarr.ReleaseProfile
{
[TestFixture]
public class ReleaseProfileParserTest
@ -17,8 +18,15 @@ namespace Trash.Tests.Sonarr.Guide
{
public Context()
{
var logger = new LoggerConfiguration()
.WriteTo.TestCorrelator()
.MinimumLevel.Debug()
.CreateLogger();
Config = Substitute.For<SonarrConfiguration>();
GuideParser = new ReleaseProfileGuideParser(Substitute.For<ILogger>());
Config.ReleaseProfiles.Add(new ReleaseProfileConfig());
GuideParser = new ReleaseProfileGuideParser(logger);
}
public SonarrConfiguration Config { get; }
@ -30,8 +38,6 @@ namespace Trash.Tests.Sonarr.Guide
public void Parse_IgnoredRequiredPreferredScores()
{
var context = new Context();
context.Config.ReleaseProfiles.Add(new ReleaseProfileConfig());
var markdown = context.TestData.GetResourceData("test_parse_markdown_complete_doc.md");
var results = context.GuideParser.ParseMarkdown(context.Config.ReleaseProfiles.First(), markdown);
@ -48,30 +54,53 @@ namespace Trash.Tests.Sonarr.Guide
public void Parse_IncludePreferredWhenRenaming()
{
var context = new Context();
context.Config.ReleaseProfiles.Add(new ReleaseProfileConfig());
var markdown = context.TestData.GetResourceData("include_preferred_when_renaming.md");
var results = context.GuideParser.ParseMarkdown(context.Config.ReleaseProfiles.First(), markdown);
results.Should()
.ContainKey("First Release Profile")
.WhichValue.IncludePreferredWhenRenaming.Should()
.Be(true);
.WhichValue.IncludePreferredWhenRenaming.Should().Be(true);
results.Should()
.ContainKey("Second Release Profile")
.WhichValue.IncludePreferredWhenRenaming.Should()
.Be(false);
.WhichValue.IncludePreferredWhenRenaming.Should().Be(false);
}
[Test]
public void Parse_PotentialScore_WarningLogged()
{
string 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.GuideParser.ParseMarkdown(context.Config.ReleaseProfiles.First(), markdown);
results.Should().ContainKey("First Release Profile")
.WhichValue.Should().BeEquivalentTo(new ProfileData());
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_StrictNegativeScores()
{
var context = new Context();
context.Config.ReleaseProfiles.Add(new ReleaseProfileConfig
context.Config.ReleaseProfiles = new List<ReleaseProfileConfig>
{
// Pretend the user specified this option for testing purposes
StrictNegativeScores = true
});
new() {StrictNegativeScores = true}
};
var markdown = context.TestData.GetResourceData("strict_negative_scores.md");
var results = context.GuideParser.ParseMarkdown(context.Config.ReleaseProfiles.First(), markdown);

@ -0,0 +1,13 @@
using System.Text.RegularExpressions;
namespace Trash.Extensions
{
public static class RegexExtensions
{
public static bool Match(this Regex re, string strToCheck, out Match match)
{
match = re.Match(strToCheck);
return match.Success;
}
}
}

@ -17,16 +17,6 @@ namespace Trash.Sonarr.Api
_config = config;
}
private string BaseUrl()
{
if (_config.ActiveConfiguration == null)
{
throw new InvalidOperationException("No active configuration available for API method");
}
return _config.ActiveConfiguration.BuildUrl();
}
public async Task<Version> GetVersion()
{
dynamic data = await BaseUrl()
@ -87,5 +77,15 @@ namespace Trash.Sonarr.Api
.PutJsonAsync(newQuality)
.ReceiveJson<List<SonarrQualityDefinitionItem>>();
}
private string BaseUrl()
{
if (_config.ActiveConfiguration == null)
{
throw new InvalidOperationException("No active configuration available for API method");
}
return _config.ActiveConfiguration.BuildUrl();
}
}
}

@ -27,6 +27,7 @@ namespace Trash.Sonarr.ReleaseProfile
private readonly Regex _regexHeader = new(@"^(#+)\s([\w\s\d]+)\s*$", RegexOptions.Compiled);
private readonly Regex _regexHeaderReleaseProfile = BuildRegex(@"release profile");
private readonly Regex _regexPotentialScore = BuildRegex(@"\[(-?[\d]+)\]");
private readonly Regex _regexScore = BuildRegex(@"score.*?\[(-?[\d]+)\]");
public ReleaseProfileGuideParser(ILogger logger)
@ -49,6 +50,7 @@ namespace Trash.Sonarr.ReleaseProfile
var reader = new StringReader(markdown);
for (var line = reader.ReadLine(); line != null; line = reader.ReadLine())
{
state.LineNumber++;
if (string.IsNullOrEmpty(line))
{
continue;
@ -158,9 +160,11 @@ namespace Trash.Sonarr.ReleaseProfile
private void ParseMarkdownOutsideFence(string line, ParserState state, IDictionary<string, ProfileData> results)
{
// ReSharper disable once InlineOutVariableDeclaration
Match match;
// Header Processing
var match = _regexHeader.Match(line);
if (match.Success)
if (_regexHeader.Match(line, out match))
{
var headerDepth = match.Groups[1].Length;
var headerText = match.Groups[2].Value;
@ -211,20 +215,24 @@ namespace Trash.Sonarr.ReleaseProfile
// return;
}
match = _regexScore.Match(line);
if (match.Success)
if (_regexScore.Match(line, out match))
{
state.Score = int.Parse(match.Groups[1].Value);
Log.Debug(" - Score [Value: {Score}]", state.Score);
}
else if (_regexPotentialScore.Match(line, out match))
{
Log.Warning("Found a potential score on line #{Line} that will be ignored because the " +
"word 'score' is missing (This is probably a bug in the guide itself): {ScoreMatch}",
state.LineNumber, match.Groups[0].Value);
}
}
private TermCategory? ParseCategory(string line)
{
foreach (var (category, regex) in _regexCategories)
{
var match = regex.Match(line);
if (match.Success)
if (regex.Match(line).Success)
{
return category;
}
@ -252,6 +260,7 @@ namespace Trash.Sonarr.ReleaseProfile
public TermCategory CurrentCategory { get; set; }
public int BracketDepth { get; set; }
public int CurrentHeaderDepth { get; set; }
public int LineNumber { get; set; }
public bool IsValid => ProfileName != null && (CurrentCategory != TermCategory.Preferred || Score != null);

@ -10,7 +10,7 @@ namespace Trash.Sonarr
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class SonarrConfiguration : BaseConfiguration
{
public List<ReleaseProfileConfig> ReleaseProfiles { get; init; } = new();
public List<ReleaseProfileConfig> ReleaseProfiles { get; set; } = new();
public SonarrQualityDefinitionType? QualityDefinition { get; init; }
public override string BuildUrl()

Loading…
Cancel
Save