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);
}