using System.Net; using System.Net.Http.Headers; using Omika.App.Local.Services; namespace Omika.Tests.Mobile.Local; /// /// Tests du SessionRefreshHandler : interception du 401, renouvellement unique, /// retry borné des GET/HEAD et non-rejeu des écritures. /// public class SessionRefreshHandlerTests { private sealed class SequenceHandler : HttpMessageHandler { private readonly Queue _responses; public List Requests { get; } = []; public SequenceHandler(params HttpStatusCode[] statuses) => _responses = new Queue(statuses.Select(s => new HttpResponseMessage(s))); protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { Requests.Add(request); return Task.FromResult(_responses.Count > 0 ? _responses.Dequeue() : new HttpResponseMessage(HttpStatusCode.OK)); } } private static HttpRequestMessage GetRequest(string token = "T1") { var request = new HttpRequestMessage(HttpMethod.Get, "http://test.local/api/clients"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); return request; } private static SessionRefreshHandler CreateHandler( SequenceHandler inner, Func> revalidate, Func getAccessToken) { var handler = new SessionRefreshHandler(inner); handler.Configure(revalidate, getAccessToken); return handler; } [Fact] public async Task Get401_RevalideUneFois_PuisRetryAvecNouveauToken() { var inner = new SequenceHandler(HttpStatusCode.Unauthorized, HttpStatusCode.OK); string currentToken = "T1"; int validateCalls = 0; SessionRefreshHandler handler = CreateHandler( inner, revalidate: (_, _) => { validateCalls++; currentToken = "T2"; return Task.FromResult(true); }, getAccessToken: () => currentToken); using var client = new HttpClient(handler); using var request = GetRequest("T1"); using HttpResponseMessage response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(1, validateCalls); Assert.Equal(2, inner.Requests.Count); Assert.Equal("Bearer T2", inner.Requests[1].Headers.Authorization!.ToString()); } [Fact] public async Task Get401_RetryEncore401_StopSansSecondValidate() { var inner = new SequenceHandler(HttpStatusCode.Unauthorized, HttpStatusCode.Unauthorized); string currentToken = "T1"; int validateCalls = 0; SessionRefreshHandler handler = CreateHandler( inner, revalidate: (_, _) => { validateCalls++; currentToken = "T2"; return Task.FromResult(true); }, getAccessToken: () => currentToken); using var client = new HttpClient(handler); using var request = GetRequest("T1"); using HttpResponseMessage response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal(1, validateCalls); Assert.Equal(2, inner.Requests.Count); } [Fact] public async Task Post401_RevalideMaisNeRejouePasEcriture() { var inner = new SequenceHandler(HttpStatusCode.Unauthorized); string currentToken = "T1"; int validateCalls = 0; SessionRefreshHandler handler = CreateHandler( inner, revalidate: (_, _) => { validateCalls++; currentToken = "T2"; return Task.FromResult(true); }, getAccessToken: () => currentToken); using var client = new HttpClient(handler); using var request = new HttpRequestMessage(HttpMethod.Post, "http://test.local/api/notes") { Content = new StringContent("{\"contenu\":\"x\"}", System.Text.Encoding.UTF8, "application/json") }; request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "T1"); using HttpResponseMessage response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal(1, validateCalls); // renouvelé pour les appels futurs… Assert.Single(inner.Requests); // …mais l'écriture n'est PAS rejouée. Assert.Equal("T2", currentToken); } [Fact] public async Task RequeteSkipAuthRefresh_NeDeclenchePasRefresh() { var inner = new SequenceHandler(HttpStatusCode.Unauthorized); int validateCalls = 0; SessionRefreshHandler handler = CreateHandler( inner, revalidate: (_, _) => { validateCalls++; return Task.FromResult(true); }, getAccessToken: () => "T1"); using var client = new HttpClient(handler); using var request = new HttpRequestMessage(HttpMethod.Post, "http://test.local/api/license/validate"); request.Options.Set(SessionRefreshHandler.SkipAuthRefresh, true); using HttpResponseMessage response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal(0, validateCalls); Assert.Single(inner.Requests); } [Fact] public async Task Get401_TokenDejaRenouvele_NeRevalidePasEtRetry() { var inner = new SequenceHandler(HttpStatusCode.Unauthorized, HttpStatusCode.OK); int validateCalls = 0; // Le token courant diffère déjà du token échoué : un autre appel a renouvelé. SessionRefreshHandler handler = CreateHandler( inner, revalidate: (_, _) => { validateCalls++; return Task.FromResult(false); }, getAccessToken: () => "T2"); using var client = new HttpClient(handler); using var request = GetRequest("T1"); using HttpResponseMessage response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(0, validateCalls); Assert.Equal(2, inner.Requests.Count); Assert.Equal("Bearer T2", inner.Requests[1].Headers.Authorization!.ToString()); } [Fact] public async Task AppelSansAuthorization_401_NeRevalidePas() { var inner = new SequenceHandler(HttpStatusCode.Unauthorized); int validateCalls = 0; SessionRefreshHandler handler = CreateHandler( inner, revalidate: (_, _) => { validateCalls++; return Task.FromResult(true); }, getAccessToken: () => "T1"); using var client = new HttpClient(handler); using var request = new HttpRequestMessage(HttpMethod.Get, "http://test.local/api/clients"); using HttpResponseMessage response = await client.SendAsync(request); Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); Assert.Equal(0, validateCalls); Assert.Single(inner.Requests); } }