test(web): add Discord auth and platform identity tests

- DiscordAuthServiceTests: authorize URL, token exchange, profile fetch
- PlatformIdentityTests: Telegram fallback, Discord identity, avatar URL

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 11:13:08 +03:00
parent 976e204102
commit 5fa7e26f72
2 changed files with 253 additions and 0 deletions
@@ -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<string, string?>
{
["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<InvalidOperationException>(() => 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<string, string?>
{
["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<string, string?>
{
["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<HttpRequestMessage, HttpResponseMessage> _handler;
public TestHttpMessageHandler(Func<HttpRequestMessage, HttpResponseMessage> handler)
{
_handler = handler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(_handler(request));
}
}
}
@@ -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<Claim>()));
var result = user.GetAvatarUrl();
Assert.Null(result);
}
}