< Summary - Kestrun — Combined Coverage

Information
Class: Private.Service.Test-KrServiceDescriptorData
Assembly: Kestrun.PowerShell.Private
File(s): /home/runner/work/Kestrun/Kestrun/src/PowerShell/Kestrun/Private/Service/Test-KrServiceDescriptorData.ps1
Tag: Kestrun/Kestrun@09cad9a8fdafda7aca15f5f5e888b4bbcc8f0674
Line coverage
78%
Covered lines: 47
Uncovered lines: 13
Coverable lines: 60
Total lines: 131
Line coverage: 78.3%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 03/26/2026 - 03:54:59 Line coverage: 78.3% (47/60) Total lines: 131 Tag: Kestrun/Kestrun@844b5179fb0492dc6b1182bae3ff65fa7365521d

Metrics

File(s)

/home/runner/work/Kestrun/Kestrun/src/PowerShell/Kestrun/Private/Service/Test-KrServiceDescriptorData.ps1

#LineLine coverage
 1<#
 2.SYNOPSIS
 3    Validates and processes a service descriptor hashtable.
 4.DESCRIPTION
 5    Validates the structure and required keys of a format 1.0 service descriptor hashtable.
 6    Also checks that referenced entry point files exist within the package root and do not escape it.
 7.PARAMETER Descriptor
 8    The service descriptor as a hashtable, typically parsed from Service.psd1.
 9.PARAMETER DescriptorPath
 10    The file path of the descriptor, used for error messages.
 11.PARAMETER PackageRoot
 12    The root directory of the package, used to resolve and validate script paths.
 13.EXAMPLE
 14    $descriptor = @{
 15        Name = 'MyService'
 16        FormatVersion = '1.0'
 17        EntryPoint = 'server.ps1'
 18        Description = 'A sample service.'
 19        Version = '1.0.0'
 20    }
 21    Test-KrServiceDescriptorData -Descriptor $descriptor -DescriptorPath '.\Service.psd1' -PackageRoot '.\'
 22#>
 23function Test-KrServiceDescriptorData {
 24    param(
 25        [hashtable]$Descriptor,
 26        [string]$DescriptorPath,
 27        [string]$PackageRoot
 28    )
 29
 130    if (-not $Descriptor.ContainsKey('Name') -or [string]::IsNullOrWhiteSpace([string]$Descriptor['Name'])) {
 031        throw "Descriptor '$DescriptorPath' is missing required key 'Name'."
 32    }
 33
 134    $packageRootFullPath = [System.IO.Path]::GetFullPath($PackageRoot)
 135    $packageRootNormalized = [System.IO.Path]::TrimEndingDirectorySeparator($packageRootFullPath)
 36
 137    $isWithinPackageRoot = {
 38        param([string]$PathToValidate)
 39
 140        $normalizedPath = [System.IO.Path]::TrimEndingDirectorySeparator($PathToValidate)
 141        $relativePath = [System.IO.Path]::GetRelativePath($packageRootNormalized, $normalizedPath)
 42
 143        if ([string]::Equals($relativePath, '.', [System.StringComparison]::Ordinal)) {
 044            return $true
 45        }
 46
 147        if ([System.IO.Path]::IsPathRooted($relativePath)) {
 048            return $false
 49        }
 50
 151        return -not (
 152            [string]::Equals($relativePath, '..', [System.StringComparison]::Ordinal) -or
 153            $relativePath.StartsWith("..$([System.IO.Path]::DirectorySeparatorChar)", [System.StringComparison]::Ordinal
 154            $relativePath.StartsWith("..$([System.IO.Path]::AltDirectorySeparatorChar)", [System.StringComparison]::Ordi
 55        )
 56    }
 57
 158    $normalizedPreservePaths = @()
 159    if ($Descriptor.ContainsKey('PreservePaths') -and $null -ne $Descriptor['PreservePaths']) {
 160        $rawPreservePaths = @()
 161        if ($Descriptor['PreservePaths'] -is [string]) {
 062            $rawPreservePaths = @([string]$Descriptor['PreservePaths'])
 163        } elseif ($Descriptor['PreservePaths'] -is [System.Collections.IEnumerable]) {
 264            $rawPreservePaths = @($Descriptor['PreservePaths'])
 65        } else {
 066            throw "Descriptor '$DescriptorPath' key 'PreservePaths' must be a string array."
 67        }
 68
 169        foreach ($preservePathValue in $rawPreservePaths) {
 170            $preservePath = [string]$preservePathValue
 171            if ([string]::IsNullOrWhiteSpace($preservePath)) {
 072                throw "Descriptor '$DescriptorPath' key 'PreservePaths' cannot contain empty values."
 73            }
 74
 175            if ([System.IO.Path]::IsPathRooted($preservePath)) {
 076                throw "Descriptor '$DescriptorPath' PreservePaths entry '$preservePath' must be a relative path."
 77            }
 78
 179            $combinedPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($packageRootFullPath, $preservePath)
 280            if (-not (& $isWithinPackageRoot $combinedPath)) {
 181                throw "Descriptor '$DescriptorPath' PreservePaths entry '$preservePath' escapes the package root."
 82            }
 83
 184            $normalizedPreservePaths += $preservePath
 85        }
 86    }
 87
 188    if (-not $Descriptor.ContainsKey('FormatVersion') -or [string]::IsNullOrWhiteSpace([string]$Descriptor['FormatVersio
 089        throw "Descriptor '$DescriptorPath' is missing required key 'FormatVersion'."
 90    }
 91
 192    $formatVersion = [string]$Descriptor['FormatVersion']
 193    if (-not [string]::Equals($formatVersion.Trim(), '1.0', [System.StringComparison]::Ordinal)) {
 094        throw "Descriptor '$DescriptorPath' has unsupported FormatVersion '$formatVersion'. Expected '1.0'."
 95    }
 96
 297    foreach ($requiredKey in @('Description', 'Version', 'EntryPoint')) {
 198        if (-not $Descriptor.ContainsKey($requiredKey) -or [string]::IsNullOrWhiteSpace([string]$Descriptor[$requiredKey
 099            throw "Descriptor '$DescriptorPath' is missing required key '$requiredKey'."
 100        }
 101    }
 102
 1103    $parsedVersion = $null
 1104    if (-not [version]::TryParse([string]$Descriptor['Version'], [ref]$parsedVersion)) {
 0105        throw "Descriptor '$DescriptorPath' has invalid Version value '$($Descriptor['Version'])'."
 106    }
 107
 1108    $entryPoint = [string]$Descriptor['EntryPoint']
 1109    if ([System.IO.Path]::IsPathRooted($entryPoint)) {
 0110        throw "Descriptor '$DescriptorPath' EntryPoint must be a relative path."
 111    }
 112
 1113    $entryPointFullPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($packageRootFullPath, $entryPoint))
 2114    if (-not (& $isWithinPackageRoot $entryPointFullPath)) {
 1115        throw "Descriptor '$DescriptorPath' EntryPoint escapes the package root."
 116    }
 117
 2118    if (-not (Test-Path -LiteralPath $entryPointFullPath -PathType Leaf)) {
 0119        throw "EntryPoint file '$entryPoint' was not found under '$PackageRoot'."
 120    }
 121
 1122    [pscustomobject]@{
 1123        Name = [string]$Descriptor['Name']
 1124        FormatVersion = '1.0'
 1125        EntryPoint = $entryPoint
 1126        Description = [string]$Descriptor['Description']
 1127        Version = $parsedVersion.ToString()
 3128        ServiceLogPath = if ($Descriptor.ContainsKey('ServiceLogPath')) { [string]$Descriptor['ServiceLogPath'] } else {
 1129        PreservePaths = $normalizedPreservePaths
 130    }
 131}

Methods/Properties

Test-KrServiceDescriptorData()