using DD.Persistence.Client; using DD.Persistence.Client.Clients; using DD.Persistence.Client.Clients.Interfaces; using DD.Persistence.Client.Clients.Interfaces.Refit; using DD.Persistence.Database.Entity; using DD.Persistence.Models.ChangeLog; using DD.Persistence.Models.Requests; using Mapster; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using UuidExtensions; using Xunit; using static System.Runtime.InteropServices.JavaScript.JSType; namespace DD.Persistence.IntegrationTests.Controllers; public class ChangeLogControllerTest : BaseIntegrationTest { private readonly IChangeLogClient client; private readonly PaginationRequest paginationRequest; private static readonly Random random = new(); public ChangeLogControllerTest(WebAppFactoryFixture factory) : base(factory) { var refitClientFactory = scope.ServiceProvider .GetRequiredService>(); var logger = scope.ServiceProvider.GetRequiredService>(); client = scope.ServiceProvider .GetRequiredService(); paginationRequest = new PaginationRequest() { Skip = 0, Take = 10, SortSettings = string.Empty, }; } [Fact] public async Task ClearAndInsertRange() { // arrange var insertedCount = 10; var idDiscriminator = Guid.NewGuid(); var otherOldDtos = GenerateChangeLogCreateRequest(10); await client.AddRange(Guid.NewGuid(), otherOldDtos, Guid.NewGuid().ToString(), CancellationToken.None); var oldDtos = GenerateChangeLogCreateRequest(10); await client.AddRange(idDiscriminator, oldDtos, Guid.NewGuid().ToString(), CancellationToken.None); var newDtos = GenerateChangeLogCreateRequest(insertedCount); //act var result = await client.ClearAndAddRange(idDiscriminator, newDtos, "Добавление новых элементов и очистка старых", CancellationToken.None); // assert Assert.Equal(insertedCount * 2, result); var data = await client.GetByDate(idDiscriminator, DateTimeOffset.Now.AddSeconds(1), new PaginationRequest { Take = 10 * insertedCount }, CancellationToken.None); Assert.Equal(insertedCount, data.Count); } [Fact] public async Task AddRange_returns_success() { // arrange var count = 3; var idDiscriminator = Guid.NewGuid(); var dtos = GenerateChangeLogCreateRequest(count); var comment = "Создаю 3 элемента"; // act var result = await client.AddRange(idDiscriminator, dtos, comment, CancellationToken.None); // assert Assert.Equal(count, result); } [Fact] public async Task Update_returns_success() { //arrange var begin = DateTimeOffset.UtcNow; var idDiscriminator = Guid.NewGuid(); var dtos = GenerateChangeLogCreateRequest(1); var original = dtos.First(); var comment = "Создаю 1 элемент"; var result = await client.AddRange(idDiscriminator, [original], comment, CancellationToken.None); var query = dbContext.Set() .Include(x => x.CreatedCommit) .Where(x => x.CreatedCommit.DiscriminatorId == idDiscriminator); var entity = await query.FirstAsync(); var modified = entity.Adapt(); var key = modified.Value.First().Key; modified.Value[key] = random.NextDouble(); // act comment = "Обновляю 1 элемент"; result = await client.UpdateRange(idDiscriminator, [modified], comment, CancellationToken.None); // assert Assert.Equal(2, result); var now = DateTimeOffset.UtcNow.AddSeconds(1); var changeLogResult = await client.GetChangeLogForInterval(idDiscriminator, begin, now, new CancellationToken()); Assert.NotNull(changeLogResult); var obsoleteDto = changeLogResult .Where(e => e.Obsolete.HasValue) .First(); var activeDto = changeLogResult .Where(e => !e.Obsolete.HasValue) .OrderByDescending(e => e.Creation) .First(); Assert.Equal(activeDto.Id, obsoleteDto.IdNext); } [Fact] public async Task UpdateRange_returns_success() { var count = 2; var idDiscriminator = Guid.NewGuid(); var dtos = GenerateChangeLogCreateRequest(count); var comment = "Создаю 3 элемента"; // act var result = await client.AddRange(idDiscriminator, dtos, comment, CancellationToken.None); var paginatedResult = await client.GetByDate(idDiscriminator, DateTimeOffset.UtcNow.AddDays(1), paginationRequest, CancellationToken.None); // act comment = "Обновляю 3 элемента"; result = await client.UpdateRange(idDiscriminator, paginatedResult.Items, comment, CancellationToken.None); // assert Assert.Equal(count * 2, result); } [Fact] public async Task DeleteRange_returns_success() { // arrange var insertedCount = 10; var idDiscriminator = Guid.NewGuid(); var otherOldDtos = GenerateChangeLogCreateRequest(10); await client.AddRange(Guid.NewGuid(), otherOldDtos, Guid.NewGuid().ToString(), CancellationToken.None); var oldDtos = GenerateChangeLogCreateRequest(10); await client.AddRange(idDiscriminator, oldDtos, Guid.NewGuid().ToString(), CancellationToken.None); var existing = await client.GetByDate(idDiscriminator, DateTimeOffset.UtcNow.AddSeconds(1), new PaginationRequest { Take= 100*insertedCount}, CancellationToken.None); var ids = existing.Items.Select(e => e.Id); // act var result = await client.DeleteRange(idDiscriminator, ids, "Удаление нескольких записей", CancellationToken.None); // assert Assert.Equal(insertedCount, result); var items = await dbContext.Set() .Where(e=> ids.Contains(e.Id)) .ToArrayAsync(CancellationToken.None); Assert.Equal(insertedCount, items.Count()); Assert.All(items, i => Assert.NotNull(i.IdObsoletedCommit)); } static ChangeLogCommit GenerateCommit(Guid discriminatorId, DateTimeOffset creation) => GenerateCommit(discriminatorId, Guid.NewGuid(), creation); static ChangeLogCommit GenerateCommit(Guid discriminatorId, Guid userId, DateTimeOffset creation) => new ChangeLogCommit { Id = Guid.NewGuid(), DiscriminatorId = discriminatorId, IdAuthor = userId, Creation = creation, Comment = Guid.NewGuid().ToString(), }; static IEnumerable GenerateChangeLog(int count, ChangeLogCommit createCommit) => GenerateChangeLog(count, createCommit, null, null); static IEnumerable GenerateChangeLog(int count, ChangeLogCommit createCommit, ChangeLogCommit? obsoleteCommit, Guid? idNext) { for (var i = 0; i< count; i++) { yield return new ChangeLog { Id = Guid.NewGuid(), CreatedCommit = createCommit, IdCreatedCommit = createCommit.Id, ObsoletedCommit = obsoleteCommit, IdObsoletedCommit = obsoleteCommit?.Id, IdNext = idNext, Value = new Dictionary { { "Guid.NewGuid()", random.NextDouble() } } }; } } static ChangeLogCommit GenerateCommitWithNewData(Guid discriminatorId, DateTimeOffset creation, int count) { var commit = GenerateCommit(discriminatorId, creation); commit.ChangeLogCreatedItems = GenerateChangeLog(count, commit).ToList(); return commit; } async Task SeedCommitWithNewData(Guid discriminatorId, DateTimeOffset creation, int count) { var data = GenerateCommitWithNewData(discriminatorId, creation, count); dbContext.Set().Add(data); await dbContext.SaveChangesAsync(); return data; } [Fact] public async Task GetDatesRange_returns_success() { //arrange var dateMin = DateTimeOffset.UtcNow.AddDays(-1); var dateMax = DateTimeOffset.UtcNow; var dateMid = dateMin + 0.5 * (dateMax - dateMin); var idDiscriminator = Guid.NewGuid(); var idOtherDiscriminator = Guid.NewGuid(); var count = 15; await SeedCommitWithNewData(idDiscriminator, dateMin, count); await SeedCommitWithNewData(idDiscriminator, dateMid, count); await SeedCommitWithNewData(idDiscriminator, dateMax, count); await SeedCommitWithNewData(idOtherDiscriminator, dateMin.AddDays(-1), count); // act var result = await client.GetDatesRange(idDiscriminator, CancellationToken.None); // assert Assert.NotNull(result); Assert.Equal(dateMin, result.From, TimeSpan.FromSeconds(1)); Assert.Equal(dateMax, result.To, TimeSpan.FromSeconds(1)); } [Fact] public async Task GetByDate_returns_success() { // arrange var dbSetCommit = dbContext.Set(); var dbSet = dbContext.Set(); var idDiscriminator = Guid.NewGuid(); var date = DateTimeOffset.UtcNow; var expected = 10; var skip = 2; var take = skip + 2; var count = expected + take; var commit = GenerateCommit(idDiscriminator, date); dbSetCommit.Add(commit); var commitData = GenerateChangeLog(count, commit); dbSet.AddRange(commitData); await dbContext.SaveChangesAsync(CancellationToken.None); var dataWithIds = await dbSet .Where(e=>e.CreatedCommit.DiscriminatorId == idDiscriminator) .AsNoTracking() .ToArrayAsync(CancellationToken.None); var itemsToDelete = dataWithIds.Skip(skip).Take(take); var idsToDelete = itemsToDelete.Select(x => x.Id).ToArray(); // act var deletedCount = await client.DeleteRange(idDiscriminator, idsToDelete, "Удаление нескольких записей", CancellationToken.None); // assert var moment = DateTimeOffset.UtcNow.AddSeconds(1); var result = await client.GetByDate(idDiscriminator, moment, new() { Take = count *100}, new CancellationToken()); Assert.NotNull(result); Assert.Equal(expected, result.Items.Count()); } [Fact] public async Task GetChangeLogForInterval_returns_success() { // arrange var dateMin = DateTimeOffset.UtcNow.AddDays(- 11); var dateMax = DateTimeOffset.UtcNow; var dateMid = dateMin + 0.5 * (dateMax - dateMin); var idDiscriminator = Guid.NewGuid(); var idOtherDiscriminator = Guid.NewGuid(); var count = 17; var commitMin = await SeedCommitWithNewData(idDiscriminator, dateMin, count); var commitMid = await SeedCommitWithNewData(idDiscriminator, dateMid, count); await SeedCommitWithNewData(idDiscriminator, dateMax, count); await SeedCommitWithNewData(idOtherDiscriminator, dateMin.AddDays(-1), count); var dateBegin = commitMin.Creation; var dateEnd = commitMid.Creation.AddSeconds(1); var expectedCount = commitMin.ChangeLogCreatedItems.Count() + commitMid.ChangeLogCreatedItems.Count(); //act var result = await client.GetChangeLogForInterval(idDiscriminator, dateBegin, dateEnd, CancellationToken.None); //assert Assert.Equal(expectedCount, result.Count()); } [Fact] public async Task GetStatistics_returns_success() { // arrange var discriminatorId = Guid.NewGuid(); // создаем записи var date0 = DateTimeOffset.UtcNow.AddDays(-17); var day0 = new DateOnly(date0.Year, date0.Month, date0.Day); var inserted0 = 17; var commit0 = await SeedCommitWithNewData(discriminatorId, date0, inserted0); // создаем еще записи var date1 = DateTimeOffset.UtcNow.AddDays(-7); var day1 = new DateOnly(date1.Year, date1.Month, date1.Day); var inserted1 = inserted0 + 5; var commit1 = await SeedCommitWithNewData(discriminatorId, date1, inserted1); // создаем еще записи с новым дискриминатором await SeedCommitWithNewData(Guid.NewGuid(), date1, 17); // обновим var date2 = DateTimeOffset.UtcNow; var day2 = new DateOnly(date2.Year, date2.Month, date2.Day); var updated = commit0.ChangeLogCreatedItems.Select(i => new ChangeLogBaseDto { Id = i.Id, Value = i.Value }); await client.UpdateRange(commit0.DiscriminatorId, updated, Guid.NewGuid().ToString(), CancellationToken.None); // удалим из коммита var idToDelete = commit1.ChangeLogCreatedItems.First().Id; await client.DeleteRange(commit1.DiscriminatorId, [idToDelete], Guid.NewGuid().ToString(), CancellationToken.None); //act var request = new ChangeLogQuery() { DiscriminatorId = commit0.DiscriminatorId, UserId = null }; var statistics = await client.GetStatistics(request, CancellationToken.None); //assert Assert.Equal(3, statistics.Count()); var stat0 = statistics.First(s => s.Date == day0); Assert.Equal(inserted0, stat0.CreatedChangeLogCount); Assert.Equal(0, stat0.ObsoletedCount); Assert.Equal(1, stat0.CommitCount); var stat1 = statistics.First(s => s.Date == day1); Assert.Equal(inserted1, stat1.CreatedChangeLogCount); Assert.Equal(0, stat1.ObsoletedCount); Assert.Equal(1, stat1.CommitCount); var stat2 = statistics.First(s => s.Date == day2); Assert.Equal(inserted0, stat2.CreatedChangeLogCount); Assert.Equal(inserted0 + 1, stat2.ObsoletedCount); Assert.Equal(2, stat2.CommitCount); } [Fact] public async Task GetHistory_returns_success() { // arrange var discriminatorId = Guid.NewGuid(); // создаем записи var date0 = DateTimeOffset.UtcNow.AddDays(-17); var inserted0 = 17; var commit0 = await SeedCommitWithNewData(discriminatorId, date0, inserted0); // обновим var date2 = DateTimeOffset.UtcNow; var updated = commit0.ChangeLogCreatedItems.Skip(1).Select(i => new ChangeLogBaseDto { Id = i.Id, Value = i.Value }); await client.UpdateRange(commit0.DiscriminatorId, updated, Guid.NewGuid().ToString(), CancellationToken.None); // удалим из коммита var idToDelete = commit0.ChangeLogCreatedItems.First().Id; await client.DeleteRange(commit0.DiscriminatorId, [idToDelete], Guid.NewGuid().ToString(), CancellationToken.None); var request = new ChangeLogQuery() { DiscriminatorId = commit0.DiscriminatorId, UserId = null }; var history = await client.GetHistory(request, CancellationToken.None); Assert.Equal(3, history.Count()); var items = history.OrderBy(e => e.Creation); var firstItem = items.First(); Assert.Equal(date0.DateTime.ToString(), firstItem.Creation.DateTime.ToString()); Assert.Equal(inserted0, firstItem.ChangeLogCreatedItems.Count()); Assert.Empty(firstItem.ChangeLogObsoletedItems); var middleItem = items.Skip(1).Take(1).First(); Assert.Equal(date2.DateTime.ToString(), middleItem.Creation.DateTime.ToString()); Assert.Equal(updated.Count(), middleItem.ChangeLogObsoletedItems.Count()); Assert.Equal(updated.Count(), middleItem.ChangeLogCreatedItems.Count()); var lastItem = items.Last(); Assert.Equal(date2.DateTime.ToString(), lastItem.Creation.DateTime.ToString()); Assert.Single(lastItem.ChangeLogObsoletedItems); //тест случая, когда данных за указанный период нет request = new ChangeLogQuery() { DiscriminatorId = commit0.DiscriminatorId, UserId = null, GeDate = DateTimeOffset.UtcNow.AddMinutes(1), LeDate = DateTimeOffset.UtcNow.AddDays(10), }; history = await client.GetHistory(request, CancellationToken.None); Assert.Empty(history); //тест случая, когда данные за указанный период есть request = new ChangeLogQuery() { DiscriminatorId = commit0.DiscriminatorId, UserId = null, GeDate = DateTimeOffset.UtcNow.AddDays(-17).AddMinutes(-10), LeDate = DateTimeOffset.UtcNow.AddDays(-1), }; history = await client.GetHistory(request, CancellationToken.None); Assert.Single(history); var createdChangeLogs = history.First().ChangeLogCreatedItems; Assert.Equal(17, createdChangeLogs.Count()); } private static IEnumerable GenerateChangeLogCreateRequest(int count) { for (int i = 0; i < count; i++) yield return new ChangeLogCreateRequest() { Value = new Dictionary() { { "Key", random.NextDouble() } } }; } }