diff --git a/src/src/StevesBot.Webhook/YouTube/Handlers/NotificationHandler.cs b/src/src/StevesBot.Webhook/YouTube/Handlers/NotificationHandler.cs index 8f600ed..0aeb3a9 100644 --- a/src/src/StevesBot.Webhook/YouTube/Handlers/NotificationHandler.cs +++ b/src/src/StevesBot.Webhook/YouTube/Handlers/NotificationHandler.cs @@ -12,6 +12,8 @@ internal static class NotificationHandler CancellationToken cancellationToken = default ) { + lastPostedStreamStore.RemoveValuesOlderThan24Hours(); + using StreamReader stream = new(context.Request.Body); var body = await stream.ReadToEndAsync(cancellationToken); diff --git a/src/src/StevesBot.Webhook/YouTube/ILastPostedStreamStore.cs b/src/src/StevesBot.Webhook/YouTube/ILastPostedStreamStore.cs index 84a85f7..3027872 100644 --- a/src/src/StevesBot.Webhook/YouTube/ILastPostedStreamStore.cs +++ b/src/src/StevesBot.Webhook/YouTube/ILastPostedStreamStore.cs @@ -4,4 +4,5 @@ internal interface ILastPostedStreamStore { void SetValue(string value); bool HasValue(string value); + void RemoveValuesOlderThan24Hours(); } \ No newline at end of file diff --git a/src/src/StevesBot.Webhook/YouTube/LastPostedStreamStore.cs b/src/src/StevesBot.Webhook/YouTube/LastPostedStreamStore.cs index 4ab8975..394f3bd 100644 --- a/src/src/StevesBot.Webhook/YouTube/LastPostedStreamStore.cs +++ b/src/src/StevesBot.Webhook/YouTube/LastPostedStreamStore.cs @@ -1,18 +1,45 @@ namespace StevesBot.Webhook.YouTube; -internal class LastPostedStreamStore : ILastPostedStreamStore +internal class LastPostedStreamStore(TimeProvider timeProvider) : ILastPostedStreamStore { - private string _value = string.Empty; + private readonly TimeProvider _timeProvider = timeProvider; + private readonly ConcurrentDictionary _values = []; public void SetValue(string value) { ArgumentNullException.ThrowIfNull(value); - _value = value; + _values[value] = _timeProvider.GetUtcNow() + .ToUnixTimeMilliseconds(); } public bool HasValue(string value) { ArgumentNullException.ThrowIfNull(value); - return _value.Equals(value, StringComparison.OrdinalIgnoreCase); + return _values.TryGetValue(value, out var _); + } + + public void RemoveValuesOlderThan24Hours() + { + var currentTime = _timeProvider.GetUtcNow(); + var twentyFourHoursAgo = currentTime + .AddHours(-24) + .ToUnixTimeMilliseconds(); + + var keysToRemove = new List(); + + foreach (var (k, v) in _values) + { + if (v > twentyFourHoursAgo) + { + continue; + } + + keysToRemove.Add(k); + } + + foreach (var k in keysToRemove) + { + _values.Remove(k, out var _); + } } } \ No newline at end of file diff --git a/src/tests/StevesBot.Webhook.Tests/Unit/LastPostedStreamStore.cs b/src/tests/StevesBot.Webhook.Tests/Unit/LastPostedStreamStore.cs deleted file mode 100644 index 90f1cd1..0000000 --- a/src/tests/StevesBot.Webhook.Tests/Unit/LastPostedStreamStore.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace StevesBot.Webhook.Tests.Unit; - -public class LastPostedStreamStoreTests -{ - [Fact] - public void SetValue_WhenCalledWithNull_ItShouldThrowArgumentNullException() - { - var store = new LastPostedStreamStore(); - - var act = () => store.SetValue(null!); - - act.Should().Throw(); - } - - [Fact] - public void HasValue_WhenCalledWithNull_ItShouldThrowArgumentNullException() - { - var store = new LastPostedStreamStore(); - - var act = () => store.HasValue(null!); - - act.Should().Throw(); - } - - - [Fact] - public void SetValueHasValue_WhenCalled_ItShouldStoreValue() - { - var store = new LastPostedStreamStore(); - var streamId = "test-stream-id"; - - store.SetValue(streamId); - - store.HasValue(streamId).Should().BeTrue(); - } -} \ No newline at end of file diff --git a/src/tests/StevesBot.Webhook.Tests/Unit/LastPostedStreamStoreTests.cs b/src/tests/StevesBot.Webhook.Tests/Unit/LastPostedStreamStoreTests.cs new file mode 100644 index 0000000..df20af0 --- /dev/null +++ b/src/tests/StevesBot.Webhook.Tests/Unit/LastPostedStreamStoreTests.cs @@ -0,0 +1,63 @@ +namespace StevesBot.Webhook.Tests.Unit; + +public class LastPostedStreamStoreTests +{ + private readonly Mock _mockTimeProvider = new(); + private readonly LastPostedStreamStore _sut; + + public LastPostedStreamStoreTests() + { + _sut = new(_mockTimeProvider.Object); + } + + [Fact] + public void SetValue_WhenCalledWithNull_ItShouldThrowArgumentNullException() + { + var act = () => _sut.SetValue(null!); + + act.Should().Throw(); + } + + [Fact] + public void HasValue_WhenCalledWithNull_ItShouldThrowArgumentNullException() + { + var act = () => _sut.HasValue(null!); + + act.Should().Throw(); + } + + + [Fact] + public void SetValueHasValue_WhenCalled_ItShouldStoreValue() + { + var streamId = "test-stream-id"; + + _sut.SetValue(streamId); + + _sut.HasValue(streamId).Should().BeTrue(); + } + + [Fact] + public void RemoveValuesOlderThan24Hours_WhenCalled_ItShouldRemoveItemsOlderThan24Hours() + { + var oldStreamId = "test-stream-id-old"; + var currentStreamId = "test-stream-id-current"; + + var currentTime = DateTimeOffset.UtcNow; + var invalidTime = DateTimeOffset.UtcNow.AddHours(-25); + var validTime = DateTimeOffset.UtcNow.AddHours(-10); + + _mockTimeProvider.SetupSequence(static m => m.GetUtcNow()) + .Returns(invalidTime) + .Returns(validTime) + .Returns(currentTime); + + _sut.SetValue(oldStreamId); + _sut.SetValue(currentStreamId); + + _sut.RemoveValuesOlderThan24Hours(); + + _sut.HasValue(oldStreamId).Should().BeFalse(); + _sut.HasValue(currentStreamId).Should().BeTrue(); + } +} \ No newline at end of file