Setpoint API #1

Merged
on.nemtina merged 10 commits from Setpoint into master 2024-11-25 10:33:36 +05:00
8 changed files with 116 additions and 113 deletions
Showing only changes of commit 9e55d6791c - Show all commits

View File

@ -15,36 +15,37 @@ namespace Persistence.API.Controllers
this.setpointRepository = setpointRepository; this.setpointRepository = setpointRepository;
} }
[HttpPost("current")] [HttpGet("current")]

Здесь метод [HttpGet]

Здесь метод [HttpGet]
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")]

Здесь метод [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")]

Здесь метод [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]

Здесь можно просто [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

Добавить todo, что необходимо решить вопрос с получением пользователя и авторизацией

Добавить todo, что необходимо решить вопрос с получением пользователя и авторизацией
await setpointRepository.Save(setpointKey, newValue, 0, token);
return Ok(result); return Ok();
} }
} }
} }

View File

@ -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; }
Review

Должно быть "Дата создания уставки"

Должно быть "Дата создания уставки"
[Comment("Id автора последнего изменения")] [Comment("Id автора последнего изменения")]

View File

@ -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; }
}
}

View File

@ -3,18 +3,23 @@ using Refit;
namespace Persistence.IntegrationTests.Clients namespace Persistence.IntegrationTests.Clients
{ {
/// <summary>
/// Интерфейс для тестирования API, предназначенного для работы с уставками
/// </summary>

Заголовки методов запроса должны соответствовать тем, что в контроллере (там часть нужно поменять с 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);
} }
} }

View File

@ -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);
} }

Эту строчку лучше убрать, а в интерфейсе ISetpointClient прописать так:

private const string BaseRoute = "/api/setpoint";

 [Post($"{BaseRoute}/current")]
	Task<IApiResponse<IEnumerable<SetpointValueDto>>> GetCurrent(IEnumerable<Guid> setpointKeys);```
Эту строчку лучше убрать, а в интерфейсе 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);

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);
} }
} }
} }

View File

@ -1,10 +1,28 @@
namespace Persistence.Repository.Data namespace Persistence.Repository.Data
{ {
/// <summary>

Решили, что для всех 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; }
} }
} }

View File

@ -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))

Здесь не строгое равенство дат.
Нужно отсортировать уставки по возрастанию дат.
Взять те, у которых Created меньше historyMoment
Из отсортированного и отфильтрованного списка взять последнюю

Здесь не строгое равенство дат. Нужно отсортировать уставки по возрастанию дат. Взять те, у которых 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());

Здесь нужно еще просортировать, чтобы гарантированно взять нужную близлежайшую к 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,
{ Value = newValue,
Key = setpointKey, IdUser = idUser,
Value = newValue, Created = DateTimeOffset.UtcNow
IdUser = idUser, };

Можно так: Created = DateTimeOffset.UtcNow

Можно так: Created = DateTimeOffset.UtcNow
Created = DateTimeOffset.Now.ToUniversalTime()
};
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;
}
} }
} }
} }

View File

@ -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);
} }