1 parent
51356c69a3
commit
417c076465
95 files changed
+14621
No files matched your search
@@ -0,0 +1,110 @@
|
||||
<#
|
||||
Apply IT-3 SQL scripts to apt_rev01 (idempotent, replayable).
|
||||
Credentials come from dev/.env first, then APT_DB_* environment variables.
|
||||
Passwords are never printed.
|
||||
SQL files are streamed to mysql stdin as raw UTF-8 bytes: PowerShell 5.1's
|
||||
text pipeline can replace Chinese literals with '?'.
|
||||
Usage:
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it3-sql.ps1
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it3-sql.ps1 -DryRun
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it3-sql.ps1 -EnvFile C:\secrets\apt.env
|
||||
#>
|
||||
param(
|
||||
[string]$EnvFile = '',
|
||||
[string]$MysqlPath = 'C:\Program Files\MySQL\MySQL Server 9.5\bin\mysql.exe',
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$devDir = Split-Path $PSScriptRoot -Parent
|
||||
$sqlDir = Join-Path $devDir 'sql/apt_rev01'
|
||||
$scripts = @('14_it3_columns.sql', '15_it3_seed.sql', '16_it3_permissions.sql')
|
||||
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
if (-not [System.IO.File]::Exists($file)) { throw "Missing SQL file: $file" }
|
||||
}
|
||||
if ($EnvFile -eq '') { $EnvFile = Join-Path $devDir '.env' }
|
||||
|
||||
$values = @{}
|
||||
if ([System.IO.File]::Exists($EnvFile)) {
|
||||
foreach ($line in [System.IO.File]::ReadAllLines($EnvFile)) {
|
||||
$trimmed = $line.Trim()
|
||||
if ($trimmed -eq '' -or $trimmed.StartsWith('#')) { continue }
|
||||
if ($trimmed -match '^([A-Za-z0-9_]+)\s*=\s*(.*)$') { $values[$Matches[1]] = $Matches[2] }
|
||||
}
|
||||
Write-Host "Credentials source: $EnvFile"
|
||||
} else {
|
||||
Write-Host "No credentials file at $EnvFile; using APT_DB_* environment variables."
|
||||
foreach ($key in @('APT_DB_HOST','APT_DB_PORT','APT_DB_NAME','APT_DB_USER','APT_DB_PASSWORD')) {
|
||||
$values[$key] = [Environment]::GetEnvironmentVariable($key)
|
||||
}
|
||||
}
|
||||
|
||||
$dbHost = $values['APT_DB_HOST']; if ([string]::IsNullOrEmpty($dbHost)) { $dbHost = '127.0.0.1' }
|
||||
$dbPort = $values['APT_DB_PORT']; if ([string]::IsNullOrEmpty($dbPort)) { $dbPort = '3306' }
|
||||
$dbName = $values['APT_DB_NAME']; if ([string]::IsNullOrEmpty($dbName)) { $dbName = 'apt_rev01' }
|
||||
$dbUser = $values['APT_DB_USER']; if ([string]::IsNullOrEmpty($dbUser)) { $dbUser = 'iotlocks' }
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host '--- DRY RUN: no database connection, no credentials required ---'
|
||||
Write-Host ("Target database: {0}:{1}/{2} (user {3})" -f $dbHost, $dbPort, $dbName, $dbUser)
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
$lineCount = ([System.IO.File]::ReadAllLines($file, [System.Text.Encoding]::UTF8)).Count
|
||||
Write-Host (" would apply {0} ({1} lines)" -f $name, $lineCount)
|
||||
}
|
||||
Write-Host 'Validation SELECTs in 14/15/16 print column, device-seed, permission and menu checks.'
|
||||
Write-Host 'Run 99_verify.sql afterwards for the IT-3 acceptance checks (sections 7~13).'
|
||||
return
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($values['APT_DB_PASSWORD'])) {
|
||||
$hint = @'
|
||||
Missing APT_DB_PASSWORD. Provide one of:
|
||||
1) copy dev/.env.example to dev/.env, fill APT_DB_PASSWORD, then re-run this script
|
||||
2) set APT_DB_PASSWORD (plus optional APT_DB_HOST/PORT/NAME/USER) in the environment
|
||||
Use -DryRun to preview SQL files without connecting to MySQL.
|
||||
'@
|
||||
throw $hint
|
||||
}
|
||||
if (-not [System.IO.File]::Exists($MysqlPath)) { throw "Missing mysql client: $MysqlPath" }
|
||||
|
||||
[Environment]::SetEnvironmentVariable('MYSQL_PWD', $values['APT_DB_PASSWORD'], 'Process')
|
||||
try {
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
Write-Host "--- applying $name ---"
|
||||
|
||||
$arguments = "--host=$dbHost --port=$dbPort --user=$dbUser --database=$dbName" +
|
||||
' --default-character-set=utf8mb4 --batch'
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $MysqlPath
|
||||
$psi.Arguments = $arguments
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.RedirectStandardInput = $true
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
|
||||
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$process = [System.Diagnostics.Process]::Start($psi)
|
||||
$bytes = [System.IO.File]::ReadAllBytes($file)
|
||||
$offset = 0
|
||||
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
|
||||
$offset = 3
|
||||
}
|
||||
$process.StandardInput.BaseStream.Write($bytes, $offset, $bytes.Length - $offset)
|
||||
$process.StandardInput.BaseStream.Flush()
|
||||
$process.StandardInput.Close()
|
||||
$stdout = $process.StandardOutput.ReadToEnd()
|
||||
$stderr = $process.StandardError.ReadToEnd()
|
||||
$process.WaitForExit()
|
||||
if ($stdout) { Write-Host $stdout }
|
||||
if ($stderr) { Write-Host $stderr }
|
||||
if ($process.ExitCode -ne 0) { throw "SQL failed: $name (exit $($process.ExitCode))" }
|
||||
}
|
||||
Write-Host 'IT-3 SQL scripts applied successfully.'
|
||||
} finally {
|
||||
[Environment]::SetEnvironmentVariable('MYSQL_PWD', $null, 'Process')
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<#
|
||||
Apply IT-4 SQL scripts to apt_rev01 (idempotent, replayable).
|
||||
Credentials come from dev/.env first, then APT_DB_* environment variables.
|
||||
Passwords are never printed.
|
||||
SQL files are streamed to mysql stdin as raw UTF-8 bytes: PowerShell 5.1's
|
||||
text pipeline can replace Chinese literals with '?'.
|
||||
Usage:
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it4-sql.ps1
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it4-sql.ps1 -DryRun
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it4-sql.ps1 -EnvFile C:\secrets\apt.env
|
||||
#>
|
||||
param(
|
||||
[string]$EnvFile = '',
|
||||
[string]$MysqlPath = 'C:\Program Files\MySQL\MySQL Server 9.5\bin\mysql.exe',
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$devDir = Split-Path $PSScriptRoot -Parent
|
||||
$sqlDir = Join-Path $devDir 'sql/apt_rev01'
|
||||
$scripts = @('17_it4_columns.sql', '18_it4_permissions.sql')
|
||||
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
if (-not [System.IO.File]::Exists($file)) { throw "Missing SQL file: $file" }
|
||||
}
|
||||
if ($EnvFile -eq '') { $EnvFile = Join-Path $devDir '.env' }
|
||||
|
||||
$values = @{}
|
||||
if ([System.IO.File]::Exists($EnvFile)) {
|
||||
foreach ($line in [System.IO.File]::ReadAllLines($EnvFile)) {
|
||||
$trimmed = $line.Trim()
|
||||
if ($trimmed -eq '' -or $trimmed.StartsWith('#')) { continue }
|
||||
if ($trimmed -match '^([A-Za-z0-9_]+)\s*=\s*(.*)$') { $values[$Matches[1]] = $Matches[2] }
|
||||
}
|
||||
Write-Host "Credentials source: $EnvFile"
|
||||
} else {
|
||||
Write-Host "No credentials file at $EnvFile; using APT_DB_* environment variables."
|
||||
foreach ($key in @('APT_DB_HOST','APT_DB_PORT','APT_DB_NAME','APT_DB_USER','APT_DB_PASSWORD')) {
|
||||
$values[$key] = [Environment]::GetEnvironmentVariable($key)
|
||||
}
|
||||
}
|
||||
|
||||
$dbHost = $values['APT_DB_HOST']; if ([string]::IsNullOrEmpty($dbHost)) { $dbHost = '127.0.0.1' }
|
||||
$dbPort = $values['APT_DB_PORT']; if ([string]::IsNullOrEmpty($dbPort)) { $dbPort = '3306' }
|
||||
$dbName = $values['APT_DB_NAME']; if ([string]::IsNullOrEmpty($dbName)) { $dbName = 'apt_rev01' }
|
||||
$dbUser = $values['APT_DB_USER']; if ([string]::IsNullOrEmpty($dbUser)) { $dbUser = 'iotlocks' }
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host '--- DRY RUN: no database connection, no credentials required ---'
|
||||
Write-Host ("Target database: {0}:{1}/{2} (user {3})" -f $dbHost, $dbPort, $dbName, $dbUser)
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
$lineCount = ([System.IO.File]::ReadAllLines($file, [System.Text.Encoding]::UTF8)).Count
|
||||
Write-Host (" would apply {0} ({1} lines)" -f $name, $lineCount)
|
||||
}
|
||||
Write-Host 'Validation SELECTs in 17/18 print column, state-dictionary, permission and menu checks.'
|
||||
Write-Host 'Run 99_verify.sql afterwards for the IT-4 acceptance checks (sections 14~18).'
|
||||
return
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($values['APT_DB_PASSWORD'])) {
|
||||
$hint = @'
|
||||
Missing APT_DB_PASSWORD. Provide one of:
|
||||
1) copy dev/.env.example to dev/.env, fill APT_DB_PASSWORD, then re-run this script
|
||||
2) set APT_DB_PASSWORD (plus optional APT_DB_HOST/PORT/NAME/USER) in the environment
|
||||
Use -DryRun to preview SQL files without connecting to MySQL.
|
||||
'@
|
||||
throw $hint
|
||||
}
|
||||
if (-not [System.IO.File]::Exists($MysqlPath)) { throw "Missing mysql client: $MysqlPath" }
|
||||
|
||||
[Environment]::SetEnvironmentVariable('MYSQL_PWD', $values['APT_DB_PASSWORD'], 'Process')
|
||||
try {
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
Write-Host "--- applying $name ---"
|
||||
|
||||
$arguments = "--host=$dbHost --port=$dbPort --user=$dbUser --database=$dbName" +
|
||||
' --default-character-set=utf8mb4 --batch'
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $MysqlPath
|
||||
$psi.Arguments = $arguments
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.RedirectStandardInput = $true
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
|
||||
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$process = [System.Diagnostics.Process]::Start($psi)
|
||||
$bytes = [System.IO.File]::ReadAllBytes($file)
|
||||
$offset = 0
|
||||
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
|
||||
$offset = 3
|
||||
}
|
||||
$process.StandardInput.BaseStream.Write($bytes, $offset, $bytes.Length - $offset)
|
||||
$process.StandardInput.BaseStream.Flush()
|
||||
$process.StandardInput.Close()
|
||||
$stdout = $process.StandardOutput.ReadToEnd()
|
||||
$stderr = $process.StandardError.ReadToEnd()
|
||||
$process.WaitForExit()
|
||||
if ($stdout) { Write-Host $stdout }
|
||||
if ($stderr) { Write-Host $stderr }
|
||||
if ($process.ExitCode -ne 0) { throw "SQL failed: $name (exit $($process.ExitCode))" }
|
||||
}
|
||||
Write-Host 'IT-4 SQL scripts applied successfully.'
|
||||
} finally {
|
||||
[Environment]::SetEnvironmentVariable('MYSQL_PWD', $null, 'Process')
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<#
|
||||
Apply IT-5 SQL scripts to apt_rev01 (idempotent, replayable).
|
||||
Credentials come from dev/.env first, then APT_DB_* environment variables.
|
||||
Passwords are never printed.
|
||||
SQL files are streamed to mysql stdin as raw UTF-8 bytes: PowerShell 5.1's
|
||||
text pipeline can replace Chinese literals with '?'.
|
||||
Usage:
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it5-sql.ps1
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it5-sql.ps1 -DryRun
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/apply-it5-sql.ps1 -EnvFile C:\secrets\apt.env
|
||||
#>
|
||||
param(
|
||||
[string]$EnvFile = '',
|
||||
[string]$MysqlPath = 'C:\Program Files\MySQL\MySQL Server 9.5\bin\mysql.exe',
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$devDir = Split-Path $PSScriptRoot -Parent
|
||||
$sqlDir = Join-Path $devDir 'sql/apt_rev01'
|
||||
$scripts = @('19_it5_columns.sql', '20_it5_permissions.sql')
|
||||
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
if (-not [System.IO.File]::Exists($file)) { throw "Missing SQL file: $file" }
|
||||
}
|
||||
if ($EnvFile -eq '') { $EnvFile = Join-Path $devDir '.env' }
|
||||
|
||||
$values = @{}
|
||||
if ([System.IO.File]::Exists($EnvFile)) {
|
||||
foreach ($line in [System.IO.File]::ReadAllLines($EnvFile)) {
|
||||
$trimmed = $line.Trim()
|
||||
if ($trimmed -eq '' -or $trimmed.StartsWith('#')) { continue }
|
||||
if ($trimmed -match '^([A-Za-z0-9_]+)\s*=\s*(.*)$') { $values[$Matches[1]] = $Matches[2] }
|
||||
}
|
||||
Write-Host "Credentials source: $EnvFile"
|
||||
} else {
|
||||
Write-Host "No credentials file at $EnvFile; using APT_DB_* environment variables."
|
||||
foreach ($key in @('APT_DB_HOST','APT_DB_PORT','APT_DB_NAME','APT_DB_USER','APT_DB_PASSWORD')) {
|
||||
$values[$key] = [Environment]::GetEnvironmentVariable($key)
|
||||
}
|
||||
}
|
||||
|
||||
$dbHost = $values['APT_DB_HOST']; if ([string]::IsNullOrEmpty($dbHost)) { $dbHost = '127.0.0.1' }
|
||||
$dbPort = $values['APT_DB_PORT']; if ([string]::IsNullOrEmpty($dbPort)) { $dbPort = '3306' }
|
||||
$dbName = $values['APT_DB_NAME']; if ([string]::IsNullOrEmpty($dbName)) { $dbName = 'apt_rev01' }
|
||||
$dbUser = $values['APT_DB_USER']; if ([string]::IsNullOrEmpty($dbUser)) { $dbUser = 'iotlocks' }
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host '--- DRY RUN: no database connection, no credentials required ---'
|
||||
Write-Host ("Target database: {0}:{1}/{2} (user {3})" -f $dbHost, $dbPort, $dbName, $dbUser)
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
$lineCount = ([System.IO.File]::ReadAllLines($file, [System.Text.Encoding]::UTF8)).Count
|
||||
Write-Host (" would apply {0} ({1} lines)" -f $name, $lineCount)
|
||||
}
|
||||
Write-Host 'Validation SELECTs in 19/20 print column, index, permission and menu checks.'
|
||||
Write-Host 'Run 99_verify.sql afterwards for the IT-5 acceptance checks (sections 20~26).'
|
||||
return
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($values['APT_DB_PASSWORD'])) {
|
||||
$hint = @'
|
||||
Missing APT_DB_PASSWORD. Provide one of:
|
||||
1) copy dev/.env.example to dev/.env, fill APT_DB_PASSWORD, then re-run this script
|
||||
2) set APT_DB_PASSWORD (plus optional APT_DB_HOST/PORT/NAME/USER) in the environment
|
||||
Use -DryRun to preview SQL files without connecting to MySQL.
|
||||
'@
|
||||
throw $hint
|
||||
}
|
||||
if (-not [System.IO.File]::Exists($MysqlPath)) { throw "Missing mysql client: $MysqlPath" }
|
||||
|
||||
[Environment]::SetEnvironmentVariable('MYSQL_PWD', $values['APT_DB_PASSWORD'], 'Process')
|
||||
try {
|
||||
foreach ($name in $scripts) {
|
||||
$file = Join-Path $sqlDir $name
|
||||
Write-Host "--- applying $name ---"
|
||||
|
||||
$arguments = "--host=$dbHost --port=$dbPort --user=$dbUser --database=$dbName" +
|
||||
' --default-character-set=utf8mb4 --batch'
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $MysqlPath
|
||||
$psi.Arguments = $arguments
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.RedirectStandardInput = $true
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
$psi.StandardOutputEncoding = [System.Text.Encoding]::UTF8
|
||||
$psi.StandardErrorEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
$process = [System.Diagnostics.Process]::Start($psi)
|
||||
$bytes = [System.IO.File]::ReadAllBytes($file)
|
||||
$offset = 0
|
||||
if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
|
||||
$offset = 3
|
||||
}
|
||||
$process.StandardInput.BaseStream.Write($bytes, $offset, $bytes.Length - $offset)
|
||||
$process.StandardInput.BaseStream.Flush()
|
||||
$process.StandardInput.Close()
|
||||
$stdout = $process.StandardOutput.ReadToEnd()
|
||||
$stderr = $process.StandardError.ReadToEnd()
|
||||
$process.WaitForExit()
|
||||
if ($stdout) { Write-Host $stdout }
|
||||
if ($stderr) { Write-Host $stderr }
|
||||
if ($process.ExitCode -ne 0) { throw "SQL failed: $name (exit $($process.ExitCode))" }
|
||||
}
|
||||
Write-Host 'IT-5 SQL scripts applied successfully.'
|
||||
} finally {
|
||||
[Environment]::SetEnvironmentVariable('MYSQL_PWD', $null, 'Process')
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
<#
|
||||
IT-2 / DV-2 end-to-end verification: M1 route, M2 room, master version markers.
|
||||
Maps to docs/apartment/07 table 7-3 M1/M2 and table 7-7 steps 4/5.
|
||||
|
||||
Prerequisites:
|
||||
1) dev/.env with APT_DB_PASSWORD; MySQL apt_rev01 and Redis database=1 reachable.
|
||||
2) dev/scripts/apply-it2-sql.ps1 applied (DDL + Mock seed + permissions).
|
||||
3) mvn -pl ruoyi-admin -am package built ruoyi-admin/target/ruoyi-admin.jar.
|
||||
|
||||
Usage:
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/verify-it2-m1m2.ps1
|
||||
powershell -ExecutionPolicy Bypass -File dev/scripts/verify-it2-m1m2.ps1 -SkipStartup
|
||||
|
||||
Notes: captcha stays enabled and sys_config is unchanged; the captcha answer is
|
||||
read (read-only) from Redis database=1. A short APT_TOKEN_SECRET is replaced
|
||||
by an ephemeral Base64 HS512 key for this run only. The script starts its own
|
||||
jar on -ServerPort (default 8099), leaving any 8080 instance untouched.
|
||||
#>
|
||||
param(
|
||||
[switch]$SkipStartup,
|
||||
[string]$BaseUrl = 'http://127.0.0.1:8099',
|
||||
[int]$ServerPort = 8099,
|
||||
[string]$JavaPath = '',
|
||||
[string]$AdminPassword = 'admin123',
|
||||
[string]$GuestPassword = 'admin123',
|
||||
[int]$ReadyAttempts = 60
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$devDir = Split-Path $PSScriptRoot -Parent
|
||||
$backendDir = Join-Path $devDir 'RuoYi-Vue-master'
|
||||
$jar = Join-Path $backendDir 'ruoyi-admin/target/ruoyi-admin.jar'
|
||||
$results = @()
|
||||
|
||||
# ---------- 1. Credentials ----------
|
||||
$envFile = Join-Path $PSScriptRoot '.env'
|
||||
if (-not [System.IO.File]::Exists($envFile)) { $envFile = Join-Path $devDir '.env' }
|
||||
if (-not [System.IO.File]::Exists($envFile)) { throw "Missing credentials file: $envFile" }
|
||||
Write-Host "Using credentials file: $envFile"
|
||||
foreach ($line in [System.IO.File]::ReadAllLines($envFile)) {
|
||||
$trimmed = $line.Trim()
|
||||
if ($trimmed -eq '' -or $trimmed.StartsWith('#')) { continue }
|
||||
if ($trimmed -match '^([A-Za-z0-9_]+)\s*=\s*(.*)$') {
|
||||
[Environment]::SetEnvironmentVariable($Matches[1], $Matches[2], 'Process')
|
||||
}
|
||||
}
|
||||
foreach ($name in @('APT_DB_PASSWORD', 'APT_TOKEN_SECRET')) {
|
||||
if ([string]::IsNullOrEmpty([Environment]::GetEnvironmentVariable($name, 'Process'))) {
|
||||
throw "Missing required environment variable $name"
|
||||
}
|
||||
}
|
||||
$secretValue = [Environment]::GetEnvironmentVariable('APT_TOKEN_SECRET', 'Process')
|
||||
$secretValid = $false
|
||||
try {
|
||||
$decoded = [System.Convert]::FromBase64String($secretValue)
|
||||
if ($decoded.Length -ge 64) { $secretValid = $true }
|
||||
}
|
||||
catch { $secretValid = $false }
|
||||
if (-not $secretValid) {
|
||||
$rng = New-Object System.Security.Cryptography.RNGCryptoServiceProvider
|
||||
$keyBytes = New-Object byte[] 64
|
||||
$rng.GetBytes($keyBytes)
|
||||
$rng.Dispose()
|
||||
[Environment]::SetEnvironmentVariable('APT_TOKEN_SECRET', [System.Convert]::ToBase64String($keyBytes), 'Process')
|
||||
Write-Host 'APT_TOKEN_SECRET was not a valid Base64 HS512 key; generated an ephemeral one for this run.'
|
||||
}
|
||||
|
||||
# ---------- 2. Redis RESP read-only helper (captcha answer) ----------
|
||||
function Send-RedisCommand {
|
||||
param([System.Net.Sockets.NetworkStream]$Stream, [string[]]$Parts)
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
[void]$sb.Append('*').Append($Parts.Count).Append("`r`n")
|
||||
foreach ($p in $Parts) {
|
||||
$n = [System.Text.Encoding]::UTF8.GetByteCount($p)
|
||||
[void]$sb.Append('$').Append($n).Append("`r`n").Append($p).Append("`r`n")
|
||||
}
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($sb.ToString())
|
||||
$Stream.Write($bytes, 0, $bytes.Length)
|
||||
$Stream.Flush()
|
||||
}
|
||||
function Read-RedisLine {
|
||||
param([System.Net.Sockets.NetworkStream]$Stream)
|
||||
$sb = [System.Text.StringBuilder]::new()
|
||||
while ($true) {
|
||||
$b = $Stream.ReadByte()
|
||||
if ($b -lt 0) { throw 'Redis connection closed unexpectedly' }
|
||||
if ($b -eq 13) { [void]$Stream.ReadByte(); break }
|
||||
[void]$sb.Append([char]$b)
|
||||
}
|
||||
return $sb.ToString()
|
||||
}
|
||||
function Read-RedisBulk {
|
||||
param([System.Net.Sockets.NetworkStream]$Stream)
|
||||
$line = Read-RedisLine -Stream $Stream
|
||||
if (-not $line.StartsWith('$')) { throw "Redis reply is not a bulk string: $line" }
|
||||
$len = [int]$line.Substring(1)
|
||||
if ($len -lt 0) { return $null }
|
||||
$buf = [byte[]]::new($len)
|
||||
$read = 0
|
||||
while ($read -lt $len) {
|
||||
$n = $Stream.Read($buf, $read, $len - $read)
|
||||
if ($n -le 0) { throw 'Incomplete Redis reply' }
|
||||
$read += $n
|
||||
}
|
||||
[void]$Stream.ReadByte()
|
||||
[void]$Stream.ReadByte()
|
||||
return [System.Text.Encoding]::UTF8.GetString($buf)
|
||||
}
|
||||
function Get-CaptchaCode {
|
||||
param([string]$Uuid)
|
||||
$tcp = [System.Net.Sockets.TcpClient]::new('127.0.0.1', 6379)
|
||||
try {
|
||||
$stream = $tcp.GetStream()
|
||||
$stream.ReadTimeout = 3000
|
||||
Send-RedisCommand -Stream $stream -Parts @('SELECT', '1')
|
||||
[void](Read-RedisLine -Stream $stream)
|
||||
Send-RedisCommand -Stream $stream -Parts @('GET', ('captcha_codes:' + $Uuid))
|
||||
$value = Read-RedisBulk -Stream $stream
|
||||
if ($null -eq $value) { throw 'Captcha answer missing in Redis database=1' }
|
||||
return $value.Trim('"')
|
||||
}
|
||||
finally { $tcp.Dispose() }
|
||||
}
|
||||
|
||||
# ---------- 3. HTTP helpers ----------
|
||||
function Invoke-Api {
|
||||
param([string]$Path, [string]$Token = '', [string]$Method = 'Get', [hashtable]$Body = $null)
|
||||
$headers = @{}
|
||||
if ($Token -ne '') { $headers['Authorization'] = 'Bearer ' + $Token }
|
||||
$params = @{
|
||||
Uri = ($BaseUrl + $Path); Method = $Method; Headers = $headers
|
||||
UseBasicParsing = $true; TimeoutSec = 20
|
||||
}
|
||||
if ($null -ne $Body) {
|
||||
$params['ContentType'] = 'application/json;charset=UTF-8'
|
||||
$params['Body'] = [System.Text.Encoding]::UTF8.GetBytes(($Body | ConvertTo-Json -Compress))
|
||||
}
|
||||
try {
|
||||
$res = Invoke-WebRequest @params
|
||||
return @{ status = [int]$res.StatusCode; raw = $res.Content; body = ($res.Content | ConvertFrom-Json) }
|
||||
}
|
||||
catch {
|
||||
$response = $_.Exception.Response
|
||||
if ($null -eq $response) { throw }
|
||||
$content = $_.ErrorDetails.Message
|
||||
if ([string]::IsNullOrEmpty($content)) {
|
||||
$reader = [System.IO.StreamReader]::new($response.GetResponseStream())
|
||||
$content = $reader.ReadToEnd()
|
||||
}
|
||||
if ([string]::IsNullOrEmpty($content)) { $content = '{}' }
|
||||
return @{ status = [int]$response.StatusCode; raw = $content; body = ($content | ConvertFrom-Json) }
|
||||
}
|
||||
}
|
||||
function Get-Token {
|
||||
param([string]$User, [string]$Password)
|
||||
$captcha = Invoke-RestMethod -Uri ($BaseUrl + '/captchaImage') -Method Get -TimeoutSec 10
|
||||
$body = @{ username = $User; password = $Password }
|
||||
if ($captcha.captchaEnabled) {
|
||||
$body.code = Get-CaptchaCode -Uuid $captcha.uuid
|
||||
$body.uuid = $captcha.uuid
|
||||
}
|
||||
$resp = Invoke-RestMethod -Uri ($BaseUrl + '/login') -Method Post `
|
||||
-ContentType 'application/json;charset=UTF-8' -Body ($body | ConvertTo-Json -Compress) -TimeoutSec 15
|
||||
if ($resp.code -ne 200) { throw "Login failed [$User]: $($resp.msg)" }
|
||||
return $resp.token
|
||||
}
|
||||
function Add-Result {
|
||||
param([string]$Case, [string]$Expected, [string]$Actual, [bool]$Pass)
|
||||
$script:results += [pscustomobject]@{
|
||||
Case = $Case; Expected = $Expected; Actual = $Actual; Pass = $Pass
|
||||
}
|
||||
}
|
||||
|
||||
# ---------- 4. Startup and readiness ----------
|
||||
$proc = $null
|
||||
if (-not $SkipStartup) {
|
||||
if (-not [System.IO.File]::Exists($jar)) { throw "Missing $jar - run mvn package first" }
|
||||
if ($JavaPath -eq '') {
|
||||
$javaHome = [Environment]::GetEnvironmentVariable('JAVA_HOME', 'Process')
|
||||
if ([string]::IsNullOrEmpty($javaHome)) { $JavaPath = 'java' }
|
||||
else { $JavaPath = Join-Path $javaHome 'bin/java.exe' }
|
||||
}
|
||||
$outLog = Join-Path $env:TEMP 'apt-it2-stdout.log'
|
||||
$errLog = Join-Path $env:TEMP 'apt-it2-stderr.log'
|
||||
$proc = Start-Process -FilePath $JavaPath -ArgumentList @('-jar', ('"' + $jar + '"'), ('--server.port=' + $ServerPort)) `
|
||||
-PassThru -WindowStyle Hidden -RedirectStandardOutput $outLog -RedirectStandardError $errLog
|
||||
Write-Host "Started application PID=$($proc.Id) on port $ServerPort, stderr log: $errLog"
|
||||
}
|
||||
$ready = $false
|
||||
for ($i = 0; $i -lt $ReadyAttempts; $i++) {
|
||||
try {
|
||||
$probe = Invoke-WebRequest -Uri ($BaseUrl + '/apt/ops/connectivity') -UseBasicParsing -TimeoutSec 3
|
||||
if ([int]$probe.StatusCode -eq 200) { $ready = $true; break }
|
||||
}
|
||||
catch { Start-Sleep -Seconds 2 }
|
||||
}
|
||||
if (-not $ready) { throw 'Application not ready - check stderr log' }
|
||||
|
||||
# ---------- 5. M0 regression + M1 + M2 + version checks ----------
|
||||
try {
|
||||
$admin = Get-Token -User 'admin' -Password $AdminPassword
|
||||
$guest = Get-Token -User 'apt_guest' -Password $GuestPassword
|
||||
|
||||
$r = Invoke-Api -Path '/apt/route/list'
|
||||
Add-Result 'M0-1 no token' 'HTTP 401 / 40101' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 401 -and $r.body.code -eq 40101)
|
||||
|
||||
$r = Invoke-Api -Path '/apt/route/list' -Token $guest
|
||||
Add-Result 'M0-2 no permission' 'HTTP 403 / 40301' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 403 -and $r.body.code -eq 40301)
|
||||
|
||||
$r = Invoke-Api -Path '/apt/room/list?gyid=APT-B' -Token $admin
|
||||
Add-Result 'M0-3 cross gyid' 'HTTP 403 / 40302' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 403 -and $r.body.code -eq 40302 -and $r.raw -notmatch '"rows"')
|
||||
|
||||
$r = Invoke-Api -Path '/apt/room/list?pageSize=1000' -Token $admin
|
||||
Add-Result 'M0-4 page cap' 'HTTP 200 / pageSize=100' "HTTP $($r.status) / pageSize=$($r.body.pageSize)" `
|
||||
($r.status -eq 200 -and $r.body.pageSize -eq 100)
|
||||
|
||||
# apt.* / springdoc group-configs now live in ruoyi-apt/apt-defaults.yml via spring.config.import
|
||||
$doc = Invoke-Api -Path '/v3/api-docs/apt'
|
||||
$aptPaths = @()
|
||||
if ($doc.status -eq 200 -and $null -ne $doc.body.paths) {
|
||||
$aptPaths = @($doc.body.paths.PSObject.Properties.Name | Where-Object { $_ -like '/apt/*' })
|
||||
}
|
||||
Add-Result 'M0-5 apt config + springdoc group' 'HTTP 200 with /apt/* paths' `
|
||||
"HTTP $($doc.status) / aptPaths=$($aptPaths.Count)" `
|
||||
($doc.status -eq 200 -and $aptPaths.Count -gt 0)
|
||||
|
||||
$routeList = Invoke-Api -Path '/apt/route/list' -Token $admin
|
||||
$routeRows = @($routeList.body.rows)
|
||||
$routeRow = $routeRows | Where-Object { $_.jlid -eq 'JL-001' } | Select-Object -First 1
|
||||
if ($null -eq $routeRow) { throw 'Mock route JL-001 missing - run apply-it2-sql.ps1' }
|
||||
$routeId = $routeRow.id
|
||||
Add-Result 'M1-1 list + mapping' 'HTTP 200, >=3 rows, jlid/jch mapped' `
|
||||
"HTTP $($routeList.status) / rows=$($routeRows.Count) / jch=$($routeRow.jch)" `
|
||||
($routeList.status -eq 200 -and $routeRows.Count -ge 3 -and $routeRow.jch)
|
||||
|
||||
$r = Invoke-Api -Path "/apt/route/$routeId" -Token $admin
|
||||
Add-Result 'M1-2 detail' 'HTTP 200 / jlid=JL-001' "HTTP $($r.status) / $($r.body.data.jlid)" `
|
||||
($r.status -eq 200 -and $r.body.data.jlid -eq 'JL-001')
|
||||
|
||||
$now = Get-Date
|
||||
$dupBody = @{
|
||||
jlid = 'JL-001'; cc = 'G101'
|
||||
kcsj = $now.AddHours(8).ToString('yyyy-MM-dd HH:mm:ss')
|
||||
jcsj = $now.AddHours(6).ToString('yyyy-MM-dd HH:mm:ss')
|
||||
lysj = $now.AddHours(5).ToString('yyyy-MM-dd HH:mm:ss')
|
||||
jch = 'e2e'; qd = 'e2e'; zd = 'e2e'
|
||||
}
|
||||
$r = Invoke-Api -Path '/apt/route' -Token $admin -Method Post -Body $dupBody
|
||||
Add-Result 'M1-3 duplicate jlid' 'HTTP 409 / 40901' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 409 -and $r.body.code -eq 40901)
|
||||
|
||||
$r = Invoke-Api -Path "/apt/route/$routeId/status" -Token $admin -Method Put -Body @{ enabled = $false }
|
||||
Add-Result 'M1-4 missing reason' 'HTTP 400 / 40001' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 400 -and $r.body.code -eq 40001)
|
||||
|
||||
$r = Invoke-Api -Path "/apt/route/$routeId/recalc-check" -Token $admin -Method Post
|
||||
Add-Result 'M1-5 recalc-check' 'HTTP 200 / computedJcsj present' `
|
||||
"HTTP $($r.status) / computedJcsj=$($r.body.data.computedJcsj)" `
|
||||
($r.status -eq 200 -and $r.body.data.computedJcsj)
|
||||
|
||||
$off = Invoke-Api -Path "/apt/route/$routeId/status" -Token $admin -Method Put `
|
||||
-Body @{ enabled = $false; reason = 'IT-2 e2e disable' }
|
||||
$on = Invoke-Api -Path "/apt/route/$routeId/status" -Token $admin -Method Put `
|
||||
-Body @{ enabled = $true; reason = 'IT-2 e2e restore' }
|
||||
Add-Result 'M1-6 status with reason' 'HTTP 200 / 200' "HTTP $($off.status) / $($on.status)" `
|
||||
($off.status -eq 200 -and $on.status -eq 200)
|
||||
|
||||
$roomList = Invoke-Api -Path '/apt/room/list' -Token $admin
|
||||
$roomRows = @($roomList.body.rows)
|
||||
$roomRow = $roomRows | Where-Object { $_.roomNo -eq '201' } | Select-Object -First 1
|
||||
if ($null -eq $roomRow) { throw 'Mock room 201 missing - run apply-it2-sql.ps1' }
|
||||
$roomId = $roomRow.id
|
||||
Add-Result 'M2-1 room list' 'HTTP 200, >=5 rows' "HTTP $($roomList.status) / rows=$($roomRows.Count)" `
|
||||
($roomList.status -eq 200 -and $roomRows.Count -ge 5)
|
||||
|
||||
$r = Invoke-Api -Path '/apt/room/list?roomStatus=4' -Token $admin
|
||||
$filtered = @($r.body.rows)
|
||||
$allFour = $true
|
||||
foreach ($row in $filtered) { if ($row.roomStatus -ne '4') { $allFour = $false } }
|
||||
Add-Result 'M2-2 status filter' 'only roomStatus=4' `
|
||||
"HTTP $($r.status) / rows=$($filtered.Count) / allFour=$allFour" `
|
||||
($r.status -eq 200 -and $filtered.Count -ge 1 -and $allFour)
|
||||
|
||||
$r = Invoke-Api -Path '/apt/room/status-summary' -Token $admin
|
||||
Add-Result 'M2-3 summary' 'HTTP 200 / total>=5' "HTTP $($r.status) / total=$($r.body.data.total)" `
|
||||
($r.status -eq 200 -and $r.body.data.total -ge 5)
|
||||
|
||||
$r = Invoke-Api -Path "/apt/room/$roomId" -Token $admin
|
||||
$hasLists = $r.body.data.PSObject.Properties.Name -contains 'bindings'
|
||||
Add-Result 'M2-4 detail' 'HTTP 200 / roomNo=201 / bindings present' `
|
||||
"HTTP $($r.status) / roomNo=$($r.body.data.roomNo) / lists=$hasLists" `
|
||||
($r.status -eq 200 -and $r.body.data.roomNo -eq '201' -and $hasLists)
|
||||
|
||||
$r = Invoke-Api -Path "/apt/room/$roomId" -Token $admin -Method Put `
|
||||
-Body @{ roomType = 'DOUBLE'; roomFloor = '3' }
|
||||
Add-Result 'M2-5 snapshot readonly' 'HTTP 409 / 40904' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 409 -and $r.body.code -eq 40904)
|
||||
|
||||
$r = Invoke-Api -Path "/apt/room/$roomId/status" -Token $admin -Method Put -Body @{ action = 'DISABLE' }
|
||||
Add-Result 'M2-6 missing reason' 'HTTP 400 / 40001' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 400 -and $r.body.code -eq 40001)
|
||||
|
||||
# Make the disable/restore sequence replayable even after an interrupted run.
|
||||
$before = Invoke-Api -Path "/apt/room/$roomId" -Token $admin
|
||||
if ($before.body.data.roomStatus -eq '4') {
|
||||
$reset = Invoke-Api -Path "/apt/room/$roomId/status" -Token $admin -Method Put `
|
||||
-Body @{ action = 'RESTORE'; reason = 'IT-2 e2e precondition reset';
|
||||
commandId = ('IT2R' + [guid]::NewGuid().ToString('N')) }
|
||||
if ($reset.status -ne 200) { throw "Precondition reset failed: $($reset.raw)" }
|
||||
$before = Invoke-Api -Path "/apt/room/$roomId" -Token $admin
|
||||
}
|
||||
$beforeVersion = [long]$before.body.data.stateVersion
|
||||
$cmdId = 'IT2D' + [guid]::NewGuid().ToString('N')
|
||||
$disableBody = @{ action = 'DISABLE'; reason = 'IT-2 e2e disable'; commandId = $cmdId }
|
||||
|
||||
$disable = Invoke-Api -Path "/apt/room/$roomId/status" -Token $admin -Method Put -Body $disableBody
|
||||
$v1 = $disable.body.data.stateVersion
|
||||
Add-Result 'M2-7 disable' 'status=4 / version+1' `
|
||||
"HTTP $($disable.status) / status=$($disable.body.data.roomStatus) / v=$beforeVersion->$v1" `
|
||||
($disable.status -eq 200 -and $disable.body.data.roomStatus -eq '4' -and [long]$v1 -eq ($beforeVersion + 1))
|
||||
|
||||
$dup = Invoke-Api -Path "/apt/room/$roomId/status" -Token $admin -Method Put -Body $disableBody
|
||||
Add-Result 'M2-8 commandId idempotent' 'duplicated=true / version unchanged' `
|
||||
"HTTP $($dup.status) / duplicated=$($dup.body.data.duplicated) / v=$($dup.body.data.stateVersion)" `
|
||||
($dup.status -eq 200 -and $dup.body.data.duplicated -eq $true -and [long]$dup.body.data.stateVersion -eq [long]$v1)
|
||||
|
||||
$after = Invoke-Api -Path "/apt/room/$roomId" -Token $admin
|
||||
$livePersonsPresent = $after.body.data.PSObject.Properties.Name -contains 'livePersons'
|
||||
Add-Result 'M2-9 disabled still queryable' 'status=4 / livePersons present' `
|
||||
"HTTP $($after.status) / status=$($after.body.data.roomStatus) / livePersons=$livePersonsPresent" `
|
||||
($after.status -eq 200 -and $after.body.data.roomStatus -eq '4' -and $livePersonsPresent)
|
||||
|
||||
$restore = Invoke-Api -Path "/apt/room/$roomId/status" -Token $admin -Method Put `
|
||||
-Body @{ action = 'RESTORE'; reason = 'IT-2 e2e restore';
|
||||
commandId = ('IT2R' + [guid]::NewGuid().ToString('N')) }
|
||||
Add-Result 'M2-10 restore recalc' 'status<>4 / version+1 again' `
|
||||
"HTTP $($restore.status) / status=$($restore.body.data.roomStatus) / v=$($restore.body.data.stateVersion)" `
|
||||
($restore.status -eq 200 -and $restore.body.data.roomStatus -ne '4' `
|
||||
-and [long]$restore.body.data.stateVersion -eq ($beforeVersion + 2))
|
||||
|
||||
$r = Invoke-Api -Path '/apt/room' -Token $admin -Method Post -Body @{}
|
||||
Add-Result 'M2-11 add disabled' 'HTTP 409 / 40940' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 409 -and $r.body.code -eq 40940)
|
||||
|
||||
$versions = Invoke-Api -Path '/apt/master/versions' -Token $admin
|
||||
$vdata = @($versions.body.data)
|
||||
$allLocal = $true
|
||||
foreach ($v in $vdata) {
|
||||
if ($v.source -ne 'LOCAL' -or $v.sourceVersion -ne 0 -or $v.localMock -ne $true) { $allLocal = $false }
|
||||
}
|
||||
Add-Result 'M2-12 version markers' '6 domains LOCAL/0/mock=true' `
|
||||
"HTTP $($versions.status) / count=$($vdata.Count) / allLocal=$allLocal / notice=$($versions.body.mockNotice)" `
|
||||
($versions.status -eq 200 -and $vdata.Count -eq 6 -and $allLocal -and $versions.body.mockNotice)
|
||||
|
||||
$r = Invoke-Api -Path '/apt/master/ROUTE/resync' -Token $admin -Method Post -Body @{}
|
||||
Add-Result 'M2-13 resync disabled' 'HTTP 409 / 40940' "HTTP $($r.status) / $($r.body.code)" `
|
||||
($r.status -eq 409 -and $r.body.code -eq 40940)
|
||||
}
|
||||
finally {
|
||||
if ($null -ne $proc -and -not $proc.HasExited) {
|
||||
Stop-Process -Id $proc.Id -Force
|
||||
Write-Host "Stopped application PID=$($proc.Id)"
|
||||
}
|
||||
}
|
||||
|
||||
$results | Format-Table -AutoSize
|
||||
$failed = @($results | Where-Object { -not $_.Pass })
|
||||
Write-Host "IT-2 checks passed: $($results.Count - $failed.Count)/$($results.Count)"
|
||||
if ($failed.Count -gt 0) { exit 1 }
|
||||
exit 0
|
||||
Reference in new issue
Block a user