Skip to content

Commit

Permalink
Added tests, added ci, and updated readme
Browse files Browse the repository at this point in the history
  • Loading branch information
Craig Boileau committed Nov 18, 2024
1 parent 7b3d490 commit 5db69ba
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/powershell-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: PowerShell Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
runs-on: windows-latest

steps:
- uses: actions/checkout@v2

- name: Install Pester
shell: pwsh
run: |
Install-Module -Name Pester -Force -SkipPublisherCheck
- name: Run Tests
shell: pwsh
run: |
$config = New-PesterConfiguration
$config.Run.Path = "Tests"
$config.Output.Verbosity = "Detailed"
Invoke-Pester -Configuration $config
62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,66 @@ Review the script source code to ensure it's safe:

https://github.com/cboileau/FWindowsUpdateReboot/blob/4eeee1f0e29e3bad1fd22c02dbe23ad78f456141/FWindowsUpdateReboot.ps1#L1-L153

## Testing

The project includes unit tests written using Pester, the standard testing framework for PowerShell. Tests can be run locally or through GitHub Actions CI pipeline.

### Running Tests Locally

#### Option 1: Using the Test Runner Script (Recommended)

1. Clone the repository:
```powershell
git clone https://github.com/cboileau/FWindowsUpdateReboot.git
cd FWindowsUpdateReboot
```

You can also run specific test categories from PowerShell:
```powershell
# Run all tests
.\Run-Tests.ps1 -TestType All
# Run only installation tests
.\Run-Tests.ps1 -TestType Installation
# Run only active hours tests
.\Run-Tests.ps1 -TestType ActiveHours
# Run only uninstallation tests
.\Run-Tests.ps1 -TestType Uninstallation
```

### CI/CD

The project uses GitHub Actions to run tests automatically on:
- Every push to the main branch
- Every pull request to the main branch

Test results can be viewed in the Actions tab of the repository.

### Running Individual Test Cases

To run specific test cases, you can use Pester's filtering:

```powershell
# Run only installation tests
$config = New-PesterConfiguration
$config.Filter.Tag = "Installation"
$config.Run.Path = "Tests"
Invoke-Pester -Configuration $config
# Run only active hours tests
$config = New-PesterConfiguration
$config.Filter.Tag = "ActiveHours"
$config.Run.Path = "Tests"
Invoke-Pester -Configuration $config
```

### Troubleshooting Tests

If tests fail, check:
1. Pester is installed correctly
2. You're in the correct directory
3. Your PowerShell execution policy allows running scripts


54 changes: 54 additions & 0 deletions Run-Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[CmdletBinding()]
param (
[Parameter()]
[ValidateSet('All', 'Installation', 'ActiveHours', 'Uninstallation')]
[string]$TestType = 'All'
)

# Ensure Pester is installed
if (-not (Get-Module -ListAvailable -Name Pester)) {
Write-Host "Pester not found. Installing Pester..." -ForegroundColor Yellow
Install-Module -Name Pester -Force -SkipPublisherCheck
}

Write-Host "Starting test run..." -ForegroundColor Cyan
Write-Host "Test type: $TestType" -ForegroundColor Cyan
Write-Host "----------------------------------------" -ForegroundColor Cyan

# Configure Pester
$config = New-PesterConfiguration
$config.Run.Path = Join-Path $PSScriptRoot "Tests"
$config.Output.Verbosity = "Detailed"
$config.Run.PassThru = $true

# Apply test filter if specified
if ($TestType -ne 'All') {
Write-Host "Running only $TestType tests..." -ForegroundColor Yellow
$config.Filter.Tag = @($TestType)
} else {
Write-Host "Running all tests..." -ForegroundColor Yellow
$config.Filter.Tag = @()
}

# Run tests
$testResults = Invoke-Pester -Configuration $config

# Display results summary
Write-Host "`nTest Results Summary:" -ForegroundColor Cyan
Write-Host "----------------------------------------" -ForegroundColor Cyan
Write-Host "Total Tests: $($testResults.TotalCount)" -ForegroundColor White
Write-Host "Passed: $($testResults.PassedCount)" -ForegroundColor Green
Write-Host "Failed: $($testResults.FailedCount)" -ForegroundColor Red
Write-Host "Skipped: $($testResults.SkippedCount)" -ForegroundColor Yellow
Write-Host "----------------------------------------" -ForegroundColor Cyan

if ($testResults.FailedCount -gt 0) {
Write-Host "`nFailed Tests:" -ForegroundColor Red
$testResults.Failed | ForEach-Object {
Write-Host "- $($_.Name)" -ForegroundColor Red
Write-Host " $($_.ErrorRecord)" -ForegroundColor Red
}
}

Write-Host "`nPress Enter to exit..." -ForegroundColor Green
$null = Read-Host
87 changes: 87 additions & 0 deletions Tests/FWindowsUpdateReboot.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# Import Pester module for mocking
Import-Module Pester

BeforeAll {
# Import the script content into a scriptblock
$scriptPath = "$PSScriptRoot\..\FWindowsUpdateReboot.ps1"
}

Describe "FWindowsUpdateReboot Script Tests" {
BeforeEach {
# Mock functions that interact with the system
Mock Set-ItemProperty { }
Mock Get-ScheduledTask { throw "Task does not exist" }
Mock Unregister-ScheduledTask { }
Mock Copy-Item { }
Mock Start-Process {
return [PSCustomObject]@{
ExitCode = 0
}
}
Mock Write-Host { }
Mock Write-Error { }
Mock Read-Host { return "Y" }
Mock Get-Date { return [DateTime]::Parse("2024-03-20 14:00:00") }

# Mock admin check properly
Mock New-Object {
if ($TypeName -eq 'Security.Principal.WindowsPrincipal') {
$mockPrincipal = New-Object PSObject
$mockPrincipal | Add-Member -MemberType ScriptMethod -Name IsInRole -Value { param($role) $true }
return $mockPrincipal
}
return $null
} -ParameterFilter { $TypeName -eq 'Security.Principal.WindowsPrincipal' }
}

Context "Active Hours Rotation" -Tag "ActiveHours" {
It "Should calculate correct active hours" {
# Execute the script with Rotate parameter
$global:PSCommandPath = $scriptPath
. $scriptPath -Rotate

# Check if Set-ItemProperty was called with correct values
Should -Invoke Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and
$Name -eq "ActiveHoursStart" -and
$Value -eq 14
}

Should -Invoke Set-ItemProperty -Times 1 -Exactly -ParameterFilter {
$Path -eq "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings" -and
$Name -eq "ActiveHoursEnd" -and
$Value -eq 8 # (14 + 18) % 24 = 8
}
}
}

Context "Installation" -Tag "Installation" {
It "Should create scheduled task correctly" {
# Execute the script
$global:PSCommandPath = $scriptPath
. $scriptPath

# Verify Start-Process was called with correct schtasks.exe parameters
Should -Invoke Start-Process -Times 1 -Exactly -ParameterFilter {
$FilePath -eq "schtasks.exe" -and
$ArgumentList -contains "/Create" -and
$ArgumentList -contains "/SC" -and
$ArgumentList -contains "HOURLY"
}
}
}

Context "Uninstallation" -Tag "Uninstallation" {
It "Should remove scheduled task and script" {
# Mock Get-ScheduledTask to return a task this time
Mock Get-ScheduledTask { return [PSCustomObject]@{} }

# Execute the script with Uninstall parameter
$global:PSCommandPath = $scriptPath
. $scriptPath -Uninstall

# Verify task removal was attempted
Should -Invoke Unregister-ScheduledTask -Times 1 -Exactly
}
}
}
37 changes: 37 additions & 0 deletions Tests/Run-Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[CmdletBinding()]
param (
[Parameter()]
[ValidateSet('All', 'Installation', 'ActiveHours', 'Uninstallation')]
[string]$TestType = 'All'
)

# Check if running as administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "Tests must be run as Administrator. Restarting with elevation..." -ForegroundColor Yellow
Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -TestType $TestType" -Verb RunAs
exit
}

# Ensure Pester is installed
if (-not (Get-Module -ListAvailable -Name Pester)) {
Write-Host "Pester not found. Installing Pester..." -ForegroundColor Yellow
Install-Module -Name Pester -Force -SkipPublisherCheck
}

# Configure Pester
$config = New-PesterConfiguration
$config.Run.Path = $PSScriptRoot
$config.Output.Verbosity = "Detailed"

# Apply test filter if specified
if ($TestType -ne 'All') {
$config.Filter.Tag = $TestType
}

# Run tests
Write-Host "Running $TestType tests..." -ForegroundColor Cyan
Invoke-Pester -Configuration $config

Write-Host "`nPress Enter to exit..." -ForegroundColor Green
$null = Read-Host

0 comments on commit 5db69ba

Please sign in to comment.