Skip to content
107 changes: 107 additions & 0 deletions Tools/Modules/FileOperations/FileOperations.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
Describe 'FileOperations Module' {
BeforeAll {
$ModulePath = Split-Path -Parent $PSCommandPath
Import-Module (Join-Path $ModulePath 'FileOperations.psd1') -Force
}

Context 'Module Import' {
It 'Should import the module successfully' {
Get-Module 'FileOperations' | Should -Not -BeNullOrEmpty
}

It 'Should export all expected functions' {
$ExportedFunctions = (Get-Module 'FileOperations').ExportedFunctions.Keys
'Initialize-Folder' -in $ExportedFunctions | Should -Be $true
'Invoke-FileCleanup' -in $ExportedFunctions | Should -Be $true
'Request-RemoveItem' -in $ExportedFunctions | Should -Be $true
}
}
}

Describe 'Initialize-Folder' {
It 'Should create a folder and return true when it does not exist' {
$tempRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.Guid]::NewGuid().ToString())
$targetFolder = Join-Path -Path $tempRoot -ChildPath 'created'

try {
$result = Initialize-Folder -FolderPath $targetFolder
$result | Should -Be $true
(Test-Path -Path $targetFolder -PathType Container) | Should -Be $true
} finally {
if (Test-Path -Path $tempRoot) { Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue }
}
}

It 'Should return true for an existing folder' {
$tempRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.Guid]::NewGuid().ToString())
New-Item -Path $tempRoot -ItemType Directory -Force | Out-Null

try {
$result = Initialize-Folder -FolderPath $tempRoot
$result | Should -Be $true
} finally {
if (Test-Path -Path $tempRoot) { Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue }
}
}

It 'Should return false when a file exists at the folder path' {
$tempFile = New-TemporaryFile

try {
$result = Initialize-Folder -FolderPath $tempFile.FullName
$result | Should -Be $false
} finally {
if (Test-Path -Path $tempFile.FullName) { Remove-Item -Path $tempFile.FullName -Force -ErrorAction SilentlyContinue }
}
}
}

Describe 'Request-RemoveItem' {
It 'Should remove a file path' {
$tempFile = New-TemporaryFile
Request-RemoveItem -Path $tempFile.FullName
(Test-Path -Path $tempFile.FullName) | Should -Be $false
}

It 'Should remove a directory path recursively' {
$tempRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.Guid]::NewGuid().ToString())
$nestedFolder = Join-Path -Path $tempRoot -ChildPath 'nested'
$nestedFile = Join-Path -Path $nestedFolder -ChildPath 'payload.txt'
New-Item -Path $nestedFolder -ItemType Directory -Force | Out-Null
Set-Content -Path $nestedFile -Value 'payload'

Request-RemoveItem -Path $tempRoot
(Test-Path -Path $tempRoot) | Should -Be $false
}

It 'Should be a no-op when path does not exist' {
$missingPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.Guid]::NewGuid().ToString())
{ Request-RemoveItem -Path $missingPath } | Should -Not -Throw
}
}

Describe 'Invoke-FileCleanup' {
It 'Should remove files and folders in the provided list' {
$tempRoot = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.Guid]::NewGuid().ToString())
$tempFolder = Join-Path -Path $tempRoot -ChildPath 'cleanup'
$tempFile = Join-Path -Path $tempRoot -ChildPath 'file.txt'
New-Item -Path $tempFolder -ItemType Directory -Force | Out-Null
New-Item -Path $tempFile -ItemType File -Force | Out-Null

Invoke-FileCleanup -FilePaths @($tempFolder, $tempFile)

(Test-Path -Path $tempFolder) | Should -Be $false
(Test-Path -Path $tempFile) | Should -Be $false

if (Test-Path -Path $tempRoot) { Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue }
}

It 'Should not throw when file list is empty' {
{ Invoke-FileCleanup -FilePaths @() } | Should -Not -Throw
}

It 'Should not throw when file list contains missing paths' {
$missingPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([System.Guid]::NewGuid().ToString())
{ Invoke-FileCleanup -FilePaths @($missingPath) } | Should -Not -Throw
}
}
54 changes: 54 additions & 0 deletions Tools/Modules/FileOperations/FileOperations.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#
# Module manifest for module 'FileOperations'
#
# Generated by: Trenly
#
# Generated on: 5/27/2026
#

@{

# Script module or binary module file associated with this manifest.
RootModule = 'FileOperations.psm1'

# Version number of this module.
ModuleVersion = '0.0.1'

# ID used to uniquely identify this module
GUID = '37c15420-b760-4f80-8c52-a154225b867c'

# Author of this module
Author = 'Microsoft Open Source Community'

# Company or vendor of this module
CompanyName = 'Microsoft Corporation'

# Copyright statement for this module
Copyright = 'Copyright (c) Microsoft Corporation. All rights reserved.'

# Description of the functionality provided by this module
Description = 'Shared folder and file operations for winget-pkgs tooling scripts and modules.'

# Functions to export from this module
FunctionsToExport = @(
'Initialize-Folder'
'Request-RemoveItem'
'Invoke-FileCleanup'
)

# Cmdlets to export from this module
CmdletsToExport = @()

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module
AliasesToExport = @()

PrivateData = @{
PSData = @{
LicenseUri = 'https://github.com/microsoft/winget-pkgs/blob/master/LICENSE'
ProjectUri = 'https://github.com/microsoft/winget-pkgs'
}
}
}
73 changes: 73 additions & 0 deletions Tools/Modules/FileOperations/FileOperations.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
function Initialize-Folder {
param (
[Parameter(Mandatory = $true)]
[String] $FolderPath
)

$FolderPath = [System.IO.Path]::GetFullPath($FolderPath)
if (Test-Path -Path $FolderPath -PathType Container) { return $true }
if (Test-Path -Path $FolderPath) { return $false }

try {
New-Item -Path $FolderPath -ItemType Directory -Force -ErrorAction Stop | Out-Null
return $true
} catch {
return $false
}
}

function Request-RemoveItem {
Param(
[Parameter(Mandatory = $true, Position = 0)]
[string] $Path,
[int] $Retries = 6,
[int] $DelayMs = 250
)

# Check if path exists using .NET
$fileInfo = [System.IO.FileInfo]$Path
$dirInfo = [System.IO.DirectoryInfo]$Path

if (-not ($fileInfo.Exists -or $dirInfo.Exists)) { return }

for ($i = 0; $i -lt $Retries; $i++) {
try {
if ($dirInfo.Exists) {
$dirInfo.Delete($true)
} elseif ($fileInfo.Exists) {
$fileInfo.Delete()
}
return
} catch [System.IO.IOException] {
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
Start-Sleep -Milliseconds $DelayMs
$DelayMs = [Math]::Min(5000, $DelayMs * 2)
} catch {
throw
}
}

Write-Warning "Could not remove file '$Path' after $Retries attempts; it may be in use by another process."
}

function Invoke-FileCleanup {
param (
[Parameter(Mandatory = $true)]
[AllowEmptyString()]
[AllowEmptyCollection()]
[String[]] $FilePaths
)

if (!$FilePaths) { return }
foreach ($path in $FilePaths) {
Write-Debug "Removing $path"
if (Test-Path -Path $path) {
Request-RemoveItem -Path $path
} else {
Write-Warning "Could not remove $path as it does not exist"
}
}
}

Export-ModuleMember -Function @('Initialize-Folder', 'Request-RemoveItem', 'Invoke-FileCleanup')
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
Describe 'YamlCreate.GitHub Module' {
BeforeAll {
# Import the module to test
$ModulePath = Split-Path -Parent $PSCommandPath
Import-Module (Join-Path $ModulePath 'YamlCreate.GitHub.psd1') -Force
}

Context 'Module Import' {
It 'Should import the module successfully' {
Get-Module 'YamlCreate.GitHub' | Should -Not -BeNullOrEmpty
}

It 'Should declare FileOperations as a required module dependency' {
$ModulePath = Split-Path -Parent $PSCommandPath
$manifest = Import-PowerShellDataFile -Path (Join-Path $ModulePath 'YamlCreate.GitHub.psd1')
(@($manifest.RequiredModules) -contains 'FileOperations') | Should -Be $true
}

It 'Should export all expected functions' {
$ExportedFunctions = (Get-Module 'YamlCreate.GitHub').ExportedFunctions.Keys
'Get-Remote' -in $ExportedFunctions | Should -Be $true
'Set-Remote' -in $ExportedFunctions | Should -Be $true
'Find-PullRequest' -in $ExportedFunctions | Should -Be $true
'Get-PrTemplate' -in $ExportedFunctions | Should -Be $true
}
}

Context 'wingetUpstream Variable' {
It 'Should have correct value for wingetUpstream' {
$wingetUpstream | Should -Be 'https://github.com/microsoft/winget-pkgs.git'
}
}

Context 'Get-Remote Function' {
It 'Should return $null for non-existent remote' {
$result = Get-Remote -RemoteName 'nonexistent-remote-xyz'
$result | Should -BeNullOrEmpty
}

It 'Should accept RemoteName parameter' {
{ Get-Remote -RemoteName 'origin' } | Should -Not -Throw
}
}

Context 'Set-Remote Function' {
It 'Should accept RemoteName and Url parameters' {
{ Set-Remote -RemoteName 'test' -Url 'https://example.com/test.git' } | Should -Not -Throw
}

It 'Should return boolean result' {
$result = Set-Remote -RemoteName 'test' -Url 'https://example.com/test.git'
($result -is [System.Boolean]) | Should -Be $true
}
}

Context 'Find-PullRequest Function' {
It 'Should accept PackageIdentifier and PackageVersion parameters' {
{ Find-PullRequest -PackageIdentifier 'Test.Package' -PackageVersion '1.0.0' } | Should -Not -Throw
}

It 'Should handle web request errors gracefully' {
{ Find-PullRequest -PackageIdentifier 'Invalid...Package' -PackageVersion '1.0.0' } | Should -Not -Throw
}
}

Context 'Get-PrTemplate Function' {
It 'Should not throw when called' {
{ Get-PrTemplate } | Should -Not -Throw
}

It 'Should return string or null' {
$result = Get-PrTemplate
(($result -is [string]) -or ($result -eq $null)) | Should -Be $true
}

It 'Should handle web request failures gracefully' {
{ $result = Get-PrTemplate; $result } | Should -Not -Throw
}
}
}
Loading
Loading