using System; using System.Collections.Concurrent; using System.Diagnostics; using NUnit.Framework; using NzbDrone.Common.Cache; using NzbDrone.Common.TPL; using NzbDrone.Test.Common; using FluentAssertions; namespace NzbDrone.Common.Test.TPLTests { [TestFixture] [Platform(Exclude = "MacOsX")] public class RateLimitServiceFixture : TestBase { private DateTime _epoch; [SetUp] public void SetUp() { // Make sure it's there so we don't affect measurements. Subject.GetType(); _epoch = DateTime.UtcNow; } private ConcurrentDictionary GetRateLimitStore() { var cache = Mocker.Resolve() .GetCache>(typeof(RateLimitService), "rateLimitStore"); return cache.Get("rateLimitStore", () => new ConcurrentDictionary()); } private void GivenExisting(string key, DateTime dateTime) { GetRateLimitStore().AddOrUpdate(key, dateTime, (s, i) => dateTime); } [Test] public void should_not_delay_if_unset() { var watch = Stopwatch.StartNew(); Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(100)); watch.Stop(); watch.ElapsedMilliseconds.Should().BeLessThan(100); } [Test] public void should_not_delay_unrelated_key() { GivenExisting("other", _epoch + TimeSpan.FromMilliseconds(200)); var watch = Stopwatch.StartNew(); Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(100)); watch.Stop(); watch.ElapsedMilliseconds.Should().BeLessThan(50); } [Test] [Retry(3)] public void should_wait_for_existing() { GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200)); var watch = Stopwatch.StartNew(); Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(400)); watch.Stop(); watch.ElapsedMilliseconds.Should().BeInRange(175, 250); } [Test] public void should_extend_delay() { GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200)); Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(100)); (GetRateLimitStore()["me"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(300)); } [Test] public void should_add_delay() { Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(100)); (GetRateLimitStore()["me"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(100)); } [Test] public void should_extend_subkey_delay() { GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200)); GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(300)); Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100)); (GetRateLimitStore()["me-sub"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(400)); } [Test] public void should_honor_basekey_delay() { GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200)); GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(0)); Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100)); (GetRateLimitStore()["me-sub"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(200)); } [Test] public void should_not_extend_basekey_delay() { GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200)); GivenExisting("me-sub", _epoch + TimeSpan.FromMilliseconds(100)); Subject.WaitAndPulse("me", "sub", TimeSpan.FromMilliseconds(100)); (GetRateLimitStore()["me"] - _epoch).Should().BeCloseTo(TimeSpan.FromMilliseconds(200)); } } }