using System.ComponentModel.DataAnnotations; using MAX.Plugins; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Store.Modules.CloudProduct; using Store.Modules.CloudProduct.Services; // 本地模拟站点验证服务自行创建的 HttpClient,不调用真实扣费接口。 var builder = WebApplication.CreateBuilder(); builder.Logging.ClearProviders(); builder.WebHost.UseUrls("http://127.0.0.1:0"); await using var app = builder.Build(); var calls = 0; var status = 200; app.MapPost("/wxxd_user/UseImageAnalysisQuota", async (HttpContext http) => { calls++; if (http.Request.Query["token"] != "key+&=?" || http.Request.Query["username"] != "用户 +&=?") throw new Exception("查询参数编码不符合外部协议。"); http.Response.StatusCode = status; await http.Response.WriteAsync("{\"success\":true}"); }); await app.StartAsync(); var address = app.Services.GetRequiredService().Features.Get()!.Addresses.Single(); using var context = new PluginContext("CloudProduct", "Plugins/CloudProduct", builder.Environment); context.Configuration["ImageAnalysisQuota:APIKey"] = "key+&=?"; var typedContext = new PluginContext(context); // 根地址带或不带末尾斜杠时,都应命中固定接口路径。 foreach (var url in new[] { address, address + "/" }) { context.Configuration["ImageAnalysisQuota:Url"] = url; using var service = new ImageAnalysisQuotaService(typedContext); var body = await service.UseImageAnalysisQuotaAsync(new() { Username = "用户 +&=?" }); if (body != "{\"success\":true}") throw new Exception("应返回原始响应。"); await RejectAsync(() => service.UseImageAnalysisQuotaAsync(new() { Username = " " })); context.Configuration["ImageAnalysisQuota:APIKey"] = ""; await RejectAsync(() => service.UseImageAnalysisQuotaAsync(new() { Username = "test" })); context.Configuration["ImageAnalysisQuota:APIKey"] = "key+&=?"; } if (calls != 2) throw new Exception("无效输入或配置不能发送扣减请求。"); context.Configuration["ImageAnalysisQuota:Url"] = "file:///invalid"; await RejectAsync(() => { using var invalid = new ImageAnalysisQuotaService(typedContext); return Task.CompletedTask; }); context.Configuration["ImageAnalysisQuota:Url"] = address; using (var service = new ImageAnalysisQuotaService(typedContext)) { status = 500; await RejectAsync(() => service.UseImageAnalysisQuotaAsync(new() { Username = "用户 +&=?" })); } if (calls != 3) throw new Exception("失败请求不能自动重试。"); await app.StopAsync(); Console.WriteLine("Image analysis quota checks passed."); static async Task RejectAsync(Func action) where T : Exception { try { await action(); } catch (T) { return; } throw new Exception($"应抛出 {typeof(T).Name}。"); }