diff --git a/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 b/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 new file mode 100644 index 0000000..27bdf94 --- /dev/null +++ b/src/functions/private/Auth/Context/Remove-PowerShellGalleryContext.ps1 @@ -0,0 +1,46 @@ +function Remove-PowerShellGalleryContext { + <# + .SYNOPSIS + Remove a PowerShell Gallery context. + + .DESCRIPTION + Remove a PowerShell Gallery context from the vault. + + .EXAMPLE + Remove-PowerShellGalleryContext -ID 'MyAccount' + + Removes the PowerShell Gallery context with ID 'MyAccount'. + #> + [OutputType([void])] + [CmdletBinding(SupportsShouldProcess)] + param( + # The ID of the context to remove. + [Parameter(Mandatory)] + [string] $ID + ) + + begin { + Write-Debug '[Remove-PowerShellGalleryContext] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + if ($PSCmdlet.ShouldProcess("Context [$ID]", 'Remove')) { + Write-Verbose "Removing context: [$ID]" + Remove-Context -ID $ID -Vault $script:PowerShellGallery.ContextVault + + # If this was the default context, clear the default + if ($script:PowerShellGallery.Config.DefaultContext -eq $ID) { + Write-Verbose 'Clearing default context' + $script:PowerShellGallery.Config.DefaultContext = $null + $configID = $script:PowerShellGallery.DefaultConfig.ID + $vault = $script:PowerShellGallery.ContextVault + $null = Set-Context -ID $configID -Context $script:PowerShellGallery.Config -Vault $vault + } + } + } + + end { + Write-Debug '[Remove-PowerShellGalleryContext] - End' + } +} diff --git a/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 b/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 new file mode 100644 index 0000000..e1a886e --- /dev/null +++ b/src/functions/private/Auth/Context/Set-PowerShellGalleryContext.ps1 @@ -0,0 +1,73 @@ +function Set-PowerShellGalleryContext { + <# + .SYNOPSIS + Sets the PowerShell Gallery context and stores it in the context vault. + + .DESCRIPTION + This function sets the PowerShell Gallery context and stores it in the context vault. + The context is used to authenticate with the PowerShell Gallery API. + + .EXAMPLE + $context = @{ + ID = 'MyAccount' + Name = 'MyAccount' + ApiKey = $secureApiKey + GalleryUrl = 'https://www.powershellgallery.com' + ConnectedAt = Get-Date + } + Set-PowerShellGalleryContext -Context $context + + Sets the PowerShell Gallery context with the specified settings as a hashtable. + #> + [OutputType([System.Object])] + [CmdletBinding(SupportsShouldProcess)] + param( + # The ID of the context. + [Parameter(Mandatory)] + [string] $ID, + + # The PowerShell Gallery context to save in the vault. + [Parameter(Mandatory)] + [hashtable] $Context, + + # Set as the default context. + [Parameter()] + [switch] $Default, + + # Pass the context through the pipeline. + [Parameter()] + [switch] $PassThru + ) + + begin { + Write-Debug '[Set-PowerShellGalleryContext] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + Write-Debug "Setting context: [$ID]" + # Create a copy of the context hashtable to avoid modifying the original + $contextObj = $Context.Clone() + $contextObj['ID'] = $ID + + if ($PSCmdlet.ShouldProcess("Context [$ID]", 'Set')) { + $vault = $script:PowerShellGallery.ContextVault + $result = Set-Context -ID $ID -Context $contextObj -Vault $vault -PassThru + + if ($Default) { + Write-Debug "Setting [$ID] as default context" + $script:PowerShellGallery.Config.DefaultContext = $ID + $configID = $script:PowerShellGallery.DefaultConfig.ID + $null = Set-Context -ID $configID -Context $script:PowerShellGallery.Config -Vault $vault + } + + if ($PassThru) { + return $result + } + } + } + + end { + Write-Debug '[Set-PowerShellGalleryContext] - End' + } +} diff --git a/src/functions/private/Config/Get-PowerShellGalleryConfig.ps1 b/src/functions/private/Config/Get-PowerShellGalleryConfig.ps1 new file mode 100644 index 0000000..dbe96c5 --- /dev/null +++ b/src/functions/private/Config/Get-PowerShellGalleryConfig.ps1 @@ -0,0 +1,33 @@ +function Get-PowerShellGalleryConfig { + <# + .SYNOPSIS + Get the PowerShell Gallery configuration. + + .DESCRIPTION + Get the PowerShell Gallery configuration. Initializes if not already loaded. + + .EXAMPLE + Get-PowerShellGalleryConfig + + Gets the PowerShell Gallery configuration. + #> + [OutputType([System.Object])] + [CmdletBinding()] + param() + + begin { + Write-Debug '[Get-PowerShellGalleryConfig] - Start' + } + + process { + if ($null -eq $script:PowerShellGallery.Config) { + Write-Debug 'Config not initialized, initializing...' + Initialize-PowerShellGalleryConfig + } + return $script:PowerShellGallery.Config + } + + end { + Write-Debug '[Get-PowerShellGalleryConfig] - End' + } +} diff --git a/src/functions/private/Config/Initialize-PowerShellGalleryConfig.ps1 b/src/functions/private/Config/Initialize-PowerShellGalleryConfig.ps1 new file mode 100644 index 0000000..0b1a649 --- /dev/null +++ b/src/functions/private/Config/Initialize-PowerShellGalleryConfig.ps1 @@ -0,0 +1,60 @@ +function Initialize-PowerShellGalleryConfig { + <# + .SYNOPSIS + Initialize the PowerShellGallery module configuration. + + .DESCRIPTION + Initialize the PowerShellGallery module configuration. + + .EXAMPLE + Initialize-PowerShellGalleryConfig + + Initializes the PowerShellGallery module configuration. + + .EXAMPLE + Initialize-PowerShellGalleryConfig -Force + + Forces the initialization of the PowerShellGallery module configuration. + #> + [OutputType([void])] + [CmdletBinding()] + param ( + # Force the initialization of the PowerShellGallery config. + [switch] $Force + ) + + begin { + Write-Debug '[Initialize-PowerShellGalleryConfig] - Start' + } + + process { + Write-Debug "Force: [$Force]" + if ($Force) { + Write-Debug 'Forcing initialization of PowerShellGalleryConfig.' + $config = Set-Context -Context $script:PowerShellGallery.DefaultConfig -Vault $script:PowerShellGallery.ContextVault -PassThru + $script:PowerShellGallery.Config = $config + return + } + + if ($null -ne $script:PowerShellGallery.Config) { + Write-Debug 'PowerShellGalleryConfig already initialized and available in memory.' + return + } + + Write-Debug 'Attempt to load the stored PowerShellGalleryConfig from ContextVault' + $config = Get-Context -ID $script:PowerShellGallery.DefaultConfig.ID -Vault $script:PowerShellGallery.ContextVault + if ($config) { + Write-Debug 'PowerShellGalleryConfig loaded into memory.' + $script:PowerShellGallery.Config = $config + return + } + + Write-Debug 'Initializing PowerShellGalleryConfig from defaults' + $config = Set-Context -Context $script:PowerShellGallery.DefaultConfig -Vault $script:PowerShellGallery.ContextVault -PassThru + $script:PowerShellGallery.Config = $config + } + + end { + Write-Debug '[Initialize-PowerShellGalleryConfig] - End' + } +} diff --git a/src/functions/public/Auth/Connect-PowerShellGallery.ps1 b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 new file mode 100644 index 0000000..595d08a --- /dev/null +++ b/src/functions/public/Auth/Connect-PowerShellGallery.ps1 @@ -0,0 +1,127 @@ +function Connect-PowerShellGallery { + <# + .SYNOPSIS + Connects to the PowerShell Gallery by storing an API key context. + + .DESCRIPTION + Connects to the PowerShell Gallery by storing an API key in a secure context vault. + When called without an API key, it opens the user's browser to the PowerShell Gallery + API key management page and prompts for the key. + + .EXAMPLE + Connect-PowerShellGallery -Name 'MyAccount' + + Opens browser to API key page and prompts for API key, then stores it with name 'MyAccount'. + + .EXAMPLE + Connect-PowerShellGallery -Name 'CIAccount' -ApiKey $secureApiKey -Silent + + Stores the provided API key without prompting or informational output. + + .EXAMPLE + $context = Connect-PowerShellGallery -Name 'MyAccount' -PassThru + + Stores the API key and returns the context object. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'The API key is received as clear text from user input.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module. Consistent with GitHub module pattern.' + )] + [CmdletBinding(DefaultParameterSetName = 'Interactive')] + [OutputType([System.Object])] + param( + # A friendly name/identifier for this connection context + [Parameter(Mandatory)] + [string] $Name, + + # The API key as a SecureString. If not provided, user will be prompted. + [Parameter(ParameterSetName = 'Interactive')] + [Parameter(Mandatory, ParameterSetName = 'NonInteractive')] + [SecureString] $ApiKey, + + # Return the context object after creation + [Parameter()] + [switch] $PassThru, + + # Suppress informational output + [Parameter()] + [switch] $Silent + ) + + begin { + Write-Debug '[Connect-PowerShellGallery] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Open browser to API key page if interactive + if ($PSCmdlet.ParameterSetName -eq 'Interactive' -and -not $ApiKey) { + if (-not $Silent) { + Write-Host '🌐 Opening PowerShell Gallery API key management page...' -ForegroundColor Cyan + } + + $apiKeyUrl = 'https://www.powershellgallery.com/account/apikeys' + + # Try to open browser + try { + if ($IsWindows -or $PSVersionTable.PSVersion.Major -le 5) { + Start-Process $apiKeyUrl + } elseif ($IsMacOS) { + Start-Process 'open' -ArgumentList $apiKeyUrl + } elseif ($IsLinux) { + Start-Process 'xdg-open' -ArgumentList $apiKeyUrl + } + } catch { + Write-Warning "Unable to open browser automatically. Please visit: $apiKeyUrl" + } + + if (-not $Silent) { + Write-Host '' + Write-Host 'Please create or copy your API key from the PowerShell Gallery.' -ForegroundColor Yellow + Write-Host '' + } + } + + # Prompt for API key if not provided + if (-not $ApiKey) { + $ApiKey = Read-Host -Prompt 'Enter your PowerShell Gallery API key' -AsSecureString + } + + # Validate API key is not empty + if ($null -eq $ApiKey) { + Write-Error 'API key is required to connect to PowerShell Gallery.' + return + } + + # Create context object using centralized configuration + $config = Get-PowerShellGalleryConfig + $context = @{ + ID = $Name + Name = $Name + ApiKey = $ApiKey + GalleryUrl = $config.GalleryUrl + ApiUrl = $config.ApiUrl + ConnectedAt = Get-Date + } + + # Store context + Write-Verbose "Storing context with ID: [$Name]" + $result = Set-PowerShellGalleryContext -ID $Name -Context $context -Default -PassThru + + if (-not $Silent) { + Write-Host "✓ Successfully connected to PowerShell Gallery as [$Name]" -ForegroundColor Green + } + + if ($PassThru) { + return $result + } + } + + end { + Write-Debug '[Connect-PowerShellGallery] - End' + } +} diff --git a/src/functions/public/Auth/Context/Get-PowerShellGalleryContext.ps1 b/src/functions/public/Auth/Context/Get-PowerShellGalleryContext.ps1 new file mode 100644 index 0000000..5a9073c --- /dev/null +++ b/src/functions/public/Auth/Context/Get-PowerShellGalleryContext.ps1 @@ -0,0 +1,76 @@ +function Get-PowerShellGalleryContext { + <# + .SYNOPSIS + Get the current PowerShell Gallery context. + + .DESCRIPTION + Get the current PowerShell Gallery context. Returns stored contexts from the vault. + + .EXAMPLE + Get-PowerShellGalleryContext + + Gets the default PowerShell Gallery context. + + .EXAMPLE + Get-PowerShellGalleryContext -ID 'MyAccount' + + Gets the PowerShell Gallery context with ID 'MyAccount'. + + .EXAMPLE + Get-PowerShellGalleryContext -ListAvailable + + Lists all available PowerShell Gallery contexts. + #> + [OutputType([System.Object])] + [CmdletBinding(DefaultParameterSetName = 'Get default context')] + param( + # The ID of the context. + [Parameter( + Mandatory, + ParameterSetName = 'Get a named context', + Position = 0 + )] + [Alias('Name')] + [Alias('Context')] + [string] $ID, + + # List all available contexts. + [Parameter( + Mandatory, + ParameterSetName = 'List all available contexts' + )] + [switch] $ListAvailable + ) + + begin { + Write-Debug '[Get-PowerShellGalleryContext] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + switch ($PSCmdlet.ParameterSetName) { + 'Get a named context' { + Write-Debug "Get a named context: [$ID]" + } + 'List all available contexts' { + Write-Debug "ListAvailable: [$ListAvailable]" + $ID = '*' + } + default { + Write-Debug 'Getting default context.' + $ID = $script:PowerShellGallery.Config.DefaultContext + if ([string]::IsNullOrEmpty($ID)) { + Write-Warning "No default PowerShell Gallery context found. Please run 'Connect-PowerShellGallery' to configure a context." + return + } + } + } + + Write-Verbose "Getting the context: [$ID]" + Get-Context -ID $ID -Vault $script:PowerShellGallery.ContextVault | Where-Object { $_.ID -ne $script:PowerShellGallery.DefaultConfig.ID } + } + + end { + Write-Debug '[Get-PowerShellGalleryContext] - End' + } +} diff --git a/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 b/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 new file mode 100644 index 0000000..2a8cb2d --- /dev/null +++ b/src/functions/public/Auth/Context/Switch-PowerShellGalleryContext.ps1 @@ -0,0 +1,53 @@ +function Switch-PowerShellGalleryContext { + <# + .SYNOPSIS + Switch the default PowerShell Gallery context. + + .DESCRIPTION + Switch the default PowerShell Gallery context to a different stored context. + + .EXAMPLE + Switch-PowerShellGalleryContext -ID 'MyAccount' + + Switches the default PowerShell Gallery context to 'MyAccount'. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module. Consistent with GitHub module pattern.' + )] + [OutputType([void])] + [CmdletBinding(SupportsShouldProcess)] + param( + # The ID of the context to set as default. + [Parameter(Mandatory)] + [string] $ID + ) + + begin { + Write-Debug '[Switch-PowerShellGalleryContext] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Verify the context exists + $context = Get-Context -ID $ID -Vault $script:PowerShellGallery.ContextVault + if (-not $context) { + $msg = "Context [$ID] not found. Use 'Get-PowerShellGalleryContext -ListAvailable' to see available contexts." + Write-Error $msg + return + } + + if ($PSCmdlet.ShouldProcess("Default context to [$ID]", 'Switch')) { + Write-Verbose "Switching default context to [$ID]" + $script:PowerShellGallery.Config.DefaultContext = $ID + $configID = $script:PowerShellGallery.DefaultConfig.ID + $vault = $script:PowerShellGallery.ContextVault + $null = Set-Context -ID $configID -Context $script:PowerShellGallery.Config -Vault $vault + Write-Host "✓ Switched to context [$ID]" -ForegroundColor Green + } + } + + end { + Write-Debug '[Switch-PowerShellGalleryContext] - End' + } +} diff --git a/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 b/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 new file mode 100644 index 0000000..043254b --- /dev/null +++ b/src/functions/public/Auth/Disconnect-PowerShellGallery.ps1 @@ -0,0 +1,62 @@ +function Disconnect-PowerShellGallery { + <# + .SYNOPSIS + Disconnect from the PowerShell Gallery by removing a stored context. + + .DESCRIPTION + Removes a stored PowerShell Gallery context from the vault. + + .EXAMPLE + Disconnect-PowerShellGallery -Name 'MyAccount' + + Removes the context with name 'MyAccount'. + + .EXAMPLE + Disconnect-PowerShellGallery + + Removes the default context. + #> + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module. Consistent with GitHub module pattern.' + )] + [OutputType([void])] + param( + # The name of the context to remove. If not specified, removes the default context. + [Parameter()] + [string] $Name + ) + + begin { + Write-Debug '[Disconnect-PowerShellGallery] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Determine which context to remove + if ([string]::IsNullOrEmpty($Name)) { + $Name = $script:PowerShellGallery.Config.DefaultContext + if ([string]::IsNullOrEmpty($Name)) { + Write-Warning 'No default context found. Use -Name to specify a context to remove.' + return + } + } + + # Verify context exists + $context = Get-Context -ID $Name -Vault $script:PowerShellGallery.ContextVault + if (-not $context) { + Write-Error "Context [$Name] not found." + return + } + + if ($PSCmdlet.ShouldProcess("Context [$Name]", 'Disconnect')) { + Remove-PowerShellGalleryContext -ID $Name + Write-Host "✓ Disconnected from PowerShell Gallery context [$Name]" -ForegroundColor Green + } + } + + end { + Write-Debug '[Disconnect-PowerShellGallery] - End' + } +} diff --git a/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 b/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 new file mode 100644 index 0000000..19fb68c --- /dev/null +++ b/src/functions/public/Auth/Get-PowerShellGalleryAccessToken.ps1 @@ -0,0 +1,86 @@ +function Get-PowerShellGalleryAccessToken { + <# + .SYNOPSIS + Retrieve the API key from a stored context. + + .DESCRIPTION + Retrieves the PowerShell Gallery API key from the specified context. + Returns as SecureString by default, or as plain text with -AsPlainText. + + SECURITY NOTE: Using -AsPlainText exposes the API key in plain text in memory. + This should only be used when necessary for API calls, and the plain text + value should be cleared from memory as soon as possible after use. + + .EXAMPLE + Get-PowerShellGalleryAccessToken + + Gets the API key from the default context as a SecureString. + + .EXAMPLE + Get-PowerShellGalleryAccessToken -Context 'MyAccount' -AsPlainText + + Gets the API key from 'MyAccount' context as plain text. + WARNING: This exposes the API key in plain text - use with caution. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'Converting from stored SecureString.' + )] + [CmdletBinding()] + [OutputType([System.Object])] + param( + # The context name to retrieve the API key from. If not specified, uses the default context. + [Parameter()] + [string] $Context, + + # Return the API key as plain text instead of SecureString + [Parameter()] + [switch] $AsPlainText + ) + + begin { + Write-Debug '[Get-PowerShellGalleryAccessToken] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Get the context + if ([string]::IsNullOrEmpty($Context)) { + $contextObj = Get-PowerShellGalleryContext + } else { + $contextObj = Get-PowerShellGalleryContext -ID $Context + } + + if (-not $contextObj) { + Write-Error 'No context found. Use Connect-PowerShellGallery to create a context.' + return + } + + # Get the API key + $apiKey = $contextObj.ApiKey + if (-not $apiKey) { + Write-Error "No API key found in context [$($contextObj.Name)]" + return + } + + # Return as plain text if requested + if ($AsPlainText) { + if ($apiKey -is [SecureString]) { + return ConvertFrom-SecureString -SecureString $apiKey -AsPlainText + } else { + return $apiKey + } + } + + # Ensure it's a SecureString + if ($apiKey -is [SecureString]) { + return $apiKey + } else { + return ConvertTo-SecureString -String $apiKey -AsPlainText -Force + } + } + + end { + Write-Debug '[Get-PowerShellGalleryAccessToken] - End' + } +} diff --git a/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 new file mode 100644 index 0000000..f81f070 --- /dev/null +++ b/src/functions/public/Auth/Test-PowerShellGalleryAccess.ps1 @@ -0,0 +1,120 @@ +function Test-PowerShellGalleryAccess { + <# + .SYNOPSIS + Validates the stored API key by testing access to the PowerShell Gallery API. + + .DESCRIPTION + Tests access to the PowerShell Gallery API using the stored context. + Attempts to query the API and returns information about the validation. + + .EXAMPLE + Test-PowerShellGalleryAccess + + Tests access using the default context. + + .EXAMPLE + Test-PowerShellGalleryAccess -Context 'MyAccount' + + Tests access using the 'MyAccount' context. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module. Consistent with GitHub module pattern.' + )] + [CmdletBinding()] + [OutputType([System.Object])] + param( + # The context to use. If not specified, uses the default context. + [Parameter()] + [string] $Context + ) + + begin { + Write-Debug '[Test-PowerShellGalleryAccess] - Start' + $null = Get-PowerShellGalleryConfig + } + + process { + # Get the context + if ([string]::IsNullOrEmpty($Context)) { + $contextObj = Get-PowerShellGalleryContext + } else { + $contextObj = Get-PowerShellGalleryContext -ID $Context + } + + if (-not $contextObj) { + Write-Error 'No context found. Use Connect-PowerShellGallery to create a context.' + return + } + + Write-Verbose "Testing access for context: [$($contextObj.Name)]" + + # Get the API key as plain text for the request + $apiKey = Get-PowerShellGalleryAccessToken -Context $contextObj.ID -AsPlainText + + if (-not $apiKey) { + Write-Error 'Failed to retrieve API key from context.' + return + } + + # Test by making a request to the PowerShell Gallery API + # The API key validation can be done by attempting to access the API + try { + Write-Verbose 'Testing API connectivity...' + + # Validate API URL is a PowerShell Gallery endpoint for security + $apiUrl = $contextObj.ApiUrl + if ($apiUrl -notmatch '^https://.*powershellgallery\.com/') { + Write-Warning "API URL does not appear to be a PowerShell Gallery endpoint: $apiUrl" + $result = [PSCustomObject]@{ + Success = $false + Context = $contextObj.Name + ApiUrl = $apiUrl + TestedAt = Get-Date + Message = 'API URL validation failed - not a PowerShell Gallery endpoint' + ConnectedAt = $contextObj.ConnectedAt + } + return $result + } + + $headers = @{ + 'X-NuGet-ApiKey' = $apiKey + } + + # Test basic API access - just verify we can connect + $null = Invoke-RestMethod -Uri $apiUrl -Method Get -Headers $headers -ErrorAction Stop + + $result = [PSCustomObject]@{ + Success = $true + Context = $contextObj.Name + ApiUrl = $apiUrl + TestedAt = Get-Date + Message = 'API access validated successfully' + ConnectedAt = $contextObj.ConnectedAt + } + + Write-Host "✓ API access validated for context [$($contextObj.Name)]" -ForegroundColor Green + return $result + + } catch { + $errorMessage = $_.Exception.Message + Write-Warning "Failed to validate API access: $errorMessage" + + $result = [PSCustomObject]@{ + Success = $false + Context = $contextObj.Name + ApiUrl = $contextObj.ApiUrl + TestedAt = Get-Date + Message = "API access validation failed: $errorMessage" + Error = $_ + ConnectedAt = $contextObj.ConnectedAt + } + + return $result + } + } + + end { + Write-Debug '[Test-PowerShellGalleryAccess] - End' + } +} diff --git a/src/variables/private/PowerShellGallery.ps1 b/src/variables/private/PowerShellGallery.ps1 new file mode 100644 index 0000000..61a41ac --- /dev/null +++ b/src/variables/private/PowerShellGallery.ps1 @@ -0,0 +1,10 @@ +$script:PowerShellGallery = @{ + ContextVault = 'PSModule.PowerShellGallery' + DefaultConfig = @{ + ID = 'Module' + DefaultContext = $null + GalleryUrl = 'https://www.powershellgallery.com' + ApiUrl = 'https://www.powershellgallery.com/api/v2' + } + Config = $null +} diff --git a/tests/PowerShellGallery.Tests.ps1 b/tests/PowerShellGallery.Tests.ps1 index 0d9b261..5b647ed 100644 --- a/tests/PowerShellGallery.Tests.ps1 +++ b/tests/PowerShellGallery.Tests.ps1 @@ -1,4 +1,14 @@ -Describe 'PowerShellGallery' { +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'Test file - converting test data to SecureString for testing purposes only.' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Test file - using Write-Host to provide test output visibility as requested.' +)] +param() + +Describe 'PowerShellGallery' { Context 'Function: Get-PSGalleryAPI' { It 'Should not throw' { { Get-PSGalleryAPI } | Should -Not -Throw @@ -14,4 +24,244 @@ { Show-PowerShellGalleryItem } | Should -Not -Throw } } + + Context 'Context Integration - Connect-PowerShellGallery' { + BeforeAll { + # Clean up any existing test contexts + $testContextName = 'TestContext_' + (Get-Random) + $script:TestContextName = $testContextName + Write-Host "Testing Connect-PowerShellGallery with context name: $testContextName" -ForegroundColor Cyan + } + + AfterAll { + # Clean up test context + try { + $null = Disconnect-PowerShellGallery -Name $script:TestContextName -Confirm:$false -ErrorAction SilentlyContinue + } catch { + Write-Verbose "Cleanup error ignored: $_" + } + } + + It 'Should accept Name parameter and connect successfully' { + Write-Host " → Connecting to PowerShell Gallery with Name parameter" -ForegroundColor Gray + $secureApiKey = ConvertTo-SecureString -String 'test-api-key-123' -AsPlainText -Force + { Connect-PowerShellGallery -Name $script:TestContextName -ApiKey $secureApiKey -Silent } | Should -Not -Throw + Write-Host " ✓ Connection succeeded" -ForegroundColor Green + } + + It 'Should create a context with correct properties' { + Write-Host " → Retrieving context by ID: $($script:TestContextName)" -ForegroundColor Gray + $context = Get-PowerShellGalleryContext -ID $script:TestContextName + $context | Should -Not -BeNullOrEmpty + $context.Name | Should -Be $script:TestContextName + Write-Host " ✓ Context retrieved with Name: $($context.Name), ID: $($context.ID)" -ForegroundColor Green + } + + It 'Should have PassThru parameter and return context' { + Write-Host " → Testing PassThru parameter" -ForegroundColor Gray + $secureApiKey = ConvertTo-SecureString -String 'test-api-key-456' -AsPlainText -Force + $context = Connect-PowerShellGallery -Name "$($script:TestContextName)_2" -ApiKey $secureApiKey -Silent -PassThru + $context | Should -Not -BeNullOrEmpty + $context.Name | Should -Be "$($script:TestContextName)_2" + Write-Host " ✓ PassThru returned context: $($context.Name)" -ForegroundColor Green + # Cleanup + $null = Disconnect-PowerShellGallery -Name "$($script:TestContextName)_2" -Confirm:$false -ErrorAction SilentlyContinue + } + } + + Context 'Context Integration - Get-PowerShellGalleryContext' { + BeforeAll { + # Setup test context + $testContextName = 'GetTestContext_' + (Get-Random) + $script:GetTestContextName = $testContextName + Write-Host "Testing Get-PowerShellGalleryContext with context name: $testContextName" -ForegroundColor Cyan + $secureApiKey = ConvertTo-SecureString -String 'test-get-api-key' -AsPlainText -Force + Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent + } + + AfterAll { + # Cleanup + try { + $null = Disconnect-PowerShellGallery -Name $script:GetTestContextName -Confirm:$false -ErrorAction SilentlyContinue + } catch { + Write-Verbose "Cleanup error ignored: $_" + } + } + + It 'Should retrieve context by ID' { + Write-Host " → Retrieving context by ID: $($script:GetTestContextName)" -ForegroundColor Gray + $context = Get-PowerShellGalleryContext -ID $script:GetTestContextName + $context | Should -Not -BeNullOrEmpty + $context.Name | Should -Be $script:GetTestContextName + Write-Host " ✓ Retrieved context: $($context.Name)" -ForegroundColor Green + } + + It 'Should list available contexts with -ListAvailable' { + Write-Host " → Listing all available contexts" -ForegroundColor Gray + $contexts = Get-PowerShellGalleryContext -ListAvailable + $contexts | Should -Not -BeNullOrEmpty + $contexts.Name | Should -Contain $script:GetTestContextName + Write-Host " ✓ Found $($contexts.Count) context(s), including: $($script:GetTestContextName)" -ForegroundColor Green + } + + It 'Should return default context when no parameters provided' { + Write-Host " → Getting default context" -ForegroundColor Gray + # This will either return a context or warn about no default + { Get-PowerShellGalleryContext } | Should -Not -Throw + Write-Host " ✓ Default context query succeeded" -ForegroundColor Green + } + } + + Context 'Context Integration - Switch-PowerShellGalleryContext' { + BeforeAll { + # Setup test contexts + $testContextName1 = 'SwitchTestContext1_' + (Get-Random) + $testContextName2 = 'SwitchTestContext2_' + (Get-Random) + $script:SwitchTestContextName1 = $testContextName1 + $script:SwitchTestContextName2 = $testContextName2 + Write-Host "Testing Switch-PowerShellGalleryContext with contexts: $testContextName1, $testContextName2" -ForegroundColor Cyan + + $secureApiKey1 = ConvertTo-SecureString -String 'test-switch-api-key-1' -AsPlainText -Force + $secureApiKey2 = ConvertTo-SecureString -String 'test-switch-api-key-2' -AsPlainText -Force + + Connect-PowerShellGallery -Name $testContextName1 -ApiKey $secureApiKey1 -Silent + Connect-PowerShellGallery -Name $testContextName2 -ApiKey $secureApiKey2 -Silent + } + + AfterAll { + # Cleanup + try { + $null = Disconnect-PowerShellGallery -Name $script:SwitchTestContextName1 -Confirm:$false -ErrorAction SilentlyContinue + $null = Disconnect-PowerShellGallery -Name $script:SwitchTestContextName2 -Confirm:$false -ErrorAction SilentlyContinue + } catch { + Write-Verbose "Cleanup error ignored: $_" + } + } + + It 'Should switch to specified context' { + Write-Host " → Switching to context: $($script:SwitchTestContextName1)" -ForegroundColor Gray + { Switch-PowerShellGalleryContext -ID $script:SwitchTestContextName1 } | Should -Not -Throw + Write-Host " ✓ Switched successfully" -ForegroundColor Green + } + + It 'Should set context as default and retrieve it' { + Write-Host " → Switching to context: $($script:SwitchTestContextName2)" -ForegroundColor Gray + Switch-PowerShellGalleryContext -ID $script:SwitchTestContextName2 + + # Verify by getting the default context + Write-Host " → Verifying default context is set correctly" -ForegroundColor Gray + $defaultContext = Get-PowerShellGalleryContext + $defaultContext | Should -Not -BeNullOrEmpty + $defaultContext.ID | Should -Be $script:SwitchTestContextName2 + Write-Host " ✓ Default context verified: $($defaultContext.ID)" -ForegroundColor Green + } + } + + Context 'Context Integration - Disconnect-PowerShellGallery' { + BeforeEach { + # Setup test context for each test + $testContextName = 'DisconnectTestContext_' + (Get-Random) + $script:DisconnectTestContextName = $testContextName + Write-Host " → Creating test context: $testContextName" -ForegroundColor Gray + $secureApiKey = ConvertTo-SecureString -String 'test-disconnect-api-key' -AsPlainText -Force + Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent + } + + It 'Should disconnect specified context' { + Write-Host " → Disconnecting context: $($script:DisconnectTestContextName)" -ForegroundColor Gray + { Disconnect-PowerShellGallery -Name $script:DisconnectTestContextName -Confirm:$false } | Should -Not -Throw + Write-Host " ✓ Disconnected successfully" -ForegroundColor Green + } + + It 'Should remove context from vault' { + Write-Host " → Disconnecting and verifying removal: $($script:DisconnectTestContextName)" -ForegroundColor Gray + Disconnect-PowerShellGallery -Name $script:DisconnectTestContextName -Confirm:$false + $context = Get-PowerShellGalleryContext -ID $script:DisconnectTestContextName -ErrorAction SilentlyContinue + $context | Should -BeNullOrEmpty + Write-Host " ✓ Context removed from vault" -ForegroundColor Green + } + } + + Context 'Context Integration - Get-PowerShellGalleryAccessToken' { + BeforeAll { + # Setup test context + $testContextName = 'TokenTestContext_' + (Get-Random) + $script:TokenTestContextName = $testContextName + $script:TestApiKey = 'test-token-api-key-xyz' + Write-Host "Testing Get-PowerShellGalleryAccessToken with context: $testContextName" -ForegroundColor Cyan + $secureApiKey = ConvertTo-SecureString -String $script:TestApiKey -AsPlainText -Force + Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent + } + + AfterAll { + # Cleanup + try { + $null = Disconnect-PowerShellGallery -Name $script:TokenTestContextName -Confirm:$false -ErrorAction SilentlyContinue + } catch { + Write-Verbose "Cleanup error ignored: $_" + } + } + + It 'Should retrieve API key as SecureString by default' { + Write-Host " → Retrieving API key as SecureString" -ForegroundColor Gray + $token = Get-PowerShellGalleryAccessToken -Context $script:TokenTestContextName + $token | Should -Not -BeNullOrEmpty + $token | Should -BeOfType [SecureString] + Write-Host " ✓ Retrieved API key as SecureString" -ForegroundColor Green + } + + It 'Should retrieve API key as plain text with -AsPlainText' { + Write-Host " → Retrieving API key as plain text" -ForegroundColor Gray + $token = Get-PowerShellGalleryAccessToken -Context $script:TokenTestContextName -AsPlainText + $token | Should -Not -BeNullOrEmpty + $token | Should -BeOfType [string] + $token | Should -Be $script:TestApiKey + Write-Host " ✓ Retrieved API key as plain text and verified value" -ForegroundColor Green + } + } + + Context 'Context Integration - Test-PowerShellGalleryAccess' { + BeforeAll { + # Setup test context + $testContextName = 'AccessTestContext_' + (Get-Random) + $script:AccessTestContextName = $testContextName + Write-Host "Testing Test-PowerShellGalleryAccess with context: $testContextName" -ForegroundColor Cyan + $secureApiKey = ConvertTo-SecureString -String 'test-access-api-key' -AsPlainText -Force + Connect-PowerShellGallery -Name $testContextName -ApiKey $secureApiKey -Silent + } + + AfterAll { + # Cleanup + try { + $null = Disconnect-PowerShellGallery -Name $script:AccessTestContextName -Confirm:$false -ErrorAction SilentlyContinue + } catch { + Write-Verbose "Cleanup error ignored: $_" + } + } + + It 'Should return a result object with expected properties' { + Write-Host " → Testing API access and retrieving result object" -ForegroundColor Gray + $result = Test-PowerShellGalleryAccess -Context $script:AccessTestContextName -ErrorAction SilentlyContinue + $result | Should -Not -BeNullOrEmpty + $result.PSObject.Properties.Name | Should -Contain 'Success' + $result.PSObject.Properties.Name | Should -Contain 'Context' + $result.PSObject.Properties.Name | Should -Contain 'ApiUrl' + $successMsg = "Result object contains Success=$($result.Success), Context=$($result.Context)" + Write-Host " ✓ $successMsg" -ForegroundColor Green + } + + It 'Should accept Context parameter' { + Write-Host " → Testing with explicit Context parameter" -ForegroundColor Gray + { Test-PowerShellGalleryAccess -Context $script:AccessTestContextName -ErrorAction SilentlyContinue } | Should -Not -Throw + Write-Host " ✓ Context parameter accepted" -ForegroundColor Green + } + } + + Context 'Context Integration - Module Initialization' { + It 'Initialize-PowerShellGalleryConfig should initialize without error' { + Write-Host " → Testing module initialization" -ForegroundColor Gray + { Initialize-PowerShellGalleryConfig } | Should -Not -Throw + Write-Host " ✓ Module initialized successfully" -ForegroundColor Green + } + } }