From 454f84b874fbb07885c327c43181e994bfe71017 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 25 Mar 2018 21:58:01 +0200 Subject: [PATCH] More tests --- .../UsageTracking/BackgroundUsageTracker.cs | 2 +- .../UsageTracking/IUsageTracker.cs | 2 +- .../Statistics/UsagesController.cs | 2 +- src/Squidex/Pipeline/ApiCostsFilter.cs | 20 +-- .../BackgroundUsageTrackerTests.cs | 4 +- .../Pipeline/ApiCostsFilterTests.cs | 168 ++++++++++++++++++ .../Pipeline/EnforceHttpsMiddlewareTests.cs | 2 +- 7 files changed, 184 insertions(+), 16 deletions(-) create mode 100644 tests/Squidex.Tests/Pipeline/ApiCostsFilterTests.cs diff --git a/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs b/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs index 7aec7db78..17d06b573 100644 --- a/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs +++ b/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs @@ -108,7 +108,7 @@ namespace Squidex.Infrastructure.UsageTracking return enrichedUsages; } - public async Task GetMonthlyCalls(string key, DateTime date) + public async Task GetMonthlyCallsAsync(string key, DateTime date) { ThrowIfDisposed(); diff --git a/src/Squidex.Infrastructure/UsageTracking/IUsageTracker.cs b/src/Squidex.Infrastructure/UsageTracking/IUsageTracker.cs index 7a28e851d..c6602440d 100644 --- a/src/Squidex.Infrastructure/UsageTracking/IUsageTracker.cs +++ b/src/Squidex.Infrastructure/UsageTracking/IUsageTracker.cs @@ -15,7 +15,7 @@ namespace Squidex.Infrastructure.UsageTracking { Task TrackAsync(string key, double weight, double elapsedMs); - Task GetMonthlyCalls(string key, DateTime date); + Task GetMonthlyCallsAsync(string key, DateTime date); Task> QueryAsync(string key, DateTime fromDate, DateTime toDate); } diff --git a/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs b/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs index a1ce33008..06d8736da 100644 --- a/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs +++ b/src/Squidex/Areas/Api/Controllers/Statistics/UsagesController.cs @@ -60,7 +60,7 @@ namespace Squidex.Areas.Api.Controllers.Statistics [ApiCosts(0)] public async Task GetMonthlyCalls(string app) { - var count = await usageTracker.GetMonthlyCalls(App.Id.ToString(), DateTime.Today); + var count = await usageTracker.GetMonthlyCallsAsync(App.Id.ToString(), DateTime.Today); var plan = appPlanProvider.GetPlanForApp(App); diff --git a/src/Squidex/Pipeline/ApiCostsFilter.cs b/src/Squidex/Pipeline/ApiCostsFilter.cs index a6e14234c..b01a3d766 100644 --- a/src/Squidex/Pipeline/ApiCostsFilter.cs +++ b/src/Squidex/Pipeline/ApiCostsFilter.cs @@ -47,20 +47,20 @@ namespace Squidex.Pipeline if (appFeature?.App != null && FilterDefinition.Weight > 0) { - var stopWatch = Stopwatch.StartNew(); + var plan = appPlanProvider.GetPlanForApp(appFeature.App); - try - { - var plan = appPlanProvider.GetPlanForApp(appFeature.App); + var usage = await usageTracker.GetMonthlyCallsAsync(appFeature.App.Id.ToString(), DateTime.Today); - var usage = await usageTracker.GetMonthlyCalls(appFeature.App.Id.ToString(), DateTime.Today); + if (plan.MaxApiCalls >= 0 && usage > plan.MaxApiCalls * 1.1) + { + context.Result = new StatusCodeResult(429); + return; + } - if (plan.MaxApiCalls >= 0 && (usage * 1.1) > plan.MaxApiCalls) - { - context.Result = new StatusCodeResult(429); - return; - } + var stopWatch = Stopwatch.StartNew(); + try + { await next(); } finally diff --git a/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs b/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs index 9675c2dbd..ecaafea85 100644 --- a/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs +++ b/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs @@ -48,7 +48,7 @@ namespace Squidex.Infrastructure.UsageTracking { sut.Dispose(); - return Assert.ThrowsAsync(() => sut.GetMonthlyCalls("key1", DateTime.Today)); + return Assert.ThrowsAsync(() => sut.GetMonthlyCallsAsync("key1", DateTime.Today)); } [Fact] @@ -67,7 +67,7 @@ namespace Squidex.Infrastructure.UsageTracking A.CallTo(() => usageStore.QueryAsync("key", new DateTime(2016, 1, 1), new DateTime(2016, 1, 31))) .Returns(originalData); - var result = await sut.GetMonthlyCalls("key", date); + var result = await sut.GetMonthlyCallsAsync("key", date); Assert.Equal(55, result); } diff --git a/tests/Squidex.Tests/Pipeline/ApiCostsFilterTests.cs b/tests/Squidex.Tests/Pipeline/ApiCostsFilterTests.cs new file mode 100644 index 000000000..b617e0084 --- /dev/null +++ b/tests/Squidex.Tests/Pipeline/ApiCostsFilterTests.cs @@ -0,0 +1,168 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschränkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FakeItEasy; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Squidex.Domain.Apps.Entities.Apps; +using Squidex.Domain.Apps.Entities.Apps.Services; +using Squidex.Infrastructure.UsageTracking; +using Squidex.Pipeline; +using Xunit; + +namespace Squidex.Tests.Pipeline +{ + public class ApiCostsFilterTests + { + private readonly IActionContextAccessor actionContextAccessor = A.Fake(); + private readonly IAppEntity appEntity = A.Fake(); + private readonly IAppPlansProvider appPlanProvider = A.Fake(); + private readonly IUsageTracker usageTracker = A.Fake(); + private readonly IAppLimitsPlan appPlan = A.Fake(); + private readonly ActionExecutingContext actionContext; + private readonly HttpContext httpContext = new DefaultHttpContext(); + private readonly ActionExecutionDelegate next; + private readonly ApiCostsFilter sut; + private long apiCallsMax; + private long apiCallsCurrent; + private bool isNextCalled; + + public ApiCostsFilterTests() + { + actionContext = + new ActionExecutingContext( + new ActionContext(httpContext, new RouteData(), + new ActionDescriptor()), + new List(), new Dictionary(), null); + + A.CallTo(() => actionContextAccessor.ActionContext) + .Returns(actionContext); + + A.CallTo(() => appPlanProvider.GetPlan(null)) + .Returns(appPlan); + + A.CallTo(() => appPlanProvider.GetPlanForApp(appEntity)) + .Returns(appPlan); + + A.CallTo(() => appPlan.MaxApiCalls) + .ReturnsLazily(x => apiCallsMax); + + A.CallTo(() => usageTracker.GetMonthlyCallsAsync(A.Ignored, DateTime.Today)) + .ReturnsLazily(x => Task.FromResult(apiCallsCurrent)); + + next = () => + { + isNextCalled = true; + + return Task.FromResult(null); + }; + + sut = new ApiCostsFilter(appPlanProvider, usageTracker); + } + + [Fact] + public async Task Should_return_429_status_code_if_max_calls_over_limit() + { + sut.FilterDefinition = new ApiCostsAttribute(1); + + SetupApp(); + + apiCallsCurrent = 1000; + apiCallsMax = 600; + + await sut.OnActionExecutionAsync(actionContext, next); + + Assert.Equal(429, (actionContext.Result as StatusCodeResult).StatusCode); + Assert.False(isNextCalled); + + A.CallTo(() => usageTracker.TrackAsync(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Should_track_if_calls_left() + { + sut.FilterDefinition = new ApiCostsAttribute(13); + + SetupApp(); + + apiCallsCurrent = 1000; + apiCallsMax = 1600; + + await sut.OnActionExecutionAsync(actionContext, next); + + Assert.True(isNextCalled); + + A.CallTo(() => usageTracker.TrackAsync(A.Ignored, 13, A.Ignored)) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_allow_small_buffer() + { + sut.FilterDefinition = new ApiCostsAttribute(13); + + SetupApp(); + + apiCallsCurrent = 1099; + apiCallsMax = 1000; + + await sut.OnActionExecutionAsync(actionContext, next); + + Assert.True(isNextCalled); + + A.CallTo(() => usageTracker.TrackAsync(A.Ignored, 13, A.Ignored)) + .MustHaveHappened(); + } + + [Fact] + public async Task Should_not_track_if_weight_is_zero() + { + sut.FilterDefinition = new ApiCostsAttribute(0); + + SetupApp(); + + apiCallsCurrent = 1000; + apiCallsMax = 600; + + await sut.OnActionExecutionAsync(actionContext, next); + + Assert.True(isNextCalled); + + A.CallTo(() => usageTracker.TrackAsync(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + [Fact] + public async Task Should_not_track_if_app_not_defined() + { + sut.FilterDefinition = new ApiCostsAttribute(1); + + apiCallsCurrent = 1000; + apiCallsMax = 600; + + await sut.OnActionExecutionAsync(actionContext, next); + + Assert.True(isNextCalled); + + A.CallTo(() => usageTracker.TrackAsync(A.Ignored, A.Ignored, A.Ignored)) + .MustNotHaveHappened(); + } + + private void SetupApp() + { + httpContext.Features.Set(new AppApiFilter.AppFeature(appEntity)); + } + } +} \ No newline at end of file diff --git a/tests/Squidex.Tests/Pipeline/EnforceHttpsMiddlewareTests.cs b/tests/Squidex.Tests/Pipeline/EnforceHttpsMiddlewareTests.cs index d61b1969f..5657141b1 100644 --- a/tests/Squidex.Tests/Pipeline/EnforceHttpsMiddlewareTests.cs +++ b/tests/Squidex.Tests/Pipeline/EnforceHttpsMiddlewareTests.cs @@ -16,8 +16,8 @@ namespace Squidex.Pipeline { public class EnforceHttpsMiddlewareTests { + private readonly RequestDelegate next; private bool isNextCalled; - private RequestDelegate next; public EnforceHttpsMiddlewareTests() {