diff --git a/tests/GmRelay.Bot.Tests/Web/DiscordAuthServiceTests.cs b/tests/GmRelay.Bot.Tests/Web/DiscordAuthServiceTests.cs new file mode 100644 index 0000000..8e27711 --- /dev/null +++ b/tests/GmRelay.Bot.Tests/Web/DiscordAuthServiceTests.cs @@ -0,0 +1,139 @@ +using System.Net; +using System.Text.Json; +using GmRelay.Web; +using GmRelay.Web.Services; +using Microsoft.Extensions.Configuration; + +namespace GmRelay.Bot.Tests.Web; + +public class DiscordAuthServiceTests +{ + [Fact] + public void BuildAuthorizeUrl_GeneratesCorrectUrl() + { + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Discord:ClientId"] = "12345", + ["Discord:ClientSecret"] = "secret", + ["Discord:RedirectUri"] = "https://example.com/callback" + }) + .Build(); + + var service = new DiscordAuthService(new TestHttpClientFactory(), config); + var url = service.BuildAuthorizeUrl("state123"); + + Assert.Contains("client_id=12345", url); + Assert.Contains("response_type=code", url); + Assert.Contains("state=state123", url); + Assert.Contains("https%3A%2F%2Fexample.com%2Fcallback", url); + } + + [Fact] + public void BuildAuthorizeUrl_WithMissingConfig_ThrowsInvalidOperationException() + { + var config = new ConfigurationBuilder().Build(); + var service = new DiscordAuthService(new TestHttpClientFactory(), config); + + Assert.Throws(() => service.BuildAuthorizeUrl("state")); + } + + [Fact] + public async Task ExchangeCodeAsync_WithValidCode_ReturnsUser() + { + var handler = new TestHttpMessageHandler((request) => + { + if (request.RequestUri?.AbsolutePath == "/api/oauth2/token") + { + var response = new DiscordTokenResponse("access_123", "Bearer", 604800, "identify"); + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(JsonSerializer.Serialize(response)) + }; + } + + if (request.RequestUri?.AbsolutePath == "/api/users/@me") + { + var user = new DiscordUser("98765", "TestUser", "0", "avatar123", null); + return new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(JsonSerializer.Serialize(user)) + }; + } + + return new HttpResponseMessage(HttpStatusCode.NotFound); + }); + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Discord:ClientId"] = "client", + ["Discord:ClientSecret"] = "secret", + ["Discord:RedirectUri"] = "https://example.com/callback" + }) + .Build(); + + var factory = new TestHttpClientFactory(handler); + var service = new DiscordAuthService(factory, config); + + var result = await service.ExchangeCodeAsync("valid_code"); + + Assert.NotNull(result); + Assert.Equal("98765", result.Id); + Assert.Equal("TestUser", result.Username); + Assert.Equal("https://cdn.discordapp.com/avatars/98765/avatar123.png", result.AvatarUrl); + } + + [Fact] + public async Task ExchangeCodeAsync_WithInvalidCode_ReturnsNull() + { + var handler = new TestHttpMessageHandler((request) => + { + return new HttpResponseMessage(HttpStatusCode.BadRequest); + }); + + var config = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + ["Discord:ClientId"] = "client", + ["Discord:ClientSecret"] = "secret", + ["Discord:RedirectUri"] = "https://example.com/callback" + }) + .Build(); + + var factory = new TestHttpClientFactory(handler); + var service = new DiscordAuthService(factory, config); + + var result = await service.ExchangeCodeAsync("invalid_code"); + + Assert.Null(result); + } + + private class TestHttpClientFactory : IHttpClientFactory + { + private readonly HttpMessageHandler? _handler; + + public TestHttpClientFactory(HttpMessageHandler? handler = null) + { + _handler = handler; + } + + public HttpClient CreateClient(string name) => + _handler is not null ? new HttpClient(_handler) : new HttpClient(); + } + + private class TestHttpMessageHandler : HttpMessageHandler + { + private readonly Func _handler; + + public TestHttpMessageHandler(Func handler) + { + _handler = handler; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromResult(_handler(request)); + } + } +} diff --git a/tests/GmRelay.Bot.Tests/Web/PlatformIdentityTests.cs b/tests/GmRelay.Bot.Tests/Web/PlatformIdentityTests.cs new file mode 100644 index 0000000..7ce8e86 --- /dev/null +++ b/tests/GmRelay.Bot.Tests/Web/PlatformIdentityTests.cs @@ -0,0 +1,114 @@ +using System.Security.Claims; +using GmRelay.Web.Services; + +namespace GmRelay.Bot.Tests.Web; + +public class PlatformIdentityTests +{ + [Fact] + public void TryGetPlatformIdentity_TelegramUser_ReturnsTelegramPlatform() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.NameIdentifier, "12345"), + new Claim("TelegramId", "12345") + })); + + var result = user.TryGetPlatformIdentity(out var platform, out var externalUserId); + + Assert.True(result); + Assert.Equal("Telegram", platform); + Assert.Equal("12345", externalUserId); + } + + [Fact] + public void TryGetPlatformIdentity_TelegramUserWithPlatformClaim_ReturnsTelegramPlatform() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.NameIdentifier, "12345"), + new Claim("Platform", "Telegram") + })); + + var result = user.TryGetPlatformIdentity(out var platform, out var externalUserId); + + Assert.True(result); + Assert.Equal("Telegram", platform); + Assert.Equal("12345", externalUserId); + } + + [Fact] + public void TryGetPlatformIdentity_DiscordUser_ReturnsDiscordPlatform() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.NameIdentifier, "98765"), + new Claim("DiscordId", "98765"), + new Claim("Platform", "Discord") + })); + + var result = user.TryGetPlatformIdentity(out var platform, out var externalUserId); + + Assert.True(result); + Assert.Equal("Discord", platform); + Assert.Equal("98765", externalUserId); + } + + [Fact] + public void TryGetPlatformIdentity_UnknownUser_ReturnsFalse() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.Name, "Anonymous") + })); + + var result = user.TryGetPlatformIdentity(out _, out _); + Assert.False(result); + } + + [Fact] + public void TryGetDiscordId_DiscordUser_ReturnsTrue() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("DiscordId", "123") + })); + + var result = user.TryGetDiscordId(out var discordId); + Assert.True(result); + Assert.Equal("123", discordId); + } + + [Fact] + public void TryGetDiscordId_TelegramUser_ReturnsFalse() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("TelegramId", "123") + })); + + var result = user.TryGetDiscordId(out _); + Assert.False(result); + } + + [Fact] + public void GetAvatarUrl_DiscordUserWithAvatar_ReturnsUrl() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] + { + new Claim("AvatarUrl", "https://cdn.discordapp.com/avatars/1/abc.png") + })); + + var result = user.GetAvatarUrl(); + Assert.Equal("https://cdn.discordapp.com/avatars/1/abc.png", result); + } + + [Fact] + public void GetAvatarUrl_UserWithoutAvatar_ReturnsNull() + { + var user = new ClaimsPrincipal(new ClaimsIdentity(Array.Empty())); + + var result = user.GetAvatarUrl(); + Assert.Null(result); + } +}