Files
Academic-Affairs-System/tests/Jiaowu.Api.Tests/TeachingTasksControllerTests.cs
T
2026-07-25 11:46:39 +08:00

244 lines
8.5 KiB
C#

using Jiaowu.Api.Controllers;
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 TeachingTasksControllerTests
{
[Fact]
public async Task Delete_AllowsArchivedTask_ButRejectsPublishedTask()
{
await using var database = await TestDatabase.CreateAsync();
var archived = await database.AddTaskAsync("ARCHIVED", TeachingTaskStatus.Closed);
var published = await database.AddTaskAsync("PUBLISHED", TeachingTaskStatus.Published);
var deleteArchived = await database.Controller.Delete(
archived.Id,
CancellationToken.None);
var deletePublished = await database.Controller.Delete(
published.Id,
CancellationToken.None);
Assert.IsType<NoContentResult>(deleteArchived);
Assert.IsType<ConflictObjectResult>(deletePublished);
Assert.False(await database.Db.TeachingTasks.AnyAsync(x => x.Id == archived.Id));
Assert.True(await database.Db.TeachingTasks.AnyAsync(x => x.Id == published.Id));
}
[Fact]
public async Task Unarchive_RestoresArchivedTaskToPublished()
{
await using var database = await TestDatabase.CreateAsync();
var task = await database.AddTaskAsync("RESTORE", TeachingTaskStatus.Closed);
var result = await database.Controller.Unarchive(task.Id, CancellationToken.None);
Assert.IsType<NoContentResult>(result);
await database.Db.Entry(task).ReloadAsync();
Assert.Equal(TeachingTaskStatus.Published, task.Status);
}
[Fact]
public async Task Batch_UnarchivesOnlyArchivedTasks()
{
await using var database = await TestDatabase.CreateAsync();
var first = await database.AddTaskAsync("FIRST", TeachingTaskStatus.Closed);
var second = await database.AddTaskAsync("SECOND", TeachingTaskStatus.Closed);
var result = await database.Controller.Batch(
new TeachingTaskBatchRequest(
TeachingTaskBatchOperation.Unarchive,
[first.Id, second.Id]),
CancellationToken.None);
Assert.IsType<OkObjectResult>(result);
Assert.All(
await database.Db.TeachingTasks
.Where(x => x.Id == first.Id || x.Id == second.Id)
.ToListAsync(),
task => Assert.Equal(TeachingTaskStatus.Published, task.Status));
}
[Fact]
public async Task Options_ReturnsAllPublishedTasksWithoutListPageTruncation()
{
await using var database = await TestDatabase.CreateAsync();
for (var index = 1; index <= 125; index++)
{
await database.AddTaskAsync(
$"OPTION-{index:D3}",
TeachingTaskStatus.Published);
}
await database.AddTaskAsync("DRAFT", TeachingTaskStatus.Draft);
var result = await database.Controller.GetOptions(
database.TermId,
TeachingTaskStatus.Published,
CancellationToken.None);
var ok = Assert.IsType<OkObjectResult>(result);
var items = Assert.IsAssignableFrom<System.Collections.IEnumerable>(ok.Value);
Assert.Equal(125, items.Cast<object>().Count());
}
[Fact]
public async Task Options_PreservesCollegeDataScope()
{
await using var database = await TestDatabase.CreateAsync(collegeScoped: true);
await database.AddTaskAsync("IN-SCOPE", TeachingTaskStatus.Published);
var otherCollege = new College { Code = "OTHER", Name = "其他学院" };
var otherCourse = new Course
{
Code = "OTHER101",
Name = "范围外课程",
College = otherCollege,
Nature = CourseNature.MajorRequired,
TotalHours = 32,
LectureHours = 32
};
database.Db.Add(new TeachingTask
{
TaskNumber = "TASK-OUT-OF-SCOPE",
Name = "范围外教学任务",
AcademicTermId = database.TermId,
Course = otherCourse,
Capacity = 40,
StartWeek = 1,
EndWeek = 16,
WeeklyHours = 2,
Status = TeachingTaskStatus.Published,
PublishedAt = DateTime.UtcNow
});
await database.Db.SaveChangesAsync();
var result = await database.Controller.GetOptions(
database.TermId,
TeachingTaskStatus.Published,
CancellationToken.None);
var ok = Assert.IsType<OkObjectResult>(result);
var items = Assert.IsAssignableFrom<System.Collections.IEnumerable>(ok.Value);
Assert.Single(items.Cast<object>());
}
private sealed class TestDatabase : IAsyncDisposable
{
private readonly SqliteConnection connection;
private readonly AcademicTerm term;
private readonly Course course;
private TestDatabase(
SqliteConnection connection,
AppDbContext db,
AcademicTerm term,
Course course,
ICurrentUserDataScope dataScope)
{
this.connection = connection;
Db = db;
this.term = term;
this.course = course;
Controller = new TeachingTasksController(
db,
dataScope);
}
public AppDbContext Db { get; }
public TeachingTasksController Controller { get; }
public Guid TermId => term.Id;
public static async Task<TestDatabase> CreateAsync(bool collegeScoped = false)
{
var connection = new SqliteConnection("Data Source=:memory:");
await connection.OpenAsync();
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseSqlite(connection)
.Options;
var db = new AppDbContext(options);
await db.Database.EnsureCreatedAsync();
var college = new College { Code = "TEST", Name = "测试学院" };
var term = new AcademicTerm
{
Code = "2026-1",
Name = "2026—2027 学年第一学期",
AcademicYear = "2026-2027",
Season = TermSeason.Autumn,
StartDate = new DateOnly(2026, 9, 1),
EndDate = new DateOnly(2027, 1, 15)
};
var course = new Course
{
Code = "TEST101",
Name = "测试课程",
CollegeId = college.Id,
Nature = CourseNature.MajorRequired,
TotalHours = 32,
LectureHours = 32
};
db.AddRange(college, term, course);
await db.SaveChangesAsync();
return new TestDatabase(
connection,
db,
term,
course,
new TestDataScope(collegeScoped ? college.Id : null));
}
public async Task<TeachingTask> AddTaskAsync(
string suffix,
TeachingTaskStatus status)
{
var task = new TeachingTask
{
TaskNumber = $"TASK-{suffix}",
Name = $"教学任务 {suffix}",
AcademicTermId = term.Id,
CourseId = course.Id,
Capacity = 40,
StartWeek = 1,
EndWeek = 16,
WeeklyHours = 2,
Status = status,
PublishedAt = status == TeachingTaskStatus.Draft
? null
: DateTime.UtcNow
};
Db.TeachingTasks.Add(task);
await Db.SaveChangesAsync();
return task;
}
public async ValueTask DisposeAsync()
{
await Db.DisposeAsync();
await connection.DisposeAsync();
}
}
private sealed class TestDataScope : ICurrentUserDataScope
{
public TestDataScope(Guid? restrictedCollegeId = null)
{
Current = new CurrentUserScope(
Guid.NewGuid(),
"测试管理员",
restrictedCollegeId,
restrictedCollegeId.HasValue ? DataScope.College : DataScope.All,
new HashSet<string>(
restrictedCollegeId.HasValue
? [SystemRoles.CollegeAdmin]
: [SystemRoles.SuperAdmin]));
}
public CurrentUserScope Current { get; }
}
}