Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sebastian Banzas exchange rates #617

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@
node_modules
bower_components
npm-debug.log
*.vsidx
*.v2
139 changes: 139 additions & 0 deletions jobs/Backend/ExchangeRateUpdaterTest/ExchangeRateProviderTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using ExchangeRateUpdater;
using ExchangeRateUpdater.API.CNB;
using ExchangeRateUpdater.API.CNB.Model.Responses;
using Moq;

namespace ExchangeRateUpdaterTest
{
public class ExchangeRateProviderTest
{
private Mock<ICNBApiClient> _cnbApiClientMock;
private ExchangeRateProvider _exchangeRateProvider;

[SetUp]
public void Setup()
{
_cnbApiClientMock = new Mock<ICNBApiClient>();
_exchangeRateProvider = new ExchangeRateProvider(_cnbApiClientMock.Object);
}

[Test]
public async Task GetExchangeRates_ShouldReturnRatesForSpecifiedCurrencies()
{
// Arrange
var currencies = new List<Currency>
{
new Currency("USD"),
new Currency("EUR")
};

var apiResponse = new ExRateDailyResponse
{
Rates = new List<ExRateDailyRest>
{
new ExRateDailyRest { CurrencyCode = "USD", Rate = 25.0m, Amount = 1, },
new ExRateDailyRest { CurrencyCode = "EUR", Rate = 27.0m, Amount = 1 },
new ExRateDailyRest { CurrencyCode = "GBP", Rate = 30.0m, Amount = 1 }
}
};

_cnbApiClientMock.Setup(c => c.GetDailyRates(It.IsAny<string>(), It.IsAny<DateTime?>())).ReturnsAsync(apiResponse);

// Act
var result = await _exchangeRateProvider.GetExchangeRates(currencies);

// Assert
Assert.That(result.Count(), Is.EqualTo(2));
Assert.That(result.Any(r => r.SourceCurrency.Code == "USD"), Is.True);
Assert.That(result.Any(r => r.SourceCurrency.Code == "EUR"), Is.True);
}

[Test]
public async Task GetExchangeRates_ShouldIgnoreUndefinedCurrencies()
{
// Arrange
var currencies = new List<Currency>
{
new Currency("USD"),
new Currency("XYZ")
};

var apiResponse = new ExRateDailyResponse
{
Rates = new List<ExRateDailyRest>
{
new ExRateDailyRest { CurrencyCode = "USD", Rate = 25.0m, Amount = 1 }
}
};

_cnbApiClientMock.Setup(c => c.GetDailyRates(It.IsAny<string>(), It.IsAny<DateTime?>())).ReturnsAsync(apiResponse);

// Act
var result = await _exchangeRateProvider.GetExchangeRates(currencies);

// Assert
Assert.That(result.Count(), Is.EqualTo(1));
Assert.That(result.Any(r => r.SourceCurrency.Code == "USD"), Is.True);
Assert.That(result.Any(r => r.SourceCurrency.Code == "XYZ"), Is.False);
}

[Test]
public async Task GetExchangeRates_ShouldReturnEmptyWhenNoRatesDefined()
{
// Arrange
var currencies = new List<Currency>
{
new Currency("USD"),
new Currency("EUR")
};

var apiResponse = new ExRateDailyResponse
{
Rates = new List<ExRateDailyRest>()
};

_cnbApiClientMock.Setup(c => c.GetDailyRates(It.IsAny<string>(), It.IsAny<DateTime?>())).ReturnsAsync(apiResponse);

// Act
var result = await _exchangeRateProvider.GetExchangeRates(currencies);

// Assert
Assert.That(result, Is.Empty);
}

[Test]
public async Task GetExchangeRates_ShouldHandleNullRatesFromApi()
{
// Arrange
var currencies = new List<Currency>
{
new Currency("USD"),
new Currency("EUR")
};

var apiResponse = new ExRateDailyResponse
{
Rates = null
};

_cnbApiClientMock.Setup(c => c.GetDailyRates(It.IsAny<string>(), It.IsAny<DateTime?>())).ReturnsAsync(apiResponse);

// Act
var result = await _exchangeRateProvider.GetExchangeRates(currencies);

// Assert
Assert.That(result, Is.Null);
}

[Test]
public void GetExchangeRates_ShouldThrowException_WhenApiClientFails()
{
// Arrange
var currencies = new List<Currency> { new Currency("USD") };
_cnbApiClientMock.Setup(c => c.GetDailyRates(It.IsAny<string>(), It.IsAny<DateTime?>())).ThrowsAsync(new Exception("API failure"));

// Act & Assert
Assert.ThrowsAsync<Exception>(() => _exchangeRateProvider.GetExchangeRates(currencies));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.3.0" />
<PackageReference Include="coverlet.collector" Version="3.1.2" />
</ItemGroup>

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

</Project>
1 change: 1 addition & 0 deletions jobs/Backend/ExchangeRateUpdaterTest/Usings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
global using NUnit.Framework;
35 changes: 35 additions & 0 deletions jobs/Backend/Task/API/CNB/CNBApiClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

using ExchangeRateUpdater.API.CNB.Model.Responses;
using System.Net.Http;
using System.Threading.Tasks;
using System.Net.Http.Json;
using System;

namespace ExchangeRateUpdater.API.CNB
{
public interface ICNBApiClient
{
Task<ExRateDailyResponse> GetDailyRates(string lang = "EN", DateTime? date = null);
}

public class CNBApiClient : ICNBApiClient
{
private const string API_URL = "https://api.cnb.cz/cnbapi";
private const string EX_RATES = "exrates";
private const string DAILY_RATES = "daily";
public async Task<ExRateDailyResponse> GetDailyRates(string lang = "EN", DateTime? date = null)
{
using var hc = new HttpClient();
var query = DAILY_RATES + $"?lang={lang}";

if (date.HasValue)
query += $"date={date.Value:yyyy-MM-dd}";

var response = await hc.GetAsync(string.Join("/", API_URL, EX_RATES, query));

response.EnsureSuccessStatusCode();

return await response.Content.ReadFromJsonAsync<ExRateDailyResponse>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;

namespace ExchangeRateUpdater.API.CNB.Model.Responses
{
public class ExRateDailyResponse
{
public IEnumerable<ExRateDailyRest> Rates { get; set; }
}
}
15 changes: 15 additions & 0 deletions jobs/Backend/Task/API/CNB/Model/Responses/ExRateDailyRest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace ExchangeRateUpdater.API.CNB.Model.Responses
{
public class ExRateDailyRest
{
public long Amount { get; set; }
public string Country { get; set; }
public string Currency { get; set; }
public string CurrencyCode { get; set; }
public int Order { get; set; }
public decimal Rate { get; set; }
public DateTime ValidFor { get; set; }
}
}
20 changes: 17 additions & 3 deletions jobs/Backend/Task/ExchangeRateProvider.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
using System.Collections.Generic;
using ExchangeRateUpdater.API.CNB;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ExchangeRateUpdater
{
public class ExchangeRateProvider
{
private readonly ICNBApiClient _cnbApiClient;
public ExchangeRateProvider(ICNBApiClient cnbApiClient)
{
_cnbApiClient = cnbApiClient;
}
/// <summary>
/// Should return exchange rates among the specified currencies that are defined by the source. But only those defined
/// by the source, do not return calculated exchange rates. E.g. if the source contains "CZK/USD" but not "USD/CZK",
/// do not return exchange rate "USD/CZK" with value calculated as 1 / "CZK/USD". If the source does not provide
/// some of the currencies, ignore them.
/// </summary>
public IEnumerable<ExchangeRate> GetExchangeRates(IEnumerable<Currency> currencies)
///

public async Task<IEnumerable<ExchangeRate>> GetExchangeRates(IEnumerable<Currency> currencies)
{
return Enumerable.Empty<ExchangeRate>();
var currencyCodes = currencies.Select(c => c.Code).ToList();

var result = await _cnbApiClient.GetDailyRates();

return result.Rates?.Where(r => currencyCodes.Contains(r.CurrencyCode))
.Select(r => new ExchangeRate(new Currency(r.CurrencyCode), new Currency("CZK"), r.Rate / r.Amount));
}
}
}
15 changes: 12 additions & 3 deletions jobs/Backend/Task/ExchangeRateUpdater.sln
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
# Visual Studio Version 17
VisualStudioVersion = 17.10.35004.147
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateUpdater", "ExchangeRateUpdater.csproj", "{7B2695D6-D24C-4460-A58E-A10F08550CE0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExchangeRateUpdater", "ExchangeRateUpdater.csproj", "{7B2695D6-D24C-4460-A58E-A10F08550CE0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateUpdaterTest", "..\ExchangeRateUpdaterTest\ExchangeRateUpdaterTest.csproj", "{F481C6F8-6A36-4803-AF21-C0A6CD26C488}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -15,8 +17,15 @@ Global
{7B2695D6-D24C-4460-A58E-A10F08550CE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B2695D6-D24C-4460-A58E-A10F08550CE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B2695D6-D24C-4460-A58E-A10F08550CE0}.Release|Any CPU.Build.0 = Release|Any CPU
{F481C6F8-6A36-4803-AF21-C0A6CD26C488}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F481C6F8-6A36-4803-AF21-C0A6CD26C488}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F481C6F8-6A36-4803-AF21-C0A6CD26C488}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F481C6F8-6A36-4803-AF21-C0A6CD26C488}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {51DB8E9B-E93F-4E6C-8A82-82669B4DCAE2}
EndGlobalSection
EndGlobal
11 changes: 7 additions & 4 deletions jobs/Backend/Task/Program.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using ExchangeRateUpdater.API.CNB;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace ExchangeRateUpdater
{
Expand All @@ -19,12 +21,13 @@ public static class Program
new Currency("XYZ")
};

public static void Main(string[] args)
public static async Task Main(string[] args)
{
try
{
var provider = new ExchangeRateProvider();
var rates = provider.GetExchangeRates(currencies);
var cnbApiClient = new CNBApiClient();
var provider = new ExchangeRateProvider(cnbApiClient);
var rates = await provider.GetExchangeRates(currencies);

Console.WriteLine($"Successfully retrieved {rates.Count()} exchange rates:");
foreach (var rate in rates)
Expand Down