-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
864b18f
commit 24b630e
Showing
14 changed files
with
531 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
96 changes: 96 additions & 0 deletions
96
PriceChecker.Core.Tests/Repositories/SettingsRepositoryTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
using System; | ||
using AutoFixture; | ||
using Genius.PriceChecker.Core.Messages; | ||
using Genius.PriceChecker.Core.Models; | ||
using Genius.PriceChecker.Core.Repositories; | ||
using Genius.PriceChecker.Core.Services; | ||
using Genius.PriceChecker.Infrastructure.Events; | ||
using Microsoft.Extensions.Logging; | ||
using Moq; | ||
using Xunit; | ||
|
||
namespace Genius.PriceChecker.Core.Tests.Repositories | ||
{ | ||
public class SettingsRepositoryTests | ||
{ | ||
private readonly Fixture _fixture = new(); | ||
private readonly Mock<IEventBus> _eventBusMock = new(); | ||
private readonly Mock<IPersister> _persisterMock = new(); | ||
|
||
[Fact] | ||
public void Constructor__Previous_settings_exist__Loaded() | ||
{ | ||
// Arrange | ||
var settings = _fixture.Create<Settings>(); | ||
|
||
// Act | ||
var sut = CreateSystemUnderTest(settings); | ||
|
||
// Verify | ||
Assert.Equal(settings, sut.Get()); | ||
} | ||
|
||
[Fact] | ||
public void Constructor__Previous_settings_dont_exist__Loaded_default() | ||
{ | ||
// Arrange | ||
Settings settings = null; | ||
|
||
// Act | ||
var sut = CreateSystemUnderTest(settings); | ||
|
||
// Verify | ||
var result = sut.Get(); | ||
Assert.False(result.AutoRefreshEnabled); | ||
Assert.Equal(1440, result.AutoRefreshMinutes); | ||
} | ||
|
||
[Fact] | ||
public void Get__returns_currently_loaded_settings() | ||
{ | ||
// Arrange | ||
var settings = _fixture.Create<Settings>(); | ||
var sut = CreateSystemUnderTest(settings); | ||
|
||
// Act | ||
var result = sut.Get(); | ||
|
||
// Verify | ||
Assert.Equal(settings, result); | ||
} | ||
|
||
[Fact] | ||
public void Store__Argument_not_provided__throws_exception() | ||
{ | ||
// Arrange | ||
var sut = CreateSystemUnderTest(); | ||
|
||
// Act & Verify | ||
Assert.Throws<ArgumentNullException>(() => sut.Store(null)); | ||
} | ||
|
||
[Fact] | ||
public void Store__Replaces_existing_settings_and_updates_cache_and_fires_event() | ||
{ | ||
// Arrange | ||
var sut = CreateSystemUnderTest(); | ||
var newSettings = _fixture.Create<Settings>(); | ||
|
||
// Act | ||
sut.Store(newSettings); | ||
|
||
// Verify | ||
Assert.Equal(newSettings, sut.Get()); | ||
_persisterMock.Verify(x => x.Store(It.IsAny<string>(), newSettings)); | ||
_eventBusMock.Verify(x => x.Publish(It.Is<SettingsUpdatedEvent>(e => e.Settings == newSettings)), Times.Once); | ||
} | ||
|
||
private SettingsRepository CreateSystemUnderTest(Settings settings = null) | ||
{ | ||
_persisterMock.Setup(x => x.Load<Settings>(It.IsAny<string>())) | ||
.Returns(settings); | ||
return new SettingsRepository(_eventBusMock.Object, _persisterMock.Object, | ||
Mock.Of<ILogger<SettingsRepository>>()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using AutoFixture; | ||
using Genius.PriceChecker.Core.Models; | ||
using Genius.PriceChecker.Core.Services; | ||
using Microsoft.Extensions.Logging; | ||
using Moq; | ||
using Xunit; | ||
|
||
namespace Genius.PriceChecker.Core.Tests.Services | ||
{ | ||
public class PriceSeekerTests | ||
{ | ||
private readonly Fixture _fixture = new(); | ||
private readonly Mock<ITrickyHttpClient> _httpMock = new(); | ||
private readonly Mock<IIoService> _ioMock = new(); | ||
private readonly Mock<ILogger<PriceSeeker>> _loggerMock = new(); | ||
|
||
private readonly PriceSeeker _sut; | ||
|
||
public PriceSeekerTests() | ||
{ | ||
_fixture.Behaviors.Add(new OmitOnRecursionBehavior(recursionDepth: 2)); | ||
|
||
_sut = new PriceSeeker(_httpMock.Object, _ioMock.Object, _loggerMock.Object); | ||
} | ||
|
||
[Fact] | ||
public async Task SeekAsync__Happy_flow_scenario() | ||
{ | ||
// Arrange | ||
var product = CreateSampleProduct(); | ||
|
||
// Act | ||
var result = await _sut.SeekAsync(product, new CancellationToken()); | ||
|
||
// Verify | ||
Assert.Equal(product.Sources.Length, result.Length); | ||
_ioMock.Verify(x => x.WriteTextToFile(It.IsAny<string>(), It.IsAny<string>()), Times.Never); | ||
TestHelpers.VerifyLogger(_loggerMock, LogLevel.Warning, Times.Never()); | ||
TestHelpers.VerifyLogger(_loggerMock, LogLevel.Error, Times.Never()); | ||
} | ||
|
||
[Fact] | ||
public async Task SeekAsync__Content_wasnt_downloaded__Returns_null() | ||
{ | ||
// Arrange | ||
var product = CreateSampleProduct(sourcesCount: 1); | ||
_httpMock.Setup(x => x.DownloadContent(It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync((string)null); | ||
|
||
// Act | ||
var result = await _sut.SeekAsync(product, new CancellationToken()); | ||
|
||
// Verify | ||
Assert.Empty(result); | ||
} | ||
|
||
[Fact] | ||
public async Task SeekAsync__Content_not_matched_the_pattern__Returns_null_and_dumps_file() | ||
{ | ||
// Arrange | ||
var product = CreateSampleProduct(sourcesCount: 1); | ||
var contentNotMatchingAnything = _fixture.Create<string>(); | ||
_httpMock.Setup(x => x.DownloadContent(It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(contentNotMatchingAnything); | ||
|
||
// Act | ||
var result = await _sut.SeekAsync(product, new CancellationToken()); | ||
|
||
// Verify | ||
Assert.Empty(result); | ||
_ioMock.Verify(x => x.WriteTextToFile(It.IsAny<string>(), It.IsAny<string>()), Times.Once); | ||
TestHelpers.VerifyLogger(_loggerMock, LogLevel.Error); | ||
} | ||
|
||
[Fact] | ||
public async Task SeekAsync__DecimalDelimiter_isnt_default__Considered_in_price_parse() | ||
{ | ||
// Arrange | ||
var product = CreateSampleProduct(sourcesCount: 1, delimiter: ';'); | ||
|
||
// Act | ||
var result = await _sut.SeekAsync(product, new CancellationToken()); | ||
|
||
// Verify | ||
Assert.Single(result); | ||
Assert.NotEqual(0, result[0].Price); | ||
Assert.NotEqual((int)result[0].Price, result[0].Price); // Check if it is decimal | ||
} | ||
|
||
[Fact] | ||
public async Task SeekAsync__Price_is_invalid__Returns_null() | ||
{ | ||
// Arrange | ||
var product = CreateSampleProduct(sourcesCount: 1); | ||
var contentPriceInvalid = "`0`"; | ||
_httpMock.Setup(x => x.DownloadContent(It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(contentPriceInvalid); | ||
|
||
// Act | ||
var result = await _sut.SeekAsync(product, new CancellationToken()); | ||
|
||
// Verify | ||
Assert.Empty(result); | ||
TestHelpers.VerifyLogger(_loggerMock, LogLevel.Warning); | ||
} | ||
|
||
[Fact] | ||
public async Task SeekAsync__Price_isnt_convertible__Returns_null() | ||
{ | ||
// Arrange | ||
var product = CreateSampleProduct(sourcesCount: 1); | ||
var contentPriceInvalid = "`not-a-number`"; | ||
_httpMock.Setup(x => x.DownloadContent(It.IsAny<string>(), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(contentPriceInvalid); | ||
|
||
// Act | ||
var result = await _sut.SeekAsync(product, new CancellationToken()); | ||
|
||
// Verify | ||
Assert.Empty(result); | ||
TestHelpers.VerifyLogger(_loggerMock, LogLevel.Error); | ||
} | ||
|
||
private Product CreateSampleProduct(int sourcesCount = 3, char delimiter = '.') | ||
{ | ||
var product = _fixture.Build<Product>() | ||
.With(x => x.Sources, _fixture.CreateMany<ProductSource>(sourcesCount).ToArray()) | ||
.Create(); | ||
foreach (var productSource in product.Sources) | ||
{ | ||
productSource.Agent = _fixture.Build<Agent>() | ||
.With(x => x.Url, _fixture.Create<string>() + "{0}") | ||
.With(x => x.PricePattern, $@"`(?<price>[\d\{delimiter}]+)`") | ||
.With(x => x.DecimalDelimiter, delimiter) | ||
.Create(); | ||
|
||
var priceDec = _fixture.Create<int>(); | ||
var priceFlt = _fixture.Create<int>(); | ||
var content = $"{_fixture.Create<string>()}`{priceDec}{delimiter}{priceFlt}`{_fixture.Create<string>()}"; | ||
|
||
_httpMock.Setup(x => x.DownloadContent( | ||
It.Is<string>(url => url == string.Format(productSource.Agent.Url, productSource.AgentArgument)), It.IsAny<CancellationToken>())) | ||
.ReturnsAsync(content); | ||
} | ||
return product; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System; | ||
using Microsoft.Extensions.Logging; | ||
using Moq; | ||
|
||
namespace Genius.PriceChecker.Core.Tests | ||
{ | ||
public static class TestHelpers | ||
{ | ||
public static void VerifyLogger<T>(Mock<ILogger<T>> loggerMock, LogLevel logLevel, Times? times = null) | ||
{ | ||
if (times == null) | ||
times = Times.Once(); | ||
loggerMock.Verify(x => x.Log(logLevel, | ||
It.IsAny<EventId>(), | ||
It.IsAny<It.IsAnyType>(), | ||
It.IsAny<Exception>(), | ||
(Func<It.IsAnyType, Exception, string>) It.IsAny<object>()), times.Value); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO; | ||
using System.Text; | ||
|
||
namespace Genius.PriceChecker.Core.Services | ||
{ | ||
public interface IIoService | ||
{ | ||
bool FileExists(string path); | ||
string ReadTextFromFile(string path); | ||
void WriteTextToFile(string path, string content); | ||
} | ||
|
||
[ExcludeFromCodeCoverage] | ||
public class IoService : IIoService | ||
{ | ||
public bool FileExists(string path) | ||
=> File.Exists(path); | ||
|
||
public string ReadTextFromFile(string path) | ||
=> File.ReadAllText(path); | ||
|
||
public void WriteTextToFile(string path, string content) | ||
=> File.WriteAllText(path, content, Encoding.UTF8); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.