This commit is contained in:
2026-07-28 15:41:28 +08:00 Unverified
parent a61dacff1a
commit 2dae303cf1
17 changed files with 6796 additions and 90 deletions
@@ -55,10 +55,19 @@ public sealed class AttendanceControllerTests
EnrollmentYear = 2026,
EnrollmentDate = new DateOnly(2026, 9, 1)
};
var secondStudentUserId = Guid.NewGuid();
var secondStudentUser = new ApplicationUser
{
Id = secondStudentUserId,
UserName = "202601002",
NormalizedUserName = "202601002",
DisplayName = "吴同学"
};
var secondStudent = new Student
{
StudentNumber = "202601002",
Name = "吴同学",
UserId = secondStudentUserId,
AdministrativeClassId = administrativeClass.Id,
EnrollmentYear = 2026,
EnrollmentDate = new DateOnly(2026, 9, 1)
@@ -95,6 +104,7 @@ public sealed class AttendanceControllerTests
};
db.AddRange(
studentUser,
secondStudentUser,
college,
major,
administrativeClass,
@@ -198,6 +208,11 @@ public sealed class AttendanceControllerTests
StudentId = firstStudent.Id,
Status = AttendanceStatus.Absent
};
var secondQrRecord = new AttendanceRecord
{
StudentId = secondStudent.Id,
Status = AttendanceStatus.Absent
};
var qrSheet = new AttendanceSheet
{
TeachingTaskId = task.Id,
@@ -208,10 +223,17 @@ public sealed class AttendanceControllerTests
CheckInToken = "TEST-QR-TOKEN",
CheckInStartsAt = now.AddMinutes(-1),
CheckInEndsAt = now.AddMinutes(10),
Records = [qrRecord]
Records = [qrRecord, secondQrRecord]
};
db.AttendanceSheets.Add(qrSheet);
await db.SaveChangesAsync();
var qrChallengeResult = await controller.GetQrChallenge(
qrSheet.Id,
CancellationToken.None);
var qrChallengeOk = Assert.IsType<OkObjectResult>(qrChallengeResult);
var qrToken = Assert.IsType<string>(
qrChallengeOk.Value!.GetType().GetProperty("Token")!.GetValue(
qrChallengeOk.Value));
var studentController = new AttendanceController(
db,
@@ -231,23 +253,55 @@ public sealed class AttendanceControllerTests
item.GetType().GetProperty("TeachingTaskId")!.GetValue(item)));
var infoResult = await studentController.GetCheckInInfo(
qrSheet.CheckInToken,
qrToken,
CancellationToken.None);
Assert.IsType<OkObjectResult>(infoResult);
Assert.IsType<NotFoundResult>(
await studentController.GetCheckInInfo(
qrSheet.CheckInToken,
CancellationToken.None));
var qrCheckInResult = await studentController.CheckIn(
new AttendanceCheckInRequest(
null,
qrSheet.CheckInToken,
qrToken,
null,
null,
null),
null,
"test-device-1",
"android"),
CancellationToken.None);
Assert.IsType<OkObjectResult>(qrCheckInResult);
Assert.Equal(AttendanceStatus.Present, qrRecord.Status);
Assert.Equal(AttendanceCheckInMethod.QrCode, qrRecord.CheckedInMethod);
Assert.NotNull(qrRecord.CheckInAt);
Assert.Null(qrRecord.CheckInLatitude);
var qrAttempt = await db.AttendanceCheckInAttempts.SingleAsync(
x => x.AttendanceSheetId == qrSheet.Id);
Assert.True(qrAttempt.IsSuccessful);
Assert.Equal("android", qrAttempt.DevicePlatform);
Assert.NotNull(qrAttempt.DeviceIdentifierHash);
var secondStudentController = new AttendanceController(
db,
new StudentDataScope(secondStudentUserId));
Assert.IsType<OkObjectResult>(
await secondStudentController.CheckIn(
new AttendanceCheckInRequest(
null,
qrToken,
null,
null,
null,
"test-device-1",
"android"),
CancellationToken.None));
var sharedDeviceAttempt = await db.AttendanceCheckInAttempts.SingleAsync(
x => x.AttendanceSheetId == qrSheet.Id &&
x.StudentId == secondStudent.Id);
Assert.Contains(
"SharedDevice",
sharedDeviceAttempt.RiskFlags ?? string.Empty);
var locationRecord = new AttendanceRecord
{
@@ -282,6 +336,40 @@ public sealed class AttendanceControllerTests
Assert.IsType<ConflictObjectResult>(outsideResult);
Assert.Null(locationRecord.CheckInAt);
var inaccurateResult = await studentController.CheckIn(
new AttendanceCheckInRequest(
locationSheet.Id,
null,
39.9001m,
116.4m,
150),
CancellationToken.None);
Assert.IsType<ConflictObjectResult>(inaccurateResult);
Assert.Null(locationRecord.CheckInAt);
var missingAccuracyResult = await studentController.CheckIn(
new AttendanceCheckInRequest(
locationSheet.Id,
null,
39.9001m,
116.4m,
null),
CancellationToken.None);
Assert.IsType<ConflictObjectResult>(missingAccuracyResult);
Assert.Null(locationRecord.CheckInAt);
var locationFailures = await db.AttendanceCheckInAttempts
.Where(x =>
x.AttendanceSheetId == locationSheet.Id &&
!x.IsSuccessful)
.OrderBy(x => x.CreatedAt)
.ToListAsync();
Assert.Equal(3, locationFailures.Count);
Assert.Contains(
locationFailures,
x => (x.RiskFlags ?? string.Empty).Contains(
"RepeatedFailures",
StringComparison.Ordinal));
var nearbyResult = await studentController.CheckIn(
new AttendanceCheckInRequest(
locationSheet.Id,