using Deal.Api.Telegram; using Deal.Contracts.Integrations.Models; 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, FakeTelegramStore store, FakeSettingsStore settings, FakeTelegramGateway gateway, _, FakeGlobalSettingsStore 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, FakeTelegramStore store, FakeSettingsStore settings, FakeTelegramGateway gateway, _, FakeGlobalSettingsStore 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, _, _, FakeTelegramGateway 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 + FakeSecretCipher). private static (TgStatusService Service, FakeTelegramStore Store, FakeSettingsStore Settings, FakeTelegramGateway Gateway, FakeSecretCipher Cipher, FakeGlobalSettingsStore GlobalSettings) Create() { var store = new FakeTelegramStore(); var settings = new FakeSettingsStore(); var gateway = new FakeTelegramGateway(); var cipher = new FakeSecretCipher(); var globalSettings = new FakeGlobalSettingsStore(); var keys = new TelegramKeysService(globalSettings, cipher); var dialogs = new DialogsService(store, settings, gateway, NullLogger.Instance); var service = new TgStatusService(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( FakeGlobalSettingsStore settings, string apiId, string apiHash) { string encrypted = new FakeSecretCipher().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); }