Tests/Environment.Tests.ps1

BeforeDiscovery {
    # -Skip se resuelve durante el descubrimiento de Pester, antes de BeforeAll.
    # Calcular aqui la plataforma es obligatorio: bajo Pester 6, leerla de una
    # variable fijada en BeforeAll aborta el fichero de pruebas entero.
    $script:EsWindows = [System.Environment]::OSVersion.Platform -eq 'Win32NT'
}

BeforeAll {
    $script:ModulePath = Join-Path -Path (Split-Path -Parent $PSCommandPath) -ChildPath '../TechCare.psd1'
    Import-Module -Name $script:ModulePath -Force
}

AfterAll {
    Remove-Module -Name TechCare -Force -ErrorAction SilentlyContinue
}

Describe 'Deteccion de entorno' {

    It 'Test-TechCarePrerequisite devuelve el contrato completo' {
        $r = Test-TechCarePrerequisite
        foreach ($prop in @(
                'IsWindows', 'IsAdmin', 'IsSupportedWindows', 'OSCaption', 'OSBuild',
                'IsManaged', 'ManagedReasons', 'RelaunchCommand', 'PSVersion',
                'PSEdition', 'ModuleVersion')) {
            $r.PSObject.Properties.Name | Should -Contain $prop
        }
    }

    It 'IsAdmin, IsWindows e IsManaged son booleanos, nunca nulos' {
        $r = Test-TechCarePrerequisite
        $r.IsAdmin    | Should -BeOfType [bool]
        $r.IsWindows  | Should -BeOfType [bool]
        $r.IsManaged  | Should -BeOfType [bool]
    }

    It 'declara la version del modulo que realmente esta cargada' {
        (Test-TechCarePrerequisite).ModuleVersion | Should -Be ((Get-Module -Name TechCare).Version.ToString())
    }

    It 'funciona en cualquier sistema operativo sin lanzar' {
        # Es el unico cmdlet que debe responder tambien fuera de Windows: su
        # trabajo es precisamente decir si el resto puede ejecutarse.
        { Test-TechCarePrerequisite } | Should -Not -Throw
    }
}

Describe 'Guarda de plataforma' -Skip:$script:EsWindows {

    It 'los cmdlets que tocan APIs de Windows abortan con un mensaje claro' {
        $motor = @(
            'Invoke-TechCare', 'Get-TechCareDiagnostics', 'Invoke-TechCareRepair',
            'Invoke-TechCareUpdate', 'Invoke-TechCareCleanup',
            'Get-TechCarePerformance', 'Get-TechCareSecurity'
        )
        foreach ($name in $motor) {
            { & $name -ErrorAction Stop } | Should -Throw -ExpectedMessage '*solo puede ejecutarse en Windows*' -Because "$name debe abortar fuera de Windows"
        }
    }

    It '-WhatIf no salva la guarda de plataforma' {
        # Un -WhatIf no convierte un modulo de Windows en multiplataforma: el
        # mensaje debe salir igual, no un error crudo de una API ausente.
        foreach ($name in @('Invoke-TechCare', 'Invoke-TechCareRepair', 'Invoke-TechCareCleanup', 'Get-TechCareSecurity')) {
            { & $name -WhatIf -ErrorAction Stop } | Should -Throw -ExpectedMessage '*solo puede ejecutarse en Windows*' -Because "$name debe abortar fuera de Windows tambien con -WhatIf"
        }
    }

    It 'el mensaje explica la alternativa por SSH o WinRM' {
        $msg = ''
        try { Get-TechCareDiagnostics } catch { $msg = $_.Exception.Message }
        $msg | Should -BeLike '*SSH*'
        $msg | Should -BeLike '*WinRM*'
    }

    It 'Test-TechCareAdmin devuelve falso fuera de Windows en vez de lanzar' {
        InModuleScope -ModuleName TechCare {
            Test-TechCareAdmin | Should -BeFalse
        }
    }

    It 'Get-TechCareManagedState devuelve una coleccion vacia fuera de Windows' {
        InModuleScope -ModuleName TechCare {
            @(Get-TechCareManagedState).Count | Should -Be 0
        }
    }
}

Describe 'Format-TechCareSize' {

    It 'formatea <Bytes> como <Esperado> en la cultura <Cultura>' -ForEach @(
        @{ Cultura = 'en-US'; Bytes = 2147483648; Esperado = '2.00 GB' }
        @{ Cultura = 'es-ES'; Bytes = 2147483648; Esperado = '2,00 GB' }
        @{ Cultura = 'en-US'; Bytes = 1536;       Esperado = '1.50 KB' }
        @{ Cultura = 'es-ES'; Bytes = 1536;       Esperado = '1,50 KB' }
        @{ Cultura = 'en-US'; Bytes = 512;        Esperado = '512 B' }
        @{ Cultura = 'es-ES'; Bytes = 512;        Esperado = '512 B' }
    ) {
        # El separador decimal depende de la cultura: en es-ES es la coma y en
        # en-US el punto. Sin fijar la cultura, esta prueba pasaba en el portatil
        # del autor y fallaba en los runners de GitHub, que corren en en-US.
        InModuleScope -ModuleName TechCare -Parameters @{ Cultura = $Cultura; Bytes = $Bytes; Esperado = $Esperado } {
            param($Cultura, $Bytes, $Esperado)
            $previa = [System.Threading.Thread]::CurrentThread.CurrentCulture
            try {
                [System.Threading.Thread]::CurrentThread.CurrentCulture = [cultureinfo]::GetCultureInfo($Cultura)
                Format-TechCareSize -Bytes $Bytes | Should -Be $Esperado
            } finally {
                [System.Threading.Thread]::CurrentThread.CurrentCulture = $previa
            }
        }
    }

    It 'elige la unidad por umbral y no se salta ninguna' {
        InModuleScope -ModuleName TechCare {
            Format-TechCareSize -Bytes 0            | Should -BeLike '*B'
            Format-TechCareSize -Bytes 1023         | Should -BeLike '*B'
            Format-TechCareSize -Bytes 1024         | Should -BeLike '*KB'
            Format-TechCareSize -Bytes 1048576      | Should -BeLike '*MB'
            Format-TechCareSize -Bytes 1073741824   | Should -BeLike '*GB'
        }
    }
}

Describe 'Escapado de HTML' {

    It 'neutraliza el marcado que venga en datos del equipo' {
        InModuleScope -ModuleName TechCare {
            $salida = ConvertTo-TechCareHtml -Text '<script>alert(1)</script> & "C:\ruta"'
            $salida | Should -Not -BeLike '*<script>*'
            $salida | Should -BeLike '*&lt;script&gt;*'
            $salida | Should -BeLike '*&amp;*'
        }
    }

    It 'devuelve cadena vacia con entrada vacia o nula, sin lanzar' {
        InModuleScope -ModuleName TechCare {
            ConvertTo-TechCareHtml -Text ''    | Should -Be ''
            ConvertTo-TechCareHtml -Text $null | Should -Be ''
        }
    }
}