namespace DodoSSH.Client.Storage.Tests; /// /// The Spelunks this device is signed in to: one sign-in per Spelunk, sealed whole, and gone with the cache. /// public sealed class SpelunkSignInStoreTests { private const string Spelunk = "https://api.spelunk.example.com"; private static CancellationToken Token => TestContext.Current.CancellationToken; [Fact] public async Task ASignInRoundTrips_AndIsFoundByItsSpelunk() { using var harness = await CacheHarness.CreateAsync(); var store = Store(harness); (await store.FindAsync(Spelunk, Token)).ShouldBeNull("nothing is signed in yet"); var signIn = SignIn(Spelunk, "spelunkapp_one"); await store.SaveAsync(signIn, Token); (await store.FindAsync(Spelunk, Token)).ShouldBe(signIn); (await store.FindAsync("https://spelunk.elsewhere.example", Token)).ShouldBeNull(); } [Fact] public async Task SigningInAgainToTheSameSpelunk_Replaces_AndOthersStay() { using var harness = await CacheHarness.CreateAsync(); var store = Store(harness); await store.SaveAsync(SignIn(Spelunk, "spelunkapp_old"), Token); await store.SaveAsync(SignIn("https://spelunk.elsewhere.example", "spelunkapp_other"), Token); await store.SaveAsync(SignIn(Spelunk, "spelunkapp_new"), Token); var all = await store.ListAsync(Token); all.Count.ShouldBe(2); (await store.FindAsync(Spelunk, Token)).ShouldNotBeNull().Token.ShouldBe("spelunkapp_new"); } [Fact] public async Task ForgettingOne_LeavesTheOthers_AndForgettingAll_LeavesNone() { using var harness = await CacheHarness.CreateAsync(); var store = Store(harness); await store.SaveAsync(SignIn(Spelunk, "spelunkapp_one"), Token); await store.SaveAsync(SignIn("https://spelunk.elsewhere.example", "spelunkapp_two"), Token); await store.ForgetAsync(Spelunk, Token); (await store.ListAsync(Token)).ShouldHaveSingleItem().ApiBase.ShouldBe("https://spelunk.elsewhere.example"); await store.ForgetAllAsync(Token); (await store.ListAsync(Token)).ShouldBeEmpty(); } [Fact] public async Task AnotherUsersCacheKey_OpensNothing() { using var owner = await CacheHarness.CreateAsync(); using var stranger = await CacheHarness.CreateAsync(); await Store(owner).SaveAsync(SignIn(Spelunk, "spelunkapp_one"), Token); (await new SpelunkSignInStore(owner.Factory, stranger.Protector, TimeProvider.System).ListAsync(Token)) .ShouldBeEmpty(); } [Fact] public async Task ResettingTheCache_SignsThisDeviceOutOfEverySpelunk() { // What signing out of DodoSSH does on disk, whatever reached Spelunk before it. using var harness = await CacheHarness.CreateAsync(); await Store(harness).SaveAsync(SignIn(Spelunk, "spelunkapp_one"), Token); await harness.Factory.ResetAsync(Token); (await Store(harness).ListAsync(Token)).ShouldBeEmpty(); } private static SpelunkSignInStore Store(CacheHarness harness) => new(harness.Factory, harness.Protector, TimeProvider.System); private static SpelunkSignInRecord SignIn(string apiBase, string token) => new(apiBase, token, Guid.CreateVersion7(), "ops@example.com", "Ops", DateTimeOffset.FromUnixTimeSeconds(1_790_000_000)); }