Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions ExchangeRateUpdater.UnitTests/ExchangeRateFilterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using ExchangeRateUpdater.Models;
using ExchangeRateUpdater.Utils;
using FluentAssertions;
using FluentAssertions.Execution;

namespace ExchangeRateUpdater.UnitTests
{
[TestClass]
public class ExchangeRateFilterTests
{
#region Test Data Fields
private readonly ExchangeRate _usdCzkExchangeRate = new ExchangeRate(new Currency("USD"), new Currency("CZK"), 22.32m);

private readonly ExchangeRate _eurCzkExchangeRate = new ExchangeRate(new Currency("EUR"), new Currency("CZK"), 24.84m);

private readonly ExchangeRate _gbpCzkExchangeRate = new ExchangeRate(new Currency("GBP"), new Currency("CZK"), 29.79m);

private readonly Currency _usd = new("USD");

private readonly Currency _eur = new("EUR");

private readonly Currency _xyz = new("XYZ");
#endregion

[TestMethod]
public void FilterByCurrencies_WithMatchingCurrencies_ReturnMatchedCurrencies()
{
// Arrange
IEnumerable<ExchangeRate> exchangeRates = new List<ExchangeRate> { _usdCzkExchangeRate, _eurCzkExchangeRate, _gbpCzkExchangeRate };
IEnumerable<Currency> currencies = new List<Currency> { _usd, _eur, _xyz };

// Act
IEnumerable<ExchangeRate> resultExchangeRates = ExchangeRateFilter.FilterByCurrencies(exchangeRates, currencies);

// Assert
using (new AssertionScope())
{
resultExchangeRates.Should().HaveCount(2);
resultExchangeRates.Should().Contain(_usdCzkExchangeRate);
resultExchangeRates.Should().Contain(_eurCzkExchangeRate);
}
}

[TestMethod]
public void FilterByCurrencies_WithNoMatchingCurrencies_ReturnEmptyList()
{
// Arrange
IEnumerable<ExchangeRate> exchangeRates = new List<ExchangeRate> { _eurCzkExchangeRate, _gbpCzkExchangeRate };
IEnumerable<Currency> currencies = new List<Currency> { _usd, _xyz };

// Act
IEnumerable<ExchangeRate> resultExchangeRates = ExchangeRateFilter.FilterByCurrencies(exchangeRates, currencies);

// Assert
resultExchangeRates.Should().BeEmpty();
}

[TestMethod]
public void FilterByCurrencies_WithEmptyExchangeRates_ReturnsEmptyList()
{
// Arrange
IEnumerable<ExchangeRate> exchangeRates = new List<ExchangeRate>();
IEnumerable<Currency> currencies = new List<Currency> { _usd, _xyz };

// Act
IEnumerable<ExchangeRate> resultExchangeRates = ExchangeRateFilter.FilterByCurrencies(exchangeRates, currencies);

// Assert
resultExchangeRates.Should().BeEmpty();
}

[TestMethod]
public void FilterByCurrencies_WithEmptyCurrencies_ThrowsArgumentException()
{
// Arrange
IEnumerable<ExchangeRate> exchangeRates = new List<ExchangeRate> { _eurCzkExchangeRate, _gbpCzkExchangeRate };
IEnumerable<Currency> currencies = new List<Currency>();

// Act
Action act = () => ExchangeRateFilter.FilterByCurrencies(exchangeRates, currencies);

// Assert
act.Should().Throw<ArgumentException>();
}

[TestMethod]
public void FilterByCurrencies_WithNullCurrencies_ThrowsArgumentException()
{
// Arrange
IEnumerable<ExchangeRate> exchangeRates = new List<ExchangeRate> { _eurCzkExchangeRate, _gbpCzkExchangeRate };

// Act
Action act = () => ExchangeRateFilter.FilterByCurrencies(exchangeRates, null);

// Assert
act.Should().Throw<ArgumentNullException>();
}
}
}
124 changes: 124 additions & 0 deletions ExchangeRateUpdater.UnitTests/ExchangeRateServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using ExchangeRateUpdater.DataFetchers;
using ExchangeRateUpdater.Models;
using ExchangeRateUpdater.Parsers;
using ExchangeRateUpdater.Services;
using FluentAssertions;
using FluentAssertions.Execution;
using Moq;

namespace ExchangeRateUpdater.UnitTests
{
[TestClass]
public class ExchangeRateServiceTests
{
#region Test Data Fields
private readonly string _validFileContent = "15 May 2025 #92\r\nCountry|Currency|Amount|Code|Rate\r\nAustralia|dollar|1|AUD|14.281\r\nBrazil|real|1|BRL|3.960\r\nBulgaria|lev|1|BGN|12.745";

private readonly string _invalidFileContent = "15 May 2025 #92\r\nCountry;Currency;Amount;Code;Rate\r\nAustralia;dollar;1;AUD;14.281\r\nBrazil;real;1;BRL;3.960";

private readonly ExchangeRate _audCzkExchangeRate = new ExchangeRate(new Currency("AUD"), new Currency("CZK"), 14.281m);

private readonly ExchangeRate _brlCzkExchangeRate = new ExchangeRate(new Currency("BRL"), new Currency("CZK"), 3.960m);

private readonly ExchangeRate _bgnCzkExchangeRate = new ExchangeRate(new Currency("BGN"), new Currency("CZK"), 12.745m);

private readonly Currency _aud = new("AUD");

private readonly Currency _eur = new("EUR");

private readonly Currency _xyz = new("XYZ");
#endregion

[TestMethod]
public void GetExchangeRates_WithValidCurrencies_ReturnsMatchingExchangeRates()
{
// Arrange
var mockFetcher = new Mock<IRemoteDataFetcher>();
var mockParser = new Mock<IParser>();

IEnumerable<ExchangeRate> exchangeRates = new List<ExchangeRate> { _audCzkExchangeRate, _brlCzkExchangeRate, _bgnCzkExchangeRate };
IEnumerable<Currency> currencies = new List<Currency>() { _aud, _eur, _xyz };

mockFetcher.Setup(f => f.FetchData()).Returns(_validFileContent);
mockParser.Setup(p => p.ParseData(_validFileContent)).Returns(exchangeRates);

ExchangeRateService exchangeRateService = new(mockFetcher.Object, mockParser.Object);

// Act
var result = exchangeRateService.GetExchangeRates(currencies);

// Assert
using (new AssertionScope())
{
result.Should().NotBeEmpty();
result.Should().HaveCount(1);
result.Should().Contain(_audCzkExchangeRate);
}
}

[TestMethod]
public void GetExchangeRates_WithNoMatchingCurrencies_ReturnsEmptyList()
{
// Arrange
var mockFetcher = new Mock<IRemoteDataFetcher>();
var mockParser = new Mock<IParser>();

IEnumerable<ExchangeRate> exchangeRates = new List<ExchangeRate> { _audCzkExchangeRate, _brlCzkExchangeRate, _bgnCzkExchangeRate };
IEnumerable<Currency> currencies = new List<Currency>() { _eur, _xyz };

mockFetcher.Setup(f => f.FetchData()).Returns(_validFileContent);
mockParser.Setup(p => p.ParseData(_validFileContent)).Returns(exchangeRates);

ExchangeRateService exchangeRateService = new(mockFetcher.Object, mockParser.Object);

// Act
var result = exchangeRateService.GetExchangeRates(currencies);

// Assert
result.Should().BeEmpty();
}

[TestMethod]
public void GetExchangeRates_WithMalformedData_ThrowsException()
{
// Arrange
var mockFetcher = new Mock<IRemoteDataFetcher>();
var mockParser = new Mock<IParser>();

IEnumerable<Currency> currencies = new List<Currency>() { _aud, _eur, _xyz };

mockFetcher.Setup(f => f.FetchData()).Returns(_invalidFileContent);
mockParser.Setup(p => p.ParseData(_invalidFileContent)).Throws(new Exception());

ExchangeRateService exchangeRateService = new(mockFetcher.Object, mockParser.Object);

// Act
Action act = () => exchangeRateService.GetExchangeRates(currencies);

// Assert
act.Should().Throw<Exception>();
}

[TestMethod]
public void GetExchangeRates_WithEmptyInput_ThrowsArgumentNullException()
{
// Arrange
var mockFetcher = new Mock<IRemoteDataFetcher>();
var mockParser = new Mock<IParser>();

string fileContent = "";
IEnumerable<Currency> currencies = new List<Currency>() { _aud, _eur, _xyz };

mockFetcher.Setup(f => f.FetchData()).Returns(fileContent);
mockParser.Setup(p => p.ParseData(fileContent)).Throws(new ArgumentNullException());

ExchangeRateService exchangeRateService = new(mockFetcher.Object, mockParser.Object);

// Act
Action act = () => exchangeRateService.GetExchangeRates(currencies);

// Assert
act.Should().Throw<ArgumentNullException>();
}
}
}
25 changes: 25 additions & 0 deletions ExchangeRateUpdater.UnitTests/ExchangeRateUpdater.UnitTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="8.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="MSTest" Version="3.6.4" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ExchangeRateUpdater\ExchangeRateUpdater.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>
136 changes: 136 additions & 0 deletions ExchangeRateUpdater.UnitTests/HttpDataFetcherTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using System.Net;
using ExchangeRateUpdater.DataFetchers;
using FluentAssertions;
using FluentAssertions.Execution;
using Moq;
using Moq.Protected;
using Polly;
using Polly.Retry;

namespace ExchangeRateUpdater.UnitTests
{
[TestClass]
public class HttpDataFetcherTests
{
#region Test Data Fields
private readonly string _methodName = "SendAsync";

private readonly string _expectedContent = "15 May 2025 #92\r\nCountry|Currency|Amount|Code|Rate\r\nAustralia|dollar|1|AUD|14.281\r\nBrazil|real|1|BRL|3.960";

private readonly AsyncRetryPolicy<HttpResponseMessage> _retryPolicy = Policy
.HandleResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.Or<HttpRequestException>()
.Or<TaskCanceledException>()
.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
#endregion

[TestMethod]
public void FetchData_WithSuccessfulReponse_ReturnsExpectedData()
{
// Arrange
var mockHandler = new Mock<HttpMessageHandler>();
mockHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(_methodName, ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(_expectedContent)
});

var httpClient = new HttpClient(mockHandler.Object);
var fetcher = new HttpDataFetcher(httpClient, _retryPolicy);

// Act
var result = fetcher.FetchData();

// Assert
result.Should().Be(_expectedContent);
}

[TestMethod]
public void FetchData_WithFailures_RetriesAndReturnsExpectedData()
{
// Arrange
var mockHandler = new Mock<HttpMessageHandler>();
int callCount = 0;

mockHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(_methodName, ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(() =>
{
callCount++;
if (callCount < 3)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}

return new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(_expectedContent)
};
});

var httpClient = new HttpClient(mockHandler.Object);
var fetcher = new HttpDataFetcher(httpClient, _retryPolicy);

// Act
var result = fetcher.FetchData();

// Assert
using (new AssertionScope())
{
result.Should().Be(_expectedContent);
mockHandler.Protected().Verify(_methodName, Times.Exactly(3), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
}

}

[TestMethod]
public void FetchData_WithNotSuccessfulReponse_ThrowsExceptionAfterMaxRetries()
{
// Arrange
var mockHandler = new Mock<HttpMessageHandler>();
mockHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(_methodName, ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage(HttpStatusCode.InternalServerError));


var httpClient = new HttpClient(mockHandler.Object);
var fetcher = new HttpDataFetcher(httpClient, _retryPolicy);

// Act
Action act = () => fetcher.FetchData();

// Assert
using (new AssertionScope())
{
act.Should().Throw<HttpRequestException>();
mockHandler.Protected().Verify(_methodName, Times.Exactly(4), ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>());
}
}

[TestMethod]
public void FetchData_WithCanceledTask_ThrowsTaskCanceledException()
{
// Arrange
var mockHandler = new Mock<HttpMessageHandler>();
mockHandler
.Protected()
.Setup<Task<HttpResponseMessage>>(_methodName, ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
.ThrowsAsync(new TaskCanceledException());

var httpClient = new HttpClient(mockHandler.Object);
var fetcher = new HttpDataFetcher(httpClient, _retryPolicy);

// Act
Action act = () => fetcher.FetchData();

// Assert
act.Should().Throw<TaskCanceledException>();
}
}
}
1 change: 1 addition & 0 deletions ExchangeRateUpdater.UnitTests/MSTestSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[assembly: Parallelize(Scope = ExecutionScope.MethodLevel)]
Loading