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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
[Tt]humbs.db
*.tgz
*.sublime-*
**/bin/
**/obj/
.vs/
packages/

node_modules
bower_components
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//using ExchangeRateUpdater.Src;
//using ExchangeRateUpdater.Src.Cnb;
//using Microsoft.Extensions.Caching.Distributed;
//using Microsoft.Extensions.DependencyInjection;
//using Microsoft.Extensions.Logging.Abstractions;
//using Microsoft.Extensions.Options;
//using NUnit.Framework;
//using System;
//using System.Linq;
//using System.Net;
//using System.Net.Http;
//using System.Text;
//using System.Text.Json;
//using System.Threading;
//using System.Threading.Tasks;

//namespace ExchangeRateUpdater.UnitTests;

//[TestFixture]
//public class ExchangeRateProviderTests
//{
// [Test]
// public async Task JsonApi_ParsesAndCaches_OnSecondCallHitsCache()
// {
// var json = """
// [
// {"CurrencyCode":"EUR","Amount":1,"Rate":25.123,"ValidFor":"2025-09-12"},
// {"CurrencyCode":"USD","Amount":1,"Rate":22.987,"ValidFor":"2025-09-12"}
// ]
// """;

// int httpHits = 0;
// var handler = new FakeHandler(_ =>
// {
// httpHits++;
// return new HttpResponseMessage(HttpStatusCode.OK)
// {
// Content = new StringContent(json, Encoding.UTF8, "application/json")
// };
// });

// var http = new HttpClient(handler);
// var services = new ServiceCollection()
// .AddDistributedMemoryCache()
// .BuildServiceProvider();

// var cache = services.GetRequiredService<IDistributedCache>();
// var opts = Options.Create(new CnbOptions());
// var provider = new ExchangeRateProvider(http, cache, opts, NullLogger<ExchangeRateProvider>.Instance);

// var date = new DateOnly(2025, 9, 12);

// var r1 = await provider.GetAsync(date);
// Assert.That(r1.Count, Is.EqualTo(2));
// var eur = r1.First(x => x.SourceCurrency == "EUR");
// Assert.That(eur.Value, Is.EqualTo(25.123m));
// Assert.That(eur.ValidFor, Is.EqualTo(date));

// var r2 = await provider.GetAsync(date);
// Assert.That(r2.Count, Is.EqualTo(2));
// Assert.That(httpHits, Is.EqualTo(1)); // cached
// }

// [Test]
// public async Task JsonApi_StaleIfError_ServesPreviousBusinessDayFromCache()
// {
// var resolved = new DateOnly(2025, 9, 12);
// var prevDay = new DateOnly(2025, 9, 11);

// var seeded = new[]
// {
// new ExchangeRate("EUR", "CZK", 25.000m, prevDay),
// new ExchangeRate("USD", "CZK", 23.000m, prevDay),
// };
// var seedJson = JsonSerializer.Serialize(seeded);

// var handler = new FakeHandler(_ => new HttpResponseMessage(HttpStatusCode.InternalServerError));
// var http = new HttpClient(handler);

// var services = new ServiceCollection()
// .AddDistributedMemoryCache()
// .BuildServiceProvider();

// var cache = services.GetRequiredService<IDistributedCache>();
// var opts = Options.Create(new CnbOptions());

// var keyPrev = $"{opts.Value.CacheKeyPrefix}{prevDay:yyyy-MM-dd}";
// await cache.SetStringAsync(keyPrev, seedJson, new DistributedCacheEntryOptions
// {
// AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
// });

// var provider = new ExchangeRateProvider(http, cache, opts, NullLogger<ExchangeRateProvider>.Instance);
// var result = await provider.GetAsync(resolved);

// Assert.That(result.First().ValidFor, Is.EqualTo(prevDay));
// Assert.That(result.Any(r => r.SourceCurrency == "EUR" && r.Value == 25.000m), Is.True);
// }

// [Test]
// public async Task JsonApi_ParsesEnvelopeWithItems()
// {
// var json = """
// {
// "date": "2025-09-12",
// "items": [
// {"CurrencyCode":"GBP","Amount":1,"Rate":"29.876","ValidFor":"2025-09-12"}
// ]
// }
// """;

// var handler = new FakeHandler(_ => new HttpResponseMessage(HttpStatusCode.OK)
// {
// Content = new StringContent(json, Encoding.UTF8, "application/json")
// });

// var http = new HttpClient(handler);
// var services = new ServiceCollection()
// .AddDistributedMemoryCache()
// .BuildServiceProvider();

// var cache = services.GetRequiredService<IDistributedCache>();
// var opts = Options.Create(new CnbOptions());
// var provider = new ExchangeRateProvider(http, cache, opts, NullLogger<ExchangeRateProvider>.Instance);

// var date = new DateOnly(2025, 9, 12);
// var rates = await provider.GetAsync(date);

// Assert.That(rates.Count, Is.EqualTo(1));
// var gbp = rates.Single();
// Assert.That(gbp.SourceCurrency, Is.EqualTo("GBP"));
// Assert.That(gbp.Value, Is.EqualTo(29.876m));
// Assert.That(gbp.ValidFor, Is.EqualTo(date));
// }

// private sealed class FakeHandler : HttpMessageHandler
// {
// private readonly Func<HttpRequestMessage, HttpResponseMessage> _responder;
// public FakeHandler(Func<HttpRequestMessage, HttpResponseMessage> responder) => _responder = responder;

// protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
// Task.FromResult(_responder(request));
// }
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Task\ExchangeRateUpdater.csproj" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
</ItemGroup>
</Project>
7 changes: 7 additions & 0 deletions jobs/Backend/Task/Contracts/IExchangeRateCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ExchangeRateUpdater.Contracts;

public interface IExchangeRateCache
{
Task<List<ExchangeRate>?> GetAsync(string key, CancellationToken ct);
Task SetAsync(string key, List<ExchangeRate> value, CancellationToken ct);
}
7 changes: 7 additions & 0 deletions jobs/Backend/Task/Contracts/IExchangeRateProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ExchangeRateUpdater.Contracts
{
public interface IExchangeRateProvider
{
Task<List<ExchangeRate>> GetAsync(DateOnly date, CancellationToken ct = default);
}
}
34 changes: 15 additions & 19 deletions jobs/Backend/Task/ExchangeRate.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,19 @@
namespace ExchangeRateUpdater
{
public class ExchangeRate
{
public ExchangeRate(Currency sourceCurrency, Currency targetCurrency, decimal value)
{
SourceCurrency = sourceCurrency;
TargetCurrency = targetCurrency;
Value = value;
}

public Currency SourceCurrency { get; }

public Currency TargetCurrency { get; }
namespace ExchangeRateUpdater;

public decimal Value { get; }
public sealed class ExchangeRate
{
public string SourceCurrency { get; }
public string TargetCurrency { get; }
public decimal Value { get; }
public DateOnly ValidFor { get; }

public override string ToString()
{
return $"{SourceCurrency}/{TargetCurrency}={Value}";
}
public ExchangeRate(string sourceCurrency, string targetCurrency, decimal value, DateOnly validFor)
{
SourceCurrency = sourceCurrency;
TargetCurrency = targetCurrency;
Value = value;
ValidFor = validFor;
}

public override string ToString() => $"{SourceCurrency}/{TargetCurrency}={Value}";
}
19 changes: 0 additions & 19 deletions jobs/Backend/Task/ExchangeRateProvider.cs

This file was deleted.

21 changes: 16 additions & 5 deletions jobs/Backend/Task/ExchangeRateUpdater.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="9.0.0" />
<PackageReference Include="Polly" Version="8.2.0" />
<PackageReference Include="Polly.Contrib.WaitAndRetry" Version="1.1.1" />
<PackageReference Include="StackExchange.Redis" Version="2.6.122" />
</ItemGroup>
</Project>
13 changes: 11 additions & 2 deletions jobs/Backend/Task/ExchangeRateUpdater.sln
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25123.0
# Visual Studio Version 17
VisualStudioVersion = 17.14.36414.22
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateUpdater", "ExchangeRateUpdater.csproj", "{7B2695D6-D24C-4460-A58E-A10F08550CE0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExchangeRateUpdater.UnitTests", "..\ExchangeRateUpdater.UnitTests\ExchangeRateUpdater.UnitTests.csproj", "{A1B84AD2-20DB-4358-908C-91AA3AB7DD62}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
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
{A1B84AD2-20DB-4358-908C-91AA3AB7DD62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B84AD2-20DB-4358-908C-91AA3AB7DD62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B84AD2-20DB-4358-908C-91AA3AB7DD62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B84AD2-20DB-4358-908C-91AA3AB7DD62}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D3F6DA87-1B07-4886-BCA5-812EA94E118F}
EndGlobalSection
EndGlobal
Loading