Files
biss 77dfa8145c 主要变更:
新增统一缓存封装:[AppCache.cs (line 12)](/E:/jiaowu/src/Jiaowu.Api/Infrastructure/Caching/AppCache.cs:12)
接入 HybridCache 和可选 Redis:[Program.cs (line 153)](/E:/jiaowu/src/Jiaowu.Api/Program.cs:153)
缓存学生激活选项、基础数据、公开课表和课表选项。
个人课表、选课容量、成绩、权限、通知和任务状态保持实时查询。
基础数据、课程、教师、教学任务、作息、考试和课表发布后自动失效相关缓存。
新增 /health/cache,Redis 故障不影响 /health/ready。
Compose 增加 256MB、allkeys-lfu、无持久化的 redis:8.8-alpine 服务;该镜像标签已由 Docker 官方镜像仓库核对。
更新 [.env.example (line 1)](/E:/jiaowu/.env.example:1) 和 [README.md (line 190)](/E:/jiaowu/README.md:190) 部署说明。
2026-07-26 14:42:27 +08:00

131 lines
4.3 KiB
C#

using Jiaowu.Api.Controllers;
using Jiaowu.Api.Domain.Academic;
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 BaseDataControllerTests : IAsyncDisposable
{
private readonly SqliteConnection connection = new("Data Source=:memory:");
private readonly AppDbContext db;
private readonly BaseDataController controller;
public BaseDataControllerTests()
{
connection.Open();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(connection)
.Options;
db = new AppDbContext(options);
db.Database.EnsureCreated();
controller = new BaseDataController(db, NoOpAppCache.Instance);
}
[Fact]
public async Task SetCurrentTerm_clears_previous_current_in_one_save()
{
var previous = CreateTerm("2025-2026-2", true);
var next = CreateTerm("2026-2027-1", false);
db.AcademicTerms.AddRange(previous, next);
await db.SaveChangesAsync();
var result = await controller.SetCurrentTerm(next.Id, CancellationToken.None);
Assert.Null(result.Result);
db.ChangeTracker.Clear();
Assert.False((await db.AcademicTerms.FindAsync(previous.Id))!.IsCurrent);
Assert.True((await db.AcademicTerms.FindAsync(next.Id))!.IsCurrent);
Assert.Equal(1, await db.AcademicTerms.CountAsync(x => x.IsCurrent));
}
[Fact]
public async Task Archive_requires_switching_current_and_is_reversible()
{
var current = CreateTerm("2026-2027-1", true);
var history = CreateTerm("2025-2026-2", false);
db.AcademicTerms.AddRange(current, history);
await db.SaveChangesAsync();
var currentResult = await controller.ArchiveTerm(
current.Id, CancellationToken.None);
Assert.IsType<ConflictObjectResult>(currentResult.Result);
var archiveResult = await controller.ArchiveTerm(
history.Id, CancellationToken.None);
Assert.Null(archiveResult.Result);
Assert.True(history.IsArchived);
Assert.NotNull(history.ArchivedAt);
Assert.True(history.IsEnabled);
var unarchiveResult = await controller.UnarchiveTerm(
history.Id, CancellationToken.None);
Assert.Null(unarchiveResult.Result);
Assert.False(history.IsArchived);
Assert.Null(history.ArchivedAt);
}
[Fact]
public async Task Archived_term_must_be_unarchived_before_becoming_current()
{
var current = CreateTerm("2026-2027-1", true);
var archived = CreateTerm("2025-2026-2", false);
archived.IsArchived = true;
archived.ArchivedAt = DateTime.UtcNow;
db.AcademicTerms.AddRange(current, archived);
await db.SaveChangesAsync();
var result = await controller.SetCurrentTerm(
archived.Id, CancellationToken.None);
Assert.IsType<ConflictObjectResult>(result.Result);
Assert.True(current.IsCurrent);
Assert.False(archived.IsCurrent);
}
[Fact]
public async Task Current_term_cannot_be_cleared_without_a_replacement()
{
var current = CreateTerm("2026-2027-1", true);
db.AcademicTerms.Add(current);
await db.SaveChangesAsync();
var request = new TermRequest(
current.Code,
current.Name,
true,
current.AcademicYear,
current.Season,
current.StartDate,
current.EndDate,
false);
var result = await controller.UpdateTerm(
current.Id, request, CancellationToken.None);
Assert.IsType<ConflictObjectResult>(result.Result);
Assert.True(current.IsCurrent);
}
private static AcademicTerm CreateTerm(string code, bool isCurrent) =>
new()
{
Code = code,
Name = code,
AcademicYear = code[..9],
Season = TermSeason.Autumn,
StartDate = new DateOnly(2026, 9, 1),
EndDate = new DateOnly(2027, 1, 20),
IsCurrent = isCurrent,
IsEnabled = true
};
public async ValueTask DisposeAsync()
{
await db.DisposeAsync();
await connection.DisposeAsync();
}
}