using Microsoft.Extensions.DependencyInjection; using Moq; using System; using AsbCloudInfrastructure.Services.Background; using System.Threading; using System.Threading.Tasks; using Xunit; namespace AsbCloudWebApi.Tests.ServicesTests { public class BackgroundWorkerServiceTest { private readonly Mock mockServiceProvider; private readonly Mock mockServiceScopeFactory; private readonly Func someAction = (string id, IServiceProvider scope, CancellationToken token) => Task.CompletedTask; public BackgroundWorkerServiceTest() { var mockServiceScope = new Mock(); mockServiceScopeFactory = new Mock(); mockServiceProvider = new Mock(); mockServiceScope.SetReturnsDefault(mockServiceProvider.Object); mockServiceProvider.SetReturnsDefault(mockServiceScopeFactory.Object); mockServiceProvider.Setup(s => s.GetService(It.IsAny())) .Returns(mockServiceScopeFactory.Object); mockServiceScopeFactory.SetReturnsDefault(mockServiceScope.Object); } [Fact] public void Contains_returns_true() { mockServiceScopeFactory.Invocations.Clear(); var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); const string work1Id = "long name 1"; const string work2Id = "long name 2"; var work1 = new WorkBase(work1Id, someAction); var work2 = new WorkPeriodic(work2Id, someAction, TimeSpan.Zero); backgroundService.Push(work1); backgroundService.Push(work2); Assert.True(backgroundService.Contains(work1Id)); Assert.True(backgroundService.Contains(work2Id)); Assert.False(backgroundService.Contains(work2Id + work1Id)); Assert.False(backgroundService.Contains(string.Empty)); } [Fact] public async Task Push_makes_new_scope_after_start() { mockServiceScopeFactory.Invocations.Clear(); var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var work = new WorkBase("", someAction); backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(10); mockServiceScopeFactory.Verify(f => f.CreateScope()); } [Fact] public async Task Makes_primary_work_done() { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var workDone = false; var work = new WorkBase("", (_, _, _) => { workDone = true; return Task.CompletedTask; }); backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(10); Assert.True(workDone); } [Fact] public async Task Sets_ExecutionTime_after_work_done() { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var work = new WorkBase("", someAction); backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(10); Assert.True(work.ExecutionTime > TimeSpan.Zero); } [Fact] public async Task Makes_periodic_work_done() { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var workDone = false; var work = new WorkPeriodic("", (_, _, _) => { workDone = true; return Task.CompletedTask; }, TimeSpan.FromMilliseconds(10)); backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(20); Assert.True(workDone); } [Fact] public async Task Does_not_start_periodic_work() { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var workDone = false; var work = new WorkPeriodic("", (_, _, _) => { workDone = true; return Task.CompletedTask; }, TimeSpan.FromSeconds(30)) { LastStart = DateTime.Now }; backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(20); Assert.False(workDone); } [Fact] public async Task Follows_work_priority() { var order = 0; var work1Order = -1; var work2Order = -1; var work1 = new WorkPeriodic("1", (_, _, _) => { work1Order = order++; return Task.CompletedTask; }, TimeSpan.FromMilliseconds(1) ); var work2 = new WorkBase("2", (_, _, _) => { work2Order = order++; return Task.CompletedTask; }); var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); backgroundService.Push(work2); backgroundService.Push(work1); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(2_100); Assert.True(work2Order < work1Order); } [Fact] public async Task Runs_second_after_delete_first() { var workDone = false; var work1 = new WorkBase("1", someAction); var work2 = new WorkPeriodic("2", (_, _, _) => { workDone = true; return Task.CompletedTask; }, TimeSpan.FromMilliseconds(1)); var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); backgroundService.Push(work1); backgroundService.Push(work2); backgroundService.Delete("1"); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(10); Assert.True(workDone); } [Fact] public async Task Aborts_long_work() { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var workCanceled = false; var work = new WorkBase("", async (_, _, token) => await Task.Delay(1000000, token)) { Timeout = TimeSpan.FromMilliseconds(1), OnErrorAsync = async (id, ex, token) => { workCanceled = ex is System.TimeoutException; await Task.CompletedTask; } }; backgroundService.Push(work); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(20 * 4); Assert.True(workCanceled); } [Fact] public async Task Execution_continues_after_work_exception() { var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); var work2done = false; var work1 = new WorkBase("1", (_, _, _) => throw new Exception()); var work2 = new WorkBase("2", (_, _, _) => { work2done = true; return Task.CompletedTask; }); backgroundService.Push(work1); backgroundService.Push(work2); await backgroundService.StartAsync(CancellationToken.None); await Task.Delay(2_100); Assert.True(work2done); } [Fact] public void Push_not_unique_id_should_throw() { var work1 = new WorkPeriodic("1", someAction, TimeSpan.FromSeconds(30)); var work2 = new WorkBase("1", someAction); var backgroundService = new BackgroundWorkerService(mockServiceProvider.Object); backgroundService.Push(work1); Assert.Throws( () => backgroundService.Push(work2)); } } }