From c31565a60c5f1fb97ee92cffe024240420f56e44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Oct 2026 20:51:46 +0000 Subject: [PATCH 1/3] Initial plan From 11ec6bb60aa7de9189c4f0eff27d70ff3f5dc24a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:06:09 +0000 Subject: [PATCH 2/3] Add SQL endpoint metadata refresh Co-authored-by: NowinskiK <25029325+NowinskiK@users.noreply.github.com> --- CHANGELOG.md | 6 + docs/en-US/FabricTools.md | 4 + docs/en-US/Refresh-FabricSQLEndpoint.md | 132 ++++++++++++++++++ src/Private/Test-FabricApiResponse.ps1 | 18 ++- src/Public/Invoke-FabricRestMethod.ps1 | 40 ++++++ .../Refresh-FabricSQLEndpoint.ps1 | 70 ++++++++++ .../Utils/Get-FabricLongRunningOperation.ps1 | 57 ++++++-- .../Get-FabricLongRunningOperation.Tests.ps1 | 37 +++++ tests/Unit/Invoke-FabricRestMethod.Tests.ps1 | 11 ++ .../Unit/Refresh-FabricSQLEndpoint.Tests.ps1 | 85 +++++++++++ tests/Unit/Test-FabricApiResponse.Tests.ps1 | 10 ++ 11 files changed, 459 insertions(+), 11 deletions(-) create mode 100644 docs/en-US/Refresh-FabricSQLEndpoint.md create mode 100644 src/Public/SQL Endpoints/Refresh-FabricSQLEndpoint.ps1 create mode 100644 tests/Unit/Refresh-FabricSQLEndpoint.Tests.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index e930a21f..ed5a3547 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ The format is based on and uses the types of changes according to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +- Added `Refresh-FabricSQLEndpoint` to refresh SQL analytics endpoint metadata. + ## 0.32.0 - 2026-05-06 ### Added diff --git a/docs/en-US/FabricTools.md b/docs/en-US/FabricTools.md index 586938f7..ca795012 100644 --- a/docs/en-US/FabricTools.md +++ b/docs/en-US/FabricTools.md @@ -358,6 +358,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/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 b719c099..1c50ae01 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" @@ -77,7 +80,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 @@ -112,7 +123,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 $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") { @@ -135,6 +146,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 448a4fb7..860a46dc 100644 --- a/src/Public/Invoke-FabricRestMethod.ps1 +++ b/src/Public/Invoke-FabricRestMethod.ps1 @@ -31,6 +31,9 @@ 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 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 +92,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 +176,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,8 +199,29 @@ 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 ($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 @@ -201,6 +235,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..6eb7ddd0 100644 --- a/src/Public/Utils/Get-FabricLongRunningOperation.ps1 +++ b/src/Public/Utils/Get-FabricLongRunningOperation.ps1 @@ -16,6 +16,9 @@ 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. + .EXAMPLE This command polls the status of the operation with the given operationId every 10 seconds until it completes. ```powershell ``` @@ -36,11 +39,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 +75,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 cb752c25..f78c8924 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' } @@ -96,5 +97,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 3a78cbcd..f4ed5f84 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)) } @@ -76,6 +77,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 "Returns PSCustomObject with 3 properties when statusCode is 202 and -NoWait is specified" { $script:statusCode = 202 $result = Test-FabricApiResponse -Response $script:response -ResponseHeader $script:responseHeader -StatusCode $script:statusCode $script:responseHeader -NoWait From 2e0daa7450aa0374e79a30eb89c01a8bb8bfa3db Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Nov 2026 20:35:06 +0000 Subject: [PATCH 3/3] Document internal timeout parameters Co-authored-by: NowinskiK <25029325+NowinskiK@users.noreply.github.com> --- docs/en-US/Get-FabricLongRunningOperation.md | 25 +++++++++++++++++-- docs/en-US/Invoke-FabricRestMethod.md | 25 +++++++++++++++++-- src/Public/Invoke-FabricRestMethod.ps1 | 3 +++ .../Utils/Get-FabricLongRunningOperation.ps1 | 3 +++ 4 files changed, 52 insertions(+), 4 deletions(-) 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/src/Public/Invoke-FabricRestMethod.ps1 b/src/Public/Invoke-FabricRestMethod.ps1 index 01bda996..008dca22 100644 --- a/src/Public/Invoke-FabricRestMethod.ps1 +++ b/src/Public/Invoke-FabricRestMethod.ps1 @@ -34,6 +34,9 @@ Function Invoke-FabricRestMethod { .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. diff --git a/src/Public/Utils/Get-FabricLongRunningOperation.ps1 b/src/Public/Utils/Get-FabricLongRunningOperation.ps1 index 6eb7ddd0..5fd5b576 100644 --- a/src/Public/Utils/Get-FabricLongRunningOperation.ps1 +++ b/src/Public/Utils/Get-FabricLongRunningOperation.ps1 @@ -19,6 +19,9 @@ The interval (in seconds) to wait between polling the operation status. The defa .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 ```