Tests/Private/Wordlist.Tests.ps1

BeforeDiscovery {
    Import-Module (Join-Path $PSScriptRoot '../../Posh-SecretSharing.psd1') -Force
}

Describe 'Private/Wordlist' -Tag Unit {
    InModuleScope 'Posh-SecretSharing' {
        Describe 'Get-SecretSharingWordlist' {
            It 'returns exactly 1024 words' {
                (Get-SecretSharingWordlist).Count | Should -Be 1024
            }

            It 'contains only lowercase-alphabetic words' {
                foreach ($word in Get-SecretSharingWordlist) {
                    $word | Should -Match '^[a-z]+$'
                }
            }

            It 'contains no duplicate words' {
                $wordlist = Get-SecretSharingWordlist
                ($wordlist | Select-Object -Unique).Count | Should -Be $wordlist.Count
            }

            It 'matches the known first and last words of the official SLIP-0039 wordlist' {
                $wordlist = Get-SecretSharingWordlist
                $wordlist[0] | Should -Be 'academic'
                $wordlist[1023] | Should -Be 'zero'
            }

            It 'matches the known SHA-256 hash of the official wordlist.txt file' {
                $path = Join-Path $script:ModuleRoot 'Private/Wordlist/wordlist.txt'
                $bytes = [System.IO.File]::ReadAllBytes($path)
                $sha256 = [System.Security.Cryptography.SHA256]::Create()
                try {
                    $hashBytes = $sha256.ComputeHash($bytes)
                } finally {
                    $sha256.Dispose()
                }
                $hashHex = -join ($hashBytes | ForEach-Object { $_.ToString('x2') })
                $hashHex | Should -Be 'bcc4555340332d169718aed8bf31dd9d5248cb7da6e5d355140ef4f1e601eec3'
            }

            It 'returns the same cached instance on repeated calls' {
                (Get-SecretSharingWordlist) | Should -Be (Get-SecretSharingWordlist)
            }
        }

        Describe 'Get-SecretSharingWordIndexMap' {
            It 'maps every word to its position in the wordlist' {
                $wordlist = Get-SecretSharingWordlist
                $map = Get-SecretSharingWordIndexMap
                $map.Count | Should -Be 1024
                $map['academic'] | Should -Be 0
                $map['zero'] | Should -Be 1023
                $map[$wordlist[512]] | Should -Be 512
            }
        }

        Describe 'ConvertTo-SecretSharingMnemonicWord' {
            It 'converts index 0 and 1023 to the expected words' {
                ConvertTo-SecretSharingMnemonicWord -Index 0 | Should -Be 'academic'
                ConvertTo-SecretSharingMnemonicWord -Index 1023 | Should -Be 'zero'
            }

            It 'throws for an out-of-range index' {
                { ConvertTo-SecretSharingMnemonicWord -Index -1 } | Should -Throw
                { ConvertTo-SecretSharingMnemonicWord -Index 1024 } | Should -Throw
            }
        }

        Describe 'ConvertFrom-SecretSharingMnemonicWord' {
            It 'converts a known word to its expected index' {
                ConvertFrom-SecretSharingMnemonicWord -Word 'academic' | Should -Be 0
                ConvertFrom-SecretSharingMnemonicWord -Word 'zero' | Should -Be 1023
            }

            It 'is case-insensitive' {
                ConvertFrom-SecretSharingMnemonicWord -Word 'ZERO' | Should -Be 1023
                ConvertFrom-SecretSharingMnemonicWord -Word 'AcAdEmIc' | Should -Be 0
            }

            It 'throws for a word that is not in the wordlist' {
                { ConvertFrom-SecretSharingMnemonicWord -Word 'notarealslip39word' } | Should -Throw
            }
        }

        Describe 'ConvertTo-SecretSharingMnemonicWord / ConvertFrom-SecretSharingMnemonicWord round-trip' {
            It 'round-trips every index in the wordlist' {
                for ($i = 0; $i -lt 1024; $i++) {
                    $word = ConvertTo-SecretSharingMnemonicWord -Index $i
                    ConvertFrom-SecretSharingMnemonicWord -Word $word | Should -Be $i
                }
            }
        }
    }
}