Files
Academic-Affairs-System/tests/Jiaowu.Api.Tests/PhysicalFitnessControllerTests.cs
2026-09-29 10:24:14 +08:00

151 lines
7.8 KiB
C#

using Jiaowu.Api.Controllers;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace Jiaowu.Api.Tests;
public sealed class PhysicalFitnessControllerTests
{
[Fact]
public async Task UpsertRecords_CalculatesAndPersistsItemSnapshot()
{
await using var fixture = await Fixture.CreateAsync();
var controller = new PhysicalFitnessController(fixture.Db, fixture.AllScope);
var action = await controller.UpsertRecords(fixture.Batch.Id,
new UpsertPhysicalTestRecordsRequest([
new PhysicalTestRecordRequest(
fixture.Student.StudentNumber, PhysicalTestRecordStatus.Normal, 1,
175, 70, 5040, 6.7m, 24.9m, 273, 19, 197, null, "已复核")
]), CancellationToken.None);
Assert.IsType<OkObjectResult>(action);
fixture.Db.ChangeTracker.Clear();
var saved = await fixture.Db.PhysicalTestRecords.Include(x => x.Items).SingleAsync();
Assert.Equal(100m, saved.TotalScore);
Assert.Equal(PhysicalTestGrade.Excellent, saved.Grade);
Assert.Equal(9, saved.Items.Count);
Assert.Equal("已复核", saved.Notes);
Assert.Equal(PhysicalTestBatchStatus.Draft, (await fixture.Db.PhysicalTestBatches.FindAsync(fixture.Batch.Id))!.Status);
fixture.Db.ChangeTracker.Clear();
var secondController = new PhysicalFitnessController(fixture.Db, fixture.AllScope);
var updated = await secondController.UpsertRecords(fixture.Batch.Id,
new UpsertPhysicalTestRecordsRequest([
new PhysicalTestRecordRequest(
fixture.Student.StudentNumber, PhysicalTestRecordStatus.Normal, 1,
175, 70, 4920, 6.8m, 23.1m, 268, 18, 202, null, "二次复核")
]), CancellationToken.None);
Assert.IsType<OkObjectResult>(updated);
fixture.Db.ChangeTracker.Clear();
var replacement = await fixture.Db.PhysicalTestRecords.Include(x => x.Items).SingleAsync();
Assert.Equal(9, replacement.Items.Count);
Assert.Equal("二次复核", replacement.Notes);
}
[Fact]
public async Task CollegeViewer_OnlySeesRecordsInsideCollegeScope()
{
await using var fixture = await Fixture.CreateAsync(includeSecondCollege: true);
fixture.Db.PhysicalTestRecords.AddRange(
new PhysicalTestRecord { PhysicalTestBatchId = fixture.Batch.Id, StudentId = fixture.Student.Id, GradeLevel = 1, Status = PhysicalTestRecordStatus.Exempt, SpecialReason = "测试" },
new PhysicalTestRecord { PhysicalTestBatchId = fixture.Batch.Id, StudentId = fixture.OtherStudent!.Id, GradeLevel = 1, Status = PhysicalTestRecordStatus.Exempt, SpecialReason = "测试" });
await fixture.Db.SaveChangesAsync();
var controller = new PhysicalFitnessController(fixture.Db, fixture.CollegeScope);
var action = Assert.IsType<OkObjectResult>(await controller.GetRecords(
fixture.Batch.Id, pageSize: 30, ct: CancellationToken.None));
var json = System.Text.Json.JsonSerializer.Serialize(action.Value);
Assert.Contains(fixture.Student.StudentNumber, json);
Assert.DoesNotContain(fixture.OtherStudent.StudentNumber, json);
var batches = Assert.IsType<OkObjectResult>(await controller.GetBatches(
pageSize: 20, ct: CancellationToken.None));
var page = Assert.IsType<CursorPagedResult<object>>(batches.Value);
var visibleCount = page.Items.Single().GetType().GetProperty("RecordCount")!.GetValue(page.Items.Single());
Assert.Equal(1, visibleCount);
}
[Fact]
public async Task Publish_RejectsEmptyBatch()
{
await using var fixture = await Fixture.CreateAsync();
var controller = new PhysicalFitnessController(fixture.Db, fixture.AllScope);
var action = await controller.Publish(fixture.Batch.Id, CancellationToken.None);
var conflict = Assert.IsType<ConflictObjectResult>(action);
Assert.Contains("没有体测记录", Assert.IsType<ProblemDetails>(conflict.Value).Detail);
}
private sealed class Fixture : IAsyncDisposable
{
private readonly SqliteConnection connection;
public AppDbContext Db { get; }
public College College { get; }
public Student Student { get; }
public Student? OtherStudent { get; }
public PhysicalTestBatch Batch { get; }
public ICurrentUserDataScope AllScope { get; }
public ICurrentUserDataScope CollegeScope { get; }
private Fixture(SqliteConnection connection, AppDbContext db, College college,
Student student, Student? otherStudent, PhysicalTestBatch batch)
{
this.connection = connection;
Db = db;
College = college;
Student = student;
OtherStudent = otherStudent;
Batch = batch;
AllScope = new TestScope(null, DataScope.All, SystemRoles.PhysicalEducationAdmin);
CollegeScope = new TestScope(college.Id, DataScope.College, SystemRoles.CollegeAdmin);
}
public static async Task<Fixture> CreateAsync(bool includeSecondCollege = false)
{
var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
var db = new AppDbContext(new DbContextOptionsBuilder<AppDbContext>().UseSqlite(connection).Options);
await db.Database.EnsureCreatedAsync();
var college = new College { Code = "CS", Name = "计算机学院" };
var major = new Major { Code = "SE", Name = "软件工程", CollegeId = college.Id, DegreeType = "工学" };
var administrativeClass = new AdministrativeClass { Code = "SE202601", Name = "软件工程 2026 级 1 班", MajorId = major.Id, Grade = 2026 };
var student = new Student { StudentNumber = "202600001", Name = "学生甲", Gender = Gender.Male, AdministrativeClassId = administrativeClass.Id, EnrollmentYear = 2026, EnrollmentDate = new DateOnly(2026, 9, 1) };
Student? otherStudent = null;
db.AddRange(college, major, administrativeClass, student);
if (includeSecondCollege)
{
var otherCollege = new College { Code = "ART", Name = "艺术学院" };
var otherMajor = new Major { Code = "ART", Name = "艺术学", CollegeId = otherCollege.Id, DegreeType = "艺术学" };
var otherClass = new AdministrativeClass { Code = "ART202601", Name = "艺术 2026 级 1 班", MajorId = otherMajor.Id, Grade = 2026 };
otherStudent = new Student { StudentNumber = "202600002", Name = "学生乙", Gender = Gender.Female, AdministrativeClassId = otherClass.Id, EnrollmentYear = 2026, EnrollmentDate = new DateOnly(2026, 9, 1) };
db.AddRange(otherCollege, otherMajor, otherClass, otherStudent);
}
var batch = new PhysicalTestBatch { Name = "秋季体测", AcademicYear = "2026-2027", TestDate = new DateOnly(2026, 10, 15), StandardVersion = "国家学生体质健康标准(2014年修订)" };
db.Add(batch);
await db.SaveChangesAsync();
return new Fixture(connection, db, college, student, otherStudent, batch);
}
public async ValueTask DisposeAsync()
{
await Db.DisposeAsync();
await connection.DisposeAsync();
}
}
private sealed class TestScope(Guid? collegeId, DataScope dataScope, string role) : ICurrentUserDataScope
{
public CurrentUserScope Current { get; } = new(
Guid.NewGuid(), "测试用户", collegeId, dataScope, new HashSet<string>([role]));
}
}