122 lines
9.2 KiB
PowerShell
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()
|
|
}
|