< 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@6135d944f8787fb570e4dfbacac6e80312799a86
Line coverage
82%
Covered lines: 56
Uncovered lines: 12
Coverable lines: 68
Total lines: 159
Line coverage: 82.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@844b5179fb0492dc6b1182bae3ff65fa7365521d04/19/2026 - 15:52:57 Line coverage: 80.8% (55/68) Total lines: 159 Tag: Kestrun/Kestrun@765a8f13c573c01494250a29d6392b6037f087c905/04/2026 - 03:10:25 Line coverage: 82.3% (56/68) Total lines: 159 Tag: Kestrun/Kestrun@476fba12d8b4c7db258e9ff68fad76f0d7e4e042

Coverage delta

Coverage delta 3 -3

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)) {
 144            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
 58    function Get-KrNormalizedDescriptorRelativePathArray {
 59        <#
 60        .SYNOPSIS
 61            Normalizes and validates relative paths from a descriptor key.
 62        .PARAMETER KeyName
 63            The key name in the descriptor hashtable to process.
 64        .PARAMETER EntryLabel
 65            A label for the entry, used in error messages.
 66        .OUTPUTS
 67            An array of normalized relative paths.
 68        #>
 69        param(
 70            [string]$KeyName,
 71            [string]$EntryLabel
 72        )
 73
 174        $normalizedPaths = @()
 175        if (-not $Descriptor.ContainsKey($KeyName) -or $null -eq $Descriptor[$KeyName]) {
 176            return $normalizedPaths
 77        }
 78
 179        $descriptorValue = $Descriptor[$KeyName]
 180        $rawPaths = @()
 181        if ($descriptorValue -is [string]) {
 082            $rawPaths = @([string]$descriptorValue)
 183        } elseif ($descriptorValue -is [hashtable] -or $descriptorValue -is [System.Collections.IDictionary]) {
 184            throw "Descriptor '$DescriptorPath' key '$KeyName' must be a string array."
 185        } elseif ($descriptorValue -is [System.Array] -or $descriptorValue -is [System.Collections.IList]) {
 286            $rawPaths = @($descriptorValue)
 87        } else {
 088            throw "Descriptor '$DescriptorPath' key '$KeyName' must be a string array."
 89        }
 90
 191        foreach ($pathValue in $rawPaths) {
 192            $relativePath = [string]$pathValue
 193            if ([string]::IsNullOrWhiteSpace($relativePath)) {
 094                throw "Descriptor '$DescriptorPath' key '$KeyName' cannot contain empty values."
 95            }
 96
 197            if ([System.IO.Path]::IsPathRooted($relativePath)) {
 098                throw "Descriptor '$DescriptorPath' $EntryLabel entry '$relativePath' must be a relative path."
 99            }
 100
 1101            $combinedPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($packageRootFullPath, $relativePath)
 2102            if (-not (& $isWithinPackageRoot $combinedPath)) {
 1103                throw "Descriptor '$DescriptorPath' $EntryLabel entry '$relativePath' escapes the package root."
 104            }
 105
 1106            $normalizedPaths += $relativePath
 107        }
 108
 1109        return $normalizedPaths
 110    }
 111
 1112    $normalizedPreservePaths = Get-KrNormalizedDescriptorRelativePathArray -KeyName 'PreservePaths' -EntryLabel 'Preserv
 1113    $normalizedApplicationDataFolders = Get-KrNormalizedDescriptorRelativePathArray -KeyName 'ApplicationDataFolders' -E
 114
 1115    if (-not $Descriptor.ContainsKey('FormatVersion') -or [string]::IsNullOrWhiteSpace([string]$Descriptor['FormatVersio
 0116        throw "Descriptor '$DescriptorPath' is missing required key 'FormatVersion'."
 117    }
 118
 1119    $formatVersion = [string]$Descriptor['FormatVersion']
 1120    if (-not [string]::Equals($formatVersion.Trim(), '1.0', [System.StringComparison]::Ordinal)) {
 0121        throw "Descriptor '$DescriptorPath' has unsupported FormatVersion '$formatVersion'. Expected '1.0'."
 122    }
 123
 2124    foreach ($requiredKey in @('Description', 'Version', 'EntryPoint')) {
 1125        if (-not $Descriptor.ContainsKey($requiredKey) -or [string]::IsNullOrWhiteSpace([string]$Descriptor[$requiredKey
 0126            throw "Descriptor '$DescriptorPath' is missing required key '$requiredKey'."
 127        }
 128    }
 129
 1130    $parsedVersion = $null
 1131    if (-not [version]::TryParse([string]$Descriptor['Version'], [ref]$parsedVersion)) {
 0132        throw "Descriptor '$DescriptorPath' has invalid Version value '$($Descriptor['Version'])'."
 133    }
 134
 1135    $entryPoint = [string]$Descriptor['EntryPoint']
 1136    if ([System.IO.Path]::IsPathRooted($entryPoint)) {
 0137        throw "Descriptor '$DescriptorPath' EntryPoint must be a relative path."
 138    }
 139
 1140    $entryPointFullPath = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($packageRootFullPath, $entryPoint))
 2141    if (-not (& $isWithinPackageRoot $entryPointFullPath)) {
 1142        throw "Descriptor '$DescriptorPath' EntryPoint escapes the package root."
 143    }
 144
 2145    if (-not (Test-Path -LiteralPath $entryPointFullPath -PathType Leaf)) {
 0146        throw "EntryPoint file '$entryPoint' was not found under '$PackageRoot'."
 147    }
 148
 1149    [pscustomobject]@{
 1150        Name = [string]$Descriptor['Name']
 1151        FormatVersion = '1.0'
 1152        EntryPoint = $entryPoint
 1153        Description = [string]$Descriptor['Description']
 1154        Version = $parsedVersion.ToString()
 3155        ServiceLogPath = if ($Descriptor.ContainsKey('ServiceLogPath')) { [string]$Descriptor['ServiceLogPath'] } else {
 1156        PreservePaths = $normalizedPreservePaths
 1157        ApplicationDataFolders = $normalizedApplicationDataFolders
 158    }
 159}