Files
gorbenko.pavel a46782920b
Tests / backend-test (pull_request) Successful in 2m10s
Tests / frontend-test (pull_request) Successful in 3m53s
feat: эндпоинт заявок по основанию (СТ/потребность) с проверкой прав на открытие
GET /api/basis-requests?requestTypeId=&basisKey= читает заявки от имени приложения
по всем партициям, а по каждой проверяет, может ли вызывающий её открыть.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:58:42 +05:00

92 lines
4.4 KiB
C#

using System.Text.Json;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Shouldly;
using Tnsu.ApprovalRoute.Application.BasisRequests;
using Tnsu.ApprovalRoute.Application.ListPartitioning;
using Tnsu.ApprovalRoute.Application.RequestTypes;
using Tnsu.ApprovalRoute.Infrastructure.Persistence;
using Tnsu.ApprovalRoute.Infrastructure.SharePoint;
using Tnsu.ApprovalRoute.IntegrationTests.Infrastructure;
namespace Tnsu.ApprovalRoute.IntegrationTests.ApplicationServices;
/// <summary>
/// Заявки по одному основанию: видны все (чтение от имени приложения), а
/// CanOpen — только там, где у вызывающего есть права на элемент.
/// </summary>
public sealed class BasisRequestsServiceTests : IClassFixture<PostgresDatabaseFixture>
{
private const string SiteUrl = "https://tnsukz.sharepoint.com/sites/requests";
private const string CallerEmail = "initiator@tnsu.kz";
private const string BasisKey = "need:8028c7ac-ac23-8dd8-11f0-946facc42464";
private readonly ServiceProvider _services;
public BasisRequestsServiceTests(PostgresDatabaseFixture fixture)
{
_services = TestServiceProviderFactory.Build(fixture.ConnectionString, CallerEmail);
}
private static JsonElement Json(object value) => JsonSerializer.SerializeToElement(value);
private async Task<(BasisRequestsService Service, Mock<ISharePointClient> SharePoint, int TypeId)> BuildAsync(
string name, string listUrl, IReadOnlyList<object> items, IReadOnlySet<int> openableIds)
{
var type = await _services.GetRequiredService<RequestTypeService>().CreateAsync(
new CreateRequestTypeRequest($"Оплата {name}", $"/оплата-{name}", listUrl, SiteUrl, "General"),
CancellationToken.None);
var sharePoint = new Mock<ISharePointClient>();
sharePoint
.Setup(x => x.GetItemsByListUrlAsync(SiteUrl, listUrl, It.IsAny<string>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(items.Select(Json).ToList());
sharePoint
.Setup(x => x.CanUserViewItemAsync(SiteUrl, listUrl, It.IsAny<int>(), CallerEmail, It.IsAny<CancellationToken>()))
.ReturnsAsync((string _, string _, int id, string _, CancellationToken _) => openableIds.Contains(id));
var service = new BasisRequestsService(
_services.GetRequiredService<ApprovalRouteDbContext>(),
sharePoint.Object,
_services.GetRequiredService<ListPartitionRegistry>(),
new TestCurrentUser(CallerEmail));
return (service, sharePoint, type.Value.Id);
}
[Fact]
public async Task Returns_all_requests_and_marks_only_permitted_as_openable()
{
const string list = "/sites/requests/Lists/Оплата-all";
var (service, sharePoint, typeId) = await BuildAsync("all", list,
[
// Number-колонка приходит дробной («50000.0») — тест держит её такой же.
new { Id = 1, RequestUniqueCode = "24-2610-1", RequestStatus = "Согласовано", PayAmount = 50000.0 },
new { Id = 2, RequestUniqueCode = "24-2610-2", RequestStatus = "На согласовании", PayAmount = 35000.5 }
], new HashSet<int> { 2 });
var result = await service.GetByBasisAsync(typeId, BasisKey, CancellationToken.None);
result.IsSuccess.ShouldBeTrue();
result.Value.Count.ShouldBe(2);
result.Value.Single(x => x.Id == 1).ShouldBe(new BasisRequestDto(1, list, "24-2610-1", "Согласовано", 50000, false));
result.Value.Single(x => x.Id == 2).ShouldBe(new BasisRequestDto(2, list, "24-2610-2", "На согласовании", 35000.5, true));
// Фильтр по основанию уходит в SharePoint, а не применяется в памяти.
sharePoint.Verify(x => x.GetItemsByListUrlAsync(
SiteUrl, list,
It.Is<string>(q => q.Contains("BasisKey eq 'need%3A8028c7ac-ac23-8dd8-11f0-946facc42464'")),
It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task Empty_basis_key_is_a_validation_error()
{
var (service, _, typeId) = await BuildAsync("empty", "/sites/requests/Lists/Оплата-empty", [], new HashSet<int>());
var result = await service.GetByBasisAsync(typeId, " ", CancellationToken.None);
result.IsFailed.ShouldBeTrue();
}
}