Удалены <remarks>, <summary> сжаты до короткой фразы, вырезаны ссылки на Task/Ruling/этап/python/прототип; //-комментарии со ссылками на процесс удалены; то же в .proto. Правила обновлены в docs/spec/Код-стайл-Дейл.md. Строк комментариев 27210 -> ~19100.
227 lines
9.3 KiB
C#
227 lines
9.3 KiB
C#
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);
|
||
}
|