diff --git a/src/PinguApps.Appwrite.Client/Handlers/ClientCookieSessionHandler.cs b/src/PinguApps.Appwrite.Client/Handlers/ClientCookieSessionHandler.cs index 536809bb..ba52b591 100644 --- a/src/PinguApps.Appwrite.Client/Handlers/ClientCookieSessionHandler.cs +++ b/src/PinguApps.Appwrite.Client/Handlers/ClientCookieSessionHandler.cs @@ -43,15 +43,27 @@ private void SaveSession(HttpResponseMessage response) var semicolonIndex = sessionCookie.IndexOf(';', afterEquals); var base64 = sessionCookie.Substring(afterEquals, semicolonIndex - afterEquals); + if (string.Equals(base64, "deleted", StringComparison.OrdinalIgnoreCase)) + { + AppwriteClient.SetSession(null); + return; + } + var decodedBytes = Convert.FromBase64String(base64); var decoded = Encoding.UTF8.GetString(decodedBytes); - var sessionData = JsonSerializer.Deserialize(decoded); + try + { + var sessionData = JsonSerializer.Deserialize(decoded); - if (sessionData is null) - return; + if (sessionData is null || sessionData.Id is null || sessionData.Secret is null) + return; - AppwriteClient.SetSession(sessionData.Secret); + AppwriteClient.SetSession(sessionData.Secret); + } + catch (JsonException) + { + } } } } diff --git a/tests/PinguApps.Appwrite.Client.Tests/Handlers/ClientCookieSessionHandlerTests.cs b/tests/PinguApps.Appwrite.Client.Tests/Handlers/ClientCookieSessionHandlerTests.cs index e8dd4273..cc993722 100644 --- a/tests/PinguApps.Appwrite.Client.Tests/Handlers/ClientCookieSessionHandlerTests.cs +++ b/tests/PinguApps.Appwrite.Client.Tests/Handlers/ClientCookieSessionHandlerTests.cs @@ -1,45 +1,177 @@ using System.Text; using Moq; -using Moq.Protected; using PinguApps.Appwrite.Client.Handlers; using PinguApps.Appwrite.Client.Internals; +using RichardSzalay.MockHttp; namespace PinguApps.Appwrite.Client.Tests.Handlers; public class ClientCookieSessionHandlerTests { + private readonly MockHttpMessageHandler _mockHttp; + private readonly Mock _mockAppwriteClient; + private readonly HttpClient _httpClient; + + public ClientCookieSessionHandlerTests() + { + _mockHttp = new MockHttpMessageHandler(); + _mockAppwriteClient = new Mock(); + var handler = new ClientCookieSessionHandler(new Lazy(() => _mockAppwriteClient.Object)) + { + InnerHandler = _mockHttp + }; + _httpClient = new HttpClient(handler); + } + [Fact] public async Task SendAsync_WhenResponseHasSessionCookie_SavesSessionCorrectly() { // Arrange - var mockInnerHandler = new Mock(); - var mockAppwriteClient = new Mock(); var sessionData = new CookieSessionData { Id = "123456", Secret = "test_secret" }; var encodedSessionData = Convert.ToBase64String(Encoding.UTF8.GetBytes(System.Text.Json.JsonSerializer.Serialize(sessionData))); var cookieValue = $"a_session={encodedSessionData}; Path=/; HttpOnly"; - mockInnerHandler.Protected() - .Setup>( - "SendAsync", - ItExpr.IsAny(), - ItExpr.IsAny() - ) - .ReturnsAsync(new HttpResponseMessage - { - StatusCode = System.Net.HttpStatusCode.OK, - Headers = { { "Set-Cookie", cookieValue } } - }) - .Verifiable(); - - var handler = new ClientCookieSessionHandler(new Lazy(() => mockAppwriteClient.Object)) - { - InnerHandler = mockInnerHandler.Object - }; - var httpClient = new HttpClient(handler); + _mockHttp.When(HttpMethod.Get, "http://test.com") + .Respond(req => + new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK, + Headers = { { "Set-Cookie", cookieValue } } + }); + + // Act + await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + + // Assert + _mockAppwriteClient.Verify(client => client.SetSession("test_secret"), Times.Once); + } + + [Fact] + public async Task SendAsync_NoSetCookieHeader_DoesNotSetSession() + { + // Arrange + + _mockHttp.When(HttpMethod.Get, "http://test.com") + .Respond(req => + new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK + }); + + // Act + await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + + // Assert + _mockAppwriteClient.Verify(client => client.SetSession(It.IsAny()), Times.Never); + } + + [Fact] + public async Task SendAsync_NoASessionCookie_DoesNotSetSession() + { + // Arrange + _mockHttp.When(HttpMethod.Get, "http://test.com") + .Respond(req => + new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK, + Headers = { { "Set-Cookie", "not_a_session=abc123; Path=/; HttpOnly" } } + }); + + // Act + await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + + // Assert + _mockAppwriteClient.Verify(client => client.SetSession(It.IsAny()), Times.Never); + } + + [Fact] + public async Task SendAsync_InvalidBase64InASessionCookie_DoesNotSetSession() + { + // Arrange + var invalidBase64 = "not_base64"; + _mockHttp.When(HttpMethod.Get, "http://test.com") + .Respond(req => + new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK, + Headers = { { "Set-Cookie", $"not_a_session={invalidBase64}; Path=/; HttpOnly" } } + }); // Act - await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); // Assert - mockAppwriteClient.Verify(client => client.SetSession("test_secret"), Times.Once); + _mockAppwriteClient.Verify(client => client.SetSession(It.IsAny()), Times.Never); } + + [Fact] + public async Task SendAsync_InvalidJsonInDecodedBase64_DoesNotSetSession() + { + // Arrange + var invalidJsonBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"SomeKey\": \"SomeValue\"}")); + _mockHttp.When(HttpMethod.Get, "http://test.com") + .Respond(req => + new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK, + Headers = { { "Set-Cookie", $"a_session={invalidJsonBase64}; Path=/; HttpOnly" } } + }); + + // Act + await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + + // Assert + _mockAppwriteClient.Verify(client => client.SetSession(It.IsAny()), Times.Never); + } + + [Fact] + public async Task SendAsync_SessionCookieMarkedAsDeleted_ClearsSession() + { + // Arrange + var deletedSessionCookie = "a_session=deleted; Path=/; HttpOnly"; + _mockHttp.When(HttpMethod.Get, "http://test.com") + .Respond(req => + new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK, + Headers = { { "Set-Cookie", deletedSessionCookie } } + }); + + // Act + await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + + // Assert + _mockAppwriteClient.Verify(client => client.SetSession(null), Times.Once); + } + + [Fact] + public async Task SendAsync_InvalidJsonInSessionCookie_DoesNotThrowJsonException() + { + // Arrange + var invalidJsonBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("invalid_json")); + var invalidJsonSessionCookie = $"a_session={invalidJsonBase64}; Path=/; HttpOnly"; + _mockHttp.When(HttpMethod.Get, "http://test.com") + .Respond(req => + new HttpResponseMessage + { + StatusCode = System.Net.HttpStatusCode.OK, + Headers = { { "Set-Cookie", invalidJsonSessionCookie } } + }); + + Exception? caughtException = null; + + try + { + // Act + await _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + } + catch (Exception ex) + { + caughtException = ex; + } + + // Assert + Assert.Null(caughtException); + _mockAppwriteClient.Verify(client => client.SetSession(It.IsAny()), Times.Never); + } + }