Skip to content

Commit

Permalink
✨ Add Move-PSKoanLibrary Function (#364)
Browse files Browse the repository at this point in the history
* ✨ 📝 Add Move-PSKoanLibrary

* 🐛 Fix issue in Set-PSKoanLocation

* ✅ Add tests!

* ✅ Fix unrelated test

How was this not failing before??!??!?!!?

* 📝 Update doc

* ✅ Refactor error command tests
  • Loading branch information
vexx32 committed Mar 2, 2020
1 parent 5fcb07f commit 29a7068
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 55 deletions.
19 changes: 13 additions & 6 deletions PSKoans/PSKoans.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,24 @@
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = @(
'Get-Blank'
'Show-Advice'
'Show-Karma'
'Register-Advice'
'Get-Karma'
'Get-PSKoan'
'Get-PSKoanLocation'
'Set-PSKoanLocation'
'Get-PSKoanSetting'

'Set-PSKoanLocation'
'Set-PSKoanSetting'

'Move-PSKoanLibrary'

'Reset-PSKoan'

'Register-Advice'

'Show-Advice'
'Show-Karma'

'Update-PSKoan'
'Get-PSKoan'
'Get-Karma'
)

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
Expand Down
4 changes: 4 additions & 0 deletions PSKoans/Private/New-PSKoanErrorRecord.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ function New-PSKoanErrorRecord {
$Exception = $ExceptionType::new( $ExceptionMessage )
}

if ($ErrorId -notmatch '^PSKoans\.') {
$ErrorId = "PSKoans.$($ErrorId)"
}

[ErrorRecord]::new(
$Exception,
$ErrorId,
Expand Down
23 changes: 23 additions & 0 deletions PSKoans/Public/Move-PSKoanLibrary.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
function Move-PSKoanLibrary {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium',
HelpUri = 'https://github.com/vexx32/PSKoans/tree/master/docs/Move-PSKoanLibrary.md')]
[OutputType([void])]
param(
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
[Alias('PSPath', 'Folder', 'Destination', 'TargetPath')]
[string]
$Path
)
process {
if ($PSCmdlet.ShouldProcess($Path, 'Move existing koan files here')) {
$OriginalPath = Get-PSKoanLocation

Write-Verbose "Moving library files from '$OriginalPath' to '$Path'"
Move-Item -Path $OriginalPath -Destination $Path -ErrorAction Stop -PassThru

if ($?) {
Set-PSKoanLocation -Path $Path
}
}
}
}
67 changes: 28 additions & 39 deletions PSKoans/Public/Set-PSKoanLocation.ps1
Original file line number Diff line number Diff line change
@@ -1,63 +1,52 @@
using namespace System.Management.Automation

class FolderTransformAttribute : ArgumentTransformationAttribute {
[object] Transform([EngineIntrinsics]$engineIntrinsics, [object] $inputData) {
switch ($inputData) {

{ $_ -is [string] } {
if (-not (Test-Path $_ -IsValid -PathType Container)) {
throw [ArgumentTransformationMetadataException]::new(
"Could not resolve path: $_",
$_.Exception
)
}

return $engineIntrinsics.SessionState.Path.GetUnresolvedProviderPathFromPSPath($_)
}

{ $_ -is [System.IO.FileSystemInfo] } {

if (-not (Test-Path -Path $_.FullName -PathType Container)) {
throw [ArgumentTransformationMetadataException]::new(
'Path could not be resolved to a valid container.'
)
}
else {
return $inputData.Fullname
}

}
}

throw [System.IO.FileNotFoundException]::new()
}
}

function Set-PSKoanLocation {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium',
HelpUri = 'https://github.com/vexx32/PSKoans/tree/master/docs/Set-PSKoanLocation.md')]
[OutputType([void])]
param(
[Parameter(Mandatory, Position = 0)]
[Alias('PSPath', 'Folder')]
[FolderTransformAttribute()]
[string]
$Path,

[Parameter()]
[switch]
$PassThru
)
begin {
$resolvedPath = $PSCmdlet.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)

if ($resolvedPath.Count -gt 1 -or [WildcardPattern]::ContainsWildcardCharacters($resolvedPath)) {
$ErrorDetails = @{
ExceptionType = [System.Management.Automation.PSArgumentException]
ExceptionMessage = 'Wildcarded paths are not supported.'
ErrorId = 'InvalidPath'
ErrorCategory = 'InvalidArgument'
TargetObject = $Path
}
$PSCmdlet.ThrowTerminatingError((New-PSKoanErrorRecord @ErrorDetails))
}

if (Test-Path $resolvedPath -PathType Leaf) {
$ErrorDetails = @{
ExceptionType = [System.Management.Automation.PSArgumentException]
ExceptionMessage = 'You cannot use a file path as the location for your PSKoans library.'
ErrorId = 'InvalidPathType'
ErrorCategory = 'InvalidArgument'
TargetObject = $Path
}
$PSCmdlet.ThrowTerminatingError((New-PSKoanErrorRecord @ErrorDetails))
}
}
process {
if ($PSCmdlet.ShouldProcess("Set PSKoans folder location to '$Path'")) {
Set-PSKoanSetting -Name KoanLocation -Value $Path
if ($PSCmdlet.ShouldProcess("Set PSKoans folder location to '$resolvedPath'")) {
Set-PSKoanSetting -Name KoanLocation -Value $resolvedPath
}
else {
Write-Warning "PSKoans folder location has not been changed."
}

if ($PassThru) {
$Path
$resolvedPath
}
}
}
46 changes: 36 additions & 10 deletions Tests/Functions/Private/New-PSKoanErrorRecord.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Describe 'New-PSKoanErrorRecord' {

Context 'With Exception Object' {

BeforeAll {
$Output, $Parameters = InModuleScope 'PSKoans' {
$Params = @{
Expand All @@ -20,16 +21,29 @@ Describe 'New-PSKoanErrorRecord' {
$Output | Should -BeOfType System.Management.Automation.ErrorRecord
}

It 'creates output with properties matching the parameters supplied' {
$Output.Exception | Should -BeOfType $Parameters.Exception.GetType().FullName
$Output.Exception.Message | Should -BeExactly $Parameters.Exception.Message
$Output.FullyQualifiedErrorId | Should -BeExactly $Parameters.ErrorId
It 'emits the same type of exception' {
$Output.Exception | Should -BeOfType $Parameters.Exception.GetType().FullName
}

It 'includes the input exception message' {
$Output.Exception.Message | Should -BeExactly $Parameters.Exception.Message
}

It 'emits the correct error ID with "PSKoans" prefix' {
$Output.FullyQualifiedErrorId | Should -BeExactly "PSKoans.$($Parameters.ErrorId)"
}

It 'assigns the requested error category' {
$Output.CategoryInfo.Category | Should -Be $Parameters.ErrorCategory
$Output.TargetObject | Should -BeNullOrEmpty
}

It 'assigns the target object' {
$Output.TargetObject | Should -Be $Params.TargetObject
}
}

Context 'With TypeName and Message' {

BeforeAll {
$Output, $Parameters = InModuleScope 'PSKoans' {
$Params = @{
Expand All @@ -48,12 +62,24 @@ Describe 'New-PSKoanErrorRecord' {
$Output | Should -BeOfType System.Management.Automation.ErrorRecord
}

It 'creates output with properties matching the parameters supplied' {
$Output.Exception | Should -BeOfType $Parameters.ExceptionType
$Output.Exception.Message | Should -BeExactly $Parameters.ExceptionMessage
$Output.FullyQualifiedErrorId | Should -BeExactly $Parameters.ErrorId
It 'creates the correct type of exception' {
$Output.Exception | Should -BeOfType $Parameters.ExceptionType
}

It 'applies the requested exception message' {
$Output.Exception.Message | Should -BeExactly $Parameters.ExceptionMessage
}

It 'emits the correct error ID with "PSKoans" prefix' {
$Output.FullyQualifiedErrorId | Should -BeExactly "PSKoans.$($Parameters.ErrorId)"
}

It 'assigns the requested error category' {
$Output.CategoryInfo.Category | Should -Be $Parameters.ErrorCategory
$Output.TargetObject | Should -BeNullOrEmpty
}

It 'assigns the target object' {
$Output.TargetObject | Should -Be $Params.TargetObject
}
}
}
82 changes: 82 additions & 0 deletions Tests/Functions/Public/Move-PSKoanLibrary.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#Requires -Modules PSKoans

Describe 'Move-PSKoanLibrary' {

Context 'Unit Tests with Mocks' {

BeforeAll {
$OriginalPath = New-Item -Path 'TestDrive:/PSKoans' -ItemType Directory |
Select-Object -ExpandProperty FullName
$TestPath = New-Item -ItemType Directory -Path 'TestDrive:/TestPath' | Join-Path -ChildPath 'Koans'

Mock Get-PSKoanLocation { $OriginalPath }.GetNewClosure() -ModuleName PSKoans
Mock Set-PSKoanLocation -ParameterFilter { $Path -eq $TestPath } -ModuleName PSKoans
Mock Move-Item -ParameterFilter { $Path -eq $OriginalPath } -MockWith { $Destination } -ModuleName PSKoans
}

It 'should output the new location' {
Move-PSKoanLibrary -Path $TestPath | Should -BeExactly $TestPath
}

It 'should call Get-PSKoanLocation' {
Assert-MockCalled Get-PSKoanLocation -ModuleName PSKoans
}

It 'should call Move-Item' {
Assert-MockCalled Move-Item -ModuleName PSKoans
}

It 'should call Set-PSKoanLocation' {
Assert-MockCalled Set-PSKoanLocation -ModuleName PSKoans
}
}

Context 'Integration Tests' {

BeforeAll {
$OldLocation = Get-PSKoanLocation

Set-PSKoanLocation -Path 'TestDrive:/PSKoans'
Update-PSKoan -Confirm:$false

$OriginalFileHashes = Get-PSKoanLocation |
Get-ChildItem -Recurse -File |
Get-FileHash |
ForEach-Object {
@{
File = ($_.Path -split [regex]::Escape([IO.Path]::DirectorySeparatorChar))[-2, -1] -join ':'
Hash = $_.Hash
}
}

$NewLocation = 'TestDrive:/NewLocation/PSKoans'
New-Item -Path ($NewLocation | Split-Path -Parent) -ItemType Directory
}

AfterAll {
Set-PSKoanLocation $OldLocation
}

It 'should move the folder to the new location' {
Move-PSKoanLibrary -Path $NewLocation
Test-Path $NewLocation -PathType Container | Should -BeTrue
}

It 'should update the KoanLocation' {
Get-PSKoanLocation | Should -BeExactly (Get-Item -Path $NewLocation).FullName
}

It 'should copy <File> to the new location' -TestCases $OriginalFileHashes {
param($File, $Hash)

$FileName = ($File -split ':')[-1]

$NewHash = Get-ChildItem -Path $NewLocation -Recurse -File -Filter "*$FileName*" |
Get-FileHash |
Select-Object -ExpandProperty Hash

$NewHash | Should -BeExactly $Hash
}
}

}
Loading

0 comments on commit 29a7068

Please sign in to comment.