admin/src/SimpleAdmin.Api.IntegrationTests/WeChatRouteIntegrationTests.cs
zzh 6c2e095196 fix(微信): 验签接口仅保留 GET 并原样返回 echostr
- /apiwechat/verification 移除 HEAD/POST,仅保留 GET 验签

- 验签通过后从 Query 原样返回 echostr 纯文本

- 扫码回调测试改走 /wechat/mp/{appId}

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 12:30:42 +08:00

109 lines
4.3 KiB
C#

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<WeChatMpOptions>(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}&timestamp={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<WeChatMpOptions>(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}&timestamp={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<WeChatMpOptions>(options =>
{
options.Token = "test-token";
options.AppId = "wx-test";
});
var repository = new Mock<ISystemOtherConfigRepository>();
repository
.Setup(x => x.GetByConfigCodeAsync(SystemOtherConfigCodes.WeChatMp, It.IsAny<CancellationToken>()))
.ReturnsAsync((SystemOtherConfig?)null);
services.RemoveAll<ISystemOtherConfigRepository>();
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();
}
}