Add domestic vendor push infrastructure
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user