7 Commits

Author SHA1 Message Date
Bill ZHANG
887f79d2d7 refactor: remove new tests 2025-07-21 13:33:16 +10:00
Bill ZHANG
5bae77eebb refactor: Enhance Select-ScoopDBItem and Get-ScoopDBItem functions for improved data handling and output 2025-07-21 12:48:41 +10:00
Bill ZHANG
210b5e45a0 fix: Improve user guidance and error handling in generate_user_manifest function 2025-07-21 12:26:31 +10:00
Bill ZHANG
3aa64baeb1 refactor: Remove deprecated install_app tests and enhance Get-HistoricalManifestFromDB for exact version matching 2025-07-21 10:56:38 +10:00
Bill ZHANG
0470ba2d04 refactor: Enhance historical manifest retrieval with improved user guidance and fallback mechanisms 2025-07-21 10:47:07 +10:00
Bill ZHANG
398aaa2330 fix: Update installation instructions to clarify search order for app versions 2025-07-21 10:44:12 +10:00
Bill ZHANG
4585f50a54 Remove best match logic from Get-HistoricalManifestFromDB
- Keep only exact match logic (lines 177-189)
- Return null immediately when no exact match found (line 193)
- Remove trailing whitespace from manifest.ps1
- Function now only supports exact version matches, no compatibility matching
2025-07-21 10:29:54 +10:00
5 changed files with 138 additions and 386 deletions

View File

@@ -264,9 +264,11 @@ function Select-ScoopDBItem {
[void]$dbAdapter.Fill($result)
}
end {
$resultCopy = $result.Copy()
$dbAdapter.Dispose()
$db.Dispose()
return $result
# Use Write-Output to ensure PowerShell properly returns the DataTable
Write-Output $resultCopy -NoEnumerate
}
}
@@ -293,13 +295,13 @@ function Select-ScoopDBItem {
function Get-ScoopDBItem {
[CmdletBinding()]
param (
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
[Parameter(Mandatory, Position = 0, ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string]
$Name,
[Parameter(Mandatory, Position = 1)]
[Parameter(Mandatory, Position = 1, ValueFromPipelineByPropertyName)]
[string]
$Bucket,
[Parameter(Position = 2)]
[Parameter(Position = 2, ValueFromPipelineByPropertyName)]
[string]
$Version
)
@@ -307,28 +309,46 @@ function Get-ScoopDBItem {
begin {
$db = Open-ScoopDB
$dbAdapter = New-Object -TypeName System.Data.SQLite.SQLiteDataAdapter
$dbCommand = $db.CreateCommand()
$dbCommand.CommandType = [System.Data.CommandType]::Text
$dbAdapter.SelectCommand = $dbCommand
}
process {
$result = New-Object System.Data.DataTable
# Build query based on whether version is specified
$dbQuery = 'SELECT * FROM app WHERE name = @Name AND bucket = @Bucket'
if ($Version) {
$dbQuery += ' AND version = @Version'
} else {
$dbQuery += ' ORDER BY version DESC LIMIT 1'
}
$dbCommand = $db.CreateCommand()
$dbCommand.CommandText = $dbQuery
$dbCommand.CommandType = [System.Data.CommandType]::Text
$dbAdapter.SelectCommand = $dbCommand
}
process {
$dbCommand.Parameters.Clear()
# Add parameters for this specific query
$dbCommand.Parameters.AddWithValue('@Name', $Name) | Out-Null
$dbCommand.Parameters.AddWithValue('@Bucket', $Bucket) | Out-Null
$dbCommand.Parameters.AddWithValue('@Version', $Version) | Out-Null
if ($Version) {
$dbCommand.Parameters.AddWithValue('@Version', $Version) | Out-Null
}
[void]$dbAdapter.Fill($result)
# Create a copy of the DataTable to avoid disposal issues
$resultCopy = $result.Copy()
# Use Write-Output to ensure PowerShell properly returns the DataTable
Write-Output $resultCopy -NoEnumerate
}
end {
# Clean up all resources
$dbAdapter.Dispose()
$dbCommand.Dispose()
$db.Dispose()
return $result
}
}

View File

@@ -180,35 +180,20 @@ function Get-HistoricalManifestFromDB($app, $bucket, $requestedVersion) {
ensure (usermanifestsdir) | Out-Null
$tempManifestPath = "$(usermanifestsdir)\$app.json"
$row.manifest | Out-UTF8File -FilePath $tempManifestPath
return @{ path = $tempManifestPath; version = $requestedVersion; source = "sqlite_exact_match" }
# Parse the manifest to get its actual version
$manifest = $row.manifest | ConvertFrom-Json
$manifestVersion = if ($manifest.version) { $manifest.version } else { $requestedVersion }
return @{ path = $tempManifestPath; version = $manifestVersion; source = "sqlite_exact_match" }
}
# If no exact match, try to find best match from all versions of this app
$allVersionsResult = Get-ScoopDBItem -Name $app -Bucket $bucket
if ($allVersionsResult.Rows.Count -gt 0) {
$availableVersions = $allVersionsResult.Rows | ForEach-Object { $_.version }
$bestMatch = Find-BestVersionMatch -RequestedVersion $requestedVersion -AvailableVersions $availableVersions
if ($bestMatch) {
$matchedRow = $allVersionsResult.Rows | Where-Object { $_.version -eq $bestMatch } | Select-Object -First 1
ensure (usermanifestsdir) | Out-Null
$tempManifestPath = "$(usermanifestsdir)\$app.json"
$matchedRow.manifest | Out-UTF8File -FilePath $tempManifestPath
return @{ path = $tempManifestPath; version = $bestMatch; source = "sqlite_best_match" }
}
}
# No exact match found, return null (no compatibility matching)
return $null
}
function Get-HistoricalManifest($app, $bucket, $requestedVersion) {
# First try to get historical manifest from SQLite
$manifestFromDB = Get-HistoricalManifestFromDB $app $bucket $requestedVersion
if ($manifestFromDB) {
return $manifestFromDB
}
# Fall back to git history if not found in SQLite
function Get-HistoricalManifestFromGitHistory($app, $bucket, $requestedVersion) {
# Only proceed if git history is enabled
if (!(get_config USE_GIT_HISTORY $true)) {
return $null
}
@@ -351,38 +336,30 @@ function Get-HistoricalManifest($app, $bucket, $requestedVersion) {
return $null
}
info "Found $($foundVersions.Count) distinct historical versions for '$app'. Finding best match for '$requestedVersion'..."
# No exact match found - display all available versions to help user choose
$allAvailableVersions = ($foundVersions | Sort-Object {
try { [version]($_.version -replace '[^\d\.].*$', '') } catch { $_.version }
} -Descending).version
$matchedVersionData = $null
$bestMatchVersionString = Find-BestVersionMatch -RequestedVersion $requestedVersion -AvailableVersions ($foundVersions.version)
Write-Host ""
Write-Host "No exact match found for version '$requestedVersion' for app '$app'."
Write-Host "Available versions in git history (newest to oldest):"
Write-Host ""
if ($bestMatchVersionString) {
$matchedVersionData = $foundVersions | Where-Object { $_.version -eq $bestMatchVersionString } | Select-Object -First 1
# Group versions for better display
$displayCount = [Math]::Min(50, $allAvailableVersions.Count) # Show up to 50 versions
for ($i = 0; $i -lt $displayCount; $i++) {
Write-Host " $($allAvailableVersions[$i])"
}
if ($matchedVersionData) {
info "Best match for '$requestedVersion' is '$($matchedVersionData.version)' (commit $($matchedVersionData.hash))."
ensure (usermanifestsdir) | Out-Null
$tempManifestPath = "$(usermanifestsdir)\$app.json"
if ($PSVersionTable.PSVersion.Major -ge 6) {
$utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($false)
Set-Content -Path $tempManifestPath -Value $matchedVersionData.manifest -Encoding $utf8NoBomEncoding -NoNewline:$false
} else {
# PowerShell 5 compatibility
$matchedVersionData.manifest | Out-UTF8File -FilePath $tempManifestPath
}
return @{
path = $tempManifestPath
version = $matchedVersionData.version
source = "git_best_match:$($matchedVersionData.hash)"
}
} else {
$availableVersionsForLog = ($foundVersions | Sort-Object {
try { [version]($_.version -replace '[^\d\.].*$', '') } catch { $_.version }
} -Descending | Select-Object -First 10).version
info "Could not find a suitable match for '$requestedVersion' for app '$app'. Available (latest 10): $($availableVersionsForLog -join ', ')"
if ($allAvailableVersions.Count -gt $displayCount) {
Write-Host " ... and $($allAvailableVersions.Count - $displayCount) more versions"
}
Write-Host ""
Write-Host "To install a specific version, use: scoop install $app@<version>"
Write-Host ""
return $null
} catch {
@@ -391,71 +368,6 @@ function Get-HistoricalManifest($app, $bucket, $requestedVersion) {
}
}
function Find-BestVersionMatch($requestedVersion, $availableVersions) {
<#
.SYNOPSIS
Find the best matching version from available versions
.PARAMETER requestedVersion
The version requested by user (e.g., "3.7", "3.7.4")
.PARAMETER availableVersions
Array of available version strings
.RETURNS
Best matching version string, or $null if no match
#>
if (!$availableVersions -or $availableVersions.Count -eq 0) {
return $null
}
debug "Searching for version '$requestedVersion' among available versions: $($availableVersions -join ', ')"
# First try exact match
if ($availableVersions -contains $requestedVersion) {
debug "Found exact match for version '$requestedVersion'"
return $requestedVersion
}
# If no exact match, try to find compatible versions
# Split requested version into parts
$requestedParts = $requestedVersion -split '\.'
# Filter versions that start with the requested version pattern
$compatibleVersions = $availableVersions | Where-Object {
$versionParts = $_ -split '\.'
$isCompatible = $true
for ($i = 0; $i -lt $requestedParts.Count; $i++) {
if ($i -ge $versionParts.Count -or $versionParts[$i] -ne $requestedParts[$i]) {
$isCompatible = $false
break
}
}
return $isCompatible
}
debug "Found $($compatibleVersions.Count) compatible versions: $($compatibleVersions -join ', ')"
if ($compatibleVersions.Count -eq 0) {
debug "No compatible versions found for '$requestedVersion'"
return $null
}
# Sort compatible versions and return the highest one
$sortedVersions = $compatibleVersions | Sort-Object {
# Convert to version object for proper sorting
try {
[version]($_ -replace '[^\d\.].*$', '') # Remove non-numeric suffixes for sorting
} catch {
$_ # Fallback to string sorting
}
} -Descending
$selectedVersion = $sortedVersions[0]
debug "Selected best match: '$selectedVersion' for requested version '$requestedVersion'"
return $selectedVersion
}
function generate_user_manifest($app, $bucket, $version) {
# 'autoupdate.ps1' 'buckets.ps1' 'manifest.ps1'
@@ -466,22 +378,48 @@ function generate_user_manifest($app, $bucket, $version) {
warn "Given version ($version) does not match manifest ($($manifest.version))"
# Try to find the version using SQLite cache first, then git history
$historicalResult = $null
# Try SQLite cache first if enabled
if (get_config USE_SQLITE_CACHE) {
info "Searching for version '$version' in cache..."
} else {
info "Searching for version '$version' in git history..."
$historicalResult = Get-HistoricalManifestFromDB $app $bucket $version
if ($historicalResult) {
info "Found version '$($historicalResult.version)' for '$app' in cache."
return $historicalResult.path
}
}
$historicalResult = Get-HistoricalManifest $app $bucket $version
if ($historicalResult) {
if ($historicalResult.source -match '^sqlite') {
info "Found version '$($historicalResult.version)' for '$app' in cache."
} else {
info "Found version '$($historicalResult.version)' for '$app' in git history (source: $($historicalResult.source))."
# Try git history if cache didn't find it
if (!$historicalResult) {
info "Searching for version '$version' in git history..."
$historicalResult = Get-HistoricalManifestFromGitHistory $app $bucket $version
if ($historicalResult) {
return $historicalResult.path
}
}
# If no historical version found, provide helpful guidance
if (!$historicalResult) {
# Try to provide additional context about what versions are available
$currentVersion = $manifest.version
if ($currentVersion) {
info "Current version available: $currentVersion"
info "To install the current version, use: scoop install $app"
}
# Check if we have autoupdate capability for fallback
if ($manifest.autoupdate) {
info "This app supports autoupdate - attempting to generate manifest for version $version"
} else {
warn "'$app' does not have autoupdate capability."
Write-Host "Available options:"
Write-Host " 1. Install current version: scoop install $app"
Write-Host " 2. Check if the requested version exists in other buckets"
Write-Host " 3. Contact the bucket maintainer to add historical version support"
Write-Host ""
abort "Could not find manifest for '$app@$version' and no autoupdate available"
}
return $historicalResult.path
}
# Fallback to autoupdate generation
@@ -490,6 +428,7 @@ function generate_user_manifest($app, $bucket, $version) {
ensure (usermanifestsdir) | Out-Null
$manifest_path = "$(usermanifestsdir)\$app.json"
# Check SQLite cache for exact cached manifest (this is different from historical search)
if (get_config USE_SQLITE_CACHE) {
$cached_manifest = (Get-ScoopDBItem -Name $app -Bucket $bucket -Version $version).manifest
if ($cached_manifest) {
@@ -507,16 +446,33 @@ function generate_user_manifest($app, $bucket, $version) {
return $manifest_path
} catch {
Write-Host -ForegroundColor DarkRed "Could not install $app@$version"
Write-Host -ForegroundColor Yellow "Autoupdate failed for version $version"
# Provide helpful guidance when autoupdate fails
Write-Host "Possible reasons:"
Write-Host " - Version $version may not exist or be available for download"
Write-Host " - Download URLs may have changed or be inaccessible"
Write-Host " - The version format may be incompatible with autoupdate patterns"
Write-Host ""
Write-Host "Suggestions:"
Write-Host " 1. Install current version: scoop install $app"
Write-Host " 2. Try a different version that was shown in the available list"
Write-Host " 3. Check the app's official releases or download page"
Write-Host ""
# If autoupdate fails and we haven't tried git history yet, try it as final fallback
if (!$historicalResult) {
if (!$historicalResult -and !(get_config USE_SQLITE_CACHE)) {
warn 'Autoupdate failed. Trying git history as final fallback...'
$historicalResult = Get-HistoricalManifest $app $bucket $version
if ($historicalResult) {
warn "Using historical manifest as fallback for '$app' version '$($historicalResult.version)'"
return $historicalResult.path
$fallbackResult = Get-HistoricalManifestFromGitHistory $app $bucket $version
if ($fallbackResult) {
warn "Using historical manifest as fallback for '$app' version '$($fallbackResult.version)'"
return $fallbackResult.path
}
}
# Final failure - provide comprehensive guidance
Write-Host -ForegroundColor Red "All attempts to find or generate manifest for '$app@$version' failed."
abort "Installation of '$app@$version' is not possible"
}
return $null

View File

@@ -4,9 +4,8 @@
# scoop install git
#
# To install a different version of the app
# (will search git history first, then auto-generate manifest as fallback):
# (will search sqlite cache if enabled, then git history (if use_git_history is $true), then auto-generate manifest as fallback):
# scoop install gh@2.7.0
# scoop install python@3.12 # Install latest 3.12.x version if available
#
# To install an app from a manifest at a URL:
# scoop install https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/runat.json

View File

@@ -4,55 +4,14 @@ BeforeAll {
. "$PSScriptRoot\..\lib\system.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\install.ps1"
. "$PSScriptRoot\..\lib\download.ps1"
. "$PSScriptRoot\..\lib\decompress.ps1"
. "$PSScriptRoot\..\lib\json.ps1"
. "$PSScriptRoot\..\lib\shortcuts.ps1"
. "$PSScriptRoot\..\lib\database.ps1"
. "$PSScriptRoot\..\lib\autoupdate.ps1"
. "$PSScriptRoot\..\lib\psmodules.ps1"
}
Describe 'install_app' -Tag 'Scoop' {
BeforeAll {
# Mock all required functions and external dependencies
Mock Get-Manifest { return @('testapp', @{ version = '1.0.0' }, 'testbucket', $null) }
Mock Get-SupportedArchitecture { return '64bit' }
Mock ensure { return "C:\test\dir" }
Mock versiondir { return "C:\test\dir" }
Mock persistdir { return "C:\test\persist" }
Mock usermanifestsdir { return "C:\test\manifests" }
Mock ensure_install_dir_not_in_path {}
Mock link_current { return "C:\test\dir" }
Mock create_shims {}
Mock create_startmenu_shortcuts {}
Mock Invoke-AutoUpdate { return "C:\temp\manifests\testapp.json" }
Mock Get-ScoopDBItem { return [PSCustomObject]@{ Rows = @() } }
Mock install_psmodule {}
Mock env_add_path {}
Mock env_set {}
Mock persist_data {}
Mock persist_permission {}
Mock save_installed_manifest {}
Mock save_install_info {}
Mock show_notes {}
Mock Write-Output {}
Mock success {}
}
It 'should handle try-catch block successfully' {
Mock Invoke-ScoopDownload { "file.txt" }
Mock Invoke-Extraction {}
Mock Invoke-HookScript {}
Mock Invoke-Installer {}
{ install_app 'testapp' '64bit' $false $false } | Should -Not -Throw
Should -Invoke -CommandName Invoke-ScoopDownload -Times 1
Should -Invoke -CommandName Invoke-Extraction -Times 1
}
}
Describe 'appname_from_url' -Tag 'Scoop' {
It 'should extract the correct name' {
appname_from_url 'https://example.org/directory/foobar.json' | Should -Be 'foobar'
}
}
Describe 'is_in_dir' -Tag 'Scoop', 'Windows' {
It 'should work correctly' {
is_in_dir 'C:\test' 'C:\foo' | Should -BeFalse
@@ -60,6 +19,7 @@ Describe 'is_in_dir' -Tag 'Scoop', 'Windows' {
is_in_dir "$PSScriptRoot\..\" "$PSScriptRoot" | Should -BeFalse
}
}
Describe 'env add and remove path' -Tag 'Scoop', 'Windows' {
BeforeAll {
# test data
@@ -69,15 +29,18 @@ Describe 'env add and remove path' -Tag 'Scoop', 'Windows' {
$testdir = Join-Path $PSScriptRoot 'path-test-directory'
$global = $false
}
It 'should concat the correct path' {
Mock Add-Path {}
Mock Remove-Path {}
# adding
env_add_path $manifest $testdir $global
Should -Invoke -CommandName Add-Path -Times 1 -ParameterFilter { $Path -like "$testdir\foo" }
Should -Invoke -CommandName Add-Path -Times 1 -ParameterFilter { $Path -like "$testdir\bar" }
Should -Invoke -CommandName Add-Path -Times 1 -ParameterFilter { $Path -like $testdir }
Should -Invoke -CommandName Add-Path -Times 0 -ParameterFilter { $Path -like $PSScriptRoot }
env_rm_path $manifest $testdir $global
Should -Invoke -CommandName Remove-Path -Times 1 -ParameterFilter { $Path -like "$testdir\foo" }
Should -Invoke -CommandName Remove-Path -Times 1 -ParameterFilter { $Path -like "$testdir\bar" }
@@ -85,6 +48,7 @@ Describe 'env add and remove path' -Tag 'Scoop', 'Windows' {
Should -Invoke -CommandName Remove-Path -Times 0 -ParameterFilter { $Path -like $PSScriptRoot }
}
}
Describe 'shim_def' -Tag 'Scoop' {
It 'should use strings correctly' {
$target, $name, $shimArgs = shim_def 'command.exe'
@@ -92,182 +56,47 @@ Describe 'shim_def' -Tag 'Scoop' {
$name | Should -Be 'command'
$shimArgs | Should -BeNullOrEmpty
}
It 'should expand the array correctly' {
$target, $name, $shimArgs = shim_def @('foo.exe', 'bar')
$target | Should -Be 'foo.exe'
$name | Should -Be 'bar'
$shimArgs | Should -BeNullOrEmpty
$target, $name, $shimArgs = shim_def @('foo.exe', 'bar', '--test')
$target | Should -Be 'foo.exe'
$name | Should -Be 'bar'
$shimArgs | Should -Be '--test'
}
}
Describe 'persist_def' -Tag 'Scoop' {
It 'parses string correctly' {
$source, $target = persist_def 'test'
$source | Should -Be 'test'
$target | Should -Be 'test'
}
It 'should handle sub-folder' {
$source, $target = persist_def 'foo/bar'
$source | Should -Be 'foo/bar'
$target | Should -Be 'foo/bar'
}
It 'should handle arrays' {
# both specified
$source, $target = persist_def @('foo', 'bar')
$source | Should -Be 'foo'
$target | Should -Be 'bar'
# only first specified
$source, $target = persist_def @('foo')
$source | Should -Be 'foo'
$target | Should -Be 'foo'
# null value specified
$source, $target = persist_def @('foo', $null)
$source | Should -Be 'foo'
$target | Should -Be 'foo'
}
}
Describe 'Get-RelativePathCompat' -Tag 'Scoop' {
It 'should return relative path using PowerShell Core method when available' {
if ($PSVersionTable.PSVersion.Major -ge 6) {
$result = Get-RelativePathCompat "C:\test\from" "C:\test\from\to\file.txt"
$result | Should -Match "to[\\\/]file\.txt"
}
}
It 'should fallback to URI method when PowerShell Core method fails or on Windows PowerShell' {
$result = Get-RelativePathCompat "C:\test\from\" "C:\test\from\to\file.txt"
$result | Should -Not -BeNullOrEmpty
}
It 'should handle different schemes gracefully' {
# This test may behave differently on different systems
# The function should return something meaningful when schemes differ
$result = Get-RelativePathCompat "file:///C:/test/from/" "http://example.com/file.txt"
$result | Should -Not -BeNullOrEmpty
}
}
Describe 'Get-HistoricalManifestFromDB' -Tag 'Scoop' {
BeforeAll {
Mock get_config {
param($key)
if ($key -eq 'USE_SQLITE_CACHE') { return $true }
return $false
}
Mock Get-Command { return $true }
Mock Get-ScoopDBItem { return [PSCustomObject]@{ Rows = @() } }
Mock ensure { return "C:\temp\manifests" }
Mock usermanifestsdir { return "C:\temp\manifests" }
Mock Out-UTF8File {}
}
It 'should return null when SQLite cache is disabled' {
Mock get_config { return $false }
$result = Get-HistoricalManifestFromDB 'testapp' 'main' '1.0.0'
$result | Should -BeNullOrEmpty
}
It 'should return null when Get-ScoopDBItem command not available' {
Mock Get-Command { return $null }
$result = Get-HistoricalManifestFromDB 'testapp' 'main' '1.0.0'
$result | Should -BeNullOrEmpty
}
It 'should return manifest for exact version match' {
Mock Get-ScoopDBItem {
$mockRow = [PSCustomObject]@{ manifest = '{"version": "1.0.0", "description": "Test app"}'; version = '1.0.0' }
return [PSCustomObject]@{ Rows = @($mockRow) }
}
Mock ConvertFrom-Json { return [PSCustomObject]@{ version = '1.0.0'; description = 'Test app' } }
$result = Get-HistoricalManifestFromDB 'testapp' 'main' '1.0.0'
$result.version | Should -Be '1.0.0'
$result.source | Should -Be 'sqlite_exact_match'
$result.path | Should -Match 'testapp\.json'
}
It 'should return null when exact version not available' {
Mock Get-ScoopDBItem {
param($Name, $Bucket, $Version)
# Always return empty results since we only support exact matches now
return [PSCustomObject]@{ Rows = @() }
}
$result = Get-HistoricalManifestFromDB 'testapp' 'main' '1.0'
$result | Should -BeNullOrEmpty
}
}
Describe 'Get-HistoricalManifest' -Tag 'Scoop' {
BeforeAll {
Mock Get-HistoricalManifestFromDB { return $null }
Mock get_config {
param($key, $default)
if ($key -eq 'USE_GIT_HISTORY') { return $default }
return $false
}
}
It 'should return database result when available' {
Mock Get-HistoricalManifestFromDB {
return @{
path = "C:\temp\testapp.json"
version = "1.0.0"
source = "sqlite_exact_match"
}
}
$result = Get-HistoricalManifest 'testapp' 'main' '1.0.0'
$result.source | Should -Be 'sqlite_exact_match'
$result.version | Should -Be '1.0.0'
}
It 'should return null when no bucket provided' {
$result = Get-HistoricalManifest 'testapp' $null '1.0.0'
$result | Should -BeNullOrEmpty
}
It 'should return null when git history disabled' {
Mock get_config { return $false }
$result = Get-HistoricalManifest 'testapp' 'main' '1.0.0'
$result | Should -BeNullOrEmpty
}
}
Describe 'generate_user_manifest Enhanced Functionality' -Tag 'Scoop' {
BeforeAll {
Mock Get-Manifest { return @('testapp', @{ version = '2.0.0' }, 'main', $null) }
Mock manifest_path { return "C:\test\manifest.json" }
Mock warn {}
Mock info {}
Mock abort { throw "aborted" }
Mock Get-HistoricalManifest { return $null }
Mock ensure { return "C:\temp" }
Mock usermanifestsdir { return "C:\temp\manifests" }
Mock Invoke-AutoUpdate { return "C:\temp\manifests\testapp.json" }
}
It 'should return existing manifest path when versions match' {
Mock Get-Manifest { return @('testapp', @{ version = '1.5.0' }, 'main', $null) }
$result = generate_user_manifest 'testapp' 'main' '1.5.0'
$result | Should -Be "C:\test\manifest.json"
}
It 'should attempt historical manifest search when versions do not match' {
Mock Get-HistoricalManifest {
return @{
path = "C:\temp\manifests\testapp.json"
version = "1.0.0"
source = "git_exact_match:abc123"
}
}
Mock get_config {
param($key)
if ($key -eq 'USE_SQLITE_CACHE') { return $true }
return $false
}
Mock warn {}
$result = generate_user_manifest 'testapp' 'main' '1.0.0'
$result | Should -Be "C:\temp\manifests\testapp.json"
Should -Invoke -CommandName info -Times 1 -ParameterFilter {
$args[0] -like "*Searching for version*in cache*"
}
}
It 'should fallback to autoupdate when historical manifest not found' {
Mock Get-HistoricalManifest { return $null }
Mock Invoke-AutoUpdate { return "C:\temp\manifests\testapp.json" }
Mock Get-Manifest { return @('testapp', @{ version = '2.0.0'; autoupdate = @{} }, 'main', $null) }
Mock get_config { return $false } # USE_SQLITE_CACHE disabled
Mock Get-ScoopDBItem { return [PSCustomObject]@{ manifest = $null; Rows = @() } }
$result = generate_user_manifest 'testapp' 'main' '1.0.0'
Should -Invoke -CommandName warn -Times 1 -ParameterFilter {
$args[0] -like "*No historical version found*"
}
}
}

View File

@@ -1,72 +1,17 @@
BeforeAll {
. "$PSScriptRoot\..\lib\core.ps1"
. "$PSScriptRoot\..\lib\json.ps1"
. "$PSScriptRoot\..\lib\manifest.ps1"
. "$PSScriptRoot\..\lib\database.ps1"
}
Describe 'JSON parse and beautify' -Tag 'Scoop' {
Context 'Parse JSON' {
It 'success with valid json' {
{ parse_json "$PSScriptRoot\fixtures\manifest\wget.json" } | Should -Not -Throw
}
It 'fails with invalid json' {
Mock warn {}
Mock ConvertFrom-Json { throw "Invalid JSON" }
$result = parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json"
$result | Should -BeNullOrEmpty
Should -Invoke -CommandName warn -Times 1 -ParameterFilter {
$args[0] -like "*Error parsing JSON*"
It 'fails with invalid json' {
{ parse_json "$PSScriptRoot\fixtures\manifest\broken_wget.json" } | Should -Throw
}
}
}
}
Describe 'Get-RelativePathCompat' -Tag 'Scoop' {
It 'should return relative path for valid URIs' {
Get-RelativePathCompat "C:\\path\from" "C:\\path\from\to" | Should -Be "to"
}
It 'should return absolute path for different schemes' {
$result = Get-RelativePathCompat "C:\\path\from" "D:\\path\to"
$result | Should -Be "D:\path\to"
}
}
Describe 'Get-HistoricalManifestFromDB' -Tag 'Scoop' {
BeforeAll {
Mock ensure { return "C:\\test\\dir" }
Mock usermanifestsdir { return "C:\\test\\manifests" }
Mock Out-UTF8File {}
Mock get_config { return $true }
}
It 'should return manifest from database if available' {
Mock Get-ScoopDBItem { $mockDbResult = [PSCustomObject] @{ Rows = @( [PSCustomObject] @{ manifest = '{"version": "1.2.3"}' }) } ; return $mockDbResult }
$result = Get-HistoricalManifestFromDB 'testapp' 'testbucket' '1.2.3'
$result.path | Should -Match 'testapp.json'
$result.source | Should -Be "sqlite_exact_match"
}
It 'should return null if database disabled' {
Mock get_config { return $false }
$result = Get-HistoricalManifestFromDB 'testapp' 'testbucket' '1.2.3'
$result | Should -BeNullOrEmpty
}
}
Describe 'Get-HistoricalManifest' -Tag 'Scoop' {
BeforeAll {
Mock ensure { return "C:\\test\\dir" }
Mock usermanifestsdir { return "C:\\test\\manifests" }
Mock Out-UTF8File {}
Mock get_config { return $true }
}
It 'should return manifest using database if available' {
Mock Get-ScoopDBItem { $mockDbResult = [PSCustomObject] @{ Rows = @( [PSCustomObject] @{ manifest = '{"version": "2.3.4"}' }) } ; return $mockDbResult }
$result = Get-HistoricalManifest 'testapp' 'testbucket' '2.3.4'
$result.path | Should -Match 'testapp.json'
$result.source | Should -Be "sqlite_exact_match"
}
It 'should return null if no bucket provided' {
$result = Get-HistoricalManifest 'testapp' $null '2.3.4'
$result | Should -BeNullOrEmpty
}
}
Describe 'JSON parse and beautify' -Tag 'Scoop' {
Context 'Beautify JSON' {
BeforeDiscovery {
$manifests = (Get-ChildItem "$PSScriptRoot\fixtures\format\formatted" -File -Filter '*.json').Name
@@ -81,6 +26,7 @@ Describe 'JSON parse and beautify' -Tag 'Scoop' {
}
}
}
Describe 'Handle ARM64 and correctly fallback' -Tag 'Scoop' {
It 'Should return "arm64" if supported' {
$manifest1 = @{ url = 'test'; architecture = @{ 'arm64' = @{ pre_install = 'test' } } }
@@ -107,12 +53,14 @@ Describe 'Handle ARM64 and correctly fallback' -Tag 'Scoop' {
Get-SupportedArchitecture $manifest3 'arm64' | Should -BeNullOrEmpty
}
}
Describe 'Manifest Validator' -Tag 'Validator' {
# Could not use backslash '\' in Linux/macOS for .NET object 'Scoop.Validator'
BeforeAll {
Add-Type -Path "$PSScriptRoot\..\supporting\validator\bin\Scoop.Validator.dll"
$schema = "$PSScriptRoot/../schema.json"
}
It 'Scoop.Validator is available' {
([System.Management.Automation.PSTypeName]'Scoop.Validator').Type | Should -Be 'Scoop.Validator'
}