Setpoint API #1
@ -15,36 +15,37 @@ namespace Persistence.API.Controllers
|
|||||||
this.setpointRepository = setpointRepository;
|
this.setpointRepository = setpointRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("current")]
|
[HttpGet("current")]
|
||||||
|
|||||||
public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetCurrent(IEnumerable<Guid> setpointKeys, CancellationToken token)
|
public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetCurrent([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
|
||||||
{
|
{
|
||||||
var result = await setpointRepository.GetCurrent(setpointKeys, token);
|
var result = await setpointRepository.GetCurrent(setpointKeys, token);
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("history")]
|
[HttpGet("history")]
|
||||||
on.nemtina
commented
Здесь метод [HttpGet] Здесь метод [HttpGet]
|
|||||||
public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetHistory(IEnumerable<Guid> setpointKeys, DateTimeOffset historyMoment, CancellationToken token)
|
public async Task<ActionResult<IEnumerable<SetpointValueDto>>> GetHistory([FromQuery] IEnumerable<Guid> setpointKeys, [FromQuery] DateTimeOffset historyMoment, CancellationToken token)
|
||||||
{
|
{
|
||||||
var result = await setpointRepository.GetHistory(setpointKeys, historyMoment, token);
|
var result = await setpointRepository.GetHistory(setpointKeys, historyMoment, token);
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("log")]
|
[HttpGet("log")]
|
||||||
on.nemtina
commented
Здесь метод [HttpGet] Здесь метод [HttpGet]
|
|||||||
public async Task<ActionResult<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([FromBody] IEnumerable<Guid> setpointKeys, CancellationToken token)
|
public async Task<ActionResult<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([FromQuery] IEnumerable<Guid> setpointKeys, CancellationToken token)
|
||||||
{
|
{
|
||||||
var result = await setpointRepository.GetLog(setpointKeys, token);
|
var result = await setpointRepository.GetLog(setpointKeys, token);
|
||||||
|
|
||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("save")]
|
[HttpPost]
|
||||||
on.nemtina
commented
Здесь можно просто [HttpPost] Здесь можно просто [HttpPost]
|
|||||||
public async Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token)
|
public async Task<ActionResult<int>> Save(Guid setpointKey, object newValue, CancellationToken token)
|
||||||
{
|
{
|
||||||
var result = await setpointRepository.Save(setpointKey, newValue, 0, token);
|
// ToDo: вычитка idUser
|
||||||
on.nemtina
commented
Добавить todo, что необходимо решить вопрос с получением пользователя и авторизацией Добавить todo, что необходимо решить вопрос с получением пользователя и авторизацией
|
|||||||
|
await setpointRepository.Save(setpointKey, newValue, 0, token);
|
||||||
|
|
||||||
return Ok(result);
|
return Ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
|
||||||
|
|
||||||
namespace Persistence.Database.Model
|
namespace Persistence.Database.Model
|
||||||
{
|
{
|
||||||
@ -13,7 +12,7 @@ namespace Persistence.Database.Model
|
|||||||
[Column(TypeName = "jsonb"), Comment("Значение уставки")]
|
[Column(TypeName = "jsonb"), Comment("Значение уставки")]
|
||||||
public required object Value { get; set; }
|
public required object Value { get; set; }
|
||||||
|
|
||||||
[Comment("Дата изменения уставки")]
|
[Comment("Дата создания уставки")]
|
||||||
public DateTimeOffset Created { get; set; }
|
public DateTimeOffset Created { get; set; }
|
||||||
on.nemtina
commented
Должно быть "Дата создания уставки" Должно быть "Дата создания уставки"
|
|||||||
|
|
||||||
[Comment("Id автора последнего изменения")]
|
[Comment("Id автора последнего изменения")]
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
|
||||||
|
|
||||||
namespace Persistence.Database.Model
|
|
||||||
{
|
|
||||||
public class SetpointDictionary
|
|
||||||
{
|
|
||||||
[Key, Comment("Ключ")]
|
|
||||||
public Guid Key { get; set; }
|
|
||||||
|
|
||||||
[Comment("Наименование")]
|
|
||||||
public required string Name { get; set; }
|
|
||||||
|
|
||||||
[Comment("Описание")]
|
|
||||||
public string? Description { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,18 +3,23 @@ using Refit;
|
|||||||
|
|
||||||
namespace Persistence.IntegrationTests.Clients
|
namespace Persistence.IntegrationTests.Clients
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Интерфейс для тестирования API, предназначенного для работы с уставками
|
||||||
|
/// </summary>
|
||||||
on.nemtina
commented
Заголовки методов запроса должны соответствовать тем, что в контроллере (там часть нужно поменять с post на get) Заголовки методов запроса должны соответствовать тем, что в контроллере (там часть нужно поменять с post на get)
|
|||||||
public interface ISetpointClient
|
public interface ISetpointClient
|
||||||
{
|
{
|
||||||
[Post("/current")]
|
private const string BaseRoute = "/api/setpoint";
|
||||||
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetCurrent(IEnumerable<Guid> setpointKeys);
|
|
||||||
|
|
||||||
[Post("/history")]
|
[Get($"{BaseRoute}/current")]
|
||||||
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetHistory(IEnumerable<Guid> setpointKeys, DateTimeOffset historyMoment);
|
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetCurrent([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys);
|
||||||
|
|
||||||
[Post("/log")]
|
[Get($"{BaseRoute}/history")]
|
||||||
Task<IApiResponse<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog(IEnumerable<Guid> setpoitKeys);
|
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetHistory([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys, [Query] DateTimeOffset historyMoment);
|
||||||
|
|
||||||
[Post("/save")]
|
[Get($"{BaseRoute}/log")]
|
||||||
Task<IApiResponse<int>> Save(Guid setpointKey, object newValue);
|
Task<IApiResponse<Dictionary<Guid, IEnumerable<SetpointLogDto>>>> GetLog([Query(CollectionFormat.Multi)] IEnumerable<Guid> setpointKeys);
|
||||||
|
|
||||||
|
[Post($"{BaseRoute}/")]
|
||||||
|
Task<IApiResponse> Save(Guid setpointKey, object newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text.Json;
|
|
||||||
using Mapster;
|
|
||||||
using Persistence.IntegrationTests.Clients;
|
using Persistence.IntegrationTests.Clients;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@ -16,8 +14,6 @@ namespace Persistence.IntegrationTests.Controllers
|
|||||||
}
|
}
|
||||||
public SetpointControllerTest(WebAppFactoryFixture factory) : base(factory)
|
public SetpointControllerTest(WebAppFactoryFixture factory) : base(factory)
|
||||||
{
|
{
|
||||||
factory.ClientOptions.BaseAddress = new Uri($"http://localhost/api/Setpoint");
|
|
||||||
|
|
||||||
client = factory.GetHttpClient<ISetpointClient>(string.Empty);
|
client = factory.GetHttpClient<ISetpointClient>(string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
on.nemtina
commented
Эту строчку лучше убрать, а в интерфейсе ISetpointClient прописать так:
Эту строчку лучше убрать, а в интерфейсе ISetpointClient прописать так:
```
private const string BaseRoute = "/api/setpoint";
[Post($"{BaseRoute}/current")]
Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetCurrent(IEnumerable<Guid> setpointKeys);```
|
|||||||
@ -40,6 +36,22 @@ namespace Persistence.IntegrationTests.Controllers
|
|||||||
Assert.Empty(response.Content);
|
Assert.Empty(response.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetCurrent_AfterSave_returns_success()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var setpointKey = await Save();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await client.GetCurrent([setpointKey]);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotEmpty(response.Content);
|
||||||
|
Assert.Equal(response.Content.FirstOrDefault()?.Key, setpointKey);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetHistory_returns_success()
|
public async Task GetHistory_returns_success()
|
||||||
{
|
{
|
||||||
@ -49,7 +61,7 @@ namespace Persistence.IntegrationTests.Controllers
|
|||||||
Guid.NewGuid(),
|
Guid.NewGuid(),
|
||||||
Guid.NewGuid()
|
Guid.NewGuid()
|
||||||
};
|
};
|
||||||
var historyMoment = DateTimeOffset.Now.ToUniversalTime();
|
var historyMoment = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
//act
|
//act
|
||||||
var response = await client.GetHistory(setpointKeys, historyMoment);
|
var response = await client.GetHistory(setpointKeys, historyMoment);
|
||||||
@ -60,6 +72,24 @@ namespace Persistence.IntegrationTests.Controllers
|
|||||||
Assert.Empty(response.Content);
|
Assert.Empty(response.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetHistory_AfterSave_returns_success()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var setpointKey = await Save();
|
||||||
|
var historyMoment = DateTimeOffset.UtcNow;
|
||||||
|
historyMoment = historyMoment.AddDays(1);
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await client.GetHistory([setpointKey], historyMoment);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotEmpty(response.Content);
|
||||||
|
Assert.Equal(response.Content.FirstOrDefault()?.Key, setpointKey);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetLog_returns_success()
|
public async Task GetLog_returns_success()
|
||||||
{
|
{
|
||||||
@ -79,8 +109,29 @@ namespace Persistence.IntegrationTests.Controllers
|
|||||||
Assert.Empty(response.Content);
|
Assert.Empty(response.Content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetLog_AfterSave_returns_success()
|
||||||
|
{
|
||||||
|
//arrange
|
||||||
|
var setpointKey = await Save();
|
||||||
|
|
||||||
|
//act
|
||||||
|
var response = await client.GetLog([setpointKey]);
|
||||||
|
|
||||||
|
//assert
|
||||||
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotEmpty(response.Content);
|
||||||
|
Assert.Equal(response.Content.FirstOrDefault().Value.FirstOrDefault()?.Key, setpointKey);
|
||||||
on.nemtina
commented
Assert.Equal(expected, actual) Assert.Equal(expected, actual)
|
|||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Save_returns_success()
|
public async Task Save_returns_success()
|
||||||
|
{
|
||||||
|
await Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Guid> Save()
|
||||||
{
|
{
|
||||||
//arrange
|
//arrange
|
||||||
var setpointKey = Guid.NewGuid();
|
var setpointKey = Guid.NewGuid();
|
||||||
@ -95,53 +146,8 @@ namespace Persistence.IntegrationTests.Controllers
|
|||||||
|
|
||||||
//assert
|
//assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.Equal(1, response.Content);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
return setpointKey;
|
||||||
public async Task General_test_success()
|
|
||||||
{
|
|
||||||
//save
|
|
||||||
var setpointKey = Guid.NewGuid();
|
|
||||||
var setpointValue = new TestObject()
|
|
||||||
{
|
|
||||||
value1 = "1",
|
|
||||||
value2 = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
var saveResponse = await client.Save(setpointKey, setpointValue);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, saveResponse.StatusCode);
|
|
||||||
Assert.Equal(1, saveResponse.Content);
|
|
||||||
|
|
||||||
//current
|
|
||||||
var currentResponse = await client.GetCurrent([setpointKey]);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, currentResponse.StatusCode);
|
|
||||||
|
|
||||||
var currentContent = currentResponse.Content;
|
|
||||||
Assert.NotNull(currentContent);
|
|
||||||
Assert.NotEmpty(currentContent);
|
|
||||||
|
|
||||||
var currentContentValue = currentContent.FirstOrDefault()?.Value?.ToString();
|
|
||||||
Assert.NotNull(currentContentValue);
|
|
||||||
Assert.NotEmpty(currentContentValue);
|
|
||||||
|
|
||||||
var testObjectValue = JsonSerializer.Deserialize<TestObject>(currentContentValue);
|
|
||||||
Assert.NotNull(testObjectValue);
|
|
||||||
Assert.Equal(setpointValue.value1, testObjectValue.value1);
|
|
||||||
Assert.Equal(setpointValue.value2, testObjectValue.value2);
|
|
||||||
|
|
||||||
//history
|
|
||||||
var historyMoment = DateTimeOffset.Now.ToUniversalTime();
|
|
||||||
var historyResponse = await client.GetHistory([setpointKey], historyMoment);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, historyResponse.StatusCode);
|
|
||||||
Assert.NotNull(historyResponse.Content);
|
|
||||||
Assert.NotEmpty(historyResponse.Content);
|
|
||||||
|
|
||||||
//log
|
|
||||||
var logResponse = await client.GetLog([setpointKey]);
|
|
||||||
Assert.Equal(HttpStatusCode.OK, logResponse.StatusCode);
|
|
||||||
Assert.NotNull(logResponse.Content);
|
|
||||||
Assert.NotEmpty(logResponse.Content);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,28 @@
|
|||||||
namespace Persistence.Repository.Data
|
namespace Persistence.Repository.Data
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
on.nemtina
commented
Решили, что для всех protected и public классов (а также для интерфейсов) нужно писать комментарии Решили, что для всех protected и public классов (а также для интерфейсов) нужно писать комментарии
|
|||||||
|
/// Модель для работы с уставкой
|
||||||
|
/// </summary>
|
||||||
public class SetpointDto
|
public class SetpointDto
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Идентификатор уставки
|
||||||
|
/// </summary>
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Значение уставки
|
||||||
|
/// </summary>
|
||||||
public required object Value { get; set; }
|
public required object Value { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дата сохранения уставки
|
||||||
|
/// </summary>
|
||||||
public DateTimeOffset Edit { get; set; }
|
public DateTimeOffset Edit { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ключ пользователя
|
||||||
|
/// </summary>
|
||||||
public int IdUser { get; set; }
|
public int IdUser { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,9 +31,13 @@ namespace Persistence.Repository.Repositories
|
|||||||
{
|
{
|
||||||
var query = GetQueryReadOnly();
|
var query = GetQueryReadOnly();
|
||||||
var entities = await query
|
var entities = await query
|
||||||
.Where(e => setpointKeys.Contains(e.Key) && e.Created.Date == historyMoment.Date)
|
.Where(e => setpointKeys.Contains(e.Key))
|
||||||
on.nemtina
commented
Здесь не строгое равенство дат. Здесь не строгое равенство дат.
Нужно отсортировать уставки по возрастанию дат.
Взять те, у которых Created меньше historyMoment
Из отсортированного и отфильтрованного списка взять последнюю
|
|||||||
.ToArrayAsync(token);
|
.ToArrayAsync(token);
|
||||||
var dtos = entities.Select(e => e.Adapt<SetpointValueDto>());
|
var filteredEntities = entities
|
||||||
|
.GroupBy(e => e.Key)
|
||||||
|
.Select(e => e.Where(e => e.Created <= historyMoment).Last());
|
||||||
on.nemtina
commented
Здесь нужно еще просортировать, чтобы гарантированно взять нужную близлежайшую к historyMoment уставку Здесь нужно еще просортировать, чтобы гарантированно взять нужную близлежайшую к historyMoment уставку
|
|||||||
|
var dtos = filteredEntities
|
||||||
|
.Select(e => e.Adapt<SetpointValueDto>());
|
||||||
|
|
||||||
return dtos;
|
return dtos;
|
||||||
}
|
}
|
||||||
@ -46,35 +50,23 @@ namespace Persistence.Repository.Repositories
|
|||||||
.ToArrayAsync(token);
|
.ToArrayAsync(token);
|
||||||
var dtos = entities
|
var dtos = entities
|
||||||
.GroupBy(e => e.Key)
|
.GroupBy(e => e.Key)
|
||||||
.Select(e => new KeyValuePair<Guid, IEnumerable<SetpointLogDto>>(
|
.ToDictionary(e => e.Key, v => v.Select(z => z.Adapt<SetpointLogDto>()));
|
||||||
e.Key,
|
|
||||||
e.Select(s => s.Adapt<SetpointLogDto>())
|
|
||||||
)).ToDictionary();
|
|
||||||
|
|
||||||
return dtos;
|
return dtos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> Save(Guid setpointKey, object newValue, int idUser, CancellationToken token)
|
public async Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token)
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var entity = new Setpoint()
|
var entity = new Setpoint()
|
||||||
{
|
{
|
||||||
Key = setpointKey,
|
Key = setpointKey,
|
||||||
Value = newValue,
|
Value = newValue,
|
||||||
IdUser = idUser,
|
IdUser = idUser,
|
||||||
Created = DateTimeOffset.Now.ToUniversalTime()
|
Created = DateTimeOffset.UtcNow
|
||||||
};
|
};
|
||||||
on.nemtina
commented
Можно так: Created = DateTimeOffset.UtcNow Можно так: Created = DateTimeOffset.UtcNow
|
|||||||
|
|
||||||
await db.Set<Setpoint>().AddAsync(entity, token);
|
await db.Set<Setpoint>().AddAsync(entity, token);
|
||||||
var result = await db.SaveChangesAsync(token);
|
await db.SaveChangesAsync(token);
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,5 +42,5 @@ public interface ISetpointRepository
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// to do
|
/// to do
|
||||||
/// id User учесть в соответствующем методе репозитория
|
/// id User учесть в соответствующем методе репозитория
|
||||||
Task<int> Save(Guid setpointKey, object newValue, int idUser, CancellationToken token);
|
Task Save(Guid setpointKey, object newValue, int idUser, CancellationToken token);
|
||||||
}
|
}
|
||||||
|
Здесь метод [HttpGet]