## 后台管理页重构 - admin 端: add_doctor / doctors / home / patients 四页重构 - doctor 端: consultations / dashboard / followup_edit / followups / home / patient_detail / patients / profile / report_detail / reports / settings 十一页重构 - 新增 backoffice 共享模块: backoffice_refresh_providers + backoffice_formatters + backoffice_ui ## 后端 - AdminService 增强(分页/搜索/统计) - doctor_endpoints 微调 - appsettings.Development 调整 - 新增 admin_service_tests ## 前端其他 - 今日健康卡片 taskRow 行高 5->7(每行 44->48px) - 健康仪表盘配色定 #4FACFE 纯色蓝 - admin_drawer / doctor_drawer 微调 - api_client IP 适配 ## 启动脚本 - start-dev.bat: 加后端就绪检测(轮询 openapi 端点, 最多等 30s) ## 清理 - 删除旧品牌图(agent_welcome_abstract / login_background_v1) - 删除 app_status_badge 组件 - 删除旧 HANDOFF 文档, 新增 07-17 版 ## 测试 - 新增 backoffice_formatters / backoffice_refresh / backoffice_ui 三个测试 - app_router_test 扩展
192 lines
6.3 KiB
C#
192 lines
6.3 KiB
C#
using Health.Application.Admin;
|
|
using Health.Domain.Entities;
|
|
using Health.Infrastructure.Admin;
|
|
using Health.Infrastructure.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace Health.Tests;
|
|
|
|
public sealed class AdminServiceTests
|
|
{
|
|
private static AppDbContext CreateDbContext()
|
|
{
|
|
var options = new DbContextOptionsBuilder<AppDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.Options;
|
|
return new AppDbContext(options);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddDoctor_CreatesDoctorLoginAndProfileTogether()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var service = new AdminService(db);
|
|
|
|
var result = await service.AddDoctorAsync(
|
|
new AddDoctorCommand("13800138001", "王医生", "主任医师", "心内科", "高血压"),
|
|
CancellationToken.None);
|
|
|
|
Assert.Equal(0, result.Code);
|
|
var doctor = await db.Doctors.SingleAsync();
|
|
var user = await db.Users.SingleAsync();
|
|
var profile = await db.DoctorProfiles.SingleAsync();
|
|
Assert.Equal("Doctor", user.Role);
|
|
Assert.Equal(doctor.Id, profile.DoctorId);
|
|
Assert.Equal(user.Id, profile.UserId);
|
|
Assert.Equal(doctor.Name, user.Name);
|
|
Assert.Equal(doctor.Name, profile.Name);
|
|
Assert.Equal(doctor.Title, profile.Title);
|
|
Assert.Equal(doctor.Department, profile.Department);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddDoctor_RejectsAPhoneAlreadyUsedByAnyAccount()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
db.Users.Add(new User
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Phone = "13800138002",
|
|
Role = "User",
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var service = new AdminService(db);
|
|
|
|
var result = await service.AddDoctorAsync(
|
|
new AddDoctorCommand("13800138002", "李医生", null, null, null),
|
|
CancellationToken.None);
|
|
|
|
Assert.NotEqual(0, result.Code);
|
|
Assert.Empty(db.Doctors);
|
|
Assert.Empty(db.DoctorProfiles);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateDoctor_SynchronizesDoctorLoginAndProfile()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var (doctor, user, profile) = await SeedDoctorAsync(db);
|
|
var service = new AdminService(db);
|
|
|
|
var result = await service.UpdateDoctorAsync(
|
|
doctor.Id,
|
|
new UpdateDoctorCommand("13800138999", "新姓名", "副主任医师", "神经内科", "脑血管"),
|
|
CancellationToken.None);
|
|
|
|
Assert.Equal(0, result.Code);
|
|
Assert.Equal("13800138999", doctor.Phone);
|
|
Assert.Equal("13800138999", user.Phone);
|
|
Assert.Equal("新姓名", doctor.Name);
|
|
Assert.Equal("新姓名", user.Name);
|
|
Assert.Equal("新姓名", profile.Name);
|
|
Assert.Equal("副主任医师", profile.Title);
|
|
Assert.Equal("神经内科", profile.Department);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ToggleDoctor_SynchronizesAvailabilityButKeepsLoginEnabled()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var (doctor, user, profile) = await SeedDoctorAsync(db);
|
|
var service = new AdminService(db);
|
|
|
|
var result = await service.ToggleDoctorAsync(doctor.Id, CancellationToken.None);
|
|
|
|
Assert.Equal(0, result.Code);
|
|
Assert.False(doctor.IsActive);
|
|
Assert.False(profile.IsActive);
|
|
Assert.Equal("Doctor", user.Role);
|
|
Assert.NotNull(await db.Users.FindAsync(user.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeleteDoctor_IsRejectedWhilePatientsAreBound()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var (doctor, user, profile) = await SeedDoctorAsync(db);
|
|
db.Users.Add(new User
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Phone = "13800138088",
|
|
Name = "患者",
|
|
Role = "User",
|
|
DoctorId = doctor.Id,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var service = new AdminService(db);
|
|
|
|
var result = await service.DeleteDoctorAsync(doctor.Id, CancellationToken.None);
|
|
|
|
Assert.NotEqual(0, result.Code);
|
|
Assert.NotNull(await db.Doctors.FindAsync(doctor.Id));
|
|
Assert.NotNull(await db.Users.FindAsync(user.Id));
|
|
Assert.NotNull(await db.DoctorProfiles.FindAsync(profile.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeleteUnusedDoctor_RemovesLoginProfileAndRefreshTokens()
|
|
{
|
|
await using var db = CreateDbContext();
|
|
var (doctor, user, _) = await SeedDoctorAsync(db);
|
|
db.RefreshTokens.Add(new RefreshToken
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = user.Id,
|
|
Token = "refresh-token",
|
|
ExpiresAt = DateTime.UtcNow.AddDays(1),
|
|
});
|
|
await db.SaveChangesAsync();
|
|
var service = new AdminService(db);
|
|
|
|
var result = await service.DeleteDoctorAsync(doctor.Id, CancellationToken.None);
|
|
|
|
Assert.Equal(0, result.Code);
|
|
Assert.Empty(db.Doctors);
|
|
Assert.Empty(db.DoctorProfiles);
|
|
Assert.Empty(db.Users);
|
|
Assert.Empty(db.RefreshTokens);
|
|
}
|
|
|
|
private static async Task<(Doctor Doctor, User User, DoctorProfile Profile)> SeedDoctorAsync(AppDbContext db)
|
|
{
|
|
var doctor = new Doctor
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Phone = "13800138003",
|
|
Name = "原姓名",
|
|
Title = "医师",
|
|
Department = "内科",
|
|
IsActive = true,
|
|
CreatedAt = DateTime.UtcNow,
|
|
};
|
|
var user = new User
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Phone = doctor.Phone,
|
|
Name = doctor.Name,
|
|
Role = "Doctor",
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
};
|
|
var profile = new DoctorProfile
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
DoctorId = doctor.Id,
|
|
UserId = user.Id,
|
|
Name = doctor.Name,
|
|
Title = doctor.Title,
|
|
Department = doctor.Department,
|
|
IsActive = true,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow,
|
|
};
|
|
db.AddRange(doctor, user, profile);
|
|
await db.SaveChangesAsync();
|
|
return (doctor, user, profile);
|
|
}
|
|
}
|