tests/Invoke-DFSqliteQuery.Tests.ps1

BeforeDiscovery {
    # -Skip: expressions evaluate during discovery, before BeforeAll runs.
    $script:HasSqlite = Test-Path "$Env:SystemRoot\System32\winsqlite3.dll"
}

BeforeAll {
    . "$PSScriptRoot/../Private/Invoke-DFSqliteQuery.ps1"
    $script:Fixture = Join-Path $PSScriptRoot 'fixtures/winget-index-v2.db'
}

Describe 'Invoke-DFSqliteQuery' {
    It 'queries rows from a SQLite database as PSCustomObjects' -Skip:(-not $script:HasSqlite) {
        $rows = @(Invoke-DFSqliteQuery -Database $script:Fixture -Query 'SELECT id, latest_version FROM packages ORDER BY id')
        $rows.Count | Should -Be 2
        $rows[0].id | Should -Be 'BurntSushi.ripgrep.MSVC'
        $rows[0].latest_version | Should -Be '15.1.0'
        $rows[1].id | Should -Be 'sharkdp.fd'
    }

    It 'reads the metadata version table' -Skip:(-not $script:HasSqlite) {
        $rows = @(Invoke-DFSqliteQuery -Database $script:Fixture -Query "SELECT value FROM metadata WHERE name = 'majorVersion'")
        $rows[0].value | Should -Be '2'
    }

    It 'returns $null for a missing database file' {
        Invoke-DFSqliteQuery -Database (Join-Path $TestDrive 'nope.db') -Query 'SELECT 1' |
            Should -BeNullOrEmpty
    }

    It 'returns $null for an invalid query instead of throwing' -Skip:(-not $script:HasSqlite) {
        Invoke-DFSqliteQuery -Database $script:Fixture -Query 'SELECT * FROM no_such_table' |
            Should -BeNullOrEmpty
    }

    It 'binds a single -Parameters value into a placeholder' -Skip:(-not $script:HasSqlite) {
        $rows = @(Invoke-DFSqliteQuery -Database $script:Fixture `
            -Query 'SELECT latest_version FROM packages WHERE id = ?' `
            -Parameters @('BurntSushi.ripgrep.MSVC'))
        $rows[0].latest_version | Should -Be '15.1.0'
    }

    It 'binds multiple -Parameters values in left-to-right order' -Skip:(-not $script:HasSqlite) {
        $rows = @(Invoke-DFSqliteQuery -Database $script:Fixture `
            -Query 'SELECT id FROM packages WHERE id = ? OR id = ? ORDER BY id' `
            -Parameters @('sharkdp.fd', 'BurntSushi.ripgrep.MSVC'))
        $rows.Count | Should -Be 2
        $rows[0].id | Should -Be 'BurntSushi.ripgrep.MSVC'
        $rows[1].id | Should -Be 'sharkdp.fd'
    }

    It 'binds a repeated -Parameters entry to each of its placeholders' -Skip:(-not $script:HasSqlite) {
        # The same value bound at three separate `?`s, matching the shape
        # DFCatalog.Winget.ps1's exact-match clause needs (id = ? OR name = ?
        # OR moniker = ?, all the same search term).
        $rows = @(Invoke-DFSqliteQuery -Database $script:Fixture `
            -Query 'SELECT id FROM packages WHERE id = ? OR id = ? OR id = ?' `
            -Parameters @('BurntSushi.ripgrep.MSVC', 'BurntSushi.ripgrep.MSVC', 'BurntSushi.ripgrep.MSVC'))
        $rows.Count | Should -Be 1
    }

    It 'treats a bound value as literal data, never as SQL' -Skip:(-not $script:HasSqlite) {
        # A classic injection payload: if it were concatenated instead of
        # bound, "id = 'x' OR '1'='1'" would match every row. Bound, it must
        # only ever match a row whose id is literally that whole string.
        $rows = @(Invoke-DFSqliteQuery -Database $script:Fixture `
            -Query 'SELECT id FROM packages WHERE id = ?' `
            -Parameters @("x' OR '1'='1"))
        $rows.Count | Should -Be 0
    }
}