Some checks failed
Desktop tests / test (push) Has been cancelled
- проект tests/Elfisa.Tests: 56 юнит-тестов на Elfisa.Core + Elfisa.Avalonia: версии апдейтера, срок JWT-сессии, ApiClient (URL/ошибки/ретраи/парсинг), DTO-контракты, черновики, корзина, дрилдаун прайса (МГ/базовое имя) - тест-швы: internal-методы через InternalsVisibleTo, ApiClient(HttpMessageHandler) - CI .github/workflows/desktop-tests.yml — dotnet test при пуше (GitHub/Gitea Actions) - README с описанием покрытия Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
101 lines
3.6 KiB
C#
101 lines
3.6 KiB
C#
using System.Net;
|
|
using System.Net.Http;
|
|
using Elfisa.Core;
|
|
using Xunit;
|
|
|
|
namespace Elfisa.Tests;
|
|
|
|
public class ApiClientTests
|
|
{
|
|
private static ApiClient Client(HttpMessageHandler h) => new(h, "https://api.test");
|
|
|
|
[Fact]
|
|
public void Url_builds_absolute_paths()
|
|
{
|
|
var c = new ApiClient("https://api.test/");
|
|
Assert.Equal("https://api.test/auth/login", c.Url("/auth/login"));
|
|
Assert.Equal("https://api.test/x/y", c.Url("x/y"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_success_returns_token_and_role()
|
|
{
|
|
var h = new FakeHttpHandler(HttpStatusCode.OK, "{\"token\":\"abc\",\"role\":\"buyer\"}");
|
|
var resp = await Client(h).LoginAsync("u", "p");
|
|
Assert.Equal("abc", resp.Token);
|
|
Assert.Equal("buyer", resp.Role);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_401_maps_to_friendly_message()
|
|
{
|
|
var h = new FakeHttpHandler(HttpStatusCode.Unauthorized, "{\"error\":\"x\"}");
|
|
var ex = await Assert.ThrowsAsync<ApiException>(() => Client(h).LoginAsync("u", "p"));
|
|
Assert.Contains("Неверный логин", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_429_maps_to_ratelimit_message()
|
|
{
|
|
var h = new FakeHttpHandler((HttpStatusCode)429, "{}");
|
|
var ex = await Assert.ThrowsAsync<ApiException>(() => Client(h).LoginAsync("u", "p"));
|
|
Assert.Contains("Слишком много", ex.Message);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_retries_transient_then_succeeds()
|
|
{
|
|
var h = new FakeHttpHandler((req, i) =>
|
|
i < 2 ? throw new HttpRequestException("transient")
|
|
: FakeHttpHandler.Json(HttpStatusCode.OK, "{\"token\":\"ok\"}"));
|
|
var resp = await Client(h).LoginAsync("u", "p");
|
|
Assert.Equal("ok", resp.Token);
|
|
Assert.Equal(3, h.Calls); // 2 сбоя + 1 успех
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Login_gives_up_after_retries()
|
|
{
|
|
var h = new FakeHttpHandler((req, i) => throw new HttpRequestException("down"));
|
|
await Assert.ThrowsAsync<ApiException>(() => Client(h).LoginAsync("u", "p"));
|
|
Assert.Equal(3, h.Calls); // 1 попытка + 2 повтора
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Summary_parses_response_and_builds_query()
|
|
{
|
|
var body = "{\"total_drugs\":2,\"summary\":[{\"supplier_price_id\":\"1\",\"drug_name\":\"Аспирин\",\"price\":10.5}]}";
|
|
var h = new FakeHttpHandler(HttpStatusCode.OK, body);
|
|
var c = Client(h);
|
|
c.SetToken("t");
|
|
var resp = await c.GetPriceSummaryAsync("асп", limit: 50, offset: 20);
|
|
Assert.Equal(2, resp.TotalDrugs);
|
|
Assert.Single(resp.Summary!);
|
|
Assert.Equal("Аспирин", resp.Summary![0].DrugName);
|
|
Assert.Equal(10.5m, resp.Summary![0].Price);
|
|
|
|
var url = h.LastRequest!.RequestUri!.ToString();
|
|
Assert.Contains("limit=50", url);
|
|
Assert.Contains("offset=20", url);
|
|
Assert.Contains("q=", url);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Summary_short_query_omits_q()
|
|
{
|
|
var h = new FakeHttpHandler(HttpStatusCode.OK, "{\"total_drugs\":0,\"summary\":[]}");
|
|
var c = Client(h);
|
|
c.SetToken("t");
|
|
await c.GetPriceSummaryAsync("a", limit: 10, offset: 0); // 1 символ < 2 → без q
|
|
Assert.DoesNotContain("q=", h.LastRequest!.RequestUri!.ToString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Authorized_call_without_token_throws()
|
|
{
|
|
var h = new FakeHttpHandler(HttpStatusCode.OK, "{}");
|
|
var c = Client(h); // токен не задан
|
|
await Assert.ThrowsAsync<ApiException>(() => c.GetPriceSummaryAsync("асп"));
|
|
}
|
|
}
|