forked from gongxuegit/tiku-backend.net
feat: add student profile endpoints
This commit is contained in:
182
Tiku.IntegrationTests/Api/ProfileEndpointTests.cs
Normal file
182
Tiku.IntegrationTests/Api/ProfileEndpointTests.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Tiku.Api.Contracts;
|
||||
using Tiku.Domain.Catalog;
|
||||
using Tiku.Domain.Common;
|
||||
using Tiku.Domain.Identity;
|
||||
using Tiku.Domain.Learning;
|
||||
using Tiku.Domain.Tenancy;
|
||||
using Tiku.Infrastructure.Auth;
|
||||
using Tiku.Infrastructure.Persistence;
|
||||
|
||||
namespace Tiku.IntegrationTests.Api;
|
||||
|
||||
public sealed class ProfileEndpointTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Logged_in_student_can_get_and_update_profile()
|
||||
{
|
||||
await using var factory = new ApiTestFactory();
|
||||
var seed = await SeedStudentAsync(factory);
|
||||
var regionId = Guid.NewGuid();
|
||||
var schoolId = Guid.NewGuid();
|
||||
var majorId = Guid.NewGuid();
|
||||
await factory.SeedAsync(
|
||||
new Region { Id = regionId, TenantId = seed.TenantId, Name = "四川" },
|
||||
new School { Id = schoolId, TenantId = seed.TenantId, RegionId = regionId, Name = "美术学院" },
|
||||
new Major { Id = majorId, TenantId = seed.TenantId, RegionId = regionId, SchoolId = schoolId, Name = "视觉传达" });
|
||||
using var client = factory.CreateClient();
|
||||
await LoginAsync(client, seed);
|
||||
|
||||
using var getResponse = await client.GetAsync("/api/profile/me");
|
||||
var getJson = await ReadJsonAsync(getResponse);
|
||||
using var patchResponse = await client.PatchAsJsonAsync(
|
||||
"/api/profile/me",
|
||||
new UpdateProfileDto
|
||||
{
|
||||
Name = "张三",
|
||||
AvatarPreset = "female",
|
||||
RegionId = regionId,
|
||||
SelectedSchoolId = schoolId,
|
||||
SelectedMajorId = majorId,
|
||||
Stats = JsonSerializer.SerializeToElement(new { level = 3 }),
|
||||
RecentActivities = JsonSerializer.SerializeToElement(new[] { new { type = "login" } })
|
||||
});
|
||||
var patchJson = await ReadJsonAsync(patchResponse);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, getResponse.StatusCode);
|
||||
Assert.NotEqual(Guid.Empty, getJson.RootElement.GetProperty("profileId").GetGuid());
|
||||
Assert.Equal(HttpStatusCode.OK, patchResponse.StatusCode);
|
||||
Assert.Equal("张三", patchJson.RootElement.GetProperty("name").GetString());
|
||||
Assert.Equal("female", patchJson.RootElement.GetProperty("avatarPreset").GetString());
|
||||
Assert.Equal(regionId, patchJson.RootElement.GetProperty("regionId").GetGuid());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Exam_countdowns_use_current_profile_target()
|
||||
{
|
||||
await using var factory = new ApiTestFactory();
|
||||
var seed = await SeedStudentAsync(factory);
|
||||
var regionId = Guid.NewGuid();
|
||||
var schoolId = Guid.NewGuid();
|
||||
await factory.SeedAsync(
|
||||
new StudentProfile
|
||||
{
|
||||
TenantId = seed.TenantId,
|
||||
UserId = seed.UserId,
|
||||
RegionId = regionId,
|
||||
SelectedSchoolId = schoolId
|
||||
},
|
||||
new ExamDate
|
||||
{
|
||||
TenantId = seed.TenantId,
|
||||
RegionId = regionId,
|
||||
SchoolId = schoolId,
|
||||
ExamName = "校考",
|
||||
ExamAt = DateTimeOffset.UtcNow.AddDays(10)
|
||||
},
|
||||
new ExamDate
|
||||
{
|
||||
TenantId = seed.TenantId,
|
||||
RegionId = Guid.NewGuid(),
|
||||
ExamName = "其他地区考试",
|
||||
ExamAt = DateTimeOffset.UtcNow.AddDays(1)
|
||||
});
|
||||
using var client = factory.CreateClient();
|
||||
await LoginAsync(client, seed);
|
||||
|
||||
using var response = await client.GetAsync("/api/profile/exam-countdowns");
|
||||
var json = await ReadJsonAsync(response);
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var item = json.RootElement.GetProperty("items").EnumerateArray().Single();
|
||||
Assert.Equal("校考", item.GetProperty("examName").GetString());
|
||||
Assert.True(item.GetProperty("daysLeft").GetInt32() >= 9);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Profile_requires_authentication()
|
||||
{
|
||||
await using var factory = new ApiTestFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
using var response = await client.GetAsync("/api/profile/me");
|
||||
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedStudentAsync(ApiTestFactory factory)
|
||||
{
|
||||
var tenantId = Guid.NewGuid();
|
||||
var userId = Guid.NewGuid();
|
||||
var phone = $"136{Random.Shared.Next(10000000, 99999999)}";
|
||||
var passwordHash = new PasswordHasher().Hash("passw0rd!");
|
||||
await factory.SeedAsync(
|
||||
new Tenant
|
||||
{
|
||||
Id = tenantId,
|
||||
Slug = tenantId.ToString("N"),
|
||||
Name = "Test Tenant",
|
||||
Status = TenantStatus.Active,
|
||||
Metadata = JsonDefaults.Object()
|
||||
},
|
||||
new User
|
||||
{
|
||||
Id = userId,
|
||||
Phone = phone,
|
||||
Name = "Student"
|
||||
},
|
||||
new TenantMembership
|
||||
{
|
||||
TenantId = tenantId,
|
||||
UserId = userId,
|
||||
Role = TenantRole.Student,
|
||||
Status = MembershipStatus.Active
|
||||
},
|
||||
new UserIdentity
|
||||
{
|
||||
UserId = userId,
|
||||
Provider = "password",
|
||||
ProviderSubject = phone,
|
||||
Phone = phone,
|
||||
SecretPayload = CreateSecretPayload(passwordHash)
|
||||
});
|
||||
|
||||
return (tenantId, userId, phone);
|
||||
}
|
||||
|
||||
private static async Task LoginAsync(
|
||||
HttpClient client,
|
||||
(Guid TenantId, Guid UserId, string Phone) seed)
|
||||
{
|
||||
var loginResponse = await client.PostAsJsonAsync(
|
||||
"/api/auth/login/password",
|
||||
new PasswordLoginDto
|
||||
{
|
||||
TenantId = seed.TenantId,
|
||||
Phone = seed.Phone,
|
||||
Password = "passw0rd!"
|
||||
});
|
||||
var loginJson = await ReadJsonAsync(loginResponse);
|
||||
var accessToken = loginJson.RootElement
|
||||
.GetProperty("tokens")
|
||||
.GetProperty("accessToken")
|
||||
.GetString();
|
||||
client.DefaultRequestHeaders.Authorization = new("Bearer", accessToken);
|
||||
}
|
||||
|
||||
private static async Task<JsonDocument> ReadJsonAsync(HttpResponseMessage response)
|
||||
{
|
||||
var stream = await response.Content.ReadAsStreamAsync();
|
||||
return await JsonDocument.ParseAsync(stream);
|
||||
}
|
||||
|
||||
private static JsonElement CreateSecretPayload(string passwordHash)
|
||||
{
|
||||
using var document = JsonDocument.Parse(
|
||||
$$"""{"passwordHash":{{JsonSerializer.Serialize(passwordHash)}}}""");
|
||||
return document.RootElement.Clone();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user