Files
Academic-Affairs-System/scripts/smoke-test.ps1
T
biss d7cf3f9e76 课程库 Excel 导入导出已完成:
支持下载标准导入模板。
导出严格沿用当前关键词、学院、分类、课程性质和状态筛选。
以课程编码为唯一键:存在则更新,不存在则新增。
校验学院编码、课程分类、课程性质、学分学时及考核方式。
整批原子导入,任一行错误则全部不写入,并提示具体行号。
学院管理员仍只能维护本学院的专业课和实践课,无法借 Excel 越权。
界面入口沿用现有课程库工具栏样式。
2026-07-24 18:56:25 +08:00

832 lines
36 KiB
PowerShell

param(
[string] $ApiExecutablePath,
[string] $ApiContentRoot
)
$ErrorActionPreference = 'Stop'
$workspaceRoot = Split-Path -Parent $PSScriptRoot
$apiExecutable = if ([string]::IsNullOrWhiteSpace($ApiExecutablePath)) {
Join-Path $workspaceRoot 'src/Jiaowu.Api/bin/Debug/net10.0/Jiaowu.Api.exe'
}
else {
[System.IO.Path]::GetFullPath($ApiExecutablePath, $workspaceRoot)
}
$apiWorkingDirectory = if ([string]::IsNullOrWhiteSpace($ApiContentRoot)) {
Join-Path $workspaceRoot 'src/Jiaowu.Api'
}
else {
[System.IO.Path]::GetFullPath($ApiContentRoot, $workspaceRoot)
}
$stdoutPath = Join-Path $env:TEMP 'jiaowu-api-smoke.out.log'
$stderrPath = Join-Path $env:TEMP 'jiaowu-api-smoke.err.log'
$courseExcelToken = [guid]::NewGuid().ToString('N')
$courseTemplatePath = Join-Path $env:TEMP "jiaowu-course-template-$courseExcelToken.xlsx"
$courseExportPath = Join-Path $env:TEMP "jiaowu-course-export-$courseExcelToken.xlsx"
$env:ASPNETCORE_ENVIRONMENT = 'Development'
$env:ASPNETCORE_URLS = 'http://localhost:5255'
$startParameters = @{
FilePath = $apiExecutable
WorkingDirectory = $apiWorkingDirectory
WindowStyle = 'Hidden'
RedirectStandardOutput = $stdoutPath
RedirectStandardError = $stderrPath
PassThru = $true
}
$process = Start-Process @startParameters
try {
$healthy = $false
for ($attempt = 0; $attempt -lt 30; $attempt++) {
Start-Sleep -Milliseconds 500
try {
$health = Invoke-RestMethod -Uri 'http://localhost:5255/health' -TimeoutSec 2
$healthy = $true
break
}
catch {
# The API may still be starting.
}
}
if (-not $healthy) {
Get-Content -LiteralPath $stdoutPath -ErrorAction SilentlyContinue
Get-Content -LiteralPath $stderrPath -ErrorAction SilentlyContinue
throw 'API did not become healthy.'
}
$loginBody = @{
userName = 'admin'
password = 'Admin@123456'
} | ConvertTo-Json
$loginParameters = @{
Method = 'Post'
Uri = 'http://localhost:5255/api/auth/login'
ContentType = 'application/json'
Body = $loginBody
}
$login = Invoke-RestMethod @loginParameters
$headers = @{ Authorization = "Bearer $($login.token)" }
$dashboard = Invoke-RestMethod -Uri 'http://localhost:5255/api/dashboard' -Headers $headers
$campuses = Invoke-RestMethod -Uri 'http://localhost:5255/api/base-data/campuses' -Headers $headers
$classes = Invoke-RestMethod -Uri 'http://localhost:5255/api/base-data/classes' -Headers $headers
$counselors = Invoke-RestMethod -Uri 'http://localhost:5255/api/base-data/counselors' -Headers $headers
$assignedClassCount = @($classes | Where-Object {
$null -ne $_.counselorUserId -and
@($counselors).id -contains $_.counselorUserId
}).Count
if ($assignedClassCount -lt 1) {
throw 'No administrative class is linked to a counselor account.'
}
$teachers = Invoke-RestMethod -Uri 'http://localhost:5255/api/personnel/teachers?page=1&pageSize=10' -Headers $headers
$students = Invoke-RestMethod -Uri 'http://localhost:5255/api/personnel/students?page=1&pageSize=10' -Headers $headers
$courses = Invoke-RestMethod -Uri 'http://localhost:5255/api/courses?page=1&pageSize=10' -Headers $headers
$courseCategories = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/base-data/course-categories' `
-Headers $headers
if (@($courseCategories).Count -lt 10 -or
@($courses.items | Where-Object { $null -eq $_.courseCategoryId }).Count -gt 0) {
throw 'Course categories were not seeded or assigned to all courses.'
}
Invoke-WebRequest `
-Uri 'http://localhost:5255/api/courses/template' `
-Headers $headers `
-OutFile $courseTemplatePath |
Out-Null
Invoke-WebRequest `
-Uri 'http://localhost:5255/api/courses/export' `
-Headers $headers `
-OutFile $courseExportPath |
Out-Null
if ((Get-Item -LiteralPath $courseTemplatePath).Length -lt 1 -or
(Get-Item -LiteralPath $courseExportPath).Length -lt 1) {
throw 'Course Excel template or export is empty.'
}
$courseImport = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/courses/import' `
-Headers $headers `
-Form @{ file = Get-Item -LiteralPath $courseExportPath }
if ($courseImport.created -ne 0 -or
$courseImport.updated -ne $courses.total -or
$courseImport.total -ne $courses.total) {
throw 'Course Excel round-trip did not update the expected records.'
}
$curriculumPlans = Invoke-RestMethod -Uri 'http://localhost:5255/api/curriculum-plans?page=1&pageSize=10' -Headers $headers
if ($curriculumPlans.total -gt 0) {
$curriculumDetail = Invoke-RestMethod `
-Uri "http://localhost:5255/api/curriculum-plans/$($curriculumPlans.items[0].id)" `
-Headers $headers
}
$teachingTasks = Invoke-RestMethod -Uri 'http://localhost:5255/api/teaching-tasks?page=1&pageSize=10' -Headers $headers
$schedulePlans = Invoke-RestMethod -Uri 'http://localhost:5255/api/schedules/plans' -Headers $headers
if (@($schedulePlans).Count -gt 0) {
$scheduleDetail = Invoke-RestMethod `
-Uri "http://localhost:5255/api/schedules/plans/$($schedulePlans[0].id)" `
-Headers $headers
}
$selectionRounds = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/course-selections/rounds' `
-Headers $headers
if (@($selectionRounds).Count -lt 1) {
throw 'Development course-selection round was not seeded.'
}
$activeSelectionRound = @($selectionRounds) |
Where-Object { $_.isAvailableNow } |
Select-Object -First 1
if ($null -eq $activeSelectionRound) {
throw 'No course-selection round is open for the smoke test.'
}
$selectionOfferings = Invoke-RestMethod `
-Uri "http://localhost:5255/api/course-selections/rounds/$($activeSelectionRound.id)/offerings" `
-Headers $headers
if (@($selectionOfferings).Count -lt 1) {
throw 'Development course-selection offering was not seeded.'
}
$managedUsers = Invoke-RestMethod -Uri 'http://localhost:5255/api/users' -Headers $headers
$teacherAccount = @($managedUsers) |
Where-Object { $_.userName -eq 'teacher' } |
Select-Object -First 1
if ($null -eq $teacherAccount) {
throw 'Development teacher account was not seeded.'
}
$teacherAccessBody = @{
staffNumber = $teacherAccount.staffNumber
collegeId = $teacherAccount.collegeId
roles = @('Teacher')
} | ConvertTo-Json
Invoke-RestMethod `
-Method Put `
-Uri "http://localhost:5255/api/users/$($teacherAccount.id)/roles" `
-Headers $headers `
-ContentType 'application/json' `
-Body $teacherAccessBody
$scopeScenarios = @(
@{
UserName = 'college'; Password = 'College@123456'; Scope = 'College'
Teachers = 2; Students = 3; Courses = 3
},
@{
UserName = 'counselor'; Password = 'Counselor@123456'; Scope = 'Class'
Teachers = 1; Students = 3; Courses = 1
},
@{
UserName = 'teacher'; Password = 'Teacher@123456'; Scope = 'Self'
Teachers = 1; Students = 3; Courses = 1
},
@{
UserName = 'student'; Password = 'Student@123456'; Scope = 'Self'
Teachers = 0; Students = 1; Courses = 1
}
)
$scopeChecks = foreach ($scenario in $scopeScenarios) {
$scenarioLoginBody = @{
userName = $scenario.UserName
password = $scenario.Password
} | ConvertTo-Json
$scenarioLogin = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/auth/login' `
-ContentType 'application/json' `
-Body $scenarioLoginBody
$scenarioHeaders = @{ Authorization = "Bearer $($scenarioLogin.token)" }
$scenarioTeachers = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/personnel/teachers?page=1&pageSize=10' `
-Headers $scenarioHeaders
$scenarioStudents = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/personnel/students?page=1&pageSize=10' `
-Headers $scenarioHeaders
$scenarioCourses = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/courses?page=1&pageSize=10' `
-Headers $scenarioHeaders
if ($scenarioLogin.user.effectiveDataScope -ne $scenario.Scope -or
$scenarioTeachers.total -ne $scenario.Teachers -or
$scenarioStudents.total -ne $scenario.Students -or
$scenarioCourses.total -ne $scenario.Courses) {
throw ("Data-scope check failed for {0}: scope={1}, teachers={2}, students={3}, courses={4}." -f
$scenario.UserName,
$scenarioLogin.user.effectiveDataScope,
$scenarioTeachers.total,
$scenarioStudents.total,
$scenarioCourses.total)
}
"$($scenario.UserName):$($scenario.Scope)"
}
$studentLoginBody = @{
userName = 'student'
password = 'Student@123456'
} | ConvertTo-Json
$studentLogin = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/auth/login' `
-ContentType 'application/json' `
-Body $studentLoginBody
$studentHeaders = @{ Authorization = "Bearer $($studentLogin.token)" }
$studentOptions = Invoke-RestMethod `
-Uri "http://localhost:5255/api/course-selections/student/options?roundId=$($activeSelectionRound.id)" `
-Headers $studentHeaders
$studentOffering = @($studentOptions.offerings) | Select-Object -First 1
if ($null -eq $studentOffering) {
throw 'Student has no eligible course-selection offering.'
}
if ($studentOffering.enrollmentStatus -ne 'Enrolled') {
$enrollmentBody = @{ offeringId = $studentOffering.id } | ConvertTo-Json
Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/course-selections/student/enrollments' `
-Headers $studentHeaders `
-ContentType 'application/json' `
-Body $enrollmentBody |
Out-Null
}
$studentEnrollments = Invoke-RestMethod `
-Uri "http://localhost:5255/api/course-selections/student/enrollments?academicTermId=$($activeSelectionRound.academicTermId)" `
-Headers $studentHeaders
$activeEnrollments = @($studentEnrollments) |
Where-Object { $_.status -eq 'Enrolled' }
if ($activeEnrollments.Count -lt 1) {
throw 'Student course enrollment was not persisted.'
}
$selectionRoster = Invoke-RestMethod `
-Uri "http://localhost:5255/api/course-selections/offerings/$($studentOffering.id)/roster" `
-Headers $headers
if ($selectionRoster.enrolledCount -lt 1) {
throw 'Course-selection roster did not include the selected student.'
}
$gradeTasks = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/grades/sheets' `
-Headers $headers
$gradeTask = @($gradeTasks) |
Where-Object { $null -ne $_.sheet } |
Select-Object -First 1
if ($null -eq $gradeTask) {
throw 'Development grade sheet was not seeded.'
}
$gradeDetail = Invoke-RestMethod `
-Uri "http://localhost:5255/api/grades/sheets/$($gradeTask.sheet.id)" `
-Headers $headers
if (@($gradeDetail.sheet.records).Count -lt 1) {
throw 'Development grade sheet has no student records.'
}
$teacherLoginBody = @{
userName = 'teacher'
password = 'Teacher@123456'
} | ConvertTo-Json
$teacherLogin = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/auth/login' `
-ContentType 'application/json' `
-Body $teacherLoginBody
$teacherHeaders = @{ Authorization = "Bearer $($teacherLogin.token)" }
$teacherGradeTasks = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/grades/sheets' `
-Headers $teacherHeaders
$teacherGradeTask = @($teacherGradeTasks) |
Where-Object { $_.id -eq $gradeTask.id } |
Select-Object -First 1
if ($null -eq $teacherGradeTask) {
throw 'Assigned teacher cannot access the grade sheet.'
}
if ($teacherGradeTask.sheet.status -eq 'Draft' -or
$teacherGradeTask.sheet.status -eq 'Returned') {
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/grades/sheets/$($teacherGradeTask.sheet.id)/submit" `
-Headers $teacherHeaders |
Out-Null
}
$collegeLoginBody = @{
userName = 'college'
password = 'College@123456'
} | ConvertTo-Json
$collegeLogin = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/auth/login' `
-ContentType 'application/json' `
-Body $collegeLoginBody
$collegeHeaders = @{ Authorization = "Bearer $($collegeLogin.token)" }
$collegeGradeTasks = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/grades/sheets' `
-Headers $collegeHeaders
$collegeGradeTask = @($collegeGradeTasks) |
Where-Object { $_.id -eq $gradeTask.id } |
Select-Object -First 1
if ($collegeGradeTask.sheet.status -eq 'Submitted') {
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/grades/sheets/$($collegeGradeTask.sheet.id)/approve" `
-Headers $collegeHeaders |
Out-Null
}
$adminGradeTasks = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/grades/sheets' `
-Headers $headers
$adminGradeTask = @($adminGradeTasks) |
Where-Object { $_.id -eq $gradeTask.id } |
Select-Object -First 1
if ($adminGradeTask.sheet.status -eq 'Approved') {
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/grades/sheets/$($adminGradeTask.sheet.id)/publish" `
-Headers $headers |
Out-Null
}
$studentTranscript = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/grades/student/transcript' `
-Headers $studentHeaders
if (@($studentTranscript.records).Count -lt 1) {
throw 'Published grade is not visible in the student transcript.'
}
$examPlans = Invoke-RestMethod -Uri 'http://localhost:5255/api/exams/plans' -Headers $headers
if (@($examPlans).Count -lt 1) { throw 'Development exam plan was not seeded.' }
$examDetail = Invoke-RestMethod `
-Uri "http://localhost:5255/api/exams/plans/$($examPlans[0].id)" `
-Headers $headers
if (@($examDetail.sessions).Count -lt 1) { throw 'Exam plan has no sessions.' }
$examRoster = Invoke-RestMethod `
-Uri "http://localhost:5255/api/exams/sessions/$($examDetail.sessions[0].id)/roster" `
-Headers $headers
$studentExams = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/exams/my-schedule' `
-Headers $studentHeaders
$teacherExams = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/exams/my-schedule' `
-Headers $teacherHeaders
if (@($studentExams).Count -lt 1 -or @($teacherExams).Count -lt 1) {
throw 'Personal exam schedule is not visible to student or invigilator.'
}
$statusOptions = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/student-status-changes/options' `
-Headers $studentHeaders
$statusChanges = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/student-status-changes' `
-Headers $studentHeaders
$openStatusChange = @($statusChanges) |
Where-Object { $_.state -in @('Submitted', 'CounselorApproved', 'CollegeApproved') } |
Select-Object -First 1
if ($null -eq $openStatusChange) {
$statusChangeType = if ($statusOptions.status -eq 'Active') {
'Suspension'
}
elseif ($statusOptions.status -eq 'Suspended') {
'Resumption'
}
else {
throw "Smoke-test student status '$($statusOptions.status)' cannot start a reversible change."
}
$statusChangeBody = @{
type = $statusChangeType
reason = '端到端验证学籍异动三级审核流程。'
} | ConvertTo-Json
$createdStatusChange = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/student-status-changes' `
-Headers $studentHeaders `
-ContentType 'application/json' `
-Body $statusChangeBody
$statusChanges = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/student-status-changes' `
-Headers $studentHeaders
$openStatusChange = @($statusChanges) |
Where-Object { $_.id -eq $createdStatusChange.id } |
Select-Object -First 1
}
$expectedStudentStatus = $openStatusChange.targetStatus
$approvalBody = @{ approved = $true; comment = '端到端审核通过。' } | ConvertTo-Json
if ($openStatusChange.state -eq 'Submitted') {
$prematureCollegeReview = Invoke-WebRequest `
-Method Post `
-Uri "http://localhost:5255/api/student-status-changes/$($openStatusChange.id)/review" `
-Headers $collegeHeaders `
-ContentType 'application/json' `
-Body $approvalBody `
-SkipHttpErrorCheck
if ($prematureCollegeReview.StatusCode -ne 409) {
throw 'College reviewer was able to bypass the counselor stage.'
}
$counselorLoginBody = @{
userName = 'counselor'
password = 'Counselor@123456'
} | ConvertTo-Json
$counselorLogin = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/auth/login' `
-ContentType 'application/json' `
-Body $counselorLoginBody
$counselorHeaders = @{ Authorization = "Bearer $($counselorLogin.token)" }
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/student-status-changes/$($openStatusChange.id)/review" `
-Headers $counselorHeaders `
-ContentType 'application/json' `
-Body $approvalBody |
Out-Null
$openStatusChange.state = 'CounselorApproved'
}
if ($openStatusChange.state -eq 'CounselorApproved') {
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/student-status-changes/$($openStatusChange.id)/review" `
-Headers $collegeHeaders `
-ContentType 'application/json' `
-Body $approvalBody |
Out-Null
$openStatusChange.state = 'CollegeApproved'
}
if ($openStatusChange.state -eq 'CollegeApproved') {
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/student-status-changes/$($openStatusChange.id)/review" `
-Headers $headers `
-ContentType 'application/json' `
-Body $approvalBody |
Out-Null
}
$completedStatusChanges = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/student-status-changes' `
-Headers $studentHeaders
$completedStatusChange = @($completedStatusChanges) |
Where-Object { $_.id -eq $openStatusChange.id } |
Select-Object -First 1
$updatedStatusOptions = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/student-status-changes/options' `
-Headers $studentHeaders
if ($completedStatusChange.state -ne 'Approved' -or
$updatedStatusOptions.status -ne $expectedStudentStatus) {
throw 'Student status change was not finalized or did not update the student record.'
}
$graduationBatches = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/graduation-audits/batches' `
-Headers $headers
$graduationBatch = @($graduationBatches) |
Where-Object { $_.name -eq '2030届端到端毕业资格审核' } |
Select-Object -First 1
if ($null -eq $graduationBatch) {
$graduationBatchBody = @{
name = '2030届端到端毕业资格审核'
graduationYear = 2030
enrollmentYear = 2026
notes = '用于验证培养方案、成绩快照、人工复核与结果发布。'
} | ConvertTo-Json
$createdGraduationBatch = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/graduation-audits/batches' `
-Headers $headers `
-ContentType 'application/json' `
-Body $graduationBatchBody
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/graduation-audits/batches/$($createdGraduationBatch.id)/calculate" `
-Headers $headers |
Out-Null
$graduationBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/graduation-audits/batches/$($createdGraduationBatch.id)" `
-Headers $headers
$studentGraduationResult = @($graduationBatch.results) |
Where-Object { $_.studentNumber -eq '202601001' } |
Select-Object -First 1
if ($null -eq $studentGraduationResult) {
throw 'Calculated graduation batch does not contain the smoke-test student.'
}
$overrideDecisionBody = @{
conclusion = 'Eligible'
comment = '端到端验证人工复核调整。'
} | ConvertTo-Json
Invoke-RestMethod `
-Method Put `
-Uri "http://localhost:5255/api/graduation-audits/results/$($studentGraduationResult.id)" `
-Headers $headers `
-ContentType 'application/json' `
-Body $overrideDecisionBody |
Out-Null
$restoreDecisionBody = @{
conclusion = 'Ineligible'
comment = '恢复规则结论后发布测试结果。'
} | ConvertTo-Json
Invoke-RestMethod `
-Method Put `
-Uri "http://localhost:5255/api/graduation-audits/results/$($studentGraduationResult.id)" `
-Headers $headers `
-ContentType 'application/json' `
-Body $restoreDecisionBody |
Out-Null
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/graduation-audits/batches/$($graduationBatch.id)/publish" `
-Headers $headers |
Out-Null
}
else {
$graduationBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/graduation-audits/batches/$($graduationBatch.id)" `
-Headers $headers
}
$graduationBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/graduation-audits/batches/$($graduationBatch.id)" `
-Headers $headers
$studentGraduationResult = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/graduation-audits/my-result' `
-Headers $studentHeaders
if ($graduationBatch.status -ne 'Published' -or
$studentGraduationResult.conclusion -ne 'Ineligible') {
throw 'Published graduation result is not visible to the student.'
}
$graduationBatches = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/graduation-audits/batches' `
-Headers $headers
$degreeSourceBatch = @($graduationBatches) |
Where-Object { $_.name -eq '2030届学位授予资格演示' } |
Select-Object -First 1
if ($null -eq $degreeSourceBatch) {
$degreeSourceBody = @{
name = '2030届学位授予资格演示'
graduationYear = 2030
enrollmentYear = 2026
notes = '为未关联登录账号的演示学生生成学位授予前置资格。'
} | ConvertTo-Json
$createdDegreeSource = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/graduation-audits/batches' `
-Headers $headers `
-ContentType 'application/json' `
-Body $degreeSourceBody
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/graduation-audits/batches/$($createdDegreeSource.id)/calculate" `
-Headers $headers |
Out-Null
$degreeSourceDetail = Invoke-RestMethod `
-Uri "http://localhost:5255/api/graduation-audits/batches/$($createdDegreeSource.id)" `
-Headers $headers
$degreeSourceResult = @($degreeSourceDetail.results) |
Where-Object { $_.studentNumber -eq '202601002' } |
Select-Object -First 1
$degreeSourceDecision = @{
conclusion = 'Eligible'
comment = '演示学生经校级专项复核符合毕业条件。'
} | ConvertTo-Json
Invoke-RestMethod `
-Method Put `
-Uri "http://localhost:5255/api/graduation-audits/results/$($degreeSourceResult.id)" `
-Headers $headers `
-ContentType 'application/json' `
-Body $degreeSourceDecision |
Out-Null
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/graduation-audits/batches/$($createdDegreeSource.id)/publish" `
-Headers $headers |
Out-Null
}
$degreeBatches = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/degree-awards/batches' `
-Headers $headers
$degreeBatch = @($degreeBatches) |
Where-Object { $_.name -eq '2030届端到端学位授予审核' } |
Select-Object -First 1
if ($null -eq $degreeBatch) {
$degreeBatchBody = @{
name = '2030届端到端学位授予审核'
graduationYear = 2030
degreeName = '工学学士'
minimumGradePoint = 0
notes = '验证毕业资格来源、绩点快照、人工复核与发布锁定。'
} | ConvertTo-Json
$createdDegreeBatch = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/degree-awards/batches' `
-Headers $headers `
-ContentType 'application/json' `
-Body $degreeBatchBody
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/degree-awards/batches/$($createdDegreeBatch.id)/calculate" `
-Headers $headers |
Out-Null
$degreeBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/degree-awards/batches/$($createdDegreeBatch.id)" `
-Headers $headers
$degreeResult = @($degreeBatch.results) | Select-Object -First 1
if ($null -eq $degreeResult -or $degreeResult.conclusion -ne 'Granted') {
throw 'Degree-award calculation did not include the eligible graduated student.'
}
$degreeRejectBody = @{
conclusion = 'NotGranted'
comment = '端到端验证学位结论人工调整。'
} | ConvertTo-Json
Invoke-RestMethod `
-Method Put `
-Uri "http://localhost:5255/api/degree-awards/results/$($degreeResult.id)" `
-Headers $collegeHeaders `
-ContentType 'application/json' `
-Body $degreeRejectBody |
Out-Null
$degreeRestoreBody = @{
conclusion = 'Granted'
comment = '恢复规则结论并完成授予发布。'
} | ConvertTo-Json
Invoke-RestMethod `
-Method Put `
-Uri "http://localhost:5255/api/degree-awards/results/$($degreeResult.id)" `
-Headers $headers `
-ContentType 'application/json' `
-Body $degreeRestoreBody |
Out-Null
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/degree-awards/batches/$($degreeBatch.id)/publish" `
-Headers $headers |
Out-Null
}
$degreeBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/degree-awards/batches/$($degreeBatch.id)" `
-Headers $headers
$collegeDegreeBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/degree-awards/batches/$($degreeBatch.id)" `
-Headers $collegeHeaders
if ($degreeBatch.status -ne 'Published' -or
@($degreeBatch.results).Count -lt 1 -or
@($collegeDegreeBatch.results).Count -lt 1) {
throw 'Published degree-award results are not visible in the authorized college scope.'
}
$clearanceBatches = Invoke-RestMethod `
-Uri 'http://localhost:5255/api/graduation-clearance/batches' `
-Headers $headers
$clearanceBatch = @($clearanceBatches) |
Where-Object { $_.name -eq '2030届端到端毕业离校手续' } |
Select-Object -First 1
if ($null -eq $clearanceBatch) {
$clearanceBatchBody = @{
name = '2030届端到端毕业离校手续'
graduationYear = 2030
notes = '验证校级、学院、辅导员分工办理与批次关闭。'
items = @(
@{
code = 'FINANCE'
name = '财务费用结清'
responsibleUnit = '财务处'
responsibleRole = 'AcademicAdmin'
isRequired = $true
},
@{
code = 'COLLEGE'
name = '学院材料归档'
responsibleUnit = '计算机学院'
responsibleRole = 'CollegeAdmin'
isRequired = $true
},
@{
code = 'DORM'
name = '宿舍退宿确认'
responsibleUnit = '学生工作办公室'
responsibleRole = 'Counselor'
isRequired = $true
}
)
} | ConvertTo-Json -Depth 5
$createdClearanceBatch = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/graduation-clearance/batches' `
-Headers $headers `
-ContentType 'application/json' `
-Body $clearanceBatchBody
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/graduation-clearance/batches/$($createdClearanceBatch.id)/generate" `
-Headers $headers |
Out-Null
$clearanceBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/graduation-clearance/batches/$($createdClearanceBatch.id)" `
-Headers $headers
if (@($clearanceBatch.records).Count -ne 3) {
throw 'Graduation-clearance records were not generated for each configured item.'
}
$academicClearanceRecord = @($clearanceBatch.records) |
Where-Object { $_.responsibleRole -eq 'AcademicAdmin' } |
Select-Object -First 1
$wrongRoleResponse = Invoke-WebRequest `
-Method Put `
-Uri "http://localhost:5255/api/graduation-clearance/records/$($academicClearanceRecord.id)" `
-Headers $collegeHeaders `
-ContentType 'application/json' `
-Body (@{ status = 'Completed'; notes = '越权测试。' } | ConvertTo-Json) `
-SkipHttpErrorCheck
if ($wrongRoleResponse.StatusCode -ne 403) {
throw 'College role was able to complete an academic-level clearance item.'
}
$counselorLoginBody = @{
userName = 'counselor'
password = 'Counselor@123456'
} | ConvertTo-Json
$counselorLogin = Invoke-RestMethod `
-Method Post `
-Uri 'http://localhost:5255/api/auth/login' `
-ContentType 'application/json' `
-Body $counselorLoginBody
$counselorHeaders = @{ Authorization = "Bearer $($counselorLogin.token)" }
foreach ($record in @($clearanceBatch.records)) {
$recordHeaders = switch ($record.responsibleRole) {
'CollegeAdmin' { $collegeHeaders }
'Counselor' { $counselorHeaders }
default { $headers }
}
$recordBody = @{
status = 'Completed'
notes = "$($record.responsibleUnit)端到端办理完成。"
} | ConvertTo-Json
Invoke-RestMethod `
-Method Put `
-Uri "http://localhost:5255/api/graduation-clearance/records/$($record.id)" `
-Headers $recordHeaders `
-ContentType 'application/json' `
-Body $recordBody |
Out-Null
}
Invoke-RestMethod `
-Method Post `
-Uri "http://localhost:5255/api/graduation-clearance/batches/$($clearanceBatch.id)/close" `
-Headers $headers |
Out-Null
}
$clearanceBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/graduation-clearance/batches/$($clearanceBatch.id)" `
-Headers $headers
$collegeClearanceBatch = Invoke-RestMethod `
-Uri "http://localhost:5255/api/graduation-clearance/batches/$($clearanceBatch.id)" `
-Headers $collegeHeaders
if ($clearanceBatch.status -ne 'Closed' -or
@($clearanceBatch.records | Where-Object { $_.status -eq 'Pending' }).Count -ne 0 -or
@($collegeClearanceBatch.records).Count -lt 1) {
throw 'Graduation-clearance batch was not closed or is missing from college scope.'
}
$frontend = Invoke-WebRequest -Uri 'http://localhost:5255/' -TimeoutSec 5
$spaFallback = Invoke-WebRequest -Uri 'http://localhost:5255/base-data' -TimeoutSec 5
$unknownApiParameters = @{
Uri = 'http://localhost:5255/api/does-not-exist'
SkipHttpErrorCheck = $true
TimeoutSec = 5
}
$unknownApi = Invoke-WebRequest @unknownApiParameters
[pscustomobject]@{
Health = $health.status
Database = $health.database
User = $login.user.displayName
Term = $dashboard.currentTerm.name
Campuses = @($campuses).Count
CounselorAssignments = $assignedClassCount
Teachers = $teachers.total
Students = $students.total
Courses = $courses.total
CourseCategories = @($courseCategories).Count
CourseExcelRows = $courseImport.total
Plans = $curriculumPlans.total
PlanModules = if ($null -ne $curriculumDetail) { @($curriculumDetail.modules).Count } else { 0 }
TeachingTasks = $teachingTasks.total
Schedules = @($schedulePlans).Count
ScheduleEntries = if ($null -ne $scheduleDetail) { @($scheduleDetail.entries).Count } else { 0 }
SelectionRounds = @($selectionRounds).Count
SelectionOfferings = @($selectionOfferings).Count
StudentEnrollments = $activeEnrollments.Count
RosterStudents = $selectionRoster.enrolledCount
GradeSheets = @($gradeTasks).Count
GradeRecords = @($gradeDetail.sheet.records).Count
TranscriptRecords = @($studentTranscript.records).Count
ExamSessions = @($examDetail.sessions).Count
ExamRoster = @($examRoster.students).Count
StatusChanges = @($completedStatusChanges).Count
StudentStatus = $updatedStatusOptions.status
GraduationResults = @($graduationBatch.results).Count
GraduationConclusion = $studentGraduationResult.conclusion
DegreeResults = @($degreeBatch.results).Count
DegreeConclusion = $degreeBatch.results[0].conclusion
ClearanceRecords = @($clearanceBatch.records).Count
ClearanceStatus = $clearanceBatch.status
AccessUpdate = $true
ScopeChecks = $scopeChecks -join ', '
StaticIndex = $frontend.Content.Contains('明序教务管理系统')
SpaFallback = $spaFallback.StatusCode
ApiNotFound = $unknownApi.StatusCode
} | Format-List
}
finally {
if (-not $process.HasExited) {
Stop-Process -Id $process.Id -Force
}
Remove-Item -LiteralPath $courseTemplatePath -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $courseExportPath -Force -ErrorAction SilentlyContinue
}