using System.Net; using System.Security.Cryptography; using System.Text; using FluentAssertions; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Moq; using Oms.Domain.SystemAggregate.Consts; using Oms.Domain.SystemAggregate.Entities; using Oms.Domain.SystemAggregate.Repositories; using SimpleAdmin.Api.IntegrationTests.Support; using SimpleAdmin.Api.WeChat.Options; namespace SimpleAdmin.Api.IntegrationTests; public class WeChatRouteIntegrationTests { [Fact] public async Task WeChatLegacyVerification_ShouldRegisterGetOnly() { await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: ConfigureWeChatFallback); var methods = host.GetHttpMethods("apiwechat/verification"); methods.Should().BeEquivalentTo(["GET"]); } [Fact] public async Task WeChatRoutes_ShouldBeRegisteredOnAuthService() { await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: ConfigureWeChatFallback); host.RoutePatterns.Should().Contain("apiwechat/verification"); host.RoutePatterns.Should().Contain("wechat/mp/{appId}"); host.RoutePatterns.Should().Contain("wechat/mp/login-qrcode"); host.RoutePatterns.Should().Contain("wechat/oauth/user-info"); host.RoutePatterns.Should().Contain("wechat/customer/register"); host.RoutePatterns.Should().Contain("wechat/customer/bind"); host.RoutePatterns.Should().Contain("rbac/auth/wx-login/status"); } [Fact] public async Task WeChatValidateLegacy_ShouldReturnEchostrWhenSignatureValid() { await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services => { ConfigureWeChatFallback(services); services.PostConfigure(options => options.Token = "test-token"); }); const string timestamp = "1400000000"; const string nonce = "nonce"; const string echostr = "hello-wechat"; var signature = SignWeChat("test-token", timestamp, nonce); var response = await host.Client.GetAsync( $"/apiwechat/verification?signature={signature}×tamp={timestamp}&nonce={nonce}&echostr={echostr}"); response.StatusCode.Should().Be(HttpStatusCode.OK); (await response.Content.ReadAsStringAsync()).Should().Be(echostr); } [Fact] public async Task WeChatValidateLegacy_ShouldReturnEchostrAsPlainTextWithoutJsonWrapper() { await using var host = await SimpleAdminApiTestHost.CreateAsync(configureServices: services => { ConfigureWeChatFallback(services); services.PostConfigure(options => options.Token = "test-token"); }); const string timestamp = "1400000000"; const string nonce = "nonce"; const string echostr = "5938472619053847261"; var signature = SignWeChat("test-token", timestamp, nonce); var response = await host.Client.GetAsync( $"/apiwechat/verification?signature={signature}×tamp={timestamp}&nonce={nonce}&echostr={echostr}"); response.StatusCode.Should().Be(HttpStatusCode.OK); response.Content.Headers.ContentType?.MediaType.Should().Be("text/plain"); var body = await response.Content.ReadAsStringAsync(); body.Should().Be(echostr); body.Should().NotStartWith("{"); } private static void ConfigureWeChatFallback(IServiceCollection services) { services.PostConfigure(options => { options.Token = "test-token"; options.AppId = "wx-test"; }); var repository = new Mock(); repository .Setup(x => x.GetByConfigCodeAsync(SystemOtherConfigCodes.WeChatMp, It.IsAny())) .ReturnsAsync((SystemOtherConfig?)null); services.RemoveAll(); services.AddSingleton(repository.Object); } private static string SignWeChat(string token, string timestamp, string nonce) { var parts = new[] { token, timestamp, nonce }.OrderBy(x => x, StringComparer.Ordinal).ToArray(); var hash = SHA1.HashData(Encoding.UTF8.GetBytes(string.Concat(parts))); return Convert.ToHexString(hash).ToLowerInvariant(); } }