forked from xiongyuxing/tiku-backend.net
168 lines
7.1 KiB
C#
168 lines
7.1 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"),
|
|
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 User { Id = seed.UserId, Phone = seed.Phone, Name = role, PrimaryRole = role }.WithTestPassword();
|
|
private static TenantMembership Membership(LoginSeed seed, TenantRole role) => new() { TenantId = seed.TenantId, UserId = seed.UserId, Role = role, Status = MembershipStatus.Active };
|
|
|
|
private static async Task LoginAsync(HttpClient client, LoginSeed seed)
|
|
{
|
|
client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone));
|
|
}
|
|
|
|
private sealed record LoginSeed(Guid TenantId, Guid UserId, string Phone);
|
|
private sealed record CommissionSeed(Guid TenantId, LoginSeed Admin, LoginSeed Referrer, LoginSeed Student);
|
|
}
|