using Deal.Grpc.Telegram; using Deal.Telegram.Telegram; using Deal.Telegram.Tests.Telegram; using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.DependencyInjection; namespace Deal.Telegram.Tests.Grpc; /// /// RPC-тесты подключения аккаунта поверх реального gRPC-хоста с фейковой фабрикой клиентов /// public sealed class TelegramSessionRpcTests { private const string TenantId = TelegramTestHost.DefaultTenantId; private const int ApiId = 12345; private const string ApiHash = "0123456789abcdef0123456789abcdef"; private const int PollTimeoutMilliseconds = 5000; /// /// StartPhone без ключей приложения → INVALID_ARGUMENT «Сначала сохраните…». /// [Fact] public async Task StartPhone_WithoutApiKeys_InvalidArgument() { await RunScenarioAsync( async channel => { var client = new TelegramService.TelegramServiceClient(channel); RpcException exception = await Assert.ThrowsAsync( () => client.StartPhoneAsync( new StartPhoneRequest { Phone = "+79990001122", ApiId = 0, ApiHash = string.Empty }, Options()).ResponseAsync); Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode); Assert.Equal(Sessions.SessionErrorMessages.NoApiKeys, exception.Status.Detail); }); } /// /// Отсутствующий tenant-id в metadata → UNAUTHENTICATED. /// [Fact] public async Task GetStatus_WithoutTenantId_Unauthenticated() { await RunScenarioAsync( async channel => { var client = new TelegramService.TelegramServiceClient(channel); RpcException exception = await Assert.ThrowsAsync( () => client.GetStatusAsync(new GetStatusRequest(), TokenOnlyOptions()).ResponseAsync); Assert.Equal(StatusCode.Unauthenticated, exception.StatusCode); Assert.Equal(Sessions.SessionErrorMessages.TenantIdMissing, exception.Status.Detail); }); } /// /// GetStatus без сессии тенанта → FAILED_PRECONDITION «Telegram не подключён». /// [Fact] public async Task GetStatus_NoSession_FailedPrecondition() { await RunScenarioAsync( async channel => { var client = new TelegramService.TelegramServiceClient(channel); RpcException exception = await Assert.ThrowsAsync( () => client.GetStatusAsync(new GetStatusRequest(), Options()).ResponseAsync); Assert.Equal(StatusCode.FailedPrecondition, exception.StatusCode); Assert.Equal(Sessions.SessionErrorMessages.NotConnected, exception.Status.Detail); }); } /// /// StartQr → фаза "qr" с URL; GetStatus показывает фазу/URL. /// [Fact] public async Task StartQr_ReturnsQrPhase_AndStatusShowsQr() { await RunScenarioAsync( async channel => { var client = new TelegramService.TelegramServiceClient(channel); StartQrReply qr = await client.StartQrAsync(new StartQrRequest { ApiId = ApiId, ApiHash = ApiHash }, Options()); Assert.Equal("qr", qr.Phase); Assert.Equal(FakeSessionClient.DefaultQrUrl, qr.QrUrl); GetStatusReply status = await client.GetStatusAsync(new GetStatusRequest(), Options()); Assert.Equal("qr", status.Phase); Assert.True(status.HasQrUrl); Assert.Equal(FakeSessionClient.DefaultQrUrl, status.QrUrl); }); } /// /// QR-сканирование /// [Fact] public async Task StartQr_ScanCompleted_ReadyWithAccount() { await RunScenarioAsync( async channel => { var client = new TelegramService.TelegramServiceClient(channel); await client.StartQrAsync(new StartQrRequest { ApiId = ApiId, ApiHash = ApiHash }, Options()); FakeSessionClient fake = SingleCreatedClient(); fake.CompleteQrScan(); GetStatusReply status = await WaitForPhaseAsync(client, "ready"); Assert.True(status.Connected); Assert.Equal("@fake_user", status.Account); Assert.False(status.HasQrUrl); }); } /// /// SendPassword вне фазы "password" /// [Fact] public async Task SendPassword_WhileQrPhase_FailedPrecondition() { await RunScenarioAsync( async channel => { var client = new TelegramService.TelegramServiceClient(channel); await client.StartQrAsync(new StartQrRequest { ApiId = ApiId, ApiHash = ApiHash }, Options()); RpcException exception = await Assert.ThrowsAsync( () => client.SendPasswordAsync(new SendPasswordRequest { Password = "x" }, Options()).ResponseAsync); Assert.Equal(StatusCode.FailedPrecondition, exception.StatusCode); Assert.Equal(Sessions.SessionErrorMessages.PasswordNotRequested, exception.Status.Detail); }); } /// /// Logout: ok=true, сессия удалена — GetStatus снова «Telegram не подключён». /// [Fact] public async Task Logout_Ok_ThenGetStatusNotConnected() { await RunScenarioAsync( async channel => { var client = new TelegramService.TelegramServiceClient(channel); await client.StartQrAsync(new StartQrRequest { ApiId = ApiId, ApiHash = ApiHash }, Options()); SingleCreatedClient().CompleteQrScan(); await WaitForPhaseAsync(client, "ready"); LogoutReply logout = await client.LogoutAsync(new LogoutRequest(), Options()); Assert.True(logout.Ok); RpcException exception = await Assert.ThrowsAsync( () => client.GetStatusAsync(new GetStatusRequest(), Options()).ResponseAsync); Assert.Equal(StatusCode.FailedPrecondition, exception.StatusCode); }); } // Прогоняет сценарий RPC на хосте с фейковой фабрикой клиентов. // scenario: Сценарий с gRPC-каналом. private static async Task RunScenarioAsync(Func scenario) { var factory = new FakeClientFactory(); CurrentFactory = factory; try { await TelegramTestHost.RunAsync( TelegramTestHost.DefaultToken, scenario, configureServices: services => services.AddSingleton(factory)); } finally { CurrentFactory = null; } } // Хранилище активной фабрики сценария (для управления «сканированием»). private static FakeClientFactory? CurrentFactory { get; set; } // Единственный созданный клиент сценария. private static FakeSessionClient SingleCreatedClient() { List created = CurrentFactory?.CreatedClients ?? new List(); Assert.Single(created); return created[0]; } // Опции вызова с токеном и tenant-id. private static CallOptions Options() => new(TelegramTestHost.CallMetadata(TelegramTestHost.DefaultToken, TenantId), deadline: Deadline()); // Опции вызова только с токеном (без tenant-id). private static CallOptions TokenOnlyOptions() => new(TelegramTestHost.CallMetadata(TelegramTestHost.DefaultToken), deadline: Deadline()); // Поллинг GetStatus до ожидаемой фазы. // client: Клиент TelegramService. // phase: Ожидаемая фаза ("ready"). private static async Task WaitForPhaseAsync(TelegramService.TelegramServiceClient client, string phase) { var deadline = DateTime.UtcNow.AddMilliseconds(PollTimeoutMilliseconds); while (DateTime.UtcNow < deadline) { GetStatusReply status = await client.GetStatusAsync(new GetStatusRequest(), Options()); if (status.Phase == phase) { return status; } await Task.Delay(25); } return await client.GetStatusAsync(new GetStatusRequest(), Options()); } // Deadline вызовов теста. private static DateTime Deadline() => DateTime.UtcNow.AddSeconds(TelegramTestHost.RpcDeadlineSeconds); }