diff --git a/Persistence.API/Controllers/ChangeLogController.cs b/Persistence.API/Controllers/ChangeLogController.cs index f65cbb1..63adaea 100644 --- a/Persistence.API/Controllers/ChangeLogController.cs +++ b/Persistence.API/Controllers/ChangeLogController.cs @@ -120,7 +120,7 @@ public class ChangeLogController : ControllerBase, IChangeLogApi public async Task GetByDate( Guid idDiscriminator, DateTimeOffset moment, - SectionPartRequest request, + [FromQuery] SectionPartRequest request, CancellationToken token) { var result = await repository.GetByDate(idDiscriminator, moment, request, token); diff --git a/Persistence.API/DependencyInjection.cs b/Persistence.API/DependencyInjection.cs index 81713e4..808d50e 100644 --- a/Persistence.API/DependencyInjection.cs +++ b/Persistence.API/DependencyInjection.cs @@ -1,5 +1,7 @@ +using System.Security.Claims; using System.Text.Json.Nodes; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; @@ -86,7 +88,7 @@ public static class DependencyInjection ValidAudience = JwtParams.Audience, ValidateLifetime = true, IssuerSigningKey = JwtParams.SecurityKey, - ValidateIssuerSigningKey = false + ValidateIssuerSigningKey = false, }; options.Events = new JwtBearerEvents { @@ -103,7 +105,13 @@ public static class DependencyInjection }, OnTokenValidated = context => { - var username = context.Principal?.Claims + if(context.Principal != null && context.Principal.HasClaim(x => x.Type != ClaimTypes.NameIdentifier)) + { + var claim = new Claim(ClaimTypes.NameIdentifier.ToString(), Guid.NewGuid().ToString()); + (context.Principal.Identity as ClaimsIdentity)!.AddClaim(claim); + } + + var username = context.Principal?.Claims .FirstOrDefault(e => e.Type == "username")?.Value; var password = context.Principal?.Claims diff --git a/Persistence.API/appsettings.json b/Persistence.API/appsettings.json index 3e1eea7..d4248fb 100644 --- a/Persistence.API/appsettings.json +++ b/Persistence.API/appsettings.json @@ -13,6 +13,14 @@ "MetadataAddress": "http://192.168.0.10:8321/realms/Persistence/.well-known/openid-configuration", "Audience": "account", "ValidIssuer": "http://192.168.0.10:8321/realms/Persistence", - "AuthorizationUrl": "http://192.168.0.10:8321/realms/Persistence/protocol/openid-connect/auth" + "AuthorizationUrl": "http://192.168.0.10:8321/realms/Persistence/protocol/openid-connect/auth" + }, + "NeedUseKeyCloak": false, + "AuthUser": { + "username": "myuser", + "password": 12345, + "clientId": "webapi", + "grantType": "password", + "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "7d9f3574-6574-4ca3-845a-0276eb4aa8f6" } } diff --git a/Persistence.Client/Clients/IChangeLogClient.cs b/Persistence.Client/Clients/IChangeLogClient.cs new file mode 100644 index 0000000..1658d5e --- /dev/null +++ b/Persistence.Client/Clients/IChangeLogClient.cs @@ -0,0 +1,121 @@ +using Microsoft.AspNetCore.Mvc; +using Persistence.Models; +using Persistence.Models.Requests; +using Refit; + +namespace Persistence.Client.Clients; + +/// +/// Интерфейс для тестирования API, предназначенного для работы с записями ChangeLod +/// +public interface IChangeLogClient +{ + private const string BaseRoute = "/api/ChangeLog"; + + //[Get($"{BaseRoute}/current")] + /// + /// Импорт с заменой: удаление старых строк и добавление новых + /// + /// + /// + /// + [Post($"{BaseRoute}/replace")] + Task> ClearAndInsertRange(Guid idDiscriminator, IEnumerable dtos); + + ///// + ///// Получение данных на текущую дату (с пагинацией) + ///// + ///// + ///// параметры запроса + ///// + ///// + //Task GetCurrent(Guid idDiscriminator, SectionPartRequest request, CancellationToken token); + + /// + /// Получение данных на определенную дату (с пагинацией) + /// + /// + /// + /// параметры запроса + /// + /// + [Get($"{BaseRoute}/moment")] + Task>> GetByDate(Guid idDiscriminator, DateTimeOffset moment, [Query]SectionPartRequest request); + + /// + /// Получение исторических данных за определенный период времени + /// + /// + /// + /// + /// + /// + [Get($"{BaseRoute}/history")] + Task>> GetChangeLogForDate(Guid idDiscriminator, DateTimeOffset dateBegin, DateTimeOffset dateEnd); + + /// + /// Добавить одну запись + /// + /// + /// + /// + [Post($"{BaseRoute}")] + Task> Add(Guid idDiscriminator, DataWithWellDepthAndSectionDto dto); + + /// + /// Добавить несколько записей + /// + /// + /// + /// + /// + [Post($"{BaseRoute}/range")] + Task> AddRange(Guid idDiscriminator, IEnumerable dtos); + + /// + /// Обновить одну запись + /// + /// + /// + /// + [Put($"{BaseRoute}")] + Task> Update(Guid idDiscriminator, DataWithWellDepthAndSectionDto dto); + + /// + /// Обновить несколько записей + /// + /// + /// + /// + /// + [Put($"{BaseRoute}/range")] + Task> UpdateRange(Guid idDiscriminator, IEnumerable dtos); + + /// + /// Удалить одну запись + /// + /// + /// + /// + [Delete($"{BaseRoute}")] + Task> Delete(Guid id); + + /// + /// Удалить несколько записей + /// + /// + /// + /// + [Delete($"{BaseRoute}/range")] + Task> DeleteRange([Body] IEnumerable ids); + + /// + /// Получение списка дат, в которые происходили изменения (день, месяц, год, без времени) + /// + /// + /// + /// + [Get($"{BaseRoute}/datesRange")] + Task> GetDatesRange(Guid idDiscriminator); + +} diff --git a/Persistence.Client/Helpers/ApiTokenHelper.cs b/Persistence.Client/Helpers/ApiTokenHelper.cs index e508922..b61f86e 100644 --- a/Persistence.Client/Helpers/ApiTokenHelper.cs +++ b/Persistence.Client/Helpers/ApiTokenHelper.cs @@ -34,7 +34,8 @@ public static class ApiTokenHelper new("client_id", authUser.ClientId), new("username", authUser.Username), new("password", authUser.Password), - new("grant_type", authUser.GrantType) + new("grant_type", authUser.GrantType), + new(ClaimTypes.NameIdentifier.ToString(), Guid.NewGuid().ToString()) }; var tokenDescriptor = new SecurityTokenDescriptor diff --git a/Persistence.Database.Postgres/PersistenceDbContext.cs b/Persistence.Database.Postgres/PersistenceDbContext.cs index 0f7ba03..c122eb6 100644 --- a/Persistence.Database.Postgres/PersistenceDbContext.cs +++ b/Persistence.Database.Postgres/PersistenceDbContext.cs @@ -41,5 +41,9 @@ public partial class PersistenceDbContext : DbContext modelBuilder.Entity() .Property(e => e.Set) .HasJsonConversion(); + + modelBuilder.Entity() + .Property(e => e.Value) + .HasJsonConversion(); } } diff --git a/Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs b/Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs new file mode 100644 index 0000000..a57433b --- /dev/null +++ b/Persistence.IntegrationTests/Controllers/ChangeLogControllerTest.cs @@ -0,0 +1,283 @@ +using Microsoft.Extensions.DependencyInjection; +using Persistence.Client.Clients; +using Persistence.Client; +using Persistence.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using System.Net; +using Mapster; +using Persistence.Database.Model; +using Persistence.Models.Requests; + +namespace Persistence.IntegrationTests.Controllers; +public class ChangeLogControllerTest : BaseIntegrationTest +{ + private readonly IChangeLogClient client; + private static Random generatorRandomDigits = new Random(); + + public ChangeLogControllerTest(WebAppFactoryFixture factory) : base(factory) + { + var persistenceClientFactory = scope.ServiceProvider + .GetRequiredService(); + + client = persistenceClientFactory.GetClient(); + } + + [Fact] + public async Task ClearAndInsertRange() + { + // arrange + var idDiscriminator = Guid.NewGuid(); + var dtos = Generate(2, DateTimeOffset.UtcNow); + + // act + var result = await client.ClearAndInsertRange(idDiscriminator, dtos); + + // assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(2, result.Content); + } + + [Fact] + public async Task Add_returns_success() + { + // arrange + var count = 1; + var idDiscriminator = Guid.NewGuid(); + var dtos = Generate(count, DateTimeOffset.UtcNow); + var dto = dtos.FirstOrDefault()!; + + // act + var result = await client.Add(idDiscriminator, dto); + + // assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(count, result.Content); + } + + [Fact] + public async Task AddRange_returns_success() + { + // arrange + var count = 3; + var idDiscriminator = Guid.NewGuid(); + var dtos = Generate(count, DateTimeOffset.UtcNow); + + // act + var result = await client.AddRange(idDiscriminator, dtos); + + // assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(count, result.Content); + } + + [Fact] + public async Task Update_returns_success() + { + // arrange + var idDiscriminator = Guid.NewGuid(); + var dtos = Generate(1, DateTimeOffset.UtcNow); + var dto = dtos.FirstOrDefault()!; + var entity = dto.Adapt(); + dbContext.ChangeLog.Add(entity); + dbContext.SaveChanges(); + + dto.Id = entity.Id; + dto.DepthStart = dto.DepthStart + 10; + dto.DepthEnd = dto.DepthEnd + 10; + + // act + var result = await client.Update(idDiscriminator, dto); + + // assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(2, result.Content); + + //var entities = dbContext.ChangeLog + // .Where(e => e.IdDiscriminator == idDiscriminator) + // .ToArray(); + //var obsoleteEntity = entities + // .Where(e => e.Obsolete.HasValue) + // .FirstOrDefault(); + + //var activeEntity = entities + // .Where(e => !e.Obsolete.HasValue) + // .FirstOrDefault(); + + //if (obsoleteEntity == null || activeEntity == null) + //{ + // Assert.Fail(); + // return; + //} + + //Assert.Equal(activeEntity.IdNext, obsoleteEntity.Id); + + } + + [Fact] + public async Task UpdateRange_returns_success() + { + // arrange + var count = 2; + var idDiscriminator = Guid.NewGuid(); + var dtos = Generate(count, DateTimeOffset.UtcNow); + var entities = dtos.Select(d => d.Adapt()); + dbContext.ChangeLog.AddRange(entities); + dbContext.SaveChanges(); + + dtos = entities.Select(c => new DataWithWellDepthAndSectionDto() + { + DepthEnd = c.DepthEnd + 10, + DepthStart = c.DepthStart + 10, + Id = c.Id, + IdSection = c.IdSection, + Value = c.Value + }); + + // act + var result = await client.UpdateRange(idDiscriminator, dtos); + + // assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(count * 2, result.Content); + } + + [Fact] + public async Task Delete_returns_success() + { + // arrange + var dtos = Generate(1, DateTimeOffset.UtcNow); + var dto = dtos.FirstOrDefault()!; + var entity = dto.Adapt(); + dbContext.ChangeLog.Add(entity); + dbContext.SaveChanges(); + + // act + var result = await client.Delete(entity.Id); + + // assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(1, result.Content); + } + + [Fact] + public async Task DeleteRange_returns_success() + { + // arrange + var count = 10; + var dtos = Generate(count, DateTimeOffset.UtcNow); + var entities = dtos.Select(d => d.Adapt()).ToArray(); + dbContext.ChangeLog.AddRange(entities); + dbContext.SaveChanges(); + + // act + var ids = entities.Select(e => e.Id); + var result = await client.DeleteRange(ids); + + // assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.Equal(count, result.Content); + } + + [Fact] + public async Task GetDatesRange_returns_success() + { + // arrange + var changeLogItems = CreateChangeLogItems(3); + var idDiscriminator = changeLogItems.Item1; + var entities = changeLogItems.Item2; + + var orderedEntities = entities.OrderBy(e => e.Creation); + var minDate = orderedEntities.First().Creation; + var maxDate = orderedEntities.Last().Creation; + + // act + var result = await client.GetDatesRange(idDiscriminator); + + // assert + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.NotNull(result.Content); + + var expectedMinDate = minDate.ToUniversalTime().ToString(); + var actualMinDate = result.Content!.From.ToUniversalTime().ToString(); + Assert.Equal(expectedMinDate, actualMinDate); + + var expectedMaxDate = maxDate.ToUniversalTime().ToString(); + var actualMaxDate = result.Content!.To.ToUniversalTime().ToString(); + Assert.Equal(expectedMaxDate, actualMaxDate); + } + + private (Guid, ChangeLog[]) CreateChangeLogItems(int count) + { + Guid idDiscriminator = Guid.NewGuid(); + var dtos = Generate(count, DateTimeOffset.UtcNow); + var entities = dtos.Select(d => + { + var entity = d.Adapt(); + entity.IdDiscriminator = idDiscriminator; + entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(-15, 15)); + + return entity; + }).ToArray(); + dbContext.ChangeLog.AddRange(entities); + dbContext.SaveChanges(); + + return (idDiscriminator, entities); + } + + [Fact] + public async Task GetByDate_returns_success() + { + // arrange + var changeLogItems = CreateChangeLogItems(3); + var idDiscriminator = changeLogItems.Item1; + var entities = changeLogItems.Item2; + var createdDates = entities.Select(e => e.Creation); + + var dtos = entities.Select(e => + { + var dto = e.Adapt(); + dto.DepthEnd = dto.DepthEnd + 10; + + return dto; + }).ToArray(); + + var updatedItems = await client.UpdateRange(idDiscriminator, dtos); + + var request = new SectionPartRequest() { + Skip = 0, + Take = 10, + SortSettings = String.Empty, + }; + + var moment = DateTimeOffset.UtcNow.AddDays(20); + var result = await client.GetByDate(idDiscriminator, moment, request); + + Assert.Equal(HttpStatusCode.OK, result.StatusCode); + Assert.NotNull(result.Content); + + } + + private static IEnumerable Generate(int count, DateTimeOffset from) + { + for (int i = 0; i < count; i++) + yield return new DataWithWellDepthAndSectionDto() + { + Value = new Dictionary() + { + { "Key", 1 } + }, + DepthStart = generatorRandomDigits.Next(1, 5), + DepthEnd = generatorRandomDigits.Next(5, 15), + Id = Guid.NewGuid(), + IdSection = Guid.NewGuid() + }; + + } +} diff --git a/Persistence.Repository/Repositories/ChangeLogRepository.cs b/Persistence.Repository/Repositories/ChangeLogRepository.cs index 98ff7d3..7b92039 100644 --- a/Persistence.Repository/Repositories/ChangeLogRepository.cs +++ b/Persistence.Repository/Repositories/ChangeLogRepository.cs @@ -15,7 +15,7 @@ public class ChangeLogRepository : IChangeLogRepository this.db = db; } - public Task InsertRange(Guid idUser, Guid idDiscriminator, IEnumerable dtos, CancellationToken token) + public async Task InsertRange(Guid idUser, Guid idDiscriminator, IEnumerable dtos, CancellationToken token) { foreach (var dto in dtos) { @@ -23,7 +23,7 @@ public class ChangeLogRepository : IChangeLogRepository db.Set().Add(entity); } - var result = db.SaveChangesAsync(token); + var result = await db.SaveChangesAsync(token); return result; } @@ -106,7 +106,7 @@ public class ChangeLogRepository : IChangeLogRepository updatedEntity.IdEditor = idUser; } - await db.SaveChangesAsync(token); + result = await db.SaveChangesAsync(token); await transaction.CommitAsync(token); return result; @@ -272,7 +272,11 @@ public class ChangeLogRepository : IChangeLogRepository public async Task GetDatesRange(Guid idDiscriminator, CancellationToken token) { - var query = this.db.Set().Where(e => e.IdDiscriminator == idDiscriminator); + var query = db.Set() + .Where(e => e.IdDiscriminator == idDiscriminator); + var test = db.Set().ToArray(); + var test2 = query.ToArray(); + var minDate = await query.MinAsync(o => o.Creation, token); var maxDate = await query.MaxAsync(o => o.Creation, token); diff --git a/Persistence/Models/Requests/Request.cs b/Persistence/Models/Requests/Request.cs index a53f5a9..c1b0583 100644 --- a/Persistence/Models/Requests/Request.cs +++ b/Persistence/Models/Requests/Request.cs @@ -21,5 +21,5 @@ public class Request /// Содержат список названий полей сортировки /// Указать направление сортировки можно через пробел "asc" или "desc" /// - public string SortSettings { get; set; } = string.Empty; + public string? SortSettings { get; set; } = string.Empty; }