food-market/tests/food-market.IntegrationTests/DataQualityTests.cs
nns 261a720b52
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
feat(data-quality): фоновая «Проверка данных» — бэкенд (п.1,3,4)
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>
2026-10-09 08:11:57 +05:00

71 lines
4.2 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, "единственная пара помечена «разные товары»");
}
}