Files
AI-Health/backend/tests/Health.Tests/admin_service_tests.cs
MingNian ae94ced2d5 feat: 后台管理页全量重构 + backoffice 共享模块 + 启动脚本优化
## 后台管理页重构
- 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 扩展
2026-07-18 17:48:44 +08:00

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);
}
}