修复统计图
Build and publish Jiaowu packages and container image / Test, package and publish (push) Successful in 34m39s
Build and publish Jiaowu packages and container image / Test, package and publish (push) Successful in 34m39s
This commit is contained in:
@@ -25,7 +25,12 @@ public sealed class StatisticsController(
|
|||||||
SystemRoles.CollegeAdmin + "," +
|
SystemRoles.CollegeAdmin + "," +
|
||||||
SystemRoles.Leader;
|
SystemRoles.Leader;
|
||||||
|
|
||||||
private Guid? RestrictedCollegeId => currentUserDataScope.Current.RestrictedCollegeId;
|
// HybridCache may execute its source factory without the request's ambient
|
||||||
|
// HttpContext. Keep the authorization scope stable for the whole controller
|
||||||
|
// invocation instead of resolving it again inside that background factory.
|
||||||
|
private readonly CurrentUserScope currentUser = currentUserDataScope.Current;
|
||||||
|
|
||||||
|
private Guid? RestrictedCollegeId => currentUser.RestrictedCollegeId;
|
||||||
|
|
||||||
private Guid? ResolveCollegeId(Guid? requestedCollegeId)
|
private Guid? ResolveCollegeId(Guid? requestedCollegeId)
|
||||||
{
|
{
|
||||||
@@ -982,7 +987,7 @@ public sealed class StatisticsController(
|
|||||||
params string?[] filters) =>
|
params string?[] filters) =>
|
||||||
AppCacheKeys.Statistics(
|
AppCacheKeys.Statistics(
|
||||||
area,
|
area,
|
||||||
currentUserDataScope.Current.Scope.ToString(),
|
currentUser.Scope.ToString(),
|
||||||
effectiveCollegeId,
|
effectiveCollegeId,
|
||||||
filters);
|
filters);
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,36 @@ public sealed class StatisticsControllerTests
|
|||||||
Assert.Equal(2, cache.SourceCalls);
|
Assert.Equal(2, cache.SourceCalls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Course_summary_keeps_request_scope_when_cache_factory_loses_http_context()
|
||||||
|
{
|
||||||
|
await using var connection = new SqliteConnection("Data Source=:memory:");
|
||||||
|
await connection.OpenAsync();
|
||||||
|
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||||
|
.UseSqlite(connection)
|
||||||
|
.Options;
|
||||||
|
await using var db = new AppDbContext(options);
|
||||||
|
await db.Database.EnsureCreatedAsync();
|
||||||
|
|
||||||
|
var college = new College { Code = "C01", Name = "第一学院" };
|
||||||
|
var category = new CourseCategory { Code = "CAT", Name = "测试分类" };
|
||||||
|
db.AddRange(college, category, CreateCourse("C001", college, category));
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
var scope = new MutableDataScope(college.Id);
|
||||||
|
var cache = new RecordingCache(() => scope.LoseHttpContext());
|
||||||
|
var controller = new StatisticsController(db, scope, cache);
|
||||||
|
|
||||||
|
var result = await controller.GetCourseSummary(
|
||||||
|
college.Id,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Equal(1, TotalCourses(result));
|
||||||
|
Assert.Equal(1, cache.SourceCalls);
|
||||||
|
}
|
||||||
|
|
||||||
private static int TotalCourses(ActionResult<object> result)
|
private static int TotalCourses(ActionResult<object> result)
|
||||||
{
|
{
|
||||||
var json = Assert.IsType<JsonElement>(result.Value);
|
var json = Assert.IsType<JsonElement>(result.Value);
|
||||||
@@ -117,7 +147,7 @@ public sealed class StatisticsControllerTests
|
|||||||
AssessmentMethod = AssessmentMethod.Examination
|
AssessmentMethod = AssessmentMethod.Examination
|
||||||
};
|
};
|
||||||
|
|
||||||
private sealed class RecordingCache : IAppCache
|
private sealed class RecordingCache(Action? beforeFactory = null) : IAppCache
|
||||||
{
|
{
|
||||||
private readonly Dictionary<string, object> values = [];
|
private readonly Dictionary<string, object> values = [];
|
||||||
|
|
||||||
@@ -137,6 +167,7 @@ public sealed class StatisticsControllerTests
|
|||||||
|
|
||||||
SourceCalls++;
|
SourceCalls++;
|
||||||
Tags.Add(tags.ToArray());
|
Tags.Add(tags.ToArray());
|
||||||
|
beforeFactory?.Invoke();
|
||||||
var loaded = await factory(cancellationToken);
|
var loaded = await factory(cancellationToken);
|
||||||
values[key] = loaded!;
|
values[key] = loaded!;
|
||||||
return loaded;
|
return loaded;
|
||||||
@@ -157,4 +188,29 @@ public sealed class StatisticsControllerTests
|
|||||||
DataScope.College,
|
DataScope.College,
|
||||||
new HashSet<string> { SystemRoles.CollegeAdmin });
|
new HashSet<string> { SystemRoles.CollegeAdmin });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class MutableDataScope(Guid collegeId) : ICurrentUserDataScope
|
||||||
|
{
|
||||||
|
private CurrentUserScope current = CreateCollegeScope(collegeId);
|
||||||
|
|
||||||
|
public CurrentUserScope Current => current;
|
||||||
|
|
||||||
|
public void LoseHttpContext()
|
||||||
|
{
|
||||||
|
current = new CurrentUserScope(
|
||||||
|
Guid.Empty,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
DataScope.Self,
|
||||||
|
new HashSet<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CurrentUserScope CreateCollegeScope(Guid value) =>
|
||||||
|
new(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
"学院管理员",
|
||||||
|
value,
|
||||||
|
DataScope.College,
|
||||||
|
new HashSet<string> { SystemRoles.CollegeAdmin });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user