180 lines
7.0 KiB
C#
180 lines
7.0 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using Omika.App.Local.Services;
|
|
|
|
namespace Omika.Tests.Mobile.Local;
|
|
|
|
/// <summary>
|
|
/// Tests du SessionRefreshHandler : interception du 401, renouvellement unique,
|
|
/// retry borné des GET/HEAD et non-rejeu des écritures.
|
|
/// </summary>
|
|
public class SessionRefreshHandlerTests
|
|
{
|
|
private sealed class SequenceHandler : HttpMessageHandler
|
|
{
|
|
private readonly Queue<HttpResponseMessage> _responses;
|
|
|
|
public List<HttpRequestMessage> Requests { get; } = [];
|
|
|
|
public SequenceHandler(params HttpStatusCode[] statuses)
|
|
=> _responses = new Queue<HttpResponseMessage>(statuses.Select(s => new HttpResponseMessage(s)));
|
|
|
|
protected override Task<HttpResponseMessage> 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<string?, CancellationToken, Task<bool>> revalidate,
|
|
Func<string?> 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);
|
|
}
|
|
}
|