diff --git a/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs b/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs new file mode 100644 index 00000000..2f5d2051 --- /dev/null +++ b/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs @@ -0,0 +1,70 @@ +#region + +using System.Threading.Tasks; +using FluentAssertions; +using FluentAssertions.Execution; +using Gotrue.Tests.Support; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Supabase.Gotrue; +using Supabase.Gotrue.Exceptions; +using Supabase.Gotrue.Interfaces; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using static Supabase.Gotrue.Constants; + +#endregion + +namespace Gotrue.Tests.Authentication; + +/// +/// Sign-out clears the local session even if the server call fails, and keeps it for the others scope. +/// +[TestClass] +[TestCategory("Contract")] +public class SignOutLocalSessionTests +{ + private IGotrueClient client = null!; + private IGotrueSessionPersistence persistence = null!; + private MockGotrueServer server = null!; + + [TestInitialize] + public void TestInitialize() + { + this.server = new MockGotrueServer(); + this.client = TestClients.Against(this.server); + this.persistence = SessionPersistenceSubstitute.Tracking(); + this.persistence.SaveSession(new Session { AccessToken = "an-access-token", RefreshToken = "a-refresh-token", ExpiresIn = 3600 }); + this.client.SetPersistence(this.persistence); + this.client.LoadSession(); + } + + [TestCleanup] + public void TestCleanup() => this.server.Dispose(); + + [TestMethod] + public async Task SignOut_ShouldClearTheLocalSession_GivenTheServerFails() + { + this.server.Given(Request.Create().WithPath("/logout").UsingPost()) + .RespondWith(Response.Create().WithStatusCode(500)); + var signOut = () => this.client.SignOut(); + await signOut.Should().ThrowAsync(); + using (new AssertionScope()) + { + this.client.CurrentSession.Should().BeNull("a failed sign-out must not leave the user signed in"); + this.persistence.LoadSession().Should().BeNull(); + } + } + + [TestMethod] + public async Task SignOut_ShouldKeepTheLocalSession_GivenOthersScope() + { + this.server.Given(Request.Create().WithPath("/logout").UsingPost()) + .RespondWith(Response.Create().WithStatusCode(204)); + await this.client.SignOut(SignOutScope.Others); + using (new AssertionScope()) + { + this.client.CurrentSession.Should().NotBeNull("the others scope signs out every session except this one"); + this.persistence.LoadSession().Should().NotBeNull(); + } + } +} diff --git a/packages/Gotrue/Gotrue/Client.cs b/packages/Gotrue/Gotrue/Client.cs index c92969af..45a0d861 100644 --- a/packages/Gotrue/Gotrue/Client.cs +++ b/packages/Gotrue/Gotrue/Client.cs @@ -507,11 +507,20 @@ public async Task SignOut(SignOutScope scope = SignOutScope.Global) { using var activity = GotrueInstrumentation.Source.StartActivity(GotrueInstrumentation.Spans.SignOut); activity?.SetTag(GotrueInstrumentation.Tags.SignOutScope, scope.ToString()); - if (this.CurrentSession?.AccessToken != null) + try + { + if (this.CurrentSession?.AccessToken != null) + { + await this.api.SignOut(this.CurrentSession.AccessToken, scope).ConfigureAwait(false); + } + } + finally { - await this.api.SignOut(this.CurrentSession.AccessToken, scope); + if (scope != SignOutScope.Others) + { + await this.UpdateSessionAsync(null).ConfigureAwait(false); + } } - await this.UpdateSessionAsync(null).ConfigureAwait(false); } ///