Files
tiku-backend.net/Tiku.IntegrationTests/Api/CommissionEndpointTests.cs

180 lines
8.0 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using Tiku.Api.Contracts;
using Tiku.Application.Auth;
using Tiku.Application.Growth;
using Tiku.Domain.Commerce;
using Tiku.Domain.Growth;
using Tiku.Domain.Identity;
using Tiku.Domain.Tenancy;
using Tiku.Infrastructure.Auth;
namespace Tiku.IntegrationTests.Api;
public sealed class CommissionEndpointTests
{
[Fact]
public async Task Anonymous_commission_request_returns_401()
{
await using var factory = new ApiTestFactory();
using var client = factory.CreateClient();
var response = await client.GetAsync("/api/commission/settings");
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Fact]
public async Task Admin_can_generate_export_and_proof_commission_settlement()
{
await using var factory = new ApiTestFactory();
var seed = await SeedCommissionAsync(factory);
using var client = factory.CreateClient();
await LoginAsync(client, seed.Admin);
var settings = await client.PutAsJsonAsync(
"/api/commission/settings",
new UpdateCommissionSettingsDto { DefaultRate = 0.2m, MinSettlementCents = 1, SettlementCycle = "monthly" });
var sources = await client.GetAsync($"/api/commission/orders?referrerUserId={seed.Referrer.UserId}&startDate=2026-07-01&endDate=2026-07-31");
var summary = await client.GetAsync($"/api/commission/summary?referrerUserId={seed.Referrer.UserId}&startDate=2026-07-01&endDate=2026-07-31");
var generate = await client.PostAsJsonAsync(
"/api/commission/settlements/generate",
new GenerateCommissionSettlementDto
{
ReferrerUserId = seed.Referrer.UserId,
StartDate = "2026-07-01",
EndDate = "2026-07-31",
Status = "pending_review"
});
var settlement = await generate.Content.ReadFromJsonAsync<CommissionSettlementItemDto>();
var duplicate = await client.PostAsJsonAsync(
"/api/commission/settlements/generate",
new GenerateCommissionSettlementDto
{
ReferrerUserId = seed.Referrer.UserId,
StartDate = "2026-07-01",
EndDate = "2026-07-31"
});
var status = await client.PostAsJsonAsync(
"/api/commission/settlements/status",
new UpdateCommissionSettlementStatusDto
{
SettlementId = settlement!.Id,
Status = "paid",
PaymentMethod = "bank",
PaymentAccount = "****1234"
});
var proof = await client.PostAsJsonAsync(
"/api/commission/settlements/proofs",
new CreateCommissionProofDto
{
SettlementId = settlement.Id,
ProofType = "payment",
ExternalUrl = "https://example.test/proof.png"
});
var proofItem = await proof.Content.ReadFromJsonAsync<CommissionProofItem>();
var proofStatus = await client.PostAsJsonAsync(
"/api/commission/settlements/proofs/status",
new UpdateCommissionProofStatusDto { ProofId = proofItem!.Id, Status = "approved" });
var export = await client.GetAsync($"/api/commission/settlements/export?settlementId={settlement.Id}&format=csv");
var exportItem = await export.Content.ReadFromJsonAsync<CommissionExportItem>();
Assert.Equal(HttpStatusCode.OK, settings.StatusCode);
Assert.Equal(HttpStatusCode.OK, sources.StatusCode);
Assert.Equal(HttpStatusCode.OK, summary.StatusCode);
Assert.Equal(HttpStatusCode.OK, generate.StatusCode);
Assert.Equal(2, settlement.SourceCount);
Assert.Equal(3000, settlement.CommissionAmountCents);
Assert.Equal(HttpStatusCode.Conflict, duplicate.StatusCode);
Assert.Equal(HttpStatusCode.OK, status.StatusCode);
Assert.Equal(HttpStatusCode.OK, proof.StatusCode);
Assert.Equal(HttpStatusCode.OK, proofStatus.StatusCode);
Assert.Equal(HttpStatusCode.OK, export.StatusCode);
Assert.Equal("csv", exportItem!.Format);
Assert.False(string.IsNullOrWhiteSpace(exportItem.Sha256));
}
private static async Task<CommissionSeed> SeedCommissionAsync(ApiTestFactory factory)
{
var tenantId = Guid.NewGuid();
var admin = new LoginSeed(tenantId, Guid.NewGuid(), "13800003001");
var referrer = new LoginSeed(tenantId, Guid.NewGuid(), "13800003002");
var student = new LoginSeed(tenantId, Guid.NewGuid(), "13800003003");
var paidAt = new DateTimeOffset(2026, 7, 10, 12, 0, 0, TimeSpan.Zero);
var batchId = Guid.NewGuid();
await factory.SeedAsync(
new Tenant { Id = tenantId, Slug = tenantId.ToString("N"), Name = "Commission Tenant" },
User(admin, "tenant_admin"),
User(referrer, "sales"),
User(student, "student"),
Identity(admin),
Identity(referrer),
Identity(student),
Membership(admin, TenantRole.TenantAdmin),
Membership(referrer, TenantRole.Sales),
Membership(student, TenantRole.Student),
new ReferralLead
{
TenantId = tenantId,
StudentUserId = student.UserId,
ReferrerUserId = referrer.UserId,
RefCode = "COMM2026",
Source = "manual"
},
new Order
{
TenantId = tenantId,
UserId = student.UserId,
OrderNo = "C202607100001",
Status = OrderStatus.Paid,
AmountCents = 10000,
ProductType = "svip",
ProductName = "SVIP",
PaidAt = paidAt
},
new CodeBatch
{
Id = batchId,
TenantId = tenantId,
Name = "代理码",
TotalCount = 1,
DefaultUnitPriceCents = 5000,
CommissionRate = 0.2m
},
new ActivationCode
{
TenantId = tenantId,
BatchId = batchId,
AgentUserId = referrer.UserId,
UsedBy = student.UserId,
Code = "CODE2026",
IsUsed = true,
UnitPriceCents = 5000,
UsedAt = paidAt
});
return new CommissionSeed(tenantId, admin, referrer, student);
}
private static User User(LoginSeed seed, string role) => new() { Id = seed.UserId, Phone = seed.Phone, Name = role, PrimaryRole = role };
private static TenantMembership Membership(LoginSeed seed, TenantRole role) => new() { TenantId = seed.TenantId, UserId = seed.UserId, Role = role, Status = MembershipStatus.Active };
private static UserIdentity Identity(LoginSeed seed) => new() { UserId = seed.UserId, Provider = "password", ProviderSubject = seed.Phone, Phone = seed.Phone, SecretPayload = CreateSecretPayload(new PasswordHasher().Hash("passw0rd!")) };
private static async Task LoginAsync(HttpClient client, LoginSeed seed)
{
var loginResponse = await client.PostAsJsonAsync("/api/auth/login/password", new PasswordLoginDto { TenantCode = seed.TenantId.ToString("N"), Phone = seed.Phone, Password = "passw0rd!" });
loginResponse.EnsureSuccessStatusCode();
using var loginJson = await JsonDocument.ParseAsync(await loginResponse.Content.ReadAsStreamAsync());
client.DefaultRequestHeaders.Authorization = new("Bearer", loginJson.RootElement.GetProperty("tokens").GetProperty("accessToken").GetString());
}
private static JsonElement CreateSecretPayload(string passwordHash)
{
using var document = JsonDocument.Parse($$"""{"passwordHash":{{JsonSerializer.Serialize(passwordHash)}}}""");
return document.RootElement.Clone();
}
private sealed record LoginSeed(Guid TenantId, Guid UserId, string Phone);
private sealed record CommissionSeed(Guid TenantId, LoginSeed Admin, LoginSeed Referrer, LoginSeed Student);
}