using Deal.Api.Telegram;
using Deal.Contracts.Integrations.Models;
using Deal.Modules.Settings.Application.Abstractions;
using Deal.Modules.Settings.Application.Models;
using Deal.Modules.Telegram.Application;
using Deal.Modules.Telegram.Application.Models;
using Deal.Tests.Unit.Contracts;
using Deal.Tests.Unit.Modules.Settings;
using Deal.Tests.Unit.Support;
using Grpc.Core;
using Microsoft.Extensions.Logging.Abstractions;
namespace Deal.Tests.Unit.Api;
///
/// Тесты сборки статуса вкладки Telegram — GET /api/tg/status.
///
public sealed class TgStatusServiceTests
{
///
/// Гейт по умолчанию
///
[Fact]
public async Task GetAsync_GatewayIdleAndEmptyTenant_ReturnsIdleForm()
{
(TgStatusService service, _, _, _, _, _) = Create();
TgStatusDto status = await service.GetAsync(CancellationToken.None);
Assert.Equal("idle", status.Phase);
Assert.False(status.Connected);
Assert.False(status.Listener);
Assert.Equal(string.Empty, status.Account);
Assert.Equal(0, status.Monitored);
Assert.False(status.KeysSet);
Assert.Null(status.Error);
Assert.Null(status.QrUrl);
}
///
/// Сервис недоступен
///
[Fact]
public async Task GetAsync_GatewayUnavailable_ReturnsIdleFormWithKvAccountCountAndKeys()
{
(TgStatusService service, TestTelegramStore store, FakeSettingsStore settings, TestTelegramGateway gateway, _, TestGlobalSettingsStore globalSettings) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true));
store.Seed(Dialog("d_2", "Группа", "group", Monitor: true));
store.Seed(Dialog("d_off", "Выключен", "channel", Monitor: false));
settings.Preload(SettingsKeys.TgAccount, "\"@user\"");
PreloadKeys(globalSettings, "123456", "abcdefghijklmnop");
gateway.StatusFailure = new RpcException(new Status(StatusCode.Unavailable, "Telegram не подключён"));
TgStatusDto status = await service.GetAsync(CancellationToken.None);
Assert.Equal("idle", status.Phase);
Assert.False(status.Connected);
Assert.False(status.Listener);
Assert.Equal("@user", status.Account);
Assert.Equal(2, status.Monitored);
Assert.True(status.KeysSet);
Assert.Null(status.Error);
Assert.Null(status.QrUrl);
}
///
/// Готовый аккаунт
///
[Fact]
public async Task GetAsync_ReadyGateway_ComposesLiveFieldsWithKvAccountMonitoredAndKeys()
{
(TgStatusService service, TestTelegramStore store, FakeSettingsStore settings, TestTelegramGateway gateway, _, TestGlobalSettingsStore globalSettings) = Create();
store.Seed(Dialog("d_1", "Канал", "channel", Monitor: true));
settings.Preload(SettingsKeys.TgAccount, "\"@realuser\"");
PreloadKeys(globalSettings, "123456", "abcdefghijklmnop");
gateway.Status = new TelegramAccountStatusDto(
Phase: "ready", Connected: true, Listener: true, Account: "gateway-account", Error: null, QrUrl: null);
TgStatusDto status = await service.GetAsync(CancellationToken.None);
Assert.Equal("ready", status.Phase);
Assert.True(status.Connected);
Assert.True(status.Listener);
Assert.Equal("@realuser", status.Account); // KV tgAccount перекрывает справочное поле гейта (Ruling 8)
Assert.Equal(1, status.Monitored);
Assert.True(status.KeysSet);
}
///
/// Фаза qr несёт qrUrl из гейта
///
[Fact]
public async Task GetAsync_QrPhase_ReturnsQrUrlFromGateway()
{
(TgStatusService service, _, _, TestTelegramGateway gateway, _, _) = Create();
gateway.Status = new TelegramAccountStatusDto(
Phase: "qr", Connected: false, Listener: false, Account: string.Empty, Error: null, QrUrl: "https://t.me/qr/test");
TgStatusDto status = await service.GetAsync(CancellationToken.None);
Assert.Equal("qr", status.Phase);
Assert.Equal("https://t.me/qr/test", status.QrUrl);
Assert.False(status.Connected);
}
// ─── Хелперы ────────────────────────────────────────────────────────────
// Собирает сервис на фейках (FakeTelegramGateway/Store/SettingsStore/GlobalSettingsStore + ISecretCipher).
private static (TgStatusService Service, TestTelegramStore Store, FakeSettingsStore Settings, TestTelegramGateway Gateway, ISecretCipher Cipher, TestGlobalSettingsStore GlobalSettings) Create()
{
var store = new TestTelegramStore();
var settings = new FakeSettingsStore();
var gateway = new TestTelegramGateway();
var cipher = TestCiphers.New();
var globalSettings = new TestGlobalSettingsStore();
var keys = new TelegramKeysService(globalSettings.Store, cipher);
var dialogs = new DialogsService(store.Store, settings, gateway.Gateway, NullLogger.Instance);
var service = new TgStatusService(gateway.Gateway, dialogs, settings, keys);
return (service, store, settings, gateway, cipher, globalSettings);
}
// Кладёт глобальную настройку telegramKeys (apiHash шифруется — enc: через шифр сценария).
// settings: Глобальное KV-хранилище сценария.
// apiId: apiId приложения (5..9 цифр).
// apiHash: Открытый api_hash (ядро шифрует при сохранении).
private static void PreloadKeys(
TestGlobalSettingsStore settings,
string apiId,
string apiHash)
{
string encrypted = TestCiphers.New().Encrypt(apiHash);
settings.Preload(GlobalSettingsKeys.TelegramKeys, $"{{\"apiId\":\"{apiId}\",\"apiHash\":\"{encrypted}\"}}");
}
// Строка каталога минимально (id/имя/kind + флаг мониторинга).
private static FakeTelegramDialogRow Dialog(
string id,
string name,
string kind,
bool Monitor)
=> new(id, name, Handle: string.Empty, kind, Hue: "#666", Monitor, LastText: string.Empty, LastAt: null, Backfilled: false, UpdatedAt: DateTimeOffset.UtcNow);
}