diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eca3393..02f4a81f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added `Refresh-FabricSQLEndpoint` to refresh SQL analytics endpoint metadata. - Added `-SQLDatabaseName` (alias `Name`) to `Remove-FabricSQLDatabase`, requiring a unique exact name match within the specified workspace before deletion (#210). - Added `WorkspaceName` (alias `Name`) to `Remove-FabricWorkspace` to delete a workspace by its exact display name, refusing deletion when no workspace or multiple workspaces match (issue #157). - Added `Start-FabricNotebook` to run a notebook by ID or unique workspace-scoped name, wait for its job status by default, or return promptly with `-NoWait`. Supports `-WhatIf`, Retry-After polling, and execution failure details (#194). diff --git a/docs/en-US/FabricTools.md b/docs/en-US/FabricTools.md index 62775d22..e3d1b621 100644 --- a/docs/en-US/FabricTools.md +++ b/docs/en-US/FabricTools.md @@ -354,6 +354,10 @@ Retrieves Fabric SQL Database details. Retrieves SQL Endpoints from a specified workspace in Fabric. +### [Refresh-FabricSQLEndpoint](Refresh-FabricSQLEndpoint.md) + +Refreshes the metadata of a SQL analytics endpoint. + ### [Get-FabricTenantSetting](Get-FabricTenantSetting.md) Retrieves tenant settings from the Fabric environment. diff --git a/docs/en-US/Get-FabricLongRunningOperation.md b/docs/en-US/Get-FabricLongRunningOperation.md index 4cc87a8c..3148f04e 100644 --- a/docs/en-US/Get-FabricLongRunningOperation.md +++ b/docs/en-US/Get-FabricLongRunningOperation.md @@ -21,7 +21,7 @@ Monitors the status of a long-running operation in Microsoft Fabric. ``` Get-FabricLongRunningOperation [[-OperationId] ] [[-Location] ] - [[-RetryAfter] ] [] + [[-RetryAfter] ] [[-Timeout] ] [] ``` ## ALIASES @@ -109,6 +109,28 @@ AcceptedValues: [] HelpMessage: '' ``` +### -Timeout + +The maximum time, in minutes, to wait for the operation. +A value of 0 waits indefinitely. + +```yaml +Type: System.Int32 +DefaultValue: 0 +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: 3 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + ### CommonParameters This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, @@ -127,4 +149,3 @@ Author: Tiago Balabuch ## RELATED LINKS {{ Fill in the related links here }} - diff --git a/docs/en-US/Invoke-FabricRestMethod.md b/docs/en-US/Invoke-FabricRestMethod.md index 5e9b0de5..f11c1a95 100644 --- a/docs/en-US/Invoke-FabricRestMethod.md +++ b/docs/en-US/Invoke-FabricRestMethod.md @@ -23,7 +23,7 @@ Sends an HTTP request to a Fabric or Power BI API endpoint and retrieve the resp Invoke-FabricRestMethod [-Uri] [[-Method] ] [[-Body] ] [[-ExtractValue] ] [[-TypeName] ] [[-ObjectIdOrName] ] [[-SuccessMessage] ] [-TestTokenExpired] [-PowerBIApi] [-NoWait] [-HandleResponse] - [] + [-Timeout ] [] ``` ## ALIASES @@ -263,6 +263,28 @@ AcceptedValues: [] HelpMessage: '' ``` +### -Timeout + +The maximum time, in minutes, to wait for a long-running operation. +A value of 0 waits indefinitely. + +```yaml +Type: System.Int32 +DefaultValue: 0 +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + ### -TypeName Optional. @@ -330,4 +352,3 @@ Author: Kamil Nowinski ## RELATED LINKS {{ Fill in the related links here }} - diff --git a/docs/en-US/Refresh-FabricSQLEndpoint.md b/docs/en-US/Refresh-FabricSQLEndpoint.md new file mode 100644 index 00000000..8a661658 --- /dev/null +++ b/docs/en-US/Refresh-FabricSQLEndpoint.md @@ -0,0 +1,132 @@ +--- +document type: cmdlet +external help file: FabricTools-Help.xml +HelpUri: '' +Locale: en-US +Module Name: FabricTools +ms.date: 10/28/2026 +title: Refresh-FabricSQLEndpoint +--- + +# Refresh-FabricSQLEndpoint + +## SYNOPSIS + +Refreshes the metadata of a SQL analytics endpoint. + +## SYNTAX + +``` +Refresh-FabricSQLEndpoint [-WorkspaceId] [-SQLEndpointId] [[-Timeout] ] + [-NoWait] [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION + +Starts a refresh of the metadata for a SQL analytics endpoint in a specified workspace. +By default, the function waits for the long-running operation to complete. + +## EXAMPLES + +### EXAMPLE 1 + +```powershell +Refresh-FabricSQLEndpoint -WorkspaceId $workspaceId -SQLEndpointId $sqlEndpointId +``` + +## PARAMETERS + +### -WorkspaceId + +The ID of the workspace containing the SQL analytics endpoint. + +```yaml +Type: System.Guid +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: 0 + IsRequired: true + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -SQLEndpointId + +The ID of the SQL analytics endpoint to refresh. + +```yaml +Type: System.Guid +DefaultValue: '' +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: 1 + IsRequired: true + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -Timeout + +The maximum time, in minutes, to wait for the refresh operation. A value of 0 waits indefinitely. + +```yaml +Type: System.Int32 +DefaultValue: 15 +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: 2 + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### -NoWait + +If specified, returns immediately after the refresh request is accepted. + +```yaml +Type: System.Management.Automation.SwitchParameter +DefaultValue: False +SupportsWildcards: false +Aliases: [] +ParameterSets: +- Name: (All) + Position: Named + IsRequired: false + ValueFromPipeline: false + ValueFromPipelineByPropertyName: false + ValueFromRemainingArguments: false +DontShow: false +AcceptedValues: [] +HelpMessage: '' +``` + +### CommonParameters + +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, +-InformationAction, -InformationVariable, -OutBuffer, -OutVariable, -PipelineVariable, +-ProgressAction, -Verbose, -WarningAction, and -WarningVariable. For more information, see +[about_CommonParameters](https://go.microsoft.com/fwlink/?LinkID=113216). + +## NOTES + +Author: Kamil Nowinski diff --git a/src/Private/Test-FabricApiResponse.ps1 b/src/Private/Test-FabricApiResponse.ps1 index 0134e39c..effd6e6c 100644 --- a/src/Private/Test-FabricApiResponse.ps1 +++ b/src/Private/Test-FabricApiResponse.ps1 @@ -33,6 +33,9 @@ function Test-FabricApiResponse { .PARAMETER NoWait If specified, the function will not wait for the operation to complete and will return immediately. +.PARAMETER Timeout + The maximum time, in minutes, to wait for a long-running operation. A value of 0 waits indefinitely. + .EXAMPLE Test-FabricApiResponse -statusCode 201 -response $response -responseHeader $header -Name "MyResource" -typeName "Fabric Item" @@ -78,7 +81,15 @@ function Test-FabricApiResponse { [string] $SuccessMessage, [Parameter(Mandatory = $false)] - [switch] $NoWait = $false + [switch] $NoWait = $false, + + [Parameter(Mandatory = $false)] + [ValidateRange(0, [int]::MaxValue)] + [int] $Timeout = 0, + + [Parameter(DontShow)] + [ValidateRange(0, [int]::MaxValue)] + [int] $TimeoutSeconds = 0 ) Write-Message -Message "::Begin" -Level Debug @@ -117,7 +128,7 @@ function Test-FabricApiResponse { Write-Message -Message "Operation ID: '$operationId'" -Level "Debug" Write-Message -Message "Getting Long Running Operation status" -Level Debug - $operationStatus = Get-FabricLongRunningOperation -operationId $res.OperationId -location $res.Location -retryAfter $res.RetryAfter + $operationStatus = Get-FabricLongRunningOperation -operationId $operationId -location $res.Location -retryAfter $res.RetryAfter -Timeout $Timeout -TimeoutSeconds $TimeoutSeconds Write-Message -Message "Long Running Operation status: $operationStatus" -Level Debug # Handle operation result if ($operationStatus.status -eq "Succeeded") { @@ -140,6 +151,9 @@ function Test-FabricApiResponse { $retryAfter = $responseHeader['Retry-After'] $retryAfterStr = $retryAfter[0].ToString() $retryAfterInt = [int]$retryAfterStr + if ($TimeoutSeconds -gt 0) { + $retryAfterInt = [Math]::Min($retryAfterInt, $TimeoutSeconds) + } Start-Sleep -Seconds $retryAfterInt return "Command:Repeat" } diff --git a/src/Public/Invoke-FabricRestMethod.ps1 b/src/Public/Invoke-FabricRestMethod.ps1 index d455bb32..008dca22 100644 --- a/src/Public/Invoke-FabricRestMethod.ps1 +++ b/src/Public/Invoke-FabricRestMethod.ps1 @@ -31,6 +31,12 @@ Function Invoke-FabricRestMethod { A switch parameter to indicate that the function should not wait for the response. This is useful for asynchronous operations. Default is False. If set to True, the function will return immediately without waiting for the operation to complete. +.PARAMETER Timeout + The maximum time, in minutes, to wait for a long-running operation. A value of 0 waits indefinitely. + +.PARAMETER RequestTimeoutSeconds + An internal request timeout, in seconds, used to keep long-running operation status requests within the remaining timeout budget. + .PARAMETER HandleResponse A switch parameter to indicate that the response should be handled after call API. Default is False. The operation will be processed based on the response status code and supports Throttling (429) and Long-Running Operations. @@ -89,6 +95,14 @@ Function Invoke-FabricRestMethod { [Parameter(Mandatory = $false)] [switch] $NoWait = $false, + [Parameter(Mandatory = $false)] + [ValidateRange(0, [int]::MaxValue)] + [int] $Timeout = 0, + + [Parameter(DontShow)] + [ValidateRange(0, [int]::MaxValue)] + [int] $RequestTimeoutSeconds = 0, + [Parameter(Mandatory = $false)] [switch] $HandleResponse = $true, @@ -165,6 +179,8 @@ Function Invoke-FabricRestMethod { $continuationToken = $null $repeat = $false + $timeoutBudgetSeconds = [long]$Timeout * 60 + $timeoutStopwatch = if ($Timeout -gt 0) { [System.Diagnostics.Stopwatch]::StartNew() } else { $null } do { $apiEndpointUrl = $Uri @@ -186,12 +202,33 @@ Function Invoke-FabricRestMethod { StatusCodeVariable = "statusCode" } + $requestTimeoutSeconds = $RequestTimeoutSeconds + if ($Timeout -gt 0) { + $remainingTimeoutSeconds = $timeoutBudgetSeconds - $timeoutStopwatch.Elapsed.TotalSeconds + if ($remainingTimeoutSeconds -le 0) { + throw "API request timed out after $Timeout minutes." + } + if ($requestTimeoutSeconds -eq 0 -or $remainingTimeoutSeconds -lt $requestTimeoutSeconds) { + $requestTimeoutSeconds = [Math]::Max(1, [Math]::Floor($remainingTimeoutSeconds)) + } + } + + if ($RequestTimeoutSeconds -gt 0) { + $request.TimeoutSec = [int][Math]::Min($requestTimeoutSeconds, [int]::MaxValue) + } elseif ($Timeout -gt 0) { + $request.TimeoutSec = [int][Math]::Min($requestTimeoutSeconds, [int]::MaxValue) + } + $response = Invoke-RestMethod @request if ($null -eq $responseHeader) { $responseHeader = @{} } + if ($Timeout -gt 0 -and $timeoutStopwatch.Elapsed.TotalSeconds -ge $timeoutBudgetSeconds) { + throw "API request timed out after $Timeout minutes." + } + # Needed for backward compatibility, example: Get-FabricWorkspace $script:statusCode = $statusCode $script:responseHeader = $responseHeader @@ -205,6 +242,12 @@ Function Invoke-FabricRestMethod { ObjectIdOrName = $ObjectIdOrName TypeName = $TypeName NoWait = $NoWait + Timeout = $Timeout + TimeoutSeconds = if ($Timeout -gt 0) { + [int][Math]::Min([Math]::Max(1, [Math]::Floor($timeoutBudgetSeconds - $timeoutStopwatch.Elapsed.TotalSeconds)), [int]::MaxValue) + } else { + 0 + } SuccessMessage = $SuccessMessage } $response = Test-FabricApiResponse @params diff --git a/src/Public/SQL Endpoints/Refresh-FabricSQLEndpoint.ps1 b/src/Public/SQL Endpoints/Refresh-FabricSQLEndpoint.ps1 new file mode 100644 index 00000000..18def30d --- /dev/null +++ b/src/Public/SQL Endpoints/Refresh-FabricSQLEndpoint.ps1 @@ -0,0 +1,70 @@ +function Refresh-FabricSQLEndpoint { + <# + .SYNOPSIS + Refreshes the metadata of a SQL analytics endpoint. + + .DESCRIPTION + Starts a refresh of the metadata for a SQL analytics endpoint in a specified workspace. + By default, the function waits for the long-running operation to complete. + + .PARAMETER WorkspaceId + The ID of the workspace containing the SQL analytics endpoint. + + .PARAMETER SQLEndpointId + The ID of the SQL analytics endpoint to refresh. + + .PARAMETER Timeout + The maximum time, in minutes, to wait for the refresh operation. A value of 0 waits indefinitely. + + .PARAMETER NoWait + If specified, returns immediately after the refresh request is accepted. + + .EXAMPLE + ```powershell + Refresh-FabricSQLEndpoint -WorkspaceId $workspaceId -SQLEndpointId $sqlEndpointId + ``` + + .NOTES + Author: Kamil Nowinski + #> + [CmdletBinding(SupportsShouldProcess)] + param ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [guid]$WorkspaceId, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [guid]$SQLEndpointId, + + [Parameter(Mandatory = $false)] + [ValidateRange(0, [int]::MaxValue)] + [int]$Timeout = 15, + + [Parameter(Mandatory = $false)] + [switch]$NoWait = $false + ) + + try { + Confirm-TokenState + + $apiEndpointUrl = "workspaces/$WorkspaceId/sqlEndpoints/$SQLEndpointId/refreshMetadata" + Write-Message -Message "API Endpoint: $apiEndpointUrl" -Level Debug + + if ($PSCmdlet.ShouldProcess($SQLEndpointId, 'Refresh SQL endpoint metadata')) { + $apiParams = @{ + Uri = $apiEndpointUrl + Method = 'Post' + TypeName = 'SQL Endpoint' + ObjectIdOrName = $SQLEndpointId + Timeout = $Timeout + NoWait = $NoWait + HandleResponse = $true + } + Invoke-FabricRestMethod @apiParams + } + } catch { + $errorDetails = $_.Exception.Message + Write-Message -Message "Failed to refresh SQL endpoint metadata. Error: $errorDetails" -Level Error + } +} diff --git a/src/Public/Utils/Get-FabricLongRunningOperation.ps1 b/src/Public/Utils/Get-FabricLongRunningOperation.ps1 index a47ecccb..5fd5b576 100644 --- a/src/Public/Utils/Get-FabricLongRunningOperation.ps1 +++ b/src/Public/Utils/Get-FabricLongRunningOperation.ps1 @@ -16,6 +16,12 @@ The URL provided in the Location header of the initial request. This is used to .PARAMETER RetryAfter The interval (in seconds) to wait between polling the operation status. The default is 5 seconds. +.PARAMETER Timeout +The maximum time, in minutes, to wait for the operation. A value of 0 waits indefinitely. + +.PARAMETER TimeoutSeconds +The remaining timeout budget, in seconds, passed internally to limit long-running operation polling. + .EXAMPLE This command polls the status of the operation with the given operationId every 10 seconds until it completes. ```powershell ``` @@ -36,11 +42,21 @@ Author: Tiago Balabuch [string]$Location, [Parameter(Mandatory = $false)] - [int]$RetryAfter = 5 + [int]$RetryAfter = 5, + + [Parameter(Mandatory = $false)] + [ValidateRange(0, [int]::MaxValue)] + [int]$Timeout = 0, + + [Parameter(DontShow)] + [ValidateRange(0, [int]::MaxValue)] + [int]$TimeoutSeconds = 0 ) Write-Message -Message "[Get-FabricLongRunningOperation]::Begin" -Level Debug Confirm-TokenState + $stopwatch = [System.Diagnostics.Stopwatch]::StartNew() + $timeoutBudgetSeconds = if ($TimeoutSeconds -gt 0) { $TimeoutSeconds } else { [long]$Timeout * 60 } # Construct the API URL if (-not $OperationId) { @@ -62,17 +78,43 @@ Author: Tiago Balabuch try { do { - # Wait before the next request - if ($RetryAfter) { - Write-Message -Message "Waiting $RetryAfter seconds..." -Level Verbose - Start-Sleep -Seconds $RetryAfter - } else { - Write-Message -Message "Waiting 5 seconds..." -Level Verbose - Start-Sleep -Seconds 5 # Default retry interval if no Retry-After header + if ($timeoutBudgetSeconds -gt 0) { + $remainingSeconds = $timeoutBudgetSeconds - $stopwatch.Elapsed.TotalSeconds + if ($remainingSeconds -le 0) { + throw "Operation timed out after $Timeout minutes." + } + } + + $sleepSeconds = if ($RetryAfter) { $RetryAfter } else { 5 } + if ($timeoutBudgetSeconds -gt 0) { + $sleepSeconds = [Math]::Min($sleepSeconds, [Math]::Ceiling($remainingSeconds)) + } + + Write-Message -Message "Waiting $sleepSeconds seconds..." -Level Verbose + Start-Sleep -Seconds $sleepSeconds + + if ($timeoutBudgetSeconds -gt 0 -and $stopwatch.Elapsed.TotalSeconds -ge $timeoutBudgetSeconds) { + throw "Operation timed out after $Timeout minutes." + } + + $apiParams = @{ + Uri = $apiEndpointUrl + Method = 'Get' + } + if ($timeoutBudgetSeconds -gt 0) { + $remainingSeconds = $timeoutBudgetSeconds - $stopwatch.Elapsed.TotalSeconds + if ($remainingSeconds -le 0) { + throw "Operation timed out after $Timeout minutes." + } + $apiParams.RequestTimeoutSeconds = [int][Math]::Min([Math]::Max(1, [Math]::Floor($remainingSeconds)), [int]::MaxValue) } # Make the API request - $response = Invoke-FabricRestMethod -Uri $apiEndpointUrl -Method Get + $response = Invoke-FabricRestMethod @apiParams + + if ($timeoutBudgetSeconds -gt 0 -and $stopwatch.Elapsed.TotalSeconds -ge $timeoutBudgetSeconds) { + throw "Operation timed out after $Timeout minutes." + } # Parse the response $jsonOperation = $response | ConvertTo-Json diff --git a/tests/Unit/Get-FabricLongRunningOperation.Tests.ps1 b/tests/Unit/Get-FabricLongRunningOperation.Tests.ps1 index 9434620a..61a4814b 100644 --- a/tests/Unit/Get-FabricLongRunningOperation.Tests.ps1 +++ b/tests/Unit/Get-FabricLongRunningOperation.Tests.ps1 @@ -20,6 +20,7 @@ Describe "Get-FabricLongRunningOperation" -Tag "UnitTests" { @{ ExpectedParameterName = 'operationId'; ExpectedParameterType = 'string'; Mandatory = 'False' } @{ ExpectedParameterName = 'location'; ExpectedParameterType = 'string'; Mandatory = 'False' } @{ ExpectedParameterName = 'retryAfter'; ExpectedParameterType = 'int'; Mandatory = 'False' } + @{ ExpectedParameterName = 'Timeout'; ExpectedParameterType = 'int'; Mandatory = 'False' } ) { $Command | Should -HaveParameter -ParameterName $ExpectedParameterName -Type $ExpectedParameterType -Mandatory:([bool]::Parse($Mandatory)) } @@ -49,6 +50,42 @@ Describe "Get-FabricLongRunningOperation" -Tag "UnitTests" { } } + Context "Timeout handling" { + BeforeAll { + Mock -CommandName Invoke-FabricRestMethod -MockWith { + [pscustomobject]@{ status = 'Running' } + } + Mock -CommandName Confirm-TokenState -MockWith { } + Mock -CommandName Write-Message -MockWith { } + Mock -CommandName Start-Sleep -MockWith { + throw 'Sleep intercepted' + } + } + + It 'Should cap the polling interval to the remaining timeout' { + { + Get-FabricLongRunningOperation -OperationId 'test-operation-id' -RetryAfter 120 -Timeout 1 + } | Should -Throw + + Should -Invoke -CommandName Start-Sleep -Times 1 -ParameterFilter { + $Seconds -le 60 + } + } + + It 'Should cap the status request timeout to the remaining time' { + Mock -CommandName Start-Sleep -MockWith { } + Mock -CommandName Invoke-FabricRestMethod -MockWith { + [pscustomobject]@{ status = 'Succeeded' } + } + + Get-FabricLongRunningOperation -OperationId 'test-operation-id' -Timeout 1 | Out-Null + + Should -Invoke -CommandName Invoke-FabricRestMethod -Times 1 -ParameterFilter { + $RequestTimeoutSeconds -gt 0 -and $RequestTimeoutSeconds -le 60 + } + } + } + Context "Error handling" { BeforeAll { Mock -CommandName Invoke-FabricRestMethod -MockWith { diff --git a/tests/Unit/Invoke-FabricRestMethod.Tests.ps1 b/tests/Unit/Invoke-FabricRestMethod.Tests.ps1 index 634e0405..f0deaba6 100644 --- a/tests/Unit/Invoke-FabricRestMethod.Tests.ps1 +++ b/tests/Unit/Invoke-FabricRestMethod.Tests.ps1 @@ -23,6 +23,7 @@ Describe "Invoke-FabricRestMethod" -Tag "UnitTests" { @{ ExpectedParameterName = 'TestTokenExpired'; ExpectedParameterType = 'switch'; Mandatory = 'False' } @{ ExpectedParameterName = 'PowerBIApi'; ExpectedParameterType = 'switch'; Mandatory = 'False' } @{ ExpectedParameterName = 'NoWait'; ExpectedParameterType = 'switch'; Mandatory = 'False' } + @{ ExpectedParameterName = 'Timeout'; ExpectedParameterType = 'int'; Mandatory = 'False' } @{ ExpectedParameterName = 'HandleResponse'; ExpectedParameterType = 'switch'; Mandatory = 'False' } @{ ExpectedParameterName = 'ExtractValue'; ExpectedParameterType = 'string'; Mandatory = 'False' } @{ ExpectedParameterName = 'TypeName'; ExpectedParameterType = 'string'; Mandatory = 'False' } @@ -117,5 +118,15 @@ Describe "Invoke-FabricRestMethod" -Tag "UnitTests" { } -Because 'Invoke-RestMethod should be called with User-Agent header set to configured value' } } + + It 'Should convert the timeout from minutes to seconds for the request' { + InModuleScope -ModuleName 'FabricTools' { + Invoke-FabricRestMethod -Uri 'https://api.fabric.microsoft.com/v1/test' -Timeout 2 -HandleResponse:$false | Out-Null + + Should -Invoke -CommandName Invoke-RestMethod -Times 1 -ParameterFilter { + $TimeoutSec -gt 0 -and $TimeoutSec -le 120 + } + } + } } } diff --git a/tests/Unit/Refresh-FabricSQLEndpoint.Tests.ps1 b/tests/Unit/Refresh-FabricSQLEndpoint.Tests.ps1 new file mode 100644 index 00000000..fd930f85 --- /dev/null +++ b/tests/Unit/Refresh-FabricSQLEndpoint.Tests.ps1 @@ -0,0 +1,85 @@ +#Requires -Module @{ ModuleName="Pester"; ModuleVersion="5.0"} + +BeforeDiscovery { + $CommandName = 'Refresh-FabricSQLEndpoint' +} + +BeforeAll { + $ModuleName = 'FabricTools' + $PSDefaultParameterValues['Mock:ModuleName'] = $ModuleName + $PSDefaultParameterValues['InModuleScope:ModuleName'] = $ModuleName + $PSDefaultParameterValues['Should:ModuleName'] = $ModuleName +} + +Describe "Refresh-FabricSQLEndpoint" -Tag "UnitTests" { + BeforeAll { + $command = Get-Command -Name Refresh-FabricSQLEndpoint + } + + Context 'Command definition' { + It 'Should have the expected parameters' { + $command | Should -HaveParameter WorkspaceId -Mandatory + $command | Should -HaveParameter SQLEndpointId -Mandatory + $command | Should -HaveParameter Timeout -Mandatory:$false + $command | Should -HaveParameter NoWait -Mandatory:$false + } + + It 'Should support ShouldProcess' { + $command.Parameters.ContainsKey('WhatIf') | Should -BeTrue + $command.Parameters.ContainsKey('Confirm') | Should -BeTrue + } + } + + Context 'When refreshing SQL endpoint metadata successfully' { + BeforeAll { + Mock -CommandName Confirm-TokenState -MockWith { } + Mock -CommandName Write-Message -MockWith { } + Mock -CommandName Invoke-FabricRestMethod -MockWith { + [pscustomobject]@{ OperationId = 'refresh-operation' } + } + } + + It 'Should call the API with the expected route and default settings' { + $workspaceId = [guid]::NewGuid() + $sqlEndpointId = [guid]::NewGuid() + + Refresh-FabricSQLEndpoint -WorkspaceId $workspaceId -SQLEndpointId $sqlEndpointId -Confirm:$false + + Should -Invoke -CommandName Invoke-FabricRestMethod -Times 1 -ParameterFilter { + $Uri -eq "workspaces/$workspaceId/sqlEndpoints/$sqlEndpointId/refreshMetadata" -and + $Method -eq 'Post' -and + $Timeout -eq 15 -and + -not $NoWait -and + $HandleResponse + } + } + + It 'Should return the refresh response' { + $result = Refresh-FabricSQLEndpoint -WorkspaceId ([guid]::NewGuid()) -SQLEndpointId ([guid]::NewGuid()) -Confirm:$false + + $result.OperationId | Should -Be 'refresh-operation' + } + + It 'Should forward the timeout and no-wait options' { + Refresh-FabricSQLEndpoint -WorkspaceId ([guid]::NewGuid()) -SQLEndpointId ([guid]::NewGuid()) -Timeout 4 -NoWait -Confirm:$false + + Should -Invoke -CommandName Invoke-FabricRestMethod -Times 1 -ParameterFilter { + $Timeout -eq 4 -and $NoWait + } + } + + It 'Should honor WhatIf without making an API request' { + Refresh-FabricSQLEndpoint -WorkspaceId ([guid]::NewGuid()) -SQLEndpointId ([guid]::NewGuid()) -WhatIf + + Should -Invoke -CommandName Invoke-FabricRestMethod -Times 0 + } + } + + Context 'Parameter validation' { + It 'Should reject a negative timeout' { + { + Refresh-FabricSQLEndpoint -WorkspaceId ([guid]::NewGuid()) -SQLEndpointId ([guid]::NewGuid()) -Timeout -1 + } | Should -Throw + } + } +} diff --git a/tests/Unit/Test-FabricApiResponse.Tests.ps1 b/tests/Unit/Test-FabricApiResponse.Tests.ps1 index aa828222..4c4bc110 100644 --- a/tests/Unit/Test-FabricApiResponse.Tests.ps1 +++ b/tests/Unit/Test-FabricApiResponse.Tests.ps1 @@ -25,6 +25,7 @@ Describe "Test-FabricApiResponse" -Tag "UnitTests" { @{ ExpectedParameterName = 'TypeName'; ExpectedParameterType = 'string'; Mandatory = 'False' } @{ ExpectedParameterName = 'SuccessMessage'; ExpectedParameterType = 'string'; Mandatory = 'False' } @{ ExpectedParameterName = 'NoWait'; ExpectedParameterType = 'switch'; Mandatory = 'False' } + @{ ExpectedParameterName = 'Timeout'; ExpectedParameterType = 'int'; Mandatory = 'False' } ) { $script:Command | Should -HaveParameter -ParameterName $ExpectedParameterName -Type $ExpectedParameterType -Mandatory:([bool]::Parse($Mandatory)) } @@ -81,6 +82,15 @@ Describe "Test-FabricApiResponse - StatusCode Handling" -Tag "UnitTests" { Should -Invoke Get-FabricLongRunningOperationResult -Exactly 1 } + It "Passes the timeout to long-running operation polling" { + $script:statusCode = 202 + Test-FabricApiResponse -Response $script:response -ResponseHeader $script:responseHeader -StatusCode $script:statusCode -Timeout 3 | Out-Null + + Should -Invoke Get-FabricLongRunningOperation -Exactly 1 -ParameterFilter { + $Timeout -eq 3 + } + } + It "Uses scalar values from enumerable 202 response headers" { $headers = [System.Collections.Generic.Dictionary[string, System.Collections.Generic.IEnumerable[string]]]::new() $headers.Add('x-ms-operation-id', [string[]]@('11111111-1111-1111-1111-111111111111', '22222222-2222-2222-2222-222222222222'))