food-market/tests/food-market.IntegrationTests/TelegramOwnerSummaryTests.cs
nns 3088237ea7
Some checks are pending
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
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
feat(telegram): OwnerDailySummaryJob + bot binding (P2-14)
Backend:
- Organization.OwnerTelegramChatId (long?) — миграция Phase9a.
- TelegramOptions / TelegramBotClient (Telegram Bot API sendMessage).
  Disabled-mode когда токен пустой (Dev/CI). HTML parse_mode.
- OwnerDailySummaryJob.RunAsync — пробегает по org с привязанным chatId,
  рендерит сводку (выручка вчера, чеков, средний чек, топ-3 по выручке,
  low-stock 5 строк) и шлёт. Best-effort на каждой org.
  RenderSummaryAsync — publicный для тестов.
- HangfireJobsConfigurator: cron "0 6 * * *" UTC = 09:00 МСК.
- TelegramBindingController: GET /status (botEnabled, username, chatId,
  deepLink), PUT /bind (тестовое сообщение → проверка chatId → save),
  DELETE (unbind).

Конфиг:
- Telegram:BotToken — env Telegram__BotToken.
- Telegram:BotUsername — для deep-link.

UI:
- OrganizationSettings.TelegramSection: показывает статус (bot enabled?
  bound?), deep-link к боту, пошаговая инструкция (start → userinfobot →
  ввести chat_id → проверка). Toast на привязку/отвязку через
  meta.successMessage.

Тесты:
- TelegramOwnerSummaryTests: рендер содержит org_name, метрики, HTML. ✓ 1/1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 19:50:33 +05:00

51 lines
2.6 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.Http.Json;
using FluentAssertions;
using foodmarket.IntegrationTests.Support;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace foodmarket.IntegrationTests;
/// <summary>Тест рендеринга ежедневной сводки OwnerDailySummaryJob.
/// Сравниваем text-output с ожидаемым: заголовок org-name, цифры,
/// топ-3 + low-stock. Не отправляем реальное сообщение — тестируем
/// чистую функцию RenderSummaryAsync (она publicна именно ради тестов).</summary>
[Collection(ApiCollection.Name)]
public class TelegramOwnerSummaryTests
{
private readonly ApiFactory _factory;
public TelegramOwnerSummaryTests(ApiFactory factory) => _factory = factory;
[Fact]
public async Task Render_summary_contains_org_name_and_metrics()
{
var actor = new ApiActor(_factory.CreateClient());
var email = $"tg-{Guid.NewGuid():N}@example.kz";
(await actor.SignupAsync(email, "Passw0rd!", "Telegram Test Org")).EnsureSuccessStatusCode();
var token = await actor.TokenAsync(email, "Passw0rd!");
actor.UseToken(token);
var meDoc = await actor.GetJsonAsync("/api/me");
var orgId = Guid.Parse(meDoc.GetProperty("orgId").GetString()!);
// Засеем демо-данные чтобы было что показать (50 товаров + 30 продаж за
// последние 30 дней + пара low-stock).
(await actor.Http.PostAsync("/api/admin/seed-demo", null)).EnsureSuccessStatusCode();
// Вытащим джоб напрямую из DI и сгенерируем текст.
using var scope = _factory.Services.CreateScope();
var job = scope.ServiceProvider.GetRequiredService<foodmarket.Api.Background.OwnerDailySummaryJob>();
var text = await job.RenderSummaryAsync(orgId, "Telegram Test Org", CancellationToken.None);
// Базовые поля
text.Should().Contain("Food Market");
text.Should().Contain("Telegram Test Org");
text.Should().Contain("Выручка");
text.Should().Contain("Чеков");
// HTML-форматирование parse_mode=HTML
text.Should().Contain("<b>");
// На свежей орге за «вчера» может ничего не быть (demo сеется на даты
// сегодня-30..сегодня). В разном таймзоне может попасть, может нет —
// главное чтобы render не падал и базовая структура была.
}
}