test(install): cubrir la ruta personalizada C:\Aduanasoft\CloudRestoreAS-win
Esa ruta es el peor caso posible y existe en produccion: la ruta por omision C:\Aduanasoft\CloudRestoreAS es PREFIJO DE CADENA de ella, asi que cualquier comparacion hecha con startsWith daria por iguales dos instalaciones distintas — y el resultado seria el fallo silencioso otra vez, actualizar una carpeta y arrancar la otra. El flujo ya la manejaba bien (Test-SamePath compara por igualdad exacta tras normalizar), pero nada lo probaba: la emulacion usaba declarada/otra-carpeta, nombres sin relacion entre si, que un startsWith mal puesto pasaria sin problema. - Escenario `sufijo` en emular-actualizacion-windows.ps1: instalacion en ...\CloudRestoreAS-win y tarea apuntando a ...\CloudRestoreAS. Verificado en Windows: reapunta la tarea, el proceso queda corriendo desde -win y el sello en la version nueva. - Nuevo scripts/probar-funciones-install.ps1: extrae las funciones del instalador por AST y las ejercita contra una tarea simulada, sin elevacion. Cubre los casos limite de la comparacion de rutas (el par de prefijo en ambos sentidos, comillas, barra final, mayusculas, `..`, ruta vacia) y que Sync-AgentTaskPath falle cuando no puede corregir. BUILD.md documenta por que se compara por igualdad exacta y no por prefijo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
17
BUILD.md
17
BUILD.md
@@ -235,16 +235,27 @@ sabiendas el binario anterior es peor que abortar. Y la confirmación de arranqu
|
||||
del proceso, no solo su nombre — un agente viejo que nunca se detuvo satisface igual de bien un
|
||||
`Get-Process -Name CloudRestoreAS`.
|
||||
|
||||
### Rutas de instalación personalizadas
|
||||
|
||||
`C:\Aduanasoft\CloudRestoreAS-win` es el caso a tener presente, y existe en producción: la ruta por
|
||||
omisión `C:\Aduanasoft\CloudRestoreAS` es **prefijo de cadena** de ella. Por eso las rutas se
|
||||
comparan por **igualdad exacta tras normalizar** (comillas, barra final, mayúsculas) y nunca con
|
||||
`startsWith` — que daría por iguales dos instalaciones distintas. En el panel eso vive en un solo
|
||||
sitio, `sameWindowsPath()`; en el instalador, en `Test-SamePath`.
|
||||
|
||||
Para reproducirlo y comprobarlo sin un servidor, desde WSL o Windows:
|
||||
|
||||
```powershell
|
||||
scripts\emular-actualizacion-windows.ps1 -Installer .\install.ps1 # caso roto
|
||||
scripts\emular-actualizacion-windows.ps1 -Installer .\install.ps1 -Escenario alineada # caso normal
|
||||
scripts\emular-actualizacion-windows.ps1 -Installer .\install.ps1 -Escenario sufijo # ...-win
|
||||
scripts\probar-funciones-install.ps1 # casos límite
|
||||
```
|
||||
|
||||
Monta un agente falso (un `.exe` real que se queda vivo), una instalación en una carpeta y una
|
||||
tarea apuntando a otra, corre el instalador y dice si la actualización surtió efecto. No necesita
|
||||
elevación ni toca la instalación real de la máquina.
|
||||
La emulación monta un agente falso (un `.exe` real que se queda vivo), una instalación en una
|
||||
carpeta y una tarea apuntando a otra, corre el instalador y dice si la actualización surtió efecto.
|
||||
`probar-funciones-install.ps1` extrae las funciones del instalador por AST y las ejercita contra una
|
||||
tarea simulada. Ninguno de los dos necesita elevación ni toca la instalación real de la máquina.
|
||||
|
||||
`-UpdateInPlace` además no vuelve a registrar la tarea (así no pisa ajustes hechos sobre ella) y
|
||||
se salta el bootstrap: una segunda instancia purgaría el `Temp\` de la que está viva. Es el modo
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
# package-release.sh (nombres de artefacto y release.json) y el reporte al PANEL.
|
||||
# Formato obligatorio: puntos y números, monotónico creciente — el PANEL compara
|
||||
# versiones como tuplas de enteros para detectar si hay una más nueva.
|
||||
__version__ = "1.1.2"
|
||||
__version__ = "1.1.3"
|
||||
__author__ = "Aduanasoft"
|
||||
|
||||
@@ -31,15 +31,29 @@ param(
|
||||
[string]$Etiqueta = 'instalador',
|
||||
# 'desalineada' reproduce el servidor que falla; 'alineada' es el caso normal, para comprobar
|
||||
# que el arreglo no lo rompe (una tarea que ya apunta bien no debe tocarse).
|
||||
[ValidateSet('desalineada', 'alineada')][string]$Escenario = 'desalineada',
|
||||
#
|
||||
# 'sufijo' es el caso PELIGROSO y existe en producción: la instalación está en
|
||||
# ...\CloudRestoreAS-win y la tarea apunta a ...\CloudRestoreAS, que es prefijo de cadena de la
|
||||
# anterior. Cualquier comparación de rutas hecha con `startsWith` las daría por iguales, no
|
||||
# corregiría la tarea, y la actualización volvería a no surtir efecto.
|
||||
[ValidateSet('desalineada', 'alineada', 'sufijo')][string]$Escenario = 'desalineada',
|
||||
[string]$Carpeta = 'C:\Users\Public\cras-e2e'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$base = $Carpeta
|
||||
$pkg = Join-Path $base 'pkg'
|
||||
|
||||
# En el escenario 'sufijo' los nombres NO son arbitrarios: reproducen el par que se da en producción,
|
||||
# donde el nombre de una carpeta es prefijo de cadena del de la otra. Con nombres sin relación entre
|
||||
# sí (declarada / otra-carpeta) un `startsWith` mal puesto pasaría la prueba sin problema.
|
||||
if ($Escenario -eq 'sufijo') {
|
||||
$declarada = Join-Path $base 'CloudRestoreAS-win' # la instalación real
|
||||
$otra = Join-Path $base 'CloudRestoreAS' # a donde apunta la tarea
|
||||
} else {
|
||||
$declarada = Join-Path $base 'declarada' # donde el panel cree que está (y está)
|
||||
$otra = Join-Path $base 'otra-carpeta' # a donde apunta la tarea
|
||||
$pkg = Join-Path $base 'pkg'
|
||||
}
|
||||
|
||||
function Nuevo-AgenteFalso {
|
||||
param([string]$Destino, [string]$Version, [switch]$EscribeSello)
|
||||
|
||||
111
scripts/probar-funciones-install.ps1
Normal file
111
scripts/probar-funciones-install.ps1
Normal file
@@ -0,0 +1,111 @@
|
||||
<#
|
||||
Pruebas de las funciones de install.ps1, sin instalar nada.
|
||||
|
||||
Las funciones se extraen del script por AST y se ejercitan contra una tarea programada simulada.
|
||||
Así se prueba el COMPORTAMIENTO —no solo que el archivo parsee— sin necesitar elevación ni
|
||||
registrar una tarea 'CloudRestoreAS' de verdad en la máquina.
|
||||
|
||||
Complementa a emular-actualizacion-windows.ps1: ese cubre el flujo completo, este cubre los casos
|
||||
límite de la comparación de rutas, que es donde se esconden los fallos silenciosos.
|
||||
|
||||
Uso: .\probar-funciones-install.ps1
|
||||
#>
|
||||
param([string]$Installer = (Join-Path (Split-Path -Parent $PSScriptRoot) 'install.ps1'))
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$tokens = $null; $errores = $null
|
||||
$ast = [System.Management.Automation.Language.Parser]::ParseFile(
|
||||
(Resolve-Path $Installer).Path, [ref]$tokens, [ref]$errores)
|
||||
if ($errores -and $errores.Count -gt 0) {
|
||||
Write-Host "install.ps1 no parsea: $($errores.Count) error(es)" -ForegroundColor Red
|
||||
$errores | ForEach-Object { Write-Host (" L" + $_.Extent.StartLineNumber + ": " + $_.Message) }
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Variables de ámbito de script que usan las funciones.
|
||||
$Prefix = 'C:\Aduanasoft\CloudRestoreAS-win'
|
||||
$dest = Join-Path $Prefix 'CloudRestoreAS.exe'
|
||||
$TaskName = 'CloudRestoreAS'
|
||||
$ProcName = 'CloudRestoreAS'
|
||||
|
||||
$queremos = @('Write-Step', 'Write-Ok', 'Write-Warn', 'Get-NormalizedPath', 'Test-SamePath',
|
||||
'Get-AgentTaskExecute', 'Repair-AgentTaskPath', 'Sync-AgentTaskPath')
|
||||
foreach ($f in $ast.FindAll({ param($n)
|
||||
$n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)) {
|
||||
if ($queremos -contains $f.Name) { . ([scriptblock]::Create($f.Extent.Text)) }
|
||||
}
|
||||
|
||||
$fallos = 0
|
||||
function Assert($cond, $msg) {
|
||||
if ($cond) { Write-Host " OK $msg" } else { Write-Host " FALLA $msg" -ForegroundColor Red; $script:fallos++ }
|
||||
}
|
||||
|
||||
Write-Host '=== Test-SamePath: rutas donde una es prefijo de cadena de la otra ==='
|
||||
# El caso que existe en produccion. Un `-like "$a*"` o un StartsWith aqui daria por iguales dos
|
||||
# instalaciones distintas, no se corregiria la tarea, y la actualizacion no surtiria efecto.
|
||||
$porOmision = 'C:\Aduanasoft\CloudRestoreAS'
|
||||
$personalizada = 'C:\Aduanasoft\CloudRestoreAS-win'
|
||||
Assert (-not (Test-SamePath $porOmision $personalizada)) 'no confunde la ruta por omision con la -win'
|
||||
Assert (-not (Test-SamePath $personalizada $porOmision)) 'ni al reves'
|
||||
Assert (-not (Test-SamePath "$porOmision\CloudRestoreAS.exe" "$personalizada\CloudRestoreAS.exe")) `
|
||||
'tampoco con el ejecutable completo'
|
||||
Assert (Test-SamePath $personalizada $personalizada) 'y la personalizada sigue siendo igual a si misma'
|
||||
|
||||
Write-Host '=== Test-SamePath: normalizacion ==='
|
||||
Assert (Test-SamePath "`"$personalizada`"" $personalizada) 'tolera comillas (la tarea las guarda asi)'
|
||||
Assert (Test-SamePath $personalizada.ToUpper() $personalizada) 'NTFS no distingue mayusculas'
|
||||
Assert (Test-SamePath "$personalizada\" $personalizada) 'ignora la barra final'
|
||||
Assert (Test-SamePath "$Prefix\..\CloudRestoreAS-win\x.exe" "$Prefix\x.exe") 'normaliza .. en la ruta'
|
||||
Assert (-not (Test-SamePath '' $personalizada)) 'una ruta vacia no es igual a nada'
|
||||
|
||||
Write-Host '=== Get-AgentTaskExecute ==='
|
||||
function Get-AgentTask { [pscustomobject]@{ Actions = @(
|
||||
[pscustomobject]@{ Execute = "`"$porOmision\CloudRestoreAS.exe`""; Arguments = '--start-engine --headless' }) } }
|
||||
Assert ((Get-AgentTaskExecute) -eq "$porOmision\CloudRestoreAS.exe") 'devuelve la ruta sin comillas'
|
||||
|
||||
# Una accion ComHandler no tiene .Execute: con StrictMode, tocarla a ciegas seria un error.
|
||||
function Get-AgentTask { [pscustomobject]@{ Actions = @(
|
||||
[pscustomobject]@{ ClassId = '{guid}' },
|
||||
[pscustomobject]@{ Execute = "$porOmision\CloudRestoreAS.exe"; Arguments = '' }) } }
|
||||
Assert ((Get-AgentTaskExecute) -eq "$porOmision\CloudRestoreAS.exe") 'se salta acciones sin Execute'
|
||||
|
||||
function Get-AgentTask { $null }
|
||||
Assert ((Get-AgentTaskExecute) -eq '') 'sin tarea devuelve cadena vacia'
|
||||
|
||||
Write-Host '=== Sync-AgentTaskPath: el fallo silencioso ==='
|
||||
$script:reapuntadoA = $null
|
||||
$script:argsPreservados = $null
|
||||
function Set-ScheduledTask { param($TaskName, $Action)
|
||||
$ejec = @($Action | Where-Object { $_.PSObject.Properties['Execute'] -and $_.Execute })
|
||||
$script:reapuntadoA = $ejec[0].Execute
|
||||
$script:argsPreservados = $ejec[0].Arguments
|
||||
return $true }
|
||||
function New-ScheduledTaskAction { param($Execute, $Argument, $WorkingDirectory)
|
||||
[pscustomobject]@{ Execute = $Execute; Arguments = $Argument } }
|
||||
|
||||
function Get-AgentTask { [pscustomobject]@{ Actions = @(
|
||||
[pscustomobject]@{ Execute = "$porOmision\CloudRestoreAS.exe"; Arguments = '--start-engine --headless' }) } }
|
||||
Sync-AgentTaskPath
|
||||
Assert ($script:reapuntadoA -eq $dest) "reapunta de la carpeta por omision a $dest"
|
||||
Assert ($script:argsPreservados -eq '--start-engine --headless') 'conserva los argumentos originales'
|
||||
|
||||
$script:reapuntadoA = $null
|
||||
function Get-AgentTask { [pscustomobject]@{ Actions = @(
|
||||
[pscustomobject]@{ Execute = $dest; Arguments = '--start-engine --headless' }) } }
|
||||
Sync-AgentTaskPath
|
||||
Assert ($null -eq $script:reapuntadoA) 'una tarea ya alineada no se toca'
|
||||
|
||||
# Si no se puede corregir tiene que FALLAR, no arrancar el binario viejo en silencio.
|
||||
function Set-ScheduledTask { param($TaskName, $Action) throw 'Acceso denegado' }
|
||||
function Get-AgentTask { [pscustomobject]@{ Actions = @(
|
||||
[pscustomobject]@{ Execute = "$porOmision\CloudRestoreAS.exe"; Arguments = '' }) } }
|
||||
$lanzo = $false; $msg = ''
|
||||
try { Sync-AgentTaskPath } catch { $lanzo = $true; $msg = $_.Exception.Message }
|
||||
Assert $lanzo 'sin permiso para corregir -> lanza en vez de seguir'
|
||||
Assert ($lanzo -and $msg -match 'sin efecto') 'el mensaje explica que la actualizacion no surtiria efecto'
|
||||
|
||||
Write-Host ''
|
||||
if ($fallos -gt 0) { Write-Host "FALLOS: $fallos" -ForegroundColor Red; exit 1 }
|
||||
Write-Host 'TODO OK' -ForegroundColor Green
|
||||
Reference in New Issue
Block a user