using ErsatzTV.Application.Auth; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Data.Repositories; using ErsatzTV.Tests.Support; using LanguageExt; using NUnit.Framework; using Shouldly; namespace ErsatzTV.Tests.Application.Auth; [TestFixture] public class RotateLocalAdminSecurityStampHandlerTests { private InMemoryTvContext _db = null!; private IConfigElementRepository _configElementRepository = null!; private ILocalPasswordHasher _passwordHasher = null!; [SetUp] public async Task SetUp() { _db = await InMemoryTvContext.CreateAsync(); _configElementRepository = new ConfigElementRepository(_db.Factory); _passwordHasher = new LocalPasswordHasher(); } [TearDown] public async Task TearDown() => await _db.DisposeAsync(); private RotateLocalAdminSecurityStampHandler MakeHandler() => new(_db.Factory); [Test] public async Task Handle_Should_Rotate_The_Stamp_When_Configured() { // Arrange: claim an admin so a stamp exists. Either claim = await new ClaimLocalAdminHandler(_db.Factory, _passwordHasher) .Handle(new ClaimLocalAdmin("admin", "supersecret"), CancellationToken.None); claim.IsRight.ShouldBeTrue(); Option before = await _configElementRepository.GetValue(ConfigElementKey.AuthSecurityStamp, CancellationToken.None); string originalStamp = before.Match(s => s, () => throw new ShouldAssertException("expected a stamp")); // Act await MakeHandler().Handle(new RotateLocalAdminSecurityStamp(), CancellationToken.None); // Assert: the stamp changed (all outstanding sessions carrying the old stamp are now stale). Option after = await _configElementRepository.GetValue(ConfigElementKey.AuthSecurityStamp, CancellationToken.None); string rotatedStamp = after.Match(s => s, () => throw new ShouldAssertException("expected a stamp")); rotatedStamp.ShouldNotBe(originalStamp); rotatedStamp.ShouldNotBeNullOrEmpty(); } [Test] public async Task Handle_Should_Be_A_No_Op_When_Unconfigured() { await MakeHandler().Handle(new RotateLocalAdminSecurityStamp(), CancellationToken.None); Option stamp = await _configElementRepository.GetValue(ConfigElementKey.AuthSecurityStamp, CancellationToken.None); stamp.IsNone.ShouldBeTrue(); } }