using Jiaowu.Api.Controllers; using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Infrastructure.Auth; using Jiaowu.Api.Infrastructure.Caching; using Jiaowu.Api.Infrastructure.Persistence; using Microsoft.AspNetCore.Mvc; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; namespace Jiaowu.Api.Tests; public sealed class StudentProfileControllerTests { [Fact] public async Task Student_can_update_profile_without_changing_identity_or_registration() { await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using var db = new AppDbContext(options); await db.Database.EnsureCreatedAsync(); var user = new ApplicationUser { Id = Guid.NewGuid(), UserName = "2026001", NormalizedUserName = "2026001", DisplayName = "原姓名" }; var college = new College { Code = "CS", Name = "计算机学院" }; var major = new Major { Code = "080901", Name = "计算机科学与技术", CollegeId = college.Id, DegreeType = "工学学士" }; var administrativeClass = new AdministrativeClass { Code = "CS2026-01", Name = "计科 2026-1 班", MajorId = major.Id, Grade = 2026 }; var student = new Student { StudentNumber = "2026001", Name = "原姓名", UserId = user.Id, AdministrativeClassId = administrativeClass.Id, EnrollmentYear = 2026, EnrollmentDate = new DateOnly(2026, 9, 1), Status = StudentStatus.Active }; db.AddRange(user, college); await db.SaveChangesAsync(); db.Add(major); await db.SaveChangesAsync(); db.Add(administrativeClass); await db.SaveChangesAsync(); db.Add(student); await db.SaveChangesAsync(); var controller = new StudentProfileController( db, new StudentDataScope(user.Id), new NoOpCache()); var request = new StudentProfileUpdateRequest( Gender.Female, new DateOnly(2008, 3, 12), " Alice ", "370000000000000000", "中国", "汉族", "共青团员", "山东济南", "户籍地址", "现住地址", "250000", "13800000000", "student@example.edu.cn", "123456", "alice-wechat", "家长", "母亲", "13900000000", "听力支持, 走读", "上课时需要靠前座位", "个人简介"); Assert.IsType(await controller.Update( request, CancellationToken.None)); db.ChangeTracker.Clear(); var updated = await db.Students.SingleAsync(); Assert.Equal("2026001", updated.StudentNumber); Assert.Equal("原姓名", updated.Name); Assert.Equal(administrativeClass.Id, updated.AdministrativeClassId); Assert.Equal(StudentStatus.Active, updated.Status); Assert.Equal("Alice", updated.EnglishName); Assert.Equal("13800000000", updated.Phone); Assert.Equal("听力支持, 走读", updated.SpecialTags); Assert.Equal("上课时需要靠前座位", updated.SpecialNeeds); } [Fact] public async Task Missing_linked_student_returns_not_found() { await using var connection = new SqliteConnection("Data Source=:memory:"); await connection.OpenAsync(); var options = new DbContextOptionsBuilder() .UseSqlite(connection) .Options; await using var db = new AppDbContext(options); await db.Database.EnsureCreatedAsync(); var controller = new StudentProfileController( db, new StudentDataScope(Guid.NewGuid()), new NoOpCache()); var result = await controller.Get(CancellationToken.None); Assert.IsType(result.Result); } private sealed class NoOpCache : IAppCache { public Task GetOrCreateAsync( string key, Func> factory, AppCacheProfile profile, IReadOnlyCollection tags, CancellationToken cancellationToken) => factory(cancellationToken); public ValueTask RemoveByTagAsync( string tag, CancellationToken cancellationToken = default) => ValueTask.CompletedTask; } private sealed class StudentDataScope(Guid userId) : ICurrentUserDataScope { public CurrentUserScope Current { get; } = new( userId, "测试学生", null, DataScope.Self, new HashSet([SystemRoles.Student])); } }