functions/_getExtensionContentHash.Tests.ps1
|
# <copyright file="_getExtensionContentHash.Tests.ps1" company="Endjin Limited"> # Copyright (c) Endjin Limited. All rights reserved. # </copyright> BeforeAll { # sut . $PSCommandPath.Replace('.Tests.ps1','.ps1') Set-StrictMode -Version Latest # Creates a folder containing files with exact content, from a hashtable of relative path => content function newFolder([hashtable] $Files) { $folder = Join-Path $TestDrive "$(New-Guid)" foreach ($relativePath in $Files.Keys) { $path = Join-Path $folder $relativePath New-Item -ItemType Directory -Path (Split-Path -Parent $path) -Force | Out-Null $content = $Files[$relativePath] if ($content -is [byte[]]) { [IO.File]::WriteAllBytes($path, $content) } else { [IO.File]::WriteAllText($path, $content) } } New-Item -ItemType Directory -Path $folder -Force | Out-Null return $folder } # Creates a link to a folder: a junction on Windows (which needs no extra rights), otherwise a symbolic link. The links # are recorded, so that they can be removed before the test drive is, as its clean-up follows a link that loops. $script:folderLinks = [System.Collections.Generic.List[string]]::new() function newFolderLink([string] $Path, [string] $Target) { New-Item -ItemType ($IsWindows ? 'Junction' : 'SymbolicLink') -Path $Path -Target $Target | Out-Null $script:folderLinks.Add($Path) } function getSha256([byte[]] $Bytes) { [Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData($Bytes)).ToLowerInvariant() } } Describe '_getExtensionContentHash' { It 'Hashes a manifest of each file''s relative path and content hash' { $folder = newFolder @{ 'a.txt' = "x`n"; 'sub/b.txt' = "y`n" } $manifest = "a.txt`0$(getSha256 ([Text.Encoding]::UTF8.GetBytes("x`n")))`n" + "sub/b.txt`0$(getSha256 ([Text.Encoding]::UTF8.GetBytes("y`n")))`n" $result = _getExtensionContentHash -Path $folder $result | Should -Be "sha256:$(getSha256 ([Text.Encoding]::UTF8.GetBytes($manifest)))" } It 'Returns the same hash for the same content in different folders' { $folder1 = newFolder @{ 'a.txt' = "x`n"; 'tasks/b.ps1' = "y`n" } $folder2 = newFolder @{ 'tasks/b.ps1' = "y`n"; 'a.txt' = "x`n" } _getExtensionContentHash -Path $folder1 | Should -Be (_getExtensionContentHash -Path $folder2) } It 'Returns a different hash when <Description>' -ForEach @( @{ Description = 'a file''s content differs'; Files = @{ 'a.txt' = "changed`n"; 'tasks/b.ps1' = "y`n" } } @{ Description = 'a file has been renamed'; Files = @{ 'renamed.txt' = "x`n"; 'tasks/b.ps1' = "y`n" } } @{ Description = 'a file has been moved'; Files = @{ 'a.txt' = "x`n"; 'functions/b.ps1' = "y`n" } } @{ Description = 'a file has been added'; Files = @{ 'a.txt' = "x`n"; 'tasks/b.ps1' = "y`n"; 'c.txt' = '' } } ) { $original = newFolder @{ 'a.txt' = "x`n"; 'tasks/b.ps1' = "y`n" } $changed = newFolder $Files _getExtensionContentHash -Path $changed | Should -Not -Be (_getExtensionContentHash -Path $original) } Context 'A linked folder' { BeforeEach { $linkedFolder = newFolder @{ 'b.ps1' = "y`n" } $folder = newFolder @{ 'a.txt' = "x`n" } newFolderLink -Path (Join-Path $folder 'tasks') -Target $linkedFolder } AfterEach { # Removing a link does not remove the folder that it links to foreach ($link in $script:folderLinks) { Remove-Item -LiteralPath $link -Force } $script:folderLinks.Clear() } It 'Includes the files in the linked folder, as if they were in the extension' { $expected = newFolder @{ 'a.txt' = "x`n"; 'tasks/b.ps1' = "y`n" } _getExtensionContentHash -Path $folder | Should -Be (_getExtensionContentHash -Path $expected) } It 'Returns a different hash when a file in the linked folder changes' { $original = _getExtensionContentHash -Path $folder [IO.File]::WriteAllText((Join-Path $linkedFolder 'b.ps1'), "changed`n") _getExtensionContentHash -Path $folder | Should -Not -Be $original } It 'Includes the files in a folder that links to itself only once' { newFolderLink -Path (Join-Path $linkedFolder 'loop') -Target $linkedFolder $expected = newFolder @{ 'a.txt' = "x`n"; 'tasks/b.ps1' = "y`n" } _getExtensionContentHash -Path $folder | Should -Be (_getExtensionContentHash -Path $expected) } } It 'Ignores the line endings of text files' { $lf = newFolder @{ 'a.ps1' = "line1`nline2`n" } $crlf = newFolder @{ 'a.ps1' = "line1`r`nline2`r`n" } _getExtensionContentHash -Path $crlf | Should -Be (_getExtensionContentHash -Path $lf) } It 'Does not change the line endings of binary files' { $lf = newFolder @{ 'a.dll' = [byte[]](0, 1, 10, 2) } $crlf = newFolder @{ 'a.dll' = [byte[]](0, 1, 13, 10, 2) } _getExtensionContentHash -Path $crlf | Should -Not -Be (_getExtensionContentHash -Path $lf) } It 'Ignores files that are written when an extension is installed' { $plain = newFolder @{ 'a.txt' = "x`n" } $installed = newFolder @{ 'a.txt' = "x`n"; 'PSGetModuleInfo.xml' = '<installed />'; '.zf-source.json' = '{}' } _getExtensionContentHash -Path $installed | Should -Be (_getExtensionContentHash -Path $plain) } It 'Includes files with the same names as the ignored files when they are in a sub-folder' { $plain = newFolder @{ 'a.txt' = "x`n" } $nested = newFolder @{ 'a.txt' = "x`n"; 'sub/PSGetModuleInfo.xml' = '<data />' } _getExtensionContentHash -Path $nested | Should -Not -Be (_getExtensionContentHash -Path $plain) } It 'Supports PowerShell drive paths' { $folder = newFolder @{ 'a.txt' = "x`n" } $drivePath = Join-Path 'TestDrive:' (Split-Path -Leaf $folder) _getExtensionContentHash -Path $drivePath | Should -Be (_getExtensionContentHash -Path $folder) } } |