diff --git a/PiHoleShell/PiHoleShell.psm1 b/PiHoleShell/PiHoleShell.psm1 index 48bce96..aa25829 100644 --- a/PiHoleShell/PiHoleShell.psm1 +++ b/PiHoleShell/PiHoleShell.psm1 @@ -28,7 +28,8 @@ Export-ModuleMember -Function @( #Padd 'Get-PiHolePadd', ` #Metrics - 'Get-PiHoleStatsRecentBlocked', 'Get-PiHoleStatsQueryType', 'Get-PiHoleStatsTopDomain', 'Get-PiHoleStatsSummary', 'Get-PiHoleStatsTopClient', 'Get-PiHoleStatsQuerySuggestions' ` + 'Get-PiHoleStatsRecentBlocked', 'Get-PiHoleStatsQueryType', 'Get-PiHoleStatsTopDomain', 'Get-PiHoleStatsSummary', 'Get-PiHoleStatsTopClient', 'Get-PiHoleStatsQuerySuggestions', ` + 'Get-PiHoleStatsUpstream', 'Get-PiHoleStatsDatabaseUpstream', 'Get-PiHoleStatsDatabaseSummary', 'Get-PiHoleStatsDatabaseTopDomain', 'Get-PiHoleStatsDatabaseTopClient', 'Get-PiHoleStatsDatabaseQueryType' ` #ListManagement 'Get-PiHoleList', 'Search-PiHoleListDomain', 'Add-PiHoleList', 'Remove-PiHoleList', ` #FTLInformation diff --git a/PiHoleShell/Private/Misc.ps1 b/PiHoleShell/Private/Misc.ps1 index b75175c..c9c50cc 100644 --- a/PiHoleShell/Private/Misc.ps1 +++ b/PiHoleShell/Private/Misc.ps1 @@ -64,7 +64,7 @@ function Remove-PiHoleCurrentAuthSession { } try { - Invoke-RestMethod @Params + $null = Invoke-RestMethod @Params } catch { diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseQueryType.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseQueryType.ps1 new file mode 100644 index 0000000..ab1f931 --- /dev/null +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseQueryType.ps1 @@ -0,0 +1,94 @@ +function Get-PiHoleStatsDatabaseQueryType { + <# +.SYNOPSIS +Get query types (long-term database) + +.DESCRIPTION +Request a breakdown of query types (A, AAAA, ...) for a given time range from the long-term +(on-disk) database, rather than the in-memory data returned by Get-PiHoleStatsQueryType. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER From +Unix timestamp from when the data should be requested + +.PARAMETER Until +Unix timestamp until when the data should be requested + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleStatsDatabaseQueryType -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/query_types')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [Parameter(Mandatory = $true)] + [int]$From, + [Parameter(Mandatory = $true)] + [int]$Until, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/query_types?from=$From&until=$Until" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Object = [PSCustomObject]@{ + A = $Response.types.A + AAAA = $Response.types.AAAA + ANY = $Response.types.ANY + SRV = $Response.types.SRV + SOA = $Response.types.SOA + PTR = $Response.types.PTR + TXT = $Response.types.TXT + NAPTR = $Response.types.NAPTR + MX = $Response.types.MX + DS = $Response.types.DS + RRSIG = $Response.types.RRSIG + DNSKEY = $Response.types.DNSKEY + NS = $Response.types.NS + SVCB = $Response.types.SVCB + HTTPS = $Response.types.HTTPS + OTHER = $Response.types.OTHER + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseSummary.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseSummary.ps1 new file mode 100644 index 0000000..0b5c003 --- /dev/null +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseSummary.ps1 @@ -0,0 +1,82 @@ +function Get-PiHoleStatsDatabaseSummary { + <# +.SYNOPSIS +Get database content details + +.DESCRIPTION +Request various database content details (total/blocked queries, percent blocked, total +clients) for a given time range from the long-term (on-disk) database. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER From +Unix timestamp from when the data should be requested + +.PARAMETER Until +Unix timestamp until when the data should be requested + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleStatsDatabaseSummary -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/summary')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [Parameter(Mandatory = $true)] + [int]$From, + [Parameter(Mandatory = $true)] + [int]$Until, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/summary?from=$From&until=$Until" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Object = [PSCustomObject]@{ + SumQueries = $Response.sum_queries + SumBlocked = $Response.sum_blocked + PercentBlocked = $Response.percent_blocked + TotalClients = $Response.total_clients + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopClient.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopClient.ps1 new file mode 100644 index 0000000..4110f2c --- /dev/null +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopClient.ps1 @@ -0,0 +1,102 @@ +function Get-PiHoleStatsDatabaseTopClient { + <# +.SYNOPSIS +Get top clients (long-term database) + +.DESCRIPTION +Request the top clients for a given time range from the long-term (on-disk) database, rather +than the in-memory data returned by Get-PiHoleStatsTopClient. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER From +Unix timestamp from when the data should be requested + +.PARAMETER Until +Unix timestamp until when the data should be requested + +.PARAMETER MaxResult +How many results should be returned + +.PARAMETER Blocked +If true, returns top clients by blocked queries instead of total queries + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleStatsDatabaseTopClient -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 -MaxResult 10 + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/top_clients')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [Parameter(Mandatory = $true)] + [int]$From, + [Parameter(Mandatory = $true)] + [int]$Until, + [int]$MaxResult = 10, + [bool]$Blocked = $false, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + switch ($Blocked) { + $false { $BlockedParam = "false" } + $true { $BlockedParam = "true" } + Default { throw "ERROR" } + } + Write-Verbose "Blocked: $BlockedParam" + Write-Verbose "MaxResult: $MaxResult" + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/top_clients?from=$From&until=$Until&blocked=$BlockedParam&count=$MaxResult" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $ObjectFinal = @() + foreach ($Item in $Response.clients) { + $Object = [PSCustomObject]@{ + IP = $Item.ip + Name = $Item.name + Count = $Item.count + } + Write-Verbose -Message "Client - $($Item.ip) ($($Item.name)): $($Item.count)" + $ObjectFinal += $Object + } + Write-Output $ObjectFinal + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopDomain.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopDomain.ps1 new file mode 100644 index 0000000..214ec85 --- /dev/null +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseTopDomain.ps1 @@ -0,0 +1,101 @@ +function Get-PiHoleStatsDatabaseTopDomain { + <# +.SYNOPSIS +Get top domains (long-term database) + +.DESCRIPTION +Request the top domains for a given time range from the long-term (on-disk) database, rather +than the in-memory data returned by Get-PiHoleStatsTopDomain. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER From +Unix timestamp from when the data should be requested + +.PARAMETER Until +Unix timestamp until when the data should be requested + +.PARAMETER MaxResult +How many results should be returned + +.PARAMETER Blocked +If true, returns top domains by blocked queries instead of total queries + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleStatsDatabaseTopDomain -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 -MaxResult 10 + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/top_domains')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [Parameter(Mandatory = $true)] + [int]$From, + [Parameter(Mandatory = $true)] + [int]$Until, + [int]$MaxResult = 10, + [bool]$Blocked = $false, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + switch ($Blocked) { + $false { $BlockedParam = "false" } + $true { $BlockedParam = "true" } + Default { throw "ERROR" } + } + Write-Verbose "Blocked: $BlockedParam" + Write-Verbose "MaxResult: $MaxResult" + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/top_domains?from=$From&until=$Until&blocked=$BlockedParam&count=$MaxResult" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $ObjectFinal = @() + foreach ($Item in $Response.domains) { + $Object = [PSCustomObject]@{ + Domain = $Item.domain + Count = $Item.count + } + Write-Verbose -Message "Domain - $($Item.domain): $($Item.count)" + $ObjectFinal += $Object + } + Write-Output $ObjectFinal + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseUpstream.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseUpstream.ps1 new file mode 100644 index 0000000..8cb0d44 --- /dev/null +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsDatabaseUpstream.ps1 @@ -0,0 +1,91 @@ +function Get-PiHoleStatsDatabaseUpstream { + <# +.SYNOPSIS +Get metrics about Pi-hole's upstream destinations (long-term database) + +.DESCRIPTION +Request upstream metrics from the long-term (on-disk) database for a given time range, rather +than the in-memory data returned by Get-PiHoleStatsUpstream. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER From +Unix timestamp from when the data should be requested + +.PARAMETER Until +Unix timestamp until when the data should be requested + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleStatsDatabaseUpstream -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" -From 1672580025 -Until 1672666425 + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/database/upstreams')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [Parameter(Mandatory = $true)] + [int]$From, + [Parameter(Mandatory = $true)] + [int]$Until, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/stats/database/upstreams?from=$From&until=$Until" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Upstreams = foreach ($Item in $Response.upstreams) { + [PSCustomObject]@{ + Ip = $Item.ip + Name = $Item.name + Port = $Item.port + Count = $Item.count + ResponseTime = $Item.statistics.response + Variance = $Item.statistics.variance + } + } + $Object = [PSCustomObject]@{ + TotalQueries = $Response.total_queries + ForwardedQueries = $Response.forwarded_queries + Upstreams = $Upstreams + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/PiHoleShell/Public/Metrics/Get-PiHoleStatsUpstream.ps1 b/PiHoleShell/Public/Metrics/Get-PiHoleStatsUpstream.ps1 new file mode 100644 index 0000000..4ec0e36 --- /dev/null +++ b/PiHoleShell/Public/Metrics/Get-PiHoleStatsUpstream.ps1 @@ -0,0 +1,81 @@ +function Get-PiHoleStatsUpstream { + <# +.SYNOPSIS +Get metrics about Pi-hole's upstream destinations + +.DESCRIPTION +Request upstream metrics: which upstream destinations have been used, how many queries each +has handled, and their average response time. + +.PARAMETER PiHoleServer +The URL to the PiHole Server, for example "http://pihole.domain.com:8080", or "http://192.168.1.100" + +.PARAMETER Password +The API Password you generated from your PiHole server + +.PARAMETER IgnoreSsl +Set to $true to skip SSL certificate validation + +.PARAMETER RawOutput +This will dump the response instead of the formatted object + +.EXAMPLE +Get-PiHoleStatsUpstream -PiHoleServer "http://pihole.domain.com:8080" -Password "fjdsjfldsjfkldjslafjskdl" + #> + [CmdletBinding(HelpUri = 'https://ftl.pi-hole.net/master/docs/#get-/stats/upstreams')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "Password")] + param ( + [Parameter(Mandatory = $true)] + [System.URI]$PiHoleServer, + [Parameter(Mandatory = $true)] + [string]$Password, + [bool]$IgnoreSsl = $false, + [bool]$RawOutput = $false + ) + + try { + $Sid = Request-PiHoleAuth -PiHoleServer $PiHoleServer -Password $Password -IgnoreSsl $IgnoreSsl + + $Params = @{ + Headers = @{sid = $($Sid) } + Uri = "$($PiHoleServer.OriginalString)/api/stats/upstreams" + Method = "Get" + SkipCertificateCheck = $IgnoreSsl + ContentType = "application/json" + } + + $Response = Invoke-RestMethod @Params + + if ($RawOutput) { + Write-Output $Response + } + else { + $Upstreams = foreach ($Item in $Response.upstreams) { + [PSCustomObject]@{ + Ip = $Item.ip + Name = $Item.name + Port = $Item.port + Count = $Item.count + ResponseTime = $Item.statistics.response + Variance = $Item.statistics.variance + } + } + $Object = [PSCustomObject]@{ + TotalQueries = $Response.total_queries + ForwardedQueries = $Response.forwarded_queries + Upstreams = $Upstreams + } + Write-Output $Object + } + } + + catch { + Write-Error -Message $_.Exception.Message + } + + finally { + if ($Sid) { + Remove-PiHoleCurrentAuthSession -PiHoleServer $PiHoleServer -Sid $Sid -IgnoreSsl $IgnoreSsl + } + } +} diff --git a/README.md b/README.md index d508237..21fc6e6 100644 --- a/README.md +++ b/README.md @@ -120,12 +120,18 @@ Functions marked 🚧 are still under active development — signatures and outp | Function | Description | |---|---| +| `Get-PiHoleStatsDatabaseQueryType` | Get query types (long-term database) | +| `Get-PiHoleStatsDatabaseSummary` | Get database content details | +| `Get-PiHoleStatsDatabaseTopClient` | Get top clients (long-term database) | +| `Get-PiHoleStatsDatabaseTopDomain` | Get top domains (long-term database) | +| `Get-PiHoleStatsDatabaseUpstream` | Get metrics about Pi-hole's upstream destinations (long-term database) | | `Get-PiHoleStatsQuerySuggestions` | Get query filter suggestions | | `Get-PiHoleStatsQueryType` | _No description yet_ | | `Get-PiHoleStatsRecentBlocked` | Request most recently blocked domain | | `Get-PiHoleStatsSummary` | Get overview of Pi-hole activity Request various query, system, and FTL properties | | `Get-PiHoleStatsTopClient` | Get top clients Request the top clients (by query count) | | `Get-PiHoleStatsTopDomain` | _No description yet_ | +| `Get-PiHoleStatsUpstream` | Get metrics about Pi-hole's upstream destinations | ### Configuration & Diagnostics diff --git a/tests/Get-PiHoleStatsDatabaseQueryType.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseQueryType.Integration.Tests.ps1 new file mode 100644 index 0000000..fe70c55 --- /dev/null +++ b/tests/Get-PiHoleStatsDatabaseQueryType.Integration.Tests.ps1 @@ -0,0 +1,45 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsDatabaseQueryType (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + + # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. + $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $script:From = $script:Until - (30 * 86400) + } + + It 'returns a formatted object with all query type properties' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseQueryType -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl + + $result | Should -Not -BeNullOrEmpty + $result.PSObject.Properties.Name | Should -Contain 'A' + $result.PSObject.Properties.Name | Should -Contain 'AAAA' + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseQueryType -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + + $result.PSObject.Properties.Name | Should -Contain 'types' + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseQueryType -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsDatabaseSummary.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseSummary.Integration.Tests.ps1 new file mode 100644 index 0000000..8f2dbdd --- /dev/null +++ b/tests/Get-PiHoleStatsDatabaseSummary.Integration.Tests.ps1 @@ -0,0 +1,45 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsDatabaseSummary (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + + # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. + $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $script:From = $script:Until - (30 * 86400) + } + + It 'returns database summary as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseSummary -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl + + $result | Should -Not -BeNullOrEmpty + $result.SumQueries | Should -BeGreaterOrEqual 0 + $result.SumBlocked | Should -BeGreaterOrEqual 0 + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseSummary -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + + $result.PSObject.Properties.Name | Should -Contain 'sum_queries' + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseSummary -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsDatabaseTopClient.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseTopClient.Integration.Tests.ps1 new file mode 100644 index 0000000..f62aa5c --- /dev/null +++ b/tests/Get-PiHoleStatsDatabaseTopClient.Integration.Tests.ps1 @@ -0,0 +1,42 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsDatabaseTopClient (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + + # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. + $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $script:From = $script:Until - (30 * 86400) + } + + It 'returns top clients without error' -Skip:(-not $script:ConfigAvailable) { + { Get-PiHoleStatsDatabaseTopClient -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 } | + Should -Not -Throw + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseTopClient -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + + $result.PSObject.Properties.Name | Should -Contain 'clients' + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseTopClient -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsDatabaseTopDomain.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseTopDomain.Integration.Tests.ps1 new file mode 100644 index 0000000..0821b53 --- /dev/null +++ b/tests/Get-PiHoleStatsDatabaseTopDomain.Integration.Tests.ps1 @@ -0,0 +1,42 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsDatabaseTopDomain (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + + # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. + $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $script:From = $script:Until - (30 * 86400) + } + + It 'returns top domains without error' -Skip:(-not $script:ConfigAvailable) { + { Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -MaxResult 5 } | + Should -Not -Throw + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + + $result.PSObject.Properties.Name | Should -Contain 'domains' + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseTopDomain -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsDatabaseUpstream.Integration.Tests.ps1 b/tests/Get-PiHoleStatsDatabaseUpstream.Integration.Tests.ps1 new file mode 100644 index 0000000..5130c86 --- /dev/null +++ b/tests/Get-PiHoleStatsDatabaseUpstream.Integration.Tests.ps1 @@ -0,0 +1,44 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsDatabaseUpstream (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + + # from=0 is rejected by the API with a 400; use a wide-but-valid recent window instead. + $script:Until = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds() + $script:From = $script:Until - (30 * 86400) + } + + It 'returns upstream metrics as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl + + $result | Should -Not -BeNullOrEmpty + $result.TotalQueries | Should -BeGreaterOrEqual 0 + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + + $result.total_queries | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsDatabaseUpstream -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -From $script:From -Until $script:Until -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +} diff --git a/tests/Get-PiHoleStatsUpstream.Integration.Tests.ps1 b/tests/Get-PiHoleStatsUpstream.Integration.Tests.ps1 new file mode 100644 index 0000000..1f501c5 --- /dev/null +++ b/tests/Get-PiHoleStatsUpstream.Integration.Tests.ps1 @@ -0,0 +1,41 @@ +# Requires -Module Pester +# +# Integration tests that call a REAL Pi-hole server. Configure tests/IntegrationConfig.local.ps1 +# (copy it from IntegrationConfig.example.ps1) before running. Tests are skipped automatically +# if that file is missing. + +$script:ConfigAvailable = Test-Path (Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1') + +Describe 'Get-PiHoleStatsUpstream (Integration)' -Tag 'Integration' { + BeforeAll { + Import-Module .\PiHoleShell\PiHoleShell.psm1 -Force + + $configPath = Join-Path $PSScriptRoot 'IntegrationConfig.local.ps1' + if (Test-Path $configPath) { + . $configPath + $script:PiHoleServer = $PiHoleServer + $script:PiHoleToken = $PiHoleToken + $script:PiHoleIgnoreSsl = $PiHoleIgnoreSsl + } + } + + It 'returns upstream metrics as a formatted object' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl + + $result | Should -Not -BeNullOrEmpty + $result.Upstreams | Should -Not -BeNullOrEmpty + $result.TotalQueries | Should -BeGreaterOrEqual 0 + } + + It 'returns the raw API response when RawOutput is set' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsUpstream -PiHoleServer $script:PiHoleServer -Password $script:PiHoleToken -IgnoreSsl $script:PiHoleIgnoreSsl -RawOutput $true + + $result.upstreams | Should -Not -BeNullOrEmpty + } + + It 'errors when given a bad password' -Skip:(-not $script:ConfigAvailable) { + $result = Get-PiHoleStatsUpstream -PiHoleServer $script:PiHoleServer -Password 'definitely-not-the-real-token' -IgnoreSsl $script:PiHoleIgnoreSsl -ErrorVariable errOut -ErrorAction SilentlyContinue + + $errOut | Should -Not -BeNullOrEmpty + } +}