Files
FT546MWS/scripts/mixed-pilot/test-mixed-pilot.ps1

122 lines
9.2 KiB
PowerShell

param(
[string]$Server = 'lpc:.',
[Parameter(Mandatory=$true)][string]$FixtureJson,
[switch]$Baseline
)
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
# Synthetic rows only. Never run fixtures in a production database.
$testDb = 'FT546WMS_MixedPilot_Test_' + [Guid]::NewGuid().ToString('N')
$cn = [Data.SqlClient.SqlConnection]::new("Server=$Server;Database=master;Integrated Security=True;Connect Timeout=8;Application Name=FT546MixedPilotTests")
$cn.Open()
function Exec([string]$sql) {
$c=$cn.CreateCommand(); $c.CommandTimeout=15
$c.CommandText=$sql
try { [void]$c.ExecuteNonQuery() } finally {$c.Dispose()}
}
function Scalar([string]$sql) {
$c=$cn.CreateCommand(); $c.CommandTimeout=15; $c.CommandText=$sql
try { $c.ExecuteScalar() } finally {$c.Dispose()}
}
function BatchFile([string]$file) {
foreach($batch in ([IO.File]::ReadAllText($file) -split '(?im)^\s*GO\s*\r?$')) {
if($batch.Trim()) { Exec $batch }
}
}
function Assert([bool]$ok,[string]$name) { if(-not $ok){throw "FAIL: $name"}; Write-Output "PASS: $name" }
$created=$false
try {
Exec "CREATE DATABASE [$testDb] ON PRIMARY (NAME=N'$testDb', FILENAME=N'$(Scalar "SELECT CONVERT(nvarchar(4000),SERVERPROPERTY('InstanceDefaultDataPath'))")$testDb.mdf',SIZE=8MB,MAXSIZE=64MB,FILEGROWTH=8MB) LOG ON (NAME=N'${testDb}_log', FILENAME=N'$(Scalar "SELECT CONVERT(nvarchar(4000),SERVERPROPERTY('InstanceDefaultLogPath'))")${testDb}_log.ldf',SIZE=8MB,MAXSIZE=64MB,FILEGROWTH=8MB)"
$created=$true; $cn.ChangeDatabase($testDb)
Exec 'SET NOCOUNT ON; SET LOCK_TIMEOUT 2000; SET DEADLOCK_PRIORITY LOW;'
$fixture=Get-Content -LiteralPath $FixtureJson -Raw -Encoding UTF8 | ConvertFrom-Json
$allowed=@('bas_cargo_space','bas_stock','mydo_job','mydo_job_detail','mydo_job_finish','bas_led','move_msg','out_stockout','out_stockout_detail')
foreach($table in ($fixture.columns|Where-Object table_name -in $allowed|Group-Object table_name)) {
$defs=@(foreach($c in $table.Group){
$type=$c.type_name
if($type -in @('varchar','char','nvarchar','nchar','varbinary','binary')) {
$size=if($c.max_length -eq -1){'max'}elseif($type.StartsWith('n')){[string]($c.max_length/2)}else{[string]$c.max_length}
$type+="($size)"
} elseif($type -in @('decimal','numeric')) {$type+="($($c.precision),$($c.scale))"}
# Nullability is relaxed for concise synthetic fixtures, types/order match site.
"[$($c.column_name)] $type NULL"
})
Exec "CREATE TABLE dbo.[$($table.Name)] ($($defs -join ','))"
}
foreach($p in $fixture.wms_objects|Where-Object name -in @('cargo_space_group_count','proc_allot_space','proc_yk_job_add','proc_yk_complete','proc_out_auto_allocation','proc_out_hand_allocation')) {Exec $p.definition}
Exec "CREATE TABLE dbo.bas_roadway(roadway_id varchar(80),out_locked varchar(10),out_locked2 varchar(10))"
Exec "CREATE PROCEDURE dbo.util_serial_job @kind varchar(10),@id varchar(255) OUTPUT AS SET @id=CONVERT(varchar(36),NEWID())"
Exec "CREATE SEQUENCE dbo.test_task_seq AS int START WITH 900001"
Exec "CREATE PROCEDURE dbo.util_serial_task @kind varchar(10),@id int OUTPUT AS SET @id=NEXT VALUE FOR dbo.test_task_seq"
if(-not $Baseline){ & "$PSScriptRoot/install-mixed-pilot.ps1" -Server $Server -Database $testDb -TestDatabase }
BatchFile "$PSScriptRoot/tests/seed.sql"
if($Baseline){
$result=Scalar "DECLARE @r int,@m varchar(255); EXEC dbo.proc_allot_space 'NEW-TRAY','TEST',@r OUTPUT,@m OUTPUT; SELECT @r"
Assert ($result -eq 10) 'new project gets designated front slot when Legacy has no position'
} else {
& "$PSScriptRoot/control-mixed-pilot.ps1" -Server $Server -Database $testDb -TestDatabase -Mode enable -ConfirmPhysicalFit -AllProjectsMayMix
Assert ((Scalar 'SELECT COUNT(*) FROM mixed_pilot.lane WHERE enabled=1') -eq 3) 'enable command opens exactly three whole channels'
& "$PSScriptRoot/control-mixed-pilot.ps1" -Server $Server -Database $testDb -TestDatabase -Mode disable
$refused=$false
try { & "$PSScriptRoot/control-mixed-pilot.ps1" -Server $Server -Database $testDb -TestDatabase -Mode uninstall } catch {$refused=$_.Exception.Message -like '*not drained*'}
Assert $refused 'uninstall refuses occupied trial channels'
BatchFile "$PSScriptRoot/tests/behavior.sql"
Write-Output 'PASS: mixed-pilot SQL behavior suite'
Exec 'DELETE dbo.mydo_job; DELETE dbo.mydo_job_detail; DELETE dbo.bas_stock; DELETE dbo.bas_cargo_space; DELETE mixed_pilot.dispatch_hold; DELETE mixed_pilot.move_audit; DELETE mixed_pilot.admission;'
BatchFile "$PSScriptRoot/tests/seed.sql"
BatchFile "$PSScriptRoot/tests/legacy-outbound.sql"
Write-Output 'PASS: actual site Legacy outbound procedure generates blocker relocation and holds rear outbound'
Exec 'DELETE dbo.mydo_job; DELETE dbo.mydo_job_detail; DELETE dbo.bas_stock; DELETE dbo.bas_cargo_space; DELETE mixed_pilot.dispatch_hold; DELETE mixed_pilot.move_audit; DELETE mixed_pilot.admission;'
BatchFile "$PSScriptRoot/tests/seed.sql"
Exec "UPDATE mixed_pilot.control SET enabled=1,all_projects_may_mix=1; UPDATE mixed_pilot.lane SET enabled=CASE WHEN lane_code='C1203' THEN 1 ELSE 0 END,physical_fit_confirmed=1; INSERT dbo.mydo_job(job_id,tray_code,warehouse_id,line_id,in_or_out,status,fault_flag) VALUES('IN-RACE','RACE-TRAY','01','RACE',1,'1','0'); INSERT dbo.mydo_job_detail(job_id,spare4,spare1,spare2,spare3,in_quantity) VALUES('IN-RACE','RACE','X','Y','Z',1)"
$raceCn=[Data.SqlClient.SqlConnection]::new("Server=$Server;Database=$testDb;Integrated Security=True;Connect Timeout=8;Application Name=FT546MixedPilotRaceTest")
$raceCn.Open()
try {
Exec 'BEGIN TRANSACTION;'
Exec "DECLARE @r int,@m varchar(255); EXEC dbo.proc_allot_space 'NEW-TRAY','TEST',@r OUTPUT,@m OUTPUT; IF @r<>10 THROW 51950,'First racing allocation failed',1;"
$raceCmd=$raceCn.CreateCommand();$raceCmd.CommandTimeout=10
$raceCmd.CommandText="DECLARE @r int,@m varchar(255); EXEC dbo.proc_allot_space 'RACE-TRAY','TEST',@r OUTPUT,@m OUTPUT; SELECT @r;"
$pending=$raceCmd.ExecuteScalarAsync()
# Hold the first reservation across the second request's lock timeout.
$second=$pending.GetAwaiter().GetResult()
Assert ($second -ne 10) 'concurrent request cannot allocate a reserved pilot slot'
Exec 'COMMIT TRANSACTION;'
Assert ((Scalar "SELECT COUNT(*) FROM dbo.mydo_job WHERE cargo_space_id='120703'") -eq 1) 'only one inbound task owns the pilot slot'
} finally {
if((Scalar 'SELECT @@TRANCOUNT') -gt 0){Exec 'ROLLBACK TRANSACTION;'}
if($raceCmd){$raceCmd.Dispose()};$raceCn.Dispose()
}
# Re-running install must preserve runtime switches and history.
Exec 'UPDATE mixed_pilot.control SET enabled=1 WHERE id=1'
& "$PSScriptRoot/install-mixed-pilot.ps1" -Server $Server -Database $testDb -TestDatabase
Assert ((Scalar 'SELECT enabled FROM mixed_pilot.control WHERE id=1') -eq 1) 'repeat install preserves switch'
$releaseDefinition=Scalar "SELECT OBJECT_DEFINITION(OBJECT_ID('mixed_pilot.release_ready_jobs'))"
Exec 'ALTER PROCEDURE mixed_pilot.release_ready_jobs AS RETURN;'
$driftRejected=$false
try { & "$PSScriptRoot/install-mixed-pilot.ps1" -Server $Server -Database $testDb -TestDatabase } catch {$driftRejected=$_.Exception.Message -like '*drift*'}
Assert $driftRejected 'repeat install detects altered pilot helpers'
Exec 'DROP PROCEDURE mixed_pilot.release_ready_jobs;'
Exec $releaseDefinition
& "$PSScriptRoot/control-mixed-pilot.ps1" -Server $Server -Database $testDb -TestDatabase -Mode disable
Exec 'DELETE dbo.mydo_job; DELETE dbo.bas_stock; UPDATE dbo.bas_cargo_space SET status=''1'',tray_code=NULL;'
& "$PSScriptRoot/control-mixed-pilot.ps1" -Server $Server -Database $testDb -TestDatabase -Mode uninstall
Assert ((Scalar 'SELECT installed FROM mixed_pilot.control WHERE id=1') -eq 0) 'uninstall removes runtime protection only after draining'
foreach($p in $fixture.wms_objects|Where-Object name -in @('proc_allot_space','proc_yk_job_add','proc_yk_complete')) {
$restored=Scalar "SELECT OBJECT_DEFINITION(OBJECT_ID('dbo.$($p.name)'))"
# SQL Server may persist CREATE even when ALTER was submitted. Compare the full body with only that keyword normalized.
$expected=[regex]::Replace($p.definition,'(?i)\b(?:CREATE|ALTER)\s+PROCEDURE','CREATE PROCEDURE')
$normalized=[regex]::Replace($restored,'(?i)\b(?:CREATE|ALTER)\s+PROCEDURE','CREATE PROCEDURE')
Assert ($normalized -ceq $expected) "uninstall restores $($p.name) body exactly"
}
}
} finally {
if($created){
[Data.SqlClient.SqlConnection]::ClearAllPools()
$cn.ChangeDatabase('master')
if($testDb -notmatch '^FT546WMS_MixedPilot_Test_[a-f0-9]{32}$'){throw 'Invalid scratch database name'}
Exec "DROP DATABASE [$testDb]"
Write-Output "Scratch database removed: $testDb"
}
$cn.Dispose()
}