using FluentAssertions; using Moq; using SequenceAuth.Lib; using System.Threading.Tasks; using Xunit; namespace SequenceAuth.Lib.Tests; public class SequenceManagerTests { private readonly Mock _mockStore; private readonly SequenceManager _manager; public SequenceManagerTests() { _mockStore = new Mock(); _manager = new SequenceManager(_mockStore.Object); } [Fact] public async Task ValidateAndRotateAsync_ValidToken_ReturnsSuccessAndGeneratesNewToken() { // Arrange var token = "valid-token"; var nextToken = "pre-generated-next-token"; var sequenceData = new SequenceData("user123", 100, SequenceState.Active, nextToken); _mockStore.Setup(s => s.GetSequenceAsync(token)) .ReturnsAsync(Option.Some(sequenceData)); _mockStore.Setup(s => s.SaveSequenceAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(StoreOutcome.Success); // Act var result = await _manager.ValidateAndRotateAsync(token); // Assert result.Outcome.Should().Be(ValidationOutcome.Success); result.NextSequence.State.Should().Be(OptionState.Some); result.NextSequence.Value.Should().Be(nextToken); // Ensure the old sequence was marked as Rotated _mockStore.Verify(s => s.SaveSequenceAsync(token, It.Is(d => d.State == SequenceState.Rotated)), Times.Once); // Ensure the new sequence was saved _mockStore.Verify(s => s.SaveSequenceAsync(nextToken, It.Is(d => d.State == SequenceState.Active)), Times.Once); } [Fact] public async Task ValidateAndRotateAsync_RotatedToken_ReturnsCompromisedAndDestroysSession() { // Arrange var token = "compromised-token"; var sequenceData = new SequenceData("user123", 100, SequenceState.Rotated, "some-next-token"); _mockStore.Setup(s => s.GetSequenceAsync(token)) .ReturnsAsync(Option.Some(sequenceData)); // Act var result = await _manager.ValidateAndRotateAsync(token); // Assert result.Outcome.Should().Be(ValidationOutcome.CompromisedSequenceDetected); result.NextSequence.State.Should().Be(OptionState.None); // Ensure InvalidateUserSessionsAsync was called for the user _mockStore.Verify(s => s.InvalidateUserSessionsAsync("user123"), Times.Once); } [Fact] public async Task ValidateAndRotateAsync_MissingToken_ReturnsNotFound() { // Arrange var token = "invalid-token"; _mockStore.Setup(s => s.GetSequenceAsync(token)) .ReturnsAsync(Option.None()); // Act var result = await _manager.ValidateAndRotateAsync(token); // Assert result.Outcome.Should().Be(ValidationOutcome.SequenceNotFound); result.NextSequence.State.Should().Be(OptionState.None); } }