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(() => 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(() => 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(() => 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(() => c.GetPriceSummaryAsync("асп")); } }