155 lines
5.8 KiB
C#
155 lines
5.8 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using MiaoJiZhang.Api.Services;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
|
|
namespace MiaoJiZhang.Api.Tests;
|
|
|
|
public sealed class PushProviderTests
|
|
{
|
|
private static readonly PushEnvelope Message = new(
|
|
"message-1",
|
|
"测试标题",
|
|
"测试正文",
|
|
"system",
|
|
"home",
|
|
null,
|
|
3600);
|
|
|
|
[Fact]
|
|
public async Task Xiaomi_Http200BusinessFailure_IsNotAccepted()
|
|
{
|
|
var factory = new StubHttpClientFactory(_ => Json(
|
|
"""{"result":"error","code":70000003,"description":"invalid registration_id"}"""));
|
|
var provider = Provider("xiaomi", factory, new Dictionary<string, string?>
|
|
{
|
|
["Push:Providers:xiaomi:production:Enabled"] = "true",
|
|
["Push:Providers:xiaomi:production:AppSecret"] = "server-secret",
|
|
});
|
|
|
|
var result = await provider.SendAsync(
|
|
"production",
|
|
"com.nx.miaoji",
|
|
"invalid-token",
|
|
Message,
|
|
CancellationToken.None);
|
|
|
|
Assert.False(result.Accepted);
|
|
Assert.True(result.InvalidToken);
|
|
Assert.Equal("provider_error", result.ErrorCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Xiaomi_Http200Success_IsAccepted()
|
|
{
|
|
var factory = new StubHttpClientFactory(_ => Json(
|
|
"""{"result":"ok","code":0,"data":{"id":"xiaomi-message"}}"""));
|
|
var provider = Provider("xiaomi", factory, new Dictionary<string, string?>
|
|
{
|
|
["Push:Providers:xiaomi:production:Enabled"] = "true",
|
|
["Push:Providers:xiaomi:production:AppSecret"] = "server-secret",
|
|
});
|
|
|
|
var result = await provider.SendAsync(
|
|
"production",
|
|
"com.nx.miaoji",
|
|
"valid-token",
|
|
Message,
|
|
CancellationToken.None);
|
|
|
|
Assert.True(result.Accepted);
|
|
Assert.False(result.Retryable);
|
|
Assert.False(result.InvalidToken);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Huawei_AccessTokens_AreCachedPerFlavor()
|
|
{
|
|
var authCalls = new Dictionary<string, int>();
|
|
var sendTokens = new Dictionary<string, List<string>>();
|
|
var factory = new StubHttpClientFactory(request =>
|
|
{
|
|
var path = request.RequestUri!.AbsolutePath;
|
|
var flavor = path.Contains("production", StringComparison.Ordinal)
|
|
? "production"
|
|
: "internal";
|
|
if (path.EndsWith("/auth", StringComparison.Ordinal))
|
|
{
|
|
authCalls[flavor] = authCalls.GetValueOrDefault(flavor) + 1;
|
|
return Json($$"""{"access_token":"{{flavor}}-token","expires_in":3600}""");
|
|
}
|
|
|
|
sendTokens.TryAdd(flavor, []);
|
|
sendTokens[flavor].Add(request.Headers.Authorization?.Parameter ?? "");
|
|
return Json("""{"code":"80000000","requestId":"huawei-message"}""");
|
|
});
|
|
var provider = Provider("huawei", factory, new Dictionary<string, string?>
|
|
{
|
|
["Push:Providers:huawei:production:Enabled"] = "true",
|
|
["Push:Providers:huawei:production:AppId"] = "production-app",
|
|
["Push:Providers:huawei:production:AppSecret"] = "production-secret",
|
|
["Push:Providers:huawei:production:AuthUrl"] = "https://push.test/production/auth",
|
|
["Push:Providers:huawei:production:SendUrl"] = "https://push.test/production/send",
|
|
["Push:Providers:huawei:internal:Enabled"] = "true",
|
|
["Push:Providers:huawei:internal:AppId"] = "internal-app",
|
|
["Push:Providers:huawei:internal:AppSecret"] = "internal-secret",
|
|
["Push:Providers:huawei:internal:AuthUrl"] = "https://push.test/internal/auth",
|
|
["Push:Providers:huawei:internal:SendUrl"] = "https://push.test/internal/send",
|
|
});
|
|
|
|
Assert.True((await provider.SendAsync(
|
|
"production", "com.nx.miaoji", "token-1", Message, CancellationToken.None)).Accepted);
|
|
Assert.True((await provider.SendAsync(
|
|
"internal", "com.nx.miaoji.internal", "token-2", Message, CancellationToken.None)).Accepted);
|
|
Assert.True((await provider.SendAsync(
|
|
"production", "com.nx.miaoji", "token-3", Message, CancellationToken.None)).Accepted);
|
|
|
|
Assert.Equal(1, authCalls["production"]);
|
|
Assert.Equal(1, authCalls["internal"]);
|
|
Assert.Equal(["production-token", "production-token"], sendTokens["production"]);
|
|
Assert.Equal(["internal-token"], sendTokens["internal"]);
|
|
}
|
|
|
|
private static OfficialPushProvider Provider(
|
|
string name,
|
|
IHttpClientFactory factory,
|
|
Dictionary<string, string?> values)
|
|
{
|
|
var configuration = new ConfigurationBuilder()
|
|
.AddInMemoryCollection(values)
|
|
.Build();
|
|
return new OfficialPushProvider(
|
|
name,
|
|
configuration,
|
|
factory,
|
|
NullLogger<OfficialPushProvider>.Instance);
|
|
}
|
|
|
|
private static HttpResponseMessage Json(string body) => new(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(body),
|
|
};
|
|
|
|
private sealed class StubHttpClientFactory : IHttpClientFactory
|
|
{
|
|
private readonly HttpClient client;
|
|
|
|
public StubHttpClientFactory(Func<HttpRequestMessage, HttpResponseMessage> response)
|
|
{
|
|
client = new HttpClient(new StubHandler(response));
|
|
}
|
|
|
|
public HttpClient CreateClient(string name) => client;
|
|
}
|
|
|
|
private sealed class StubHandler(Func<HttpRequestMessage, HttpResponseMessage> response)
|
|
: HttpMessageHandler
|
|
{
|
|
protected override Task<HttpResponseMessage> SendAsync(
|
|
HttpRequestMessage request,
|
|
CancellationToken cancellationToken) =>
|
|
Task.FromResult(response(request));
|
|
}
|
|
}
|