Files
Deal/src/telegram-service/Deal.Telegram.Tests/Grpc/TelegramSessionRpcTests.cs
T
Rustam Khalimov b053d58335 Почистить комментарии от упоминаний процесса
Удалены <remarks>, <summary> сжаты до короткой фразы, вырезаны
ссылки на Task/Ruling/этап/python/прототип; //-комментарии со ссылками
на процесс удалены; то же в .proto. Правила обновлены в
docs/spec/Код-стайл-Дейл.md. Строк комментариев 27210 -> ~19100.
2026-09-11 13:39:39 +03:00

227 lines
9.3 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 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;
/// <summary>
/// RPC-тесты подключения аккаунта поверх реального gRPC-хоста с фейковой фабрикой клиентов
/// </summary>
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;
/// <summary>
/// StartPhone без ключей приложения → INVALID_ARGUMENT «Сначала сохраните…».
/// </summary>
[Fact]
public async Task StartPhone_WithoutApiKeys_InvalidArgument()
{
await RunScenarioAsync(
async channel =>
{
var client = new TelegramService.TelegramServiceClient(channel);
RpcException exception = await Assert.ThrowsAsync<RpcException>(
() => 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);
});
}
/// <summary>
/// Отсутствующий tenant-id в metadata → UNAUTHENTICATED.
/// </summary>
[Fact]
public async Task GetStatus_WithoutTenantId_Unauthenticated()
{
await RunScenarioAsync(
async channel =>
{
var client = new TelegramService.TelegramServiceClient(channel);
RpcException exception = await Assert.ThrowsAsync<RpcException>(
() => client.GetStatusAsync(new GetStatusRequest(), TokenOnlyOptions()).ResponseAsync);
Assert.Equal(StatusCode.Unauthenticated, exception.StatusCode);
Assert.Equal(Sessions.SessionErrorMessages.TenantIdMissing, exception.Status.Detail);
});
}
/// <summary>
/// GetStatus без сессии тенанта → FAILED_PRECONDITION «Telegram не подключён».
/// </summary>
[Fact]
public async Task GetStatus_NoSession_FailedPrecondition()
{
await RunScenarioAsync(
async channel =>
{
var client = new TelegramService.TelegramServiceClient(channel);
RpcException exception = await Assert.ThrowsAsync<RpcException>(
() => client.GetStatusAsync(new GetStatusRequest(), Options()).ResponseAsync);
Assert.Equal(StatusCode.FailedPrecondition, exception.StatusCode);
Assert.Equal(Sessions.SessionErrorMessages.NotConnected, exception.Status.Detail);
});
}
/// <summary>
/// StartQr → фаза "qr" с URL; GetStatus показывает фазу/URL.
/// </summary>
[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);
});
}
/// <summary>
/// QR-сканирование
/// </summary>
[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);
});
}
/// <summary>
/// SendPassword вне фазы "password"
/// </summary>
[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<RpcException>(
() => client.SendPasswordAsync(new SendPasswordRequest { Password = "x" }, Options()).ResponseAsync);
Assert.Equal(StatusCode.FailedPrecondition, exception.StatusCode);
Assert.Equal(Sessions.SessionErrorMessages.PasswordNotRequested, exception.Status.Detail);
});
}
/// <summary>
/// Logout: ok=true, сессия удалена — GetStatus снова «Telegram не подключён».
/// </summary>
[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<RpcException>(
() => client.GetStatusAsync(new GetStatusRequest(), Options()).ResponseAsync);
Assert.Equal(StatusCode.FailedPrecondition, exception.StatusCode);
});
}
// Прогоняет сценарий RPC на хосте с фейковой фабрикой клиентов.
// scenario: Сценарий с gRPC-каналом.
private static async Task RunScenarioAsync(Func<GrpcChannel, Task> scenario)
{
var factory = new FakeClientFactory();
CurrentFactory = factory;
try
{
await TelegramTestHost.RunAsync(
TelegramTestHost.DefaultToken,
scenario,
configureServices: services => services.AddSingleton<ITelegramClientFactory>(factory));
}
finally
{
CurrentFactory = null;
}
}
// Хранилище активной фабрики сценария (для управления «сканированием»).
private static FakeClientFactory? CurrentFactory { get; set; }
// Единственный созданный клиент сценария.
private static FakeSessionClient SingleCreatedClient()
{
List<FakeSessionClient> created = CurrentFactory?.CreatedClients ?? new List<FakeSessionClient>();
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<GetStatusReply> 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);
}