Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker API / Build + push API (push) Waiting to run
Docker API / Deploy API on stage (push) Blocked by required conditions
Hangfire DataQualityJob (ночью 02:20 UTC) считает по орг. счётчики проблем: без группы
(корень/«Без группы»), без цены продажи, без штрихкода (кроме услуг), дубль штрихкода,
возможный дубль имя+единица; товары в минусе — ссылкой на центр (не дублируем). Результаты
в data_quality_results (Phase43), живые детальные списки — по запросу.
API /api/data-quality: сводка, {check}/products, recompute, dismiss («это разные товары»),
bulk set-group/set-price/generate-barcodes, PUT settings (отключение проверок). unknown-cost
отложен до FIFO; PLU убран (поправка владельца). Integration-тест: счётчики/список/bulk/dismiss.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
71 lines
4.2 KiB
C#
71 lines
4.2 KiB
C#
using System.Net;
|
||
using System.Net.Http.Json;
|
||
using System.Text.Json;
|
||
using FluentAssertions;
|
||
using foodmarket.IntegrationTests.Support;
|
||
using Xunit;
|
||
|
||
namespace foodmarket.IntegrationTests;
|
||
|
||
/// <summary>Фоновая «Проверка данных»: счётчики (без группы/цены), детальный список,
|
||
/// массовое исправление, «это разные товары».</summary>
|
||
[Collection(ApiCollection.Name)]
|
||
public class DataQualityTests
|
||
{
|
||
private readonly ApiFactory _factory;
|
||
public DataQualityTests(ApiFactory factory) => _factory = factory;
|
||
|
||
private async Task<string> QuickProductAsync(ApiActor api, string name, decimal? price = null)
|
||
{
|
||
var r = await api.Http.PostAsJsonAsync("/api/catalog/products/quick", new { name, salePrice = price });
|
||
r.StatusCode.Should().Be(HttpStatusCode.Created, await r.Content.ReadAsStringAsync());
|
||
return (await r.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("id").GetString()!;
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Counts_detail_bulkfix_and_dismiss_work()
|
||
{
|
||
var api = new ApiActor(_factory.CreateClient());
|
||
await api.SignupAndLoginAsync($"dq-{Guid.NewGuid():N}");
|
||
|
||
// Товары с проблемами: без цены (и в корне → без группы), + дубль по имени.
|
||
var p1 = await QuickProductAsync(api, "Молоко разливное"); // нет цены, корень
|
||
await QuickProductAsync(api, "Хлеб белый", 180m); // есть цена, но корень
|
||
await QuickProductAsync(api, "Дубль-товар");
|
||
await QuickProductAsync(api, "Дубль-товар"); // пара → dup-name-unit
|
||
|
||
// Пересчёт → сводка со счётчиками.
|
||
var sumResp = await api.Http.PostAsJsonAsync("/api/data-quality/recompute", new { });
|
||
sumResp.EnsureSuccessStatusCode();
|
||
var sum = await sumResp.Content.ReadFromJsonAsync<JsonElement>();
|
||
var checks = sum.GetProperty("checks").EnumerateArray()
|
||
.ToDictionary(x => x.GetProperty("checkType").GetString()!, x => x.GetProperty("count").GetInt32());
|
||
checks["no-group"].Should().BeGreaterThanOrEqualTo(4, "все 4 товара в корневой группе");
|
||
checks["no-sale-price"].Should().BeGreaterThanOrEqualTo(3, "три без цены");
|
||
checks["dup-name-unit"].Should().BeGreaterThanOrEqualTo(1, "пара «Дубль-товар»");
|
||
|
||
// Детальный список без цены содержит p1.
|
||
var noPrice = await api.ListAsync("/api/data-quality/no-sale-price/products");
|
||
noPrice.Should().Contain(x => x.GetProperty("id").GetString() == p1);
|
||
|
||
// Массовое: задать цену p1 → в списке «без цены» его больше нет.
|
||
var setPrice = await api.Http.PostAsJsonAsync("/api/data-quality/bulk/set-price",
|
||
new { productIds = new[] { p1 }, priceTypeId = (string?)null, amount = 300m });
|
||
setPrice.EnsureSuccessStatusCode();
|
||
var noPrice2 = await api.ListAsync("/api/data-quality/no-sale-price/products");
|
||
noPrice2.Should().NotContain(x => x.GetProperty("id").GetString() == p1);
|
||
|
||
// «Это разные товары» для дубля → счётчик дублей падает.
|
||
var dupList = await api.ListAsync("/api/data-quality/dup-name-unit/products");
|
||
var dupId = dupList.First().GetProperty("id").GetString();
|
||
(await api.Http.PostAsJsonAsync("/api/data-quality/dismiss",
|
||
new { checkType = "dup-name-unit", productId = dupId, barcode = (string?)null })).EnsureSuccessStatusCode();
|
||
var afterResp = await api.Http.PostAsJsonAsync("/api/data-quality/recompute", new { });
|
||
afterResp.EnsureSuccessStatusCode();
|
||
var afterDismiss = await afterResp.Content.ReadFromJsonAsync<JsonElement>();
|
||
var dupAfter = afterDismiss.GetProperty("checks").EnumerateArray()
|
||
.First(x => x.GetProperty("checkType").GetString() == "dup-name-unit").GetProperty("count").GetInt32();
|
||
dupAfter.Should().Be(0, "единственная пара помечена «разные товары»");
|
||
}
|
||
}
|