增加Shop API
This commit is contained in:
1 parent
3769193696
commit
5d017740c6
83 files changed
+4451
-696
No files matched your search
@@ -0,0 +1,147 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Asp.Versioning;
|
||||
using MAX.Framework.Commons;
|
||||
using MAX.Framework.Conventions;
|
||||
using MAX.Framework.Dtos;
|
||||
using MAX.Framework.Security;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Store.Shops.Controllers;
|
||||
using Store.Shops.Entities;
|
||||
using Store.Shops.Services;
|
||||
using Store.Users.Contracts;
|
||||
|
||||
namespace Store.Shops.Tests;
|
||||
|
||||
public sealed class ShopHttpTests : IAsyncLifetime
|
||||
{
|
||||
private readonly ShopTestContext context = new();
|
||||
private WebApplication app = null!;
|
||||
private HttpClient client = null!;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions { EnvironmentName = "Testing" });
|
||||
builder.WebHost.UseTestServer();
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Services.AddControllers(options =>
|
||||
{
|
||||
options.Conventions.Add(new ApiRoutePrefixConvention());
|
||||
options.Filters.Add<GlobalExceptionFilter>();
|
||||
options.Filters.Add<UnifyResultFilter>();
|
||||
}).AddApplicationPart(typeof(ShopController).Assembly).ConfigureApiBehaviorOptions(options =>
|
||||
{
|
||||
options.InvalidModelStateResponseFactory = _ =>
|
||||
new ObjectResult(ApiResult.Fail("参数错误", ResultCode.ValidationError)) { StatusCode = 200 };
|
||||
});
|
||||
builder.Services.AddApiVersioning(options =>
|
||||
{
|
||||
options.DefaultApiVersion = new ApiVersion(1, 0);
|
||||
options.ApiVersionReader = new UrlSegmentApiVersionReader();
|
||||
}).AddMvc();
|
||||
builder.Services.AddAuthentication("Test").AddScheme<AuthenticationSchemeOptions, ShopTestAuthentication>("Test", _ => { });
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<ICurrentUser, CurrentUser>();
|
||||
builder.Services.AddSingleton(context.Db);
|
||||
builder.Services.AddSingleton<IUserQueryService>(context);
|
||||
builder.Services.AddSingleton(context.Configuration);
|
||||
builder.Services.AddSingleton(context.Gateway);
|
||||
builder.Services.AddTransient<ShopManagementService>();
|
||||
builder.Services.AddTransient<ShopService>();
|
||||
app = builder.Build();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
await app.StartAsync();
|
||||
client = app.GetTestClient();
|
||||
client.DefaultRequestHeaders.Add("X-Test-User", "owner");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("GetPddClients")]
|
||||
[InlineData("GetPddShopClients")]
|
||||
[InlineData("GetPddShopToken")]
|
||||
[InlineData("GetAllPddShops")]
|
||||
[InlineData("AddWeidianShop")]
|
||||
[InlineData("UpdateWeidianShop")]
|
||||
[InlineData("UpdateWeidianShopLogin")]
|
||||
[InlineData("DeleteWeidianShop")]
|
||||
public async Task ActionsOnlyAcceptPost(string action)
|
||||
{
|
||||
using var response = await client.GetAsync("/api/v1/shop/" + action);
|
||||
Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode);
|
||||
Assert.Contains("POST", response.Content.Headers.Allow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ActionsBindDtoAndReturnSingleApiResultEnvelope()
|
||||
{
|
||||
using var create = await client.PostAsJsonAsync("/api/v1/shop/AddWeidianShop", new { loginName = "login", shopId = "shop", notes = "old" });
|
||||
Assert.Equal(HttpStatusCode.OK, create.StatusCode);
|
||||
var json = await create.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(200, json.GetProperty("code").GetInt32());
|
||||
Assert.Equal(JsonValueKind.Null, json.GetProperty("data").ValueKind);
|
||||
using var update = await client.PostAsJsonAsync("/api/v1/shop/UpdateWeidianShop", new { loginName = "login", shopId = "shop", notes = "new" });
|
||||
Assert.Equal(200, (await update.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("code").GetInt32());
|
||||
using var cookie = await client.PostAsJsonAsync("/api/v1/shop/UpdateWeidianShopLogin", new { shopId = "shop", cookie = "session" });
|
||||
Assert.Equal(200, (await cookie.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("code").GetInt32());
|
||||
var row = Assert.Single(await context.Db.Select<WeidianShopEntity>().ToListAsync());
|
||||
Assert.Equal("new", row.Notes);
|
||||
Assert.Equal("session", row.Cookie);
|
||||
Assert.Equal("owner", row.UserName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidationAndBusinessErrorsReturnHttp200WithFailureCode()
|
||||
{
|
||||
using var invalid = await client.PostAsJsonAsync("/api/v1/shop/AddWeidianShop", new { });
|
||||
Assert.Equal(HttpStatusCode.OK, invalid.StatusCode);
|
||||
Assert.Equal(ResultCode.ValidationError, (await invalid.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("code").GetInt32());
|
||||
using var denied = await client.PostAsJsonAsync("/api/v1/shop/GetAllPddShops", new { });
|
||||
Assert.Equal(HttpStatusCode.OK, denied.StatusCode);
|
||||
Assert.Equal(ResultCode.BusinessError, (await denied.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("code").GetInt32());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CredentialResponseIsWrappedAndNotCached_AnonymousCannotRead()
|
||||
{
|
||||
using var response = await client.PostAsJsonAsync("/api/v1/shop/GetPddClients", new { });
|
||||
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
|
||||
Assert.Equal(200, result.GetProperty("code").GetInt32());
|
||||
Assert.Equal("test-client", result.GetProperty("data")[0].GetProperty("clientId").GetString());
|
||||
Assert.True(response.Headers.CacheControl!.NoStore);
|
||||
client.DefaultRequestHeaders.Remove("X-Test-User");
|
||||
using var anonymous = await client.PostAsJsonAsync("/api/v1/shop/GetPddClients", new { });
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, anonymous.StatusCode);
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
client.Dispose();
|
||||
await app.DisposeAsync();
|
||||
context.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ShopTestAuthentication(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger, UrlEncoder encoder) : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
|
||||
{
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
if (!Request.Headers.TryGetValue("X-Test-User", out var name))
|
||||
return Task.FromResult(AuthenticateResult.NoResult());
|
||||
var principal = new ClaimsPrincipal(new ClaimsIdentity([new Claim(JwtClaimTypes.UserName, name.ToString())], Scheme.Name));
|
||||
return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
using MAX.Framework.Commons;
|
||||
using Store.Shops.Dtos;
|
||||
using Store.Shops.Entities;
|
||||
|
||||
namespace Store.Shops.Tests;
|
||||
|
||||
public sealed class ShopManagementTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CreateThenImportAgain_UpdatesWithoutDuplicating_AndNullPreservesFields()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await c.Service.SaveWeidian(new() { LoginName = "login", ShopId = "shop", Notes = "keep", Cookie = "old" }, true);
|
||||
await c.Service.SaveWeidian(new() { LoginName = "login", ShopId = "shop", Cookie = "new" }, true);
|
||||
var row = Assert.Single(await c.Db.Select<WeidianShopEntity>().ToListAsync());
|
||||
Assert.Equal("keep", row.Notes);
|
||||
Assert.Equal("new", row.Cookie);
|
||||
Assert.Equal("owner", row.UserName);
|
||||
await c.Service.SaveWeidian(new() { LoginName = "login", ShopId = "shop", Notes = "", NewShopId = "new-id" }, false);
|
||||
row = await c.Db.Select<WeidianShopEntity>().FirstAsync();
|
||||
Assert.Equal("", row.Notes);
|
||||
Assert.Equal("new-id", row.ShopId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WrongOwnerCannotUpdateCookieOrDelete_EvenWithSameShopId()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await c.Db.Insert(new WeidianShopEntity { LoginName = "login", ShopId = "shop", UserName = "other", Cookie = "secret" }).ExecuteAffrowsAsync();
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.UpdateWeidianCookie(new() { ShopId = "shop", Cookie = "attack" }));
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.SaveWeidian(new() { LoginName = "login", ShopId = "shop", Notes = "attack" }, false));
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.DeleteWeidian(new() { ShopId = "shop" }));
|
||||
Assert.Equal("secret", (await c.Db.Select<WeidianShopEntity>().FirstAsync()).Cookie);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AssignedAccountMatchesWholeName_AndCannotReassignOrDelete()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await c.Db.Insert(new WeidianShopEntity { LoginName = "login", ShopId = "shop", UserName = "other", SubUserName = "coowner,owner2" }).ExecuteAffrowsAsync();
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.UpdateWeidianCookie(new() { ShopId = "shop", Cookie = "attack" }));
|
||||
await c.Db.Update<WeidianShopEntity>().Where(s => s.ShopId == "shop").Set(s => s.SubUserName, "first,owner,last").ExecuteAffrowsAsync();
|
||||
await c.Service.UpdateWeidianCookie(new() { ShopId = "shop", Cookie = "allowed" });
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.SaveWeidian(new() { LoginName = "login", ShopId = "shop", SubUserName = "new" }, false));
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.DeleteWeidian(new() { ShopId = "shop" }));
|
||||
Assert.Equal("allowed", (await c.Db.Select<WeidianShopEntity>().FirstAsync()).Cookie);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CookieUpdateOnlyChangesAuthorizedRows_AndOwnerCanDelete()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await c.Db.Insert(new[] {
|
||||
new WeidianShopEntity { ShopId = "shop", UserName = "owner", WdToken = "keep" },
|
||||
new WeidianShopEntity { ShopId = "shop", UserName = "other", Cookie = "secret" }
|
||||
}).ExecuteAffrowsAsync();
|
||||
await c.Service.UpdateWeidianCookie(new() { ShopId = "shop", Cookie = "new", LoginStatus = "online" });
|
||||
Assert.Equal("keep", (await c.Db.Select<WeidianShopEntity>().Where(s => s.UserName == "owner").FirstAsync()).WdToken);
|
||||
await c.Service.DeleteWeidian(new() { ShopId = "shop" });
|
||||
Assert.Equal("secret", Assert.Single(await c.Db.Select<WeidianShopEntity>().ToListAsync()).Cookie);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AmbiguousLoginIsRejectedWithoutChangingEitherShop()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await c.Service.SaveWeidian(new() { LoginName = "same", ShopId = "one" }, true);
|
||||
await c.Service.SaveWeidian(new() { LoginName = "same", ShopId = "two" }, true);
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.SaveWeidian(new() { LoginName = "same", Notes = "bad" }, false));
|
||||
Assert.All(await c.Db.Select<WeidianShopEntity>().ToListAsync(), s => Assert.Equal("", s.Notes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidationAndAnonymousRequestsDoNotWrite()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await Assert.ThrowsAsync<ValidationException>(() => c.Service.SaveWeidian(new(), true));
|
||||
await Assert.ThrowsAsync<ValidationException>(() => c.Service.UpdateWeidianCookie(new() { ShopId = "shop", WdToken = "only" }));
|
||||
await Assert.ThrowsAsync<ValidationException>(() => c.Service.DeleteWeidian(new()));
|
||||
c.UserName = null;
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => c.Service.SaveWeidian(new() { LoginName = "x" }, true));
|
||||
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => c.Service.GetPddClients());
|
||||
Assert.Equal(0, await c.Db.Select<WeidianShopEntity>().CountAsync());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0, 50)]
|
||||
[InlineData(1, 0)]
|
||||
[InlineData(1, 501)]
|
||||
[InlineData(int.MaxValue, 500)]
|
||||
public async Task InvalidPaginationIsRejected(int page, int size)
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await Assert.ThrowsAsync<ValidationException>(() => c.Service.GetAdminShops(new() { Page = page, PageSize = size }));
|
||||
await Assert.ThrowsAsync<ValidationException>(() => c.Service.GetShopClientInfo(new() { Page = page, PageSize = size }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClientInfoScopesCountAndRowsBeforePagination()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await c.Db.Insert(new[] {
|
||||
new PddShopEntity { ShopId = "one", UserName = "owner" },
|
||||
new PddShopEntity { ShopId = "two", UserName = "other", SubUserName = "owner" },
|
||||
new PddShopEntity { ShopId = "three", UserName = "other", SubUserName = "owner2" }
|
||||
}).ExecuteAffrowsAsync();
|
||||
foreach (var id in new[] { "one", "two", "three" })
|
||||
await c.Db.Insert(new ShopApiAuthEntity { ShopId = id, AppKey = id, AppSecret = "secret" }).ExecuteAffrowsAsync();
|
||||
var page = await c.Service.GetShopClientInfo(new() { PageSize = 1 });
|
||||
Assert.Equal(2, page.Total);
|
||||
Assert.Equal("two", Assert.Single(page.Items).ShopId);
|
||||
Assert.Empty((await c.Service.GetShopClientInfo(new() { ShopIds = ["three"] })).Items);
|
||||
c.UserName = "admin";
|
||||
Assert.Empty((await c.Service.GetShopClientInfo(new())).Items);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AdminListResetsOldMonthOnceAndKeepsCurrentQuota()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.GetAdminShops(new()));
|
||||
c.UserName = "admin";
|
||||
await c.Db.Insert(new PddShopEntity { ShopId = "old", ShopAccount = "test", LoginPass = "password", Cookie = "secret" }).ExecuteAffrowsAsync();
|
||||
await c.Db.Insert(new ShopProductQuotaEntity { ShopId = "old", AddProductMonth = DateTime.Now.Month % 12 + 1, AddProductRemaining = 2 }).ExecuteAffrowsAsync();
|
||||
var page = await c.Service.GetAdminShops(new() { SearchName = "test" });
|
||||
Assert.Equal(1, page.Total);
|
||||
Assert.Equal(300, Assert.Single(page.Items).AddProductRemaining);
|
||||
var json = JsonSerializer.Serialize(page);
|
||||
Assert.DoesNotContain("LoginPass", json);
|
||||
Assert.DoesNotContain("Cookie", json);
|
||||
await c.Db.Update<ShopProductQuotaEntity>().Where(s => s.ShopId == "old").Set(s => s.AddProductRemaining, 17).ExecuteAffrowsAsync();
|
||||
Assert.Equal(17, Assert.Single((await c.Service.GetAdminShops(new())).Items).AddProductRemaining);
|
||||
Assert.Equal(17, (await c.Db.Select<PddShopEntity>().FirstAsync()).AddProductRemaining);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task QuotaAndShopUpdatesRollbackTogether()
|
||||
{
|
||||
using var c = new ShopTestContext { UserName = "admin" };
|
||||
await c.Db.Insert(new PddShopEntity { ShopId = "shop" }).ExecuteAffrowsAsync();
|
||||
await c.Db.Insert(new ShopProductQuotaEntity { ShopId = "shop", AddProductMonth = 0, AddProductRemaining = 7 }).ExecuteAffrowsAsync();
|
||||
c.Db.Ado.ExecuteNonQuery("CREATE TRIGGER reject_shop_update BEFORE UPDATE ON pdd_shops BEGIN SELECT RAISE(ABORT, 'test failure'); END");
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => c.Service.GetAdminShops(new()));
|
||||
var quota = await c.Db.Select<ShopProductQuotaEntity>().FirstAsync();
|
||||
Assert.Equal(0, quota.AddProductMonth);
|
||||
Assert.Equal(7, quota.AddProductRemaining);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TokenRequiresShopAccess_AndRetriesStructuredFailure()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.GetPddToken(new() { MallId = "mall" }));
|
||||
Assert.Equal(0, c.Handler.Calls);
|
||||
await c.Db.Insert(new PddShopEntity { MallId = "mall&x=1", UserName = "owner" }).ExecuteAffrowsAsync();
|
||||
c.Handler.Responses.Enqueue("""{"code":1,"msg":"failed"}""");
|
||||
var token = await c.Service.GetPddToken(new() { MallId = "mall&x=1" });
|
||||
Assert.Equal("test-token", token.GetProperty("accessToken").GetString());
|
||||
Assert.Equal(2, c.Handler.Calls);
|
||||
Assert.Contains("mall%26x%3D1", c.Handler.LastUri!.AbsoluteUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TokenMalformedResponsesFailWithoutLeakingUpstreamContent()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
c.Handler.Responses.Enqueue("private upstream error");
|
||||
c.Handler.Responses.Enqueue("""{"code":3}""");
|
||||
var error = await Assert.ThrowsAsync<AppException>(() => c.Gateway.GetToken("mall", default));
|
||||
Assert.DoesNotContain("private", error.Message);
|
||||
Assert.Equal(2, c.Handler.Calls);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PddClientsComeFromConfiguration()
|
||||
{
|
||||
using var c = new ShopTestContext();
|
||||
Assert.Equal("test-client", Assert.Single(await c.Service.GetPddClients()).ClientId);
|
||||
c.Configuration["Shops:PddClients:0:ClientSecret"] = "";
|
||||
await Assert.ThrowsAsync<AppException>(() => c.Service.GetPddClients());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using FreeSql;
|
||||
using MAX.Framework.Security;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Store.Shops.Entities;
|
||||
using Store.Shops.Services;
|
||||
using Store.Users.Contracts;
|
||||
using Store.Users.Contracts.Dtos;
|
||||
using Store.Users.Contracts.Enums;
|
||||
|
||||
namespace Store.Shops.Tests;
|
||||
|
||||
internal sealed class ShopTestContext : IDisposable, ICurrentUser, IUserQueryService
|
||||
{
|
||||
private readonly string path = Path.Combine(Path.GetTempPath(), $"store-shops-{Guid.NewGuid():N}.db");
|
||||
public IFreeSql Db { get; }
|
||||
public string? UserName { get; set; } = "owner";
|
||||
public string? NickName => UserName;
|
||||
public bool IsAuthenticated => UserName != null;
|
||||
public IEnumerable<Claim> Claims => [];
|
||||
public string? GetClaimValue(string type) => null;
|
||||
public T? GetClaimValue<T>(string type) => default;
|
||||
public Task<UserInfo> GetUser(string name) => Task.FromResult(new UserInfo
|
||||
{
|
||||
UserName = name, SaLevel = name == "admin" ? UserAdminLevel.SuperAdmin : UserAdminLevel.Normal
|
||||
});
|
||||
public StubHttp Handler { get; } = new();
|
||||
public PddTokenClient Gateway { get; }
|
||||
public ShopManagementService Service { get; }
|
||||
public IConfiguration Configuration { get; } = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["Shops:PddClients:0:ClientId"] = "test-client",
|
||||
["Shops:PddClients:0:ClientSecret"] = "test-secret",
|
||||
["Shops:PddClients:0:RedirectUri"] = "https://example.test/callback"
|
||||
}).Build();
|
||||
|
||||
public ShopTestContext()
|
||||
{
|
||||
Db = new FreeSqlBuilder().UseMappingPriority(FreeSql.Internal.MappingPriorityType.Attribute,
|
||||
FreeSql.Internal.MappingPriorityType.Aop, FreeSql.Internal.MappingPriorityType.FluentApi)
|
||||
.UseConnectionString(DataType.Sqlite, $"Data Source={path};Pooling=False").Build();
|
||||
Db.CodeFirst.ConfigEntity<PddShopEntity>(e => e.Name("pdd_shops"));
|
||||
Db.CodeFirst.ConfigEntity<WeidianShopEntity>(e => e.Name("weidian_shops"));
|
||||
Db.CodeFirst.ConfigEntity<ShopApiAuthEntity>(e => e.Name("shop_auth"));
|
||||
Db.CodeFirst.ConfigEntity<ShopProductQuotaEntity>(e => e.Name("shop_quota"));
|
||||
Db.CodeFirst.SyncStructure(typeof(PddShopEntity), typeof(WeidianShopEntity), typeof(ShopApiAuthEntity), typeof(ShopProductQuotaEntity));
|
||||
Gateway = new(new HttpClient(Handler));
|
||||
Service = new(Db, this, this, Configuration, Gateway);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Gateway.Dispose();
|
||||
Db.Dispose();
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class StubHttp : HttpMessageHandler
|
||||
{
|
||||
public Queue<string> Responses { get; } = new();
|
||||
public int Calls { get; private set; }
|
||||
public Uri? LastUri { get; private set; }
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
Calls++;
|
||||
LastUri = request.RequestUri;
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(Responses.Count == 0 ? """{"code":0,"data":{"accessToken":"test-token"}}""" : Responses.Dequeue())
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Modules/Store.Shops/Store.Shops.csproj" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="10.0.10" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="FreeSql.Provider.Sqlite" Version="3.5.311" />
|
||||
</ItemGroup>
|
||||
<ItemGroup><Using Include="Xunit" /></ItemGroup>
|
||||
</Project>
|
||||
Reference in new issue
Block a user