Files
Academic-Affairs-System/scripts/smoke-test.ps1
T
biss 151a22554f 选课批次:时间窗口、退课截止、学分上限、开放/关闭。
教学班投放:容量、班级范围、全校开放。
学生选退课:容量、重复课程、学分上限、课表冲突校验。
教学班实时名单。
校级、学院级、学生角色分级操作。
SQLite 本地增量升级及演示数据。
MySQL 正式 EF Core 迁移。
Vue 已编译进 wwwroot,单服务运行无需 npm run dev。
学生端和管理端均完成桌面、手机响应式检查。
2026-07-24 15:17:26 +08:00

265 lines
11 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'
$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
$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.'
}
$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
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
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
}
}