using System.Text.Json; using GmRelay.Shared.Telegram; using GmRelay.Web.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Primitives; namespace GmRelay.Bot.Tests.Web; public sealed class TelegramAuthServiceTests { [Fact] public void Verify_ShouldAcceptValidTelegramPayload() { const string botToken = "test-bot-token"; var result = TelegramAuthPayloadBuilder.BuildLoginWidget( botToken, 424242L, "Ada", "Lovelace", "ada"); var query = ParseQueryString(result.QueryString); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.Verify(query, out var telegramId, out var name); Assert.True(verified); Assert.Equal(424242L, telegramId); Assert.Equal("Ada Lovelace", name); } [Fact] public void Verify_ShouldRejectTamperedHash() { const string botToken = "test-bot-token"; var result = TelegramAuthPayloadBuilder.BuildLoginWidget(botToken, 424242L, "Ada"); var tamperedQuery = ParseQueryString(result.QueryString.Replace("hash=", "hash=00", StringComparison.Ordinal)); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.Verify(tamperedQuery, out _, out _); Assert.False(verified); } [Fact] public void Verify_ShouldRejectExpiredPayload() { const string botToken = "test-bot-token"; var expiredAuthDate = DateTimeOffset.UtcNow.AddDays(-2).ToUnixTimeSeconds(); var result = TelegramAuthPayloadBuilder.BuildLoginWidget( botToken, 424242L, "Ada", authDate: expiredAuthDate); var query = ParseQueryString(result.QueryString); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.Verify(query, out _, out _); Assert.False(verified); } [Fact] public void VerifyWebAppInitData_ShouldAcceptValidTelegramWebAppPayload() { const string botToken = "test-bot-token"; var result = TelegramAuthPayloadBuilder.BuildMiniAppInitData( botToken, 424242L, "Ada", "Lovelace", "ada", queryId: "AAHdF6IQAAAAAN0XohDhrOrc"); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.VerifyWebAppInitData(result.InitDataRaw, out var telegramId, out var name); Assert.True(verified); Assert.Equal(424242L, telegramId); Assert.Equal("Ada Lovelace", name); } [Fact] public void VerifyWebAppInitData_ShouldRejectTamperedHash() { const string botToken = "test-bot-token"; var result = TelegramAuthPayloadBuilder.BuildMiniAppInitData(botToken, 424242L, "Ada"); var tamperedInitData = result.InitDataRaw.Replace("hash=", "hash=00", StringComparison.Ordinal); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.VerifyWebAppInitData(tamperedInitData, out _, out _); Assert.False(verified); } [Fact] public void VerifyWebAppInitData_ShouldRejectExpiredPayload() { const string botToken = "test-bot-token"; var expiredAuthDate = DateTimeOffset.UtcNow.AddDays(-2).ToUnixTimeSeconds(); var result = TelegramAuthPayloadBuilder.BuildMiniAppInitData( botToken, 424242L, "Ada", authDate: expiredAuthDate); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.VerifyWebAppInitData(result.InitDataRaw, out _, out _); Assert.False(verified); } [Fact] public void VerifyLoginPayload_ShouldAcceptValidTelegramWidgetCallbackPayload() { const string botToken = "test-bot-token"; var authDate = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var result = TelegramAuthPayloadBuilder.BuildLoginWidget( botToken, 424242L, "Ada", "Lovelace", "ada", "https://t.me/i/userpic/320/ada.jpg", authDate); var payload = new TelegramLoginPayload( result.TelegramId, result.FirstName, result.LastName, result.Username, result.PhotoUrl, result.AuthDate, result.Hash); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.VerifyLoginPayload(payload, out var telegramId, out var name); Assert.True(verified); Assert.Equal(424242L, telegramId); Assert.Equal("Ada Lovelace", name); } [Fact] public void VerifyLoginPayload_ShouldRejectTamperedCallbackHash() { var payload = new TelegramLoginPayload( 424242, "Ada", null, null, null, DateTimeOffset.UtcNow.ToUnixTimeSeconds(), "00"); var service = new TelegramAuthService(CreateConfiguration("test-bot-token")); var verified = service.VerifyLoginPayload(payload, out _, out _); Assert.False(verified); } [Fact] public void VerifyLoginPayload_ShouldRejectExpiredCallbackPayload() { const string botToken = "test-bot-token"; var authDate = DateTimeOffset.UtcNow.AddDays(-2).ToUnixTimeSeconds(); var result = TelegramAuthPayloadBuilder.BuildLoginWidget( botToken, 424242L, "Ada", authDate: authDate); var payload = new TelegramLoginPayload( result.TelegramId, result.FirstName, result.LastName, result.Username, result.PhotoUrl, result.AuthDate, result.Hash); var service = new TelegramAuthService(CreateConfiguration(botToken)); var verified = service.VerifyLoginPayload(payload, out _, out _); Assert.False(verified); } [Fact] public void VerifyLoginPayload_ShouldRejectMissingRequiredCallbackFields() { var payload = new TelegramLoginPayload( 0, "", null, null, null, DateTimeOffset.UtcNow.ToUnixTimeSeconds(), ""); var service = new TelegramAuthService(CreateConfiguration("test-bot-token")); var verified = service.VerifyLoginPayload(payload, out _, out _); Assert.False(verified); } [Fact] public void TelegramLoginPayload_ShouldDeserializeTelegramWidgetSnakeCaseJson() { var payload = JsonSerializer.Deserialize( """ { "id": 424242, "first_name": "Ada", "last_name": "Lovelace", "username": "ada", "photo_url": "https://t.me/i/userpic/320/ada.jpg", "auth_date": 1714300000, "hash": "abcdef" } """); Assert.NotNull(payload); Assert.Equal(424242L, payload.Id); Assert.Equal("Ada", payload.FirstName); Assert.Equal("Lovelace", payload.LastName); Assert.Equal("ada", payload.Username); Assert.Equal("https://t.me/i/userpic/320/ada.jpg", payload.PhotoUrl); Assert.Equal(1714300000L, payload.AuthDate); Assert.Equal("abcdef", payload.Hash); } private static IConfiguration CreateConfiguration(string botToken) => new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Telegram:BotToken"] = botToken }) .Build(); private static QueryCollection ParseQueryString(string queryString) { var parsed = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(queryString); return new QueryCollection(parsed.ToDictionary( pair => pair.Key, pair => pair.Value)); } }