Skip to content

Commit

Permalink
test: increase coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
mu88 committed Oct 12, 2023
1 parent a6e8b33 commit 2dddfdd
Show file tree
Hide file tree
Showing 21 changed files with 227 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
run: 'src/ScreenshotCreator.Api/bin/Debug/net7.0/playwright.ps1 install chromium'
shell: pwsh
- name: Test and collect coverage
run: dotnet test --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=opencover
run: dotnet test --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=opencover '/p:ExcludeByFile=\"**/Log.Prefix.cs\"'
- name: End Sonar scan
run: dotnet sonarscanner end /d:sonar.login=${{ secrets.SONAR_TOKEN }}
- name: Set up QEMU
Expand Down
2 changes: 1 addition & 1 deletion src/ScreenshotCreator.Api/HeaderDictionaryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static void AddWaveshareInstructions(this IHeaderDictionary headers,
headers.Add("waveshare-last-modified-local-time",
GetLastModifiedAsLocalTime(screenshotFile, getLastWriteTimeUtc ?? File.GetLastWriteTimeUtc, localTimeZoneId ?? TimeZoneInfo.Local.Id));
headers.Add("waveshare-sleep-between-updates", screenshotOptions.CalculateSleepBetweenUpdates());
headers.Add("waveshare-update-screen", screenshotOptions.Activity.DisplayShouldBeActive() ? true.ToString() : false.ToString());
headers.Add("waveshare-update-screen", screenshotOptions.Activity.DisplayShouldBeActive().ToString());
}

private static string GetLastModifiedAsLocalTime(string file, Func<string, DateTime> getLastWriteTimeUtc, string localTimeZoneId) =>
Expand Down
6 changes: 6 additions & 0 deletions src/ScreenshotCreator.Api/Log.Prefix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ScreenshotCreator.Api;

public static partial class Log
{
private const string Prefix = nameof(Api);
}
2 changes: 0 additions & 2 deletions src/ScreenshotCreator.Api/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

public static partial class Log
{
private const string Prefix = nameof(Api);

[LoggerMessage(EventId = 0,
EventName = Prefix + nameof(BackgroundServiceTriggered),
Level = LogLevel.Information,
Expand Down
1 change: 1 addition & 0 deletions src/ScreenshotCreator.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
.ValidateDataAnnotations()
.ValidateOnStart();
builder.Services.AddSingleton<IScreenshotCreator, Creator>();
builder.Services.AddSingleton<IPlaywrightHelper, PlaywrightHelper>();
builder.Services.AddSingleton<ImageProcessor>();
builder.Services.AddHostedService<BackgroundScreenshotCreator>();

Expand Down
10 changes: 10 additions & 0 deletions src/ScreenshotCreator.Logic/IPlaywrightHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.Playwright;

namespace ScreenshotCreator.Logic;

public interface IPlaywrightHelper
{
ValueTask<IPage> InitializePlaywrightAsync();

Task WaitAsync();
}
1 change: 0 additions & 1 deletion src/ScreenshotCreator.Logic/IScreenshotCreator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
namespace ScreenshotCreator.Logic;

public interface IScreenshotCreator
: IAsyncDisposable
{
Task CreateScreenshotAsync(uint width, uint height);
}
6 changes: 5 additions & 1 deletion src/ScreenshotCreator.Logic/ImageProcessor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Net.Mime;
using ImageMagick;
using Microsoft.Extensions.Logging;
Expand All @@ -25,11 +26,14 @@ public async Task<ProcessingResult> ProcessAsync(string screenshotFile, bool bla
var bytes = asWaveshareBytes
? ToWaveshareBytes(image)
: image.ToByteArray();
var contentType = asWaveshareBytes ? MediaTypeNames.Application.Octet : image.FormatInfo?.MimeType;
var contentType = asWaveshareBytes ? MediaTypeNames.Application.Octet : GetImageMimeType(image);

return new ProcessingResult(bytes, contentType);
}

[ExcludeFromCodeCoverage(Justification = "Didn't find a of setting FormatInfo to null")]
private static string GetImageMimeType(MagickImage image) => image.FormatInfo?.MimeType ?? "NoClue";

private byte[] ToWaveshareBytes(MagickImage image)
{
if (image.Width != 800 || image.Height != 480)
Expand Down
6 changes: 6 additions & 0 deletions src/ScreenshotCreator.Logic/Log.Prefix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace ScreenshotCreator.Logic;

public static partial class Log
{
private const string Prefix = nameof(Logic);
}
2 changes: 0 additions & 2 deletions src/ScreenshotCreator.Logic/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ namespace ScreenshotCreator.Logic;

public static partial class Log
{
private const string Prefix = nameof(Logic);

[LoggerMessage(EventId = 0,
EventName = Prefix + nameof(PlaywrightInitialized),
Level = LogLevel.Information,
Expand Down
54 changes: 54 additions & 0 deletions src/ScreenshotCreator.Logic/PlaywrightHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Playwright;

namespace ScreenshotCreator.Logic;

[ExcludeFromCodeCoverage]
public sealed class PlaywrightHelper : IPlaywrightHelper, IAsyncDisposable
{
private readonly ILogger<PlaywrightHelper> _logger;
private readonly ScreenshotOptions _screenshotOptions;
private IPlaywright? _playwright;
private IBrowser? _browser;
private IPage? _page;
private bool _disposed;

public PlaywrightHelper(IOptions<ScreenshotOptions> options, ILogger<PlaywrightHelper> logger)
{
_logger = logger;
_screenshotOptions = options.Value;
}

public async ValueTask DisposeAsync()
{
if (_disposed) return;

if (_browser != null) await _browser.DisposeAsync();
_playwright?.Dispose();

_disposed = true;
}

/// <inheritdoc />
public async ValueTask<IPage> InitializePlaywrightAsync()
{
if (_page != null)
{
_logger.ReusingPlaywrightPage();
return _page;
}

_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync();
_page = await _browser.NewPageAsync(new BrowserNewPageOptions { TimezoneId = Environment.GetEnvironmentVariable("TZ") });

_logger.PlaywrightInitialized();

return _page;
}

/// <inheritdoc />
public async Task WaitAsync() => await Task.Delay(TimeSpan.FromSeconds(_screenshotOptions.TimeBetweenHttpCallsInSeconds));
}
55 changes: 12 additions & 43 deletions src/ScreenshotCreator.Logic/ScreenshotCreator.cs
Original file line number Diff line number Diff line change
@@ -1,70 +1,52 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Playwright;

namespace ScreenshotCreator.Logic;

public sealed class ScreenshotCreator : IScreenshotCreator
{
private readonly IPlaywrightHelper _playwrightHelper;
private readonly ILogger<ScreenshotCreator> _logger;
private readonly ScreenshotOptions _screenshotOptions;
private IPage? _page;
private bool _disposed;
private IPlaywright? _playwright;
private IBrowser? _browser;

public ScreenshotCreator(IOptions<ScreenshotOptions> options, ILogger<ScreenshotCreator> logger)
public ScreenshotCreator(IPlaywrightHelper playwrightHelper, IOptions<ScreenshotOptions> options, ILogger<ScreenshotCreator> logger)
{
_playwrightHelper = playwrightHelper;
_logger = logger;
_screenshotOptions = options.Value;
}

public async Task CreateScreenshotAsync(uint width, uint height)
{
if (_page == null)
_page = await InitializePlaywrightAsync();
else
_logger.ReusingPlaywrightPage();
var page = await _playwrightHelper.InitializePlaywrightAsync();

await _page.SetViewportSizeAsync((int)width, (int)height);
if (await NeedsLoginAsync(_page)) { await LoginAsync(_page); }
await page.SetViewportSizeAsync((int)width, (int)height);
if (await NeedsLoginAsync(page)) await LoginAsync(page);

await NavigateToUrlAsync(_page);
await NavigateToUrlAsync(page);

await _page.ScreenshotAsync(new PageScreenshotOptions { Path = _screenshotOptions.ScreenshotFileName, Type = ScreenshotType.Png });
await page.ScreenshotAsync(new PageScreenshotOptions { Path = _screenshotOptions.ScreenshotFileName, Type = ScreenshotType.Png });

_logger.ScreenshotCreated();
}

/// <inheritdoc />
[ExcludeFromCodeCoverage(Justification = "Default Dispose pattern")]
public async ValueTask DisposeAsync()
{
if (_disposed) return;

if (_browser != null) await _browser.DisposeAsync();
_playwright?.Dispose();

_disposed = true;
}

private async Task NavigateToUrlAsync(IPage page)
{
await page.GotoAsync(_screenshotOptions.Url);
await WaitAsync();
await _playwrightHelper.WaitAsync();
}

private async Task LoginAsync(IPage page)
{
_logger.LoggingIn();

await page.GotoAsync(GetBaseUrl());
await WaitAsync();
await _playwrightHelper.WaitAsync();
await page.GetByPlaceholder("User Name").FillAsync(_screenshotOptions.Username);
await page.GetByPlaceholder("Password", new PageGetByPlaceholderOptions { Exact = true }).FillAsync(_screenshotOptions.Password);
await page.GetByRole(AriaRole.Button).ClickAsync();
await WaitAsync();
await _playwrightHelper.WaitAsync();
}

private async Task<bool> NeedsLoginAsync(IPage page)
Expand All @@ -83,18 +65,5 @@ private async Task<bool> NeedsLoginAsync(IPage page)
return needsLogin;
}

private async Task<IPage> InitializePlaywrightAsync()
{
_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync();
var page = await _browser.NewPageAsync(new BrowserNewPageOptions { TimezoneId = Environment.GetEnvironmentVariable("TZ") });

_logger.PlaywrightInitialized();

return page;
}

private async Task WaitAsync() => await Task.Delay(TimeSpan.FromSeconds(_screenshotOptions.TimeBetweenHttpCallsInSeconds));

private string GetBaseUrl() => new Uri(_screenshotOptions.Url).GetLeftPart(UriPartial.Authority);
}
1 change: 1 addition & 0 deletions stryker-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"project-info": {
"name": "github.com/mu88/ScreenshotCreator"
},
"test-case-filter": "TestCategory=Unit",
"ignore-methods": [
"_logger.*"
]
Expand Down
2 changes: 1 addition & 1 deletion tests/Tests/Performance/Logic/ImageProcessorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ public void ProcessImage_ShouldNotConsumeTooMuchMemory_WhenCreatingBlackWhiteIma
public class ImageProcessorBenchmarks
{
[Benchmark]
public async Task ProcessAsync() => await new ImageProcessor(new Mock<ILogger<ImageProcessor>>().Object).ProcessAsync("testData/Screenshot.png", true, true);
public static async Task ProcessAsync() => await new ImageProcessor(new Mock<ILogger<ImageProcessor>>().Object).ProcessAsync("testData/Screenshot.png", true, true);
}
30 changes: 25 additions & 5 deletions tests/Tests/Unit/Api/HeaderDictionaryExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using FluentAssertions;
using FluentAssertions.Extensions;
using Microsoft.AspNetCore.Http;
using Moq;
using ScreenshotCreator.Api;
using ScreenshotCreator.Logic;

Expand All @@ -10,21 +11,40 @@ namespace Tests.Unit.Api;
[Category("Unit")]
public class HeaderDictionaryExtensionsTests
{
[Test]
public void AddWaveshareInstructions()
[TestCase(true,
"",
"",
"15:00",
true,
"1953")]
[TestCase(false,
"16:00",
"13:00",
"15:00",
false,
"15")]
public void AddWaveshareInstructions(bool isNull,
string activeFrom,
string activeTo,
string now,
bool expectedDisplayState,
string expectedSleep)
{
// Arrange
Environment.SetEnvironmentVariable("TZ", null);
var timeProviderMock = new Mock<TimeProvider>();
timeProviderMock.Setup(provider => provider.GetUtcNow()).Returns(12.April(1953).Add(TimeOnly.Parse(now).ToTimeSpan()));
var activity = isNull ? null : new Activity(TimeOnly.Parse(activeFrom), TimeOnly.Parse(activeTo), 15u);
var getLastWriteTimeUtc = (string file) => 12.April(2023).At(19, 53).AsUtc();
var screenshotOptions = new ScreenshotOptions { RefreshIntervalInSeconds = 1953 };
var screenshotOptions = new ScreenshotOptions { RefreshIntervalInSeconds = 1953, Activity = activity };
var testee = new HeaderDictionary();

// Act
testee.AddWaveshareInstructions(screenshotOptions, "testData/Screenshot.png", getLastWriteTimeUtc, "Europe/Berlin");

// Assert
testee.Should().Contain(header => header.Key == "waveshare-update-screen" && header.Value.Single() == "True");
testee.Should().Contain(header => header.Key == "waveshare-sleep-between-updates" && header.Value.Single() == "1953");
testee.Should().Contain(header => header.Key == "waveshare-update-screen" && header.Value.Single() == expectedDisplayState.ToString());
testee.Should().Contain(header => header.Key == "waveshare-sleep-between-updates" && header.Value.Single() == expectedSleep);
testee.Should().Contain(header => header.Key == "waveshare-last-modified-local-time");
TimeOnly.Parse(testee["waveshare-last-modified-local-time"].Single()!)
.Should()
Expand Down
6 changes: 5 additions & 1 deletion tests/Tests/Unit/Logic/ActivityExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ public class ActivityExtensionsTests
{
[TestCase(true, "", "", "15:00", true)]
[TestCase(false, "13:00", "16:00", "15:00", true)]
[TestCase(false, "15:00", "16:00", "15:00", true)]
[TestCase(false, "14:00", "15:00", "15:00", true)]
[TestCase(false, "16:00", "13:00", "15:00", false)]
[TestCase(false, "14:00", "14:30", "15:00", false)]
public void DisplayShouldBeActive(bool isNull, string activeFrom, string activeTo, string now, bool expectedResult)
{
// Arrange
Environment.SetEnvironmentVariable("TZ", null);
var timeProviderMock = new Mock<TimeProvider>();
timeProviderMock.Setup(provider => provider.GetUtcNow()).Returns(12.April(1953).Add(TimeOnly.Parse(now).ToTimeSpan()));
timeProviderMock.Setup(provider => provider.GetUtcNow()).Returns(12.April(2023).Add(TimeOnly.Parse(now).ToTimeSpan()));
var testee = isNull ? null : new Activity(TimeOnly.Parse(activeFrom), TimeOnly.Parse(activeTo), 0u);

// Act
Expand Down
8 changes: 5 additions & 3 deletions tests/Tests/Unit/Logic/ImageProcessorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,16 @@ public async Task ProcessImage_ShouldCreateBlackWhiteImageInWaveshareFormat()
result.MediaType.Should().Be(MediaTypeNames.Application.Octet);
}

[Test]
public async Task ProcessImage_ShouldReturnEmptyIfImageHasInvalidDimensions()
[TestCase("testData/Screenshot_invalidByInvalid.png")]
[TestCase("testData/Screenshot_invalidBy480.png")]
[TestCase("testData/Screenshot_800ByInvalid.png")]
public async Task ProcessImage_ShouldReturnEmptyIfImageHasInvalidDimensions(string fileName)
{
// Arrange
var testee = new ImageProcessor(new Mock<ILogger<ImageProcessor>>().Object);

// Act
var result = await testee.ProcessAsync("testData/Screenshot_invalid_dimensions.png", true, true);
var result = await testee.ProcessAsync(fileName, true, true);

// Assert
result.Data.Should().HaveCount(0);
Expand Down
Loading

0 comments on commit 2dddfdd

Please sign in to comment.