Files
GmRelayBot/tests/GmRelay.Bot.Tests/Web/DiscordAuthServiceTests.cs
T
Toutsu 7e02e86cd6
PR Checks / test-and-build (pull_request) Failing after 6m20s
fix: add Discord OAuth token exchange logging for production diagnostics
- Log status code and response body when Discord /oauth2/token fails
- Helps identify why ExchangeCodeAsync returns null in production

Bump version → 2.8.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 12:46:56 +03:00

141 lines
4.9 KiB
C#

using System.Net;
using System.Text.Json;
using GmRelay.Web;
using GmRelay.Web.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging.Abstractions;
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, NullLogger<DiscordAuthService>.Instance);
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, NullLogger<DiscordAuthService>.Instance);
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, NullLogger<DiscordAuthService>.Instance);
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, NullLogger<DiscordAuthService>.Instance);
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));
}
}
}