feat: add referral management endpoints

This commit is contained in:
xiong
2026-07-26 20:22:09 +08:00
parent 9fff468bd7
commit ecfb57bd30
5 changed files with 724 additions and 1 deletions

View File

@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
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;
@@ -131,6 +132,77 @@ public sealed class ReferralEndpointTests
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task Non_admin_cannot_access_referral_management()
{
await using var factory = new ApiTestFactory();
var seed = await SeedReferralAsync(factory, includeCode: true);
using var client = factory.CreateClient();
await LoginAsync(client, seed.Student);
var response = await client.GetAsync("/api/referral/stats");
Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
}
[Fact]
public async Task Admin_can_manage_referral_team_and_query_stats()
{
await using var factory = new ApiTestFactory();
var seed = await SeedReferralAsync(factory, includeCode: true);
using var client = factory.CreateClient();
await LoginAsync(client, seed.Admin);
var manualBind = await client.PostAsJsonAsync(
"/api/referral/manual-bind",
new ManualBindReferralDto
{
StudentUserId = seed.Student.UserId,
ReferrerUserId = seed.Referrer.UserId,
Source = "manual",
Force = true
});
var team = await client.PutAsJsonAsync(
"/api/referral/team",
new UpsertReferralTeamDto
{
MemberUserId = seed.Referrer.UserId,
LeaderUserId = seed.Admin.UserId,
RelationType = "sales_team",
Status = "active"
});
await factory.SeedAsync(new Order
{
TenantId = seed.TenantId,
UserId = seed.Student.UserId,
OrderNo = "R202607260001",
Status = OrderStatus.Paid,
ProductType = "svip",
ProductName = "SVIP",
AmountCents = 9900,
PaidAt = DateTimeOffset.UtcNow
});
var stats = await client.GetAsync($"/api/referral/stats?referrerUserId={seed.Referrer.UserId}");
var salesStats = await client.GetAsync("/api/referral/sales-stats");
var conversion = await client.GetAsync("/api/referral/conversion-report?days=30");
var clients = await client.GetAsync($"/api/referral/sales-clients?referrerUserId={seed.Referrer.UserId}");
var teamList = await client.GetAsync($"/api/referral/team?leaderUserId={seed.Admin.UserId}");
var statsItem = await stats.Content.ReadFromJsonAsync<ReferralStatsItem>();
Assert.Equal(HttpStatusCode.OK, manualBind.StatusCode);
Assert.Equal(HttpStatusCode.OK, team.StatusCode);
Assert.Equal(HttpStatusCode.OK, stats.StatusCode);
Assert.Equal(1, statsItem!.LeadCount);
Assert.Equal(1, statsItem.PaidLeadCount);
Assert.Equal(9900, statsItem.PaidAmountCents);
Assert.Equal(HttpStatusCode.OK, salesStats.StatusCode);
Assert.Equal(HttpStatusCode.OK, conversion.StatusCode);
Assert.Equal(HttpStatusCode.OK, clients.StatusCode);
Assert.Equal(HttpStatusCode.OK, teamList.StatusCode);
Assert.Contains(seed.Referrer.UserId.ToString(), await teamList.Content.ReadAsStringAsync(), StringComparison.OrdinalIgnoreCase);
}
private static async Task<ReferralSeed> SeedReferralAsync(
ApiTestFactory factory,
bool includeCode = false)
@@ -139,14 +211,17 @@ public sealed class ReferralEndpointTests
var tenantCode = tenantId.ToString("N");
var referrer = new LoginSeed(tenantId, Guid.NewGuid(), "13800001001");
var student = new LoginSeed(tenantId, Guid.NewGuid(), "13800001002");
var admin = new LoginSeed(tenantId, Guid.NewGuid(), "13800001003");
const string referralCode = "ABC12345";
var entities = new List<object>
{
new Tenant { Id = tenantId, Slug = tenantCode, Name = "Referral Tenant" },
User(referrer.UserId, referrer.Phone, "Referral Teacher", "teacher"),
User(student.UserId, student.Phone, "Referral Student", "student"),
User(admin.UserId, admin.Phone, "Referral Admin", "tenant_admin"),
Identity(referrer),
Identity(student),
Identity(admin),
new TenantMembership
{
TenantId = tenantId,
@@ -160,6 +235,13 @@ public sealed class ReferralEndpointTests
UserId = student.UserId,
Role = TenantRole.Student,
Status = MembershipStatus.Active
},
new TenantMembership
{
TenantId = tenantId,
UserId = admin.UserId,
Role = TenantRole.TenantAdmin,
Status = MembershipStatus.Active
}
};
if (includeCode)
@@ -174,7 +256,7 @@ public sealed class ReferralEndpointTests
}
await factory.SeedAsync(entities.ToArray());
return new ReferralSeed(tenantId, tenantCode, referrer, student, referralCode);
return new ReferralSeed(tenantId, tenantCode, referrer, student, admin, referralCode);
}
private static User User(Guid userId, string phone, string name, string role)
@@ -233,5 +315,6 @@ public sealed class ReferralEndpointTests
string TenantCode,
LoginSeed Referrer,
LoginSeed Student,
LoginSeed Admin,
string ReferralCode);
}