17 Commits
1.1.0 ... 1.3.1

Author SHA1 Message Date
dd47a1b7e2 Bump module version to 1.3.1 in the module manifest.
All checks were successful
Publish Powershell Module to Gitea Repository / test (push) Successful in 9s
Publish Powershell Module to Gitea Repository / deploy (push) Successful in 32s
2025-04-30 15:23:44 -04:00
c65cde9fbc Refactor path handling for cross-platform compatibility and update configuration directory path 2025-04-30 15:20:52 -04:00
c18f68bc46 Remove unnecessary blank line in Pester test execution step 2025-04-30 15:05:05 -04:00
51a3a15a4f Bump module version to 1.3.0 in the module manifest 2025-04-30 15:02:25 -04:00
0451788a13 Merge pull request 'Merge in Get-GiteaReleases and related pester tests' (#3) from pester-dev into main
Reviewed-on: public/ps-modules-giteautilities#3
2025-04-30 15:00:34 -04:00
34fcb73d32 Add unit tests for Get-GiteaReleases function to validate behavior and error handling 2025-04-30 14:59:51 -04:00
172922ba61 Add Get-GiteaReleases function to retrieve releases from a Gitea repository 2025-04-29 17:18:00 -04:00
24f2e90cc9 Merge pull request 'Merge in pester tests' (#2) from pester-dev into main
Reviewed-on: public/ps-modules-giteautilities#2
2025-04-28 08:21:34 -04:00
135af8dfb8 Add CI workflow to run Pester tests before deployment 2025-04-28 08:21:09 -04:00
c5c9f81c50 Add unit tests for Set-GiteaConfiguration, Get-GiteaConfiguration, and Get-GiteaFileContent functions 2025-04-25 17:33:38 -04:00
68da95104b Bump module version to 1.2.0
All checks were successful
Publish Powershell Module to Gitea Repository / deploy (push) Successful in 34s
2025-04-25 16:12:59 -04:00
6ec0944a32 Merge pull request '1.2.0-rc1' (#1) from 1.2.0-rc1 into main
Reviewed-on: public/ps-modules-giteautilities#1
2025-04-25 16:12:25 -04:00
9cdcfeddff Refactor Get-GiteaChildItem to improve LFS handling; extract size and SHA from LFS pointers. Remove excess entries in returned object. 2025-04-25 16:10:52 -04:00
8e1a5f187a Enhance Get-GiteaChildItem to retrieve and parse LFS pointer details for size and SHA; update download URL for LFS files. 2025-04-25 15:44:37 -04:00
dfb34f240b Refactor verbose logging to debug level for token information; enhance Get-GiteaChildItem to handle LFS pointer details and update download URLs. 2025-04-25 15:03:33 -04:00
0c40d07bcd Remove Get-GiteaLFSFile function to streamline codebase; enhance clarity and maintainability. 2025-04-25 14:25:13 -04:00
3cdf0a6610 Add LoadVariables and Force switches to Get-GiteaConfiguration; implement Get-GiteaLFSFile for LFS file retrieval 2025-04-25 14:07:34 -04:00
9 changed files with 908 additions and 31 deletions

View File

@@ -8,7 +8,25 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
test:
runs-on: ubuntu-22.04
container:
image: catthehacker/ubuntu:pwsh-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Run pester tests and verify all tests pass
shell: pwsh
run: |
Write-Host "Changing to repository directory: ${{ github.workspace }}"
Set-Location -Path ${{ github.workspace }}
Write-Host "Running Pester tests"
Invoke-Pester -Path . -PassThru | Select-Object -ExpandProperty FailedCount | Should -Be 0 -ErrorAction stop
deploy: deploy:
needs: test
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: container:
image: catthehacker/ubuntu:pwsh-latest image: catthehacker/ubuntu:pwsh-latest

View File

@@ -12,7 +12,7 @@
RootModule = 'PS-GiteaUtilities.psm1' RootModule = 'PS-GiteaUtilities.psm1'
# Version number of this module. # Version number of this module.
ModuleVersion = '1.1.0' ModuleVersion = '1.3.1'
# Supported PSEditions # Supported PSEditions
# CompatiblePSEditions = @() # CompatiblePSEditions = @()

View File

@@ -11,7 +11,7 @@ Function Set-GiteaConfiguration {
) )
# Create configuration directory if it doesn't exist # Create configuration directory if it doesn't exist
$configDir = Join-Path -Path $env:USERPROFILE -ChildPath ".giteautils" $configDir = Join-Path -Path $HOME -ChildPath ".giteautils"
if (-not (Test-Path -Path $configDir)) { if (-not (Test-Path -Path $configDir)) {
New-Item -Path $configDir -ItemType Directory | Out-Null New-Item -Path $configDir -ItemType Directory | Out-Null
} }
@@ -31,12 +31,42 @@ Function Set-GiteaConfiguration {
Function Get-GiteaConfiguration { Function Get-GiteaConfiguration {
[CmdletBinding()] [CmdletBinding()]
param() param(
[switch]$LoadVariables,
[switch]$Force
)
$configPath = Join-Path -Path $env:USERPROFILE -ChildPath ".giteautils\config.xml" $configPath = Join-Path -Path $HOME -ChildPath ".giteautils/config.xml"
if (Test-Path -Path $configPath) { if (Test-Path -Path $configPath) {
return Import-Clixml -Path $configPath $config = Import-Clixml -Path $configPath
# If LoadVariables switch is used, set each config value as a variable in the global scope
if ($LoadVariables) {
foreach ($key in $config.Keys) {
# Check if variable exists in global scope
$variableExists = $false
try {
$existingVar = Get-Variable -Name $key -Scope Global -ErrorAction Stop
$variableExists = $true
}
catch {
$variableExists = $false
}
# Set variable if it doesn't exist or if Force is used
if (-not $variableExists -or $Force) {
Write-Verbose "Loading configuration variable: $key = $($config[$key])"
Set-Variable -Name $key -Value $config[$key] -Scope Global
Write-Host "Created global variable: `$$key" -ForegroundColor Green
}
else {
Write-Verbose "Skipping existing variable: $key (use -Force to override)"
}
}
}
return $config
} }
else { else {
Write-Warning "Gitea configuration not found. Use Set-GiteaConfiguration to set up." Write-Warning "Gitea configuration not found. Use Set-GiteaConfiguration to set up."
@@ -139,7 +169,7 @@ Function Get-GiteaFileContent {
Write-Verbose "repoOwner: $repoOwner" Write-Verbose "repoOwner: $repoOwner"
Write-Verbose "repoName: $repoName" Write-Verbose "repoName: $repoName"
Write-Verbose "branch: $branch" Write-Verbose "branch: $branch"
Write-Verbose "token: $token" Write-Debug "Token: $token"
Write-Verbose "decode: $decode" Write-Verbose "decode: $decode"
$headers = @{ $headers = @{
@@ -150,6 +180,8 @@ Function Get-GiteaFileContent {
process { process {
foreach ($file in $filePath) { foreach ($file in $filePath) {
# Normalize path separators for cross-platform compatibility
$file = $file -replace '\\', '/'
Write-Verbose "Processing file: $file" Write-Verbose "Processing file: $file"
$encodedFile = [System.Uri]::EscapeDataString($file) $encodedFile = [System.Uri]::EscapeDataString($file)
Write-Verbose "Encoded file: $encodedFile" Write-Verbose "Encoded file: $encodedFile"
@@ -161,7 +193,6 @@ Function Get-GiteaFileContent {
$url += "/$encodedFile" $url += "/$encodedFile"
$url += "?ref=$branch" $url += "?ref=$branch"
Write-Verbose "URL: $url" Write-Verbose "URL: $url"
try { try {
$fileContent = Invoke-RestMethod -Uri $url -Method Get -Headers $headers $fileContent = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
@@ -249,7 +280,7 @@ Function Get-GiteaLFSConfiguration {
Write-Verbose "giteaURL: $giteaURL" Write-Verbose "giteaURL: $giteaURL"
Write-Verbose "repoOwner: $repoOwner" Write-Verbose "repoOwner: $repoOwner"
Write-Verbose "repoName: $repoName" Write-Verbose "repoName: $repoName"
Write-Verbose "token: $token" Write-Debug "Token: $token"
$filePath = ".gitattributes" $filePath = ".gitattributes"
@@ -359,7 +390,7 @@ Function Invoke-GiteaFileDownload {
} }
Write-Verbose "Parameters:" Write-Verbose "Parameters:"
Write-Verbose "token: $token (length: $(if($token){$token.Length}else{0}))" Write-Debug "Token: $token (length: $(if($token){$token.Length}else{0}))"
Write-Verbose "PreserveRelativePath: $PreserveRelativePath" Write-Verbose "PreserveRelativePath: $PreserveRelativePath"
# Create a WebClient to be reused # Create a WebClient to be reused
@@ -368,6 +399,8 @@ Function Invoke-GiteaFileDownload {
} }
process { process {
# Normalize path separators for cross-platform compatibility
$filePath = $filePath -replace '\\', '/'
Write-Verbose "File path: $filePath" Write-Verbose "File path: $filePath"
Write-Verbose "Type: $type" Write-Verbose "Type: $type"
@@ -615,7 +648,7 @@ Function Get-GiteaChildItem {
Write-Verbose "repoOwner: $repoOwner" Write-Verbose "repoOwner: $repoOwner"
Write-Verbose "repoName: $repoName" Write-Verbose "repoName: $repoName"
Write-Verbose "Path: $Path" Write-Verbose "Path: $Path"
Write-Verbose "token: $token" Write-Debug "Token: $token"
Write-Verbose "File: $File" Write-Verbose "File: $File"
$headers = @{ $headers = @{
@@ -634,11 +667,10 @@ Function Get-GiteaChildItem {
$paths = $path $paths = $path
foreach ($path in $paths) { foreach ($path in $paths) {
Write-Verbose "Processing path: $path" Write-Verbose "Processing path: $path"
# Normalize the path format - replace backslashes with forward slashes and trim trailing slashes # Normalize path separators for cross-platform compatibility
$normalizedPath = $path -replace '\\', '/' -replace '/$', '' $normalizedPath = $path -replace '\\', '/' -replace '/$', ''
$encodedPath = [System.Uri]::EscapeDataString($normalizedPath)
Write-Verbose "Normalized path: $normalizedPath" Write-Verbose "Normalized path: $normalizedPath"
Write-Verbose "Encoded path: $encodedPath" $encodedPath = [System.Uri]::EscapeDataString($normalizedPath)
$url = "$giteaURL" $url = "$giteaURL"
$url += "/api/v1/repos" $url += "/api/v1/repos"
$url += "/$repoOwner" $url += "/$repoOwner"
@@ -668,21 +700,31 @@ Function Get-GiteaChildItem {
} }
if ($isLFS) { if ($isLFS) {
$item.type = "lfs" $item.type = "lfs"
# Save the original download URL for LFS pointers to be used to populate the size and sha fields
$GitLFSPointerURL = $item.download_url
# Use the Gitea API to get the LFS pointer details
# For LFS files, we need to get the pointer file and parse it for details
$lfsPointerContent = Invoke-RestMethod -Uri $GitLFSPointerURL -Method Get -Headers $headers -ErrorAction Stop
Write-Debug "LFS Pointer Content: $($lfsPointerContent)"
# Parse the LFS pointer to extract size and SHA
if ($lfsPointerContent -match 'oid sha256:([a-f0-9]+)') {
$item.sha = $matches[1]
}
if ($lfsPointerContent -match 'size (\d+)') {
$item.size = [long]$matches[1]
}
# Set the download URL to the media endpoint for LFS files to download the actual file
$item.download_url = "$giteaURL/api/v1/repos/$repoOwner/$repoName/media/$($item.path)" $item.download_url = "$giteaURL/api/v1/repos/$repoOwner/$repoName/media/$($item.path)"
} }
$itemObj = [PSCustomObject]@{ $itemObj = [PSCustomObject]@{
filePath = $item.path
Path = $item.path
repoOwner = $repoOwner
repoName = $repoName
giteaURL = $giteaURL
downloadURL = $item.download_url
branch = $branch
type = $item.type
name = $item.name name = $item.name
Path = $item.path
type = $item.type
size = $item.size size = $item.size
sha = $item.sha sha = $item.sha
downloadURL = $item.download_url
Success = $true Success = $true
Error = $null Error = $null
Level = 0 Level = 0
@@ -741,21 +783,31 @@ Function Get-GiteaChildItem {
} }
if ($isLFS) { if ($isLFS) {
$subItem.type = "lfs" $subItem.type = "lfs"
# Save the original download URL for LFS pointers to be used to populate the size and sha fields
$GitLFSPointerURL = $subItem.download_url
# Use the Gitea API to get the LFS pointer details
# For LFS files, we need to get the pointer file and parse it for details
$lfsPointerContent = Invoke-RestMethod -Uri $GitLFSPointerURL -Method Get -Headers $headers -ErrorAction Stop
Write-Debug "LFS Pointer Content: $($lfsPointerContent)"
# Parse the LFS pointer to extract size and SHA
if ($lfsPointerContent -match 'oid sha256:([a-f0-9]+)') {
$subItem.sha = $matches[1]
}
if ($lfsPointerContent -match 'size (\d+)') {
$subItem.size = [long]$matches[1]
}
# Set the download URL to the media endpoint for LFS files to download the actual file
$subItem.download_url = "$giteaURL/api/v1/repos/$repoOwner/$repoName/media/$($subItem.path)" $subItem.download_url = "$giteaURL/api/v1/repos/$repoOwner/$repoName/media/$($subItem.path)"
} }
$subItemObj = [PSCustomObject]@{ $subItemObj = [PSCustomObject]@{
filePath = $subItem.path
Path = $subItem.path
repoOwner = $repoOwner
repoName = $repoName
giteaURL = $giteaURL
downloadURL = $subItem.download_url
branch = $branch
type = $subItem.type
name = $subItem.name name = $subItem.name
Path = $subItem.path
type = $subItem.type
size = $subItem.size size = $subItem.size
sha = $subItem.sha sha = $subItem.sha
downloadURL = $subItem.download_url
Success = $true Success = $true
Error = $null Error = $null
Level = $currentLevel Level = $currentLevel
@@ -793,7 +845,6 @@ Function Get-GiteaChildItem {
Write-Error "Failed to retrieve '$normalizedPath' from Gitea: $($_.Exception.Message)`n$($errorDetails | ConvertTo-Json -Depth 1 -Compress)" Write-Error "Failed to retrieve '$normalizedPath' from Gitea: $($_.Exception.Message)`n$($errorDetails | ConvertTo-Json -Depth 1 -Compress)"
$results += [PSCustomObject]@{ $results += [PSCustomObject]@{
filePath = $normalizedPath
Path = $normalizedPath Path = $normalizedPath
repoOwner = $repoOwner repoOwner = $repoOwner
repoName = $repoName repoName = $repoName
@@ -823,4 +874,142 @@ Function Get-GiteaChildItem {
} }
} }
Export-ModuleMember -Function Set-GiteaConfiguration, Get-GiteaConfiguration, Get-GiteaFileContent, Invoke-GiteaFileDownload, Get-GiteaChildItem function Get-GiteaReleases {
<#
.SYNOPSIS
Retrieves the releases for a Gitea repository.
.DESCRIPTION
This function retrieves the releases for a Gitea repository using the Gitea API. The function requires the URL of the Gitea server, the owner of the repository, the name of the repository, and a personal access token.
#>
[cmdletbinding()]
param(
[Parameter(ValueFromPipelineByPropertyName)]
[string]$giteaURL = "https://gitea.norwichct.tech",
[Parameter(ValueFromPipelineByPropertyName)]
[string]$repoOwner,
[Parameter(ValueFromPipelineByPropertyName)]
[string]$repoName,
[int]$page,
[int]$limit,
[switch]$includeDrafts,
[switch]$includePreReleases,
[string]$token
)
begin {
# Use configuration if parameters aren't provided
if (-not $PSBoundParameters.ContainsKey('giteaURL') -or
-not $PSBoundParameters.ContainsKey('repoOwner') -or
-not $PSBoundParameters.ContainsKey('token')) {
$config = Get-GiteaConfiguration
if ($config) {
if (-not $PSBoundParameters.ContainsKey('giteaURL')) { $giteaURL = $config.giteaURL }
if (-not $PSBoundParameters.ContainsKey('repoOwner')) { $repoOwner = $config.defaultOwner }
if (-not $PSBoundParameters.ContainsKey('token')) { $token = $config.token }
}
}
# Validate that we have all required parameters
$missingParams = @()
if (-not $giteaURL) { $missingParams += "giteaURL" }
if (-not $repoOwner) { $missingParams += "repoOwner" }
if (-not $token) { $missingParams += "token" }
if ($missingParams.Count -gt 0) {
throw "Missing required parameters: $($missingParams -join ', '). Either provide them directly or set them with Set-GiteaConfiguration."
}
$headers = @{
"Authorization" = "token $token"
"Accept" = "application/json"
}
}
process {
Write-Verbose "Parameters:"
Write-Verbose "giteaURL: $giteaURL"
Write-Verbose "repoOwner: $repoOwner"
Write-Verbose "repoName: $repoName"
Write-Verbose "page: $page"
Write-Verbose "limit: $limit"
Write-Verbose "includeDrafts: $includeDrafts"
Write-Verbose "includePreReleases: $includePreReleases"
Write-Debug "Token: $token"
Write-Verbose "Processing releases for repository: $repoName"
$url = "$giteaURL"
$url += "/api/v1/repos"
$url += "/$repoOwner"
$url += "/$repoName"
$url += "/releases"
if ($includeDrafts -eq $true) {
$url += "?draft=true"
}
else {
$url += "?draft=false"
}
if ($includePreReleases -eq $true) {
$url += "&pre-release=true"
}
if ($page -gt 1) {
$url += "&page=$page"
}
if ($limit -gt 0) {
$url += "&limit=$limit"
}
Write-Verbose "URL: $url"
try {
$result = Invoke-RestMethod -Uri $url -Method Get -Headers $headers
$releases = foreach ($release in $result) {
# Process each asset into its own object
$assets = foreach ($asset in $release.assets) {
[PSCustomObject]@{
Id = $asset.id
Name = $asset.name
Size = $asset.size
DownloadCount = $asset.download_count
CreatedAt = [DateTime]$asset.created_at
UUID = $asset.uuid
DownloadURL = $asset.browser_download_url
}
}
# Create the release object with nested author and assets
[PSCustomObject]@{
Id = $release.id
TagName = $release.tag_name
TargetCommit = $release.target_commitish
Name = $release.name
Description = $release.body
URL = $release.url
HTMLURL = $release.html_url
TarballURL = $release.tarball_url
ZipballURL = $release.zipball_url
UploadURL = $release.upload_url
IsDraft = $release.draft
IsPrerelease = $release.prerelease
CreatedAt = [DateTime]$release.created_at
PublishedAt = [DateTime]$release.published_at
Author = [PSCustomObject]@{
Id = $release.author.id
Username = $release.author.username
Email = $release.author.email
AvatarURL = $release.author.avatar_url
HTMLURL = $release.author.html_url
}
Assets = $assets
}
}
return $releases
}
catch {
Write-Error "Failed to retrieve file '$file' from Gitea: $_"
}
}
}
Export-ModuleMember -Function Set-GiteaConfiguration, Get-GiteaConfiguration, Get-GiteaFileContent, Invoke-GiteaFileDownload, Get-GiteaChildItem, Get-GiteaLFSFile, Get-GiteaReleases

View File

@@ -0,0 +1,173 @@
# Get-GiteaChildItem.Tests.ps1
# Import the module under test
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\PS-GiteaUtilities.psm1'
Import-Module -Name $modulePath -Force
Describe 'Get-GiteaChildItem' {
BeforeEach {
# Mock config load
Mock -CommandName Get-GiteaConfiguration -ModuleName PS-GiteaUtilities -MockWith {
return @{
giteaURL = 'https://mock.gitea.com'
defaultOwner = 'mockuser'
defaultRepo = 'mockrepo'
defaultBranch = 'main'
token = 'mocktoken'
}
}
# Mock LFS config
Mock -CommandName Get-GiteaLFSConfiguration -ModuleName PS-GiteaUtilities -MockWith {
return @(".lfs")
}
# Mock web call to list files
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
return @(
@{
name = "file1.txt"
path = "file1.txt"
type = "file"
size = 123
sha = "abc123"
download_url = "https://mock.gitea.com/file1.txt"
},
@{
name = "subdir"
path = "subdir"
type = "dir"
size = 0
sha = $null
download_url = $null
}
)
}
Mock -CommandName Write-Error -ModuleName PS-GiteaUtilities -MockWith { }
}
Context 'When listing basic items' {
It 'Should return files and directories correctly' {
$result = Get-GiteaChildItem -repoOwner 'mockuser' -repoName 'mockrepo' -Path '' -token 'mocktoken'
$result | Should -Not -BeNullOrEmpty
$result.Count | Should -BeGreaterThan 0
$file = $result | Where-Object { $_.type -eq 'file' }
$dir = $result | Where-Object { $_.type -eq 'dir' }
$file.name | Should -Be 'file1.txt'
$dir.name | Should -Be 'subdir'
}
}
Context 'When filtering only files' {
It 'Should return only files when -File is used' {
$result = Get-GiteaChildItem -repoOwner 'mockuser' -repoName 'mockrepo' -Path '' -token 'mocktoken' -File
$result | Should -Not -BeNullOrEmpty
$result.Count | Should -Be 1
$result[0].type | Should -Be 'file'
}
}
Context 'When recursing into directories' {
BeforeEach {
# Mock recursion call
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
param($Uri)
if ($Uri -like "*subdir*") {
return @(
@{
name = "nestedfile.txt"
path = "subdir/nestedfile.txt"
type = "file"
size = 456
sha = "def456"
download_url = "https://mock.gitea.com/subdir/nestedfile.txt"
}
)
} else {
return @(
@{
name = "file1.txt"
path = "file1.txt"
type = "file"
size = 123
sha = "abc123"
download_url = "https://mock.gitea.com/file1.txt"
},
@{
name = "subdir"
path = "subdir"
type = "dir"
size = 0
sha = $null
download_url = $null
}
)
}
}
}
It 'Should retrieve nested files when -Recurse is used' {
$result = Get-GiteaChildItem -repoOwner 'mockuser' -repoName 'mockrepo' -Path '' -token 'mocktoken' -Recurse
$nested = $result | Where-Object { $_.Path -eq 'subdir/nestedfile.txt' }
$nested | Should -Not -BeNullOrEmpty
$nested.size | Should -Be 456
}
}
Context 'When recursion depth limit is enforced' {
BeforeEach {
# Same recursion mock as above
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
param($Uri)
if ($Uri -like "*subdir*") {
return @(
@{
name = "nestedfile.txt"
path = "subdir/nestedfile.txt"
type = "file"
size = 456
sha = "def456"
download_url = "https://mock.gitea.com/subdir/nestedfile.txt"
}
)
} else {
return @(
@{
name = "file1.txt"
path = "file1.txt"
type = "file"
size = 123
sha = "abc123"
download_url = "https://mock.gitea.com/file1.txt"
},
@{
name = "subdir"
path = "subdir"
type = "dir"
size = 0
sha = $null
download_url = $null
}
)
}
}
}
It 'Should not recurse deeper than depth limit' {
$result = Get-GiteaChildItem -repoOwner 'mockuser' -repoName 'mockrepo' -Path '' -token 'mocktoken' -Recurse -Depth 0
$nested = $result | Where-Object { $_.Path -eq 'subdir/nestedfile.txt' }
$nested | Should -BeNullOrEmpty
}
}
}

View File

@@ -0,0 +1,75 @@
# Get-GiteaConfiguration.Tests.ps1
# Import the module under test
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\PS-GiteaUtilities.psm1'
Import-Module -Name $modulePath -Force
Describe 'Get-GiteaConfiguration' {
Context 'When configuration file exists' {
BeforeEach {
# Mock filesystem and import
Mock -CommandName Test-Path -ModuleName PS-GiteaUtilities -MockWith { $true }
Mock -CommandName Import-Clixml -ModuleName PS-GiteaUtilities -MockWith {
return @{
giteaURL = 'https://gitea.example.com'
token = 'ABC123'
}
}
}
It 'Should import configuration and return it' {
$result = Get-GiteaConfiguration
$result | Should -Not -BeNullOrEmpty
$result.giteaURL | Should -Be 'https://gitea.example.com'
$result.token | Should -Be 'ABC123'
}
It 'Should create global variables when LoadVariables is used' {
# Remove any existing variables first
Remove-Variable -Name giteaURL, token -Scope Global -ErrorAction SilentlyContinue
Get-GiteaConfiguration -LoadVariables
(Get-Variable -Name giteaURL -Scope Global).Value | Should -Be 'https://gitea.example.com'
(Get-Variable -Name token -Scope Global).Value | Should -Be 'ABC123'
}
It 'Should overwrite existing variables when Force is used' {
# Set initial variables
Set-Variable -Name giteaURL -Value 'OldURL' -Scope Global
Set-Variable -Name token -Value 'OldToken' -Scope Global
Get-GiteaConfiguration -LoadVariables -Force
(Get-Variable -Name giteaURL -Scope Global).Value | Should -Be 'https://gitea.example.com'
(Get-Variable -Name token -Scope Global).Value | Should -Be 'ABC123'
}
It 'Should not overwrite existing variables without Force' {
# Set initial variables
Set-Variable -Name giteaURL -Value 'OldURL' -Scope Global
Set-Variable -Name token -Value 'OldToken' -Scope Global
Get-GiteaConfiguration -LoadVariables
(Get-Variable -Name giteaURL -Scope Global).Value | Should -Be 'OldURL'
(Get-Variable -Name token -Scope Global).Value | Should -Be 'OldToken'
}
}
Context 'When configuration file does not exist' {
BeforeEach {
Mock -CommandName Test-Path -ModuleName PS-GiteaUtilities -MockWith { $false }
Mock -CommandName Write-Warning -ModuleName PS-GiteaUtilities -MockWith { }
}
It 'Should return null and warn the user' {
$result = Get-GiteaConfiguration
$result | Should -BeNullOrEmpty
Assert-MockCalled -CommandName Write-Warning -ModuleName PS-GiteaUtilities -Times 1
}
}
}

View File

@@ -0,0 +1,119 @@
# Get-GiteaFileContent.Tests.ps1
# Import the module under test
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\PS-GiteaUtilities.psm1'
Import-Module -Name $modulePath -Force
Describe 'Get-GiteaFileContent' {
Context 'When all parameters are provided' {
BeforeEach {
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
return @{
content = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('Hello World'))
size = 11
sha = 'fake-sha'
}
}
}
It 'Should return file content without decoding by default' {
$result = Get-GiteaFileContent -repoOwner 'user' -repoName 'repo' -filePath 'README.md' -token 'abc123'
$result | Should -Not -BeNullOrEmpty
$result.Success | Should -Be $true
$result.Content | Should -Be ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('Hello World')))
}
It 'Should decode content when -decode is used' {
$result = Get-GiteaFileContent -repoOwner 'user' -repoName 'repo' -filePath 'README.md' -token 'abc123' -decode
$result | Should -Not -BeNullOrEmpty
$result.Success | Should -Be $true
$result.Content | Should -Be 'Hello World'
}
}
Context 'When missing parameters and config is loaded' {
BeforeEach {
Mock -CommandName Get-GiteaConfiguration -ModuleName PS-GiteaUtilities -MockWith {
return @{
giteaURL = 'https://mock.gitea.com'
defaultOwner = 'mockuser'
defaultRepo = 'mockrepo'
defaultBranch = 'main'
token = 'mocktoken'
}
}
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
return @{
content = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('Mock Config Content'))
size = 20
sha = 'mock-sha'
}
}
}
It 'Should load configuration from Get-GiteaConfiguration if not all parameters are given' {
$result = Get-GiteaFileContent -filePath 'test.ps1'
$result | Should -Not -BeNullOrEmpty
$result.Path | Should -Be 'test.ps1'
$result.Content | Should -Be ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('Mock Config Content')))
}
}
Context 'When API call fails' {
BeforeEach {
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith { throw "API call failed" }
Mock -CommandName Write-Error -ModuleName PS-GiteaUtilities -MockWith { }
}
It 'Should capture error and mark result as unsuccessful' {
$result = Get-GiteaFileContent -repoOwner 'user' -repoName 'repo' -filePath 'badfile.ps1' -token 'abc123'
$result.Success | Should -Be $false
$result.Error | Should -Match 'API call failed'
}
}
Context 'Parameter validation' {
It 'Should throw if required parameters are missing and no config' {
Mock -CommandName Get-GiteaConfiguration -ModuleName PS-GiteaUtilities -MockWith { return $null }
{ Get-GiteaFileContent -filePath 'something.ps1' } | Should -Throw
}
}
Context 'When decoding file content' {
BeforeEach {
# Simulate API returning Base64 content
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
return @{
content = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('Hello from API!'))
size = 15
sha = 'fake-sha-for-decode'
}
}
}
It 'Should return base64 encoded content by default (no decode switch)' {
$result = Get-GiteaFileContent -repoOwner 'user' -repoName 'repo' -filePath 'testfile.txt' -token 'abc123'
$result | Should -Not -BeNullOrEmpty
$result.Success | Should -Be $true
$result.Content | Should -Be ([System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('Hello from API!')))
}
It 'Should decode base64 content into plain text when -decode is used' {
$result = Get-GiteaFileContent -repoOwner 'user' -repoName 'repo' -filePath 'testfile.txt' -token 'abc123' -decode
$result | Should -Not -BeNullOrEmpty
$result.Success | Should -Be $true
$result.Content | Should -Be 'Hello from API!'
}
}
}

View File

@@ -0,0 +1,117 @@
# Get-GiteaReleases.Tests.ps1
# Import the module
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\PS-GiteaUtilities.psm1'
Import-Module -Name $modulePath -Force
Describe 'Get-GiteaReleases' {
BeforeEach {
# Mock configuration load
Mock -CommandName Get-GiteaConfiguration -ModuleName PS-GiteaUtilities -MockWith {
return @{
giteaURL = 'https://mock.gitea.com'
defaultOwner = 'mockuser'
token = 'mocktoken'
}
}
# Mock API response
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
return @(
@{
id = 1
tag_name = "v1.0"
target_commitish = "main"
name = "Release 1"
body = "Initial release"
url = "https://mock.gitea.com/api/v1/releases/1"
html_url = "https://mock.gitea.com/releases/v1.0"
tarball_url = "https://mock.gitea.com/releases/v1.0.tar.gz"
zipball_url = "https://mock.gitea.com/releases/v1.0.zip"
upload_url = "https://mock.gitea.com/releases/uploads"
draft = $false
prerelease = $false
created_at = "2024-01-01T00:00:00Z"
published_at = "2024-01-01T01:00:00Z"
author = @{
id = 101
username = "devuser"
email = "dev@norwichct.org"
avatar_url = "https://mock.gitea.com/avatar.jpg"
html_url = "https://mock.gitea.com/devuser"
}
assets = @(
@{
id = 201
name = "tool.exe"
size = 123456
download_count = 42
created_at = "2024-01-01T00:30:00Z"
uuid = "uuid-1234"
browser_download_url = "https://mock.gitea.com/releases/assets/tool.exe"
}
)
}
)
}
Mock -CommandName Write-Error -ModuleName PS-GiteaUtilities -MockWith { }
}
Context 'Basic usage' {
It 'Should return a list of release objects' {
$result = Get-GiteaReleases -repoOwner 'mockuser' -repoName 'mockrepo' -token 'mocktoken'
$result | Should -Not -BeNullOrEmpty
$result[0].Name | Should -Be 'Release 1'
$result[0].Author.Username | Should -Be 'devuser'
$result[0].Assets.Count | Should -Be 1
$result[0].Assets[0].Name | Should -Be 'tool.exe'
}
}
Context 'Optional switches affect URL' {
It 'Should include draft=true and pre-release=true in URL if switches are set' {
$calledUrl = $null
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
param($Uri)
$script:calledUrl = $Uri
return @()
}
Get-GiteaReleases -repoOwner 'mockuser' -repoName 'mockrepo' -includeDrafts -includePreReleases -page 2 -limit 10 -token 'mocktoken'
$script:calledUrl | Should -Match 'draft=true'
$script:calledUrl | Should -Match 'pre-release=true'
$script:calledUrl | Should -Match 'page=2'
$script:calledUrl | Should -Match 'limit=10'
}
}
Context 'When required params are missing and no config' {
BeforeEach {
Mock -CommandName Get-GiteaConfiguration -ModuleName PS-GiteaUtilities -MockWith { return $null }
}
It 'Should throw when required parameters are not supplied' {
{ Get-GiteaReleases -repoName 'mockrepo' } | Should -Throw
}
}
Context 'When API call fails' {
BeforeEach {
Mock -CommandName Invoke-RestMethod -ModuleName PS-GiteaUtilities -MockWith {
throw "API call failed"
}
Mock -CommandName Write-Error -ModuleName PS-GiteaUtilities -MockWith { }
}
It 'Should catch and handle the error' {
$null = Get-GiteaReleases -repoOwner 'mockuser' -repoName 'mockrepo' -token 'mocktoken'
# Expect no throw, Write-Error should catch it
}
}
}

View File

@@ -0,0 +1,122 @@
# Invoke-GiteaFileDownload.Tests.ps1
# Import the module under test
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\PS-GiteaUtilities.psm1'
Import-Module -Name $modulePath -Force
Describe 'Invoke-GiteaFileDownload' {
BeforeEach {
# Always mock Get-GiteaConfiguration to avoid needing real config
Mock -CommandName Get-GiteaConfiguration -ModuleName PS-GiteaUtilities -MockWith {
return @{ token = 'mocktoken' }
}
# Correct webClient Mock: headers, download, dispose
Mock -CommandName New-Object -ModuleName PS-GiteaUtilities -MockWith {
$headers = New-Object System.Net.WebHeaderCollection
# Create a real object
$webClient = New-Object PSObject
# Add Headers
Add-Member -InputObject $webClient -MemberType NoteProperty -Name Headers -Value $headers
# Add a DownloadFile method
Add-Member -InputObject $webClient -MemberType ScriptMethod -Name DownloadFile -Value {
param($url, $path)
# Fake download (do nothing)
}
# Add a Dispose method
Add-Member -InputObject $webClient -MemberType ScriptMethod -Name Dispose -Value {
# Fake dispose (do nothing)
}
return $webClient
}
# Mock filesystem behavior
Mock -CommandName Test-Path -ModuleName PS-GiteaUtilities -MockWith { $false }
Mock -CommandName New-Item -ModuleName PS-GiteaUtilities -MockWith { }
# Clean captured download data before each test
$script:LastDownloadUrl = $null
$script:LastDownloadPath = $null
}
Context 'When all parameters are valid' {
It 'Should download a file successfully' {
$result = Invoke-GiteaFileDownload -downloadURL 'https://gitea.example.com/raw/path/to/file.txt' -token 'abc123'
$result | Should -Not -BeNullOrEmpty
$result.Success | Should -Be $true
$result.Path | Should -Match 'file\.txt$'
}
}
Context 'When preserving relative paths' {
It 'Should create a path based on filePath with PreserveRelativePath' {
$result = Invoke-GiteaFileDownload -downloadURL 'https://gitea.example.com/raw/path/to/file.txt' `
-filePath 'docs/manual/file.txt' -PreserveRelativePath -token 'abc123'
$result.Path | Should -Match 'docs[\\/]+manual[\\/]+file\.txt$'
$result.Success | Should -Be $true
}
}
Context 'When skipping directories' {
It 'Should skip items with type = dir' {
$result = Invoke-GiteaFileDownload -filePath 'docs/' -type 'dir' -token 'abc123'
$result.Success | Should -Be $true
$result.Error | Should -Match 'Skipped'
}
}
Context 'When file exists and -Force is not used' {
BeforeEach {
# Simulate file already exists
Mock -CommandName Test-Path -ModuleName PS-GiteaUtilities -MockWith {
param($Path, $PathType)
if ($PathType -eq 'Leaf') { return $true } else { return $false }
}
Mock -CommandName Write-Error -ModuleName PS-GiteaUtilities -MockWith { }
}
It 'Should not overwrite existing file without Force' {
$result = Invoke-GiteaFileDownload -downloadURL 'https://gitea.example.com/raw/path/to/existingfile.txt' -token 'abc123'
$result.Success | Should -Be $false
$result.Error | Should -Match 'already exists'
}
}
Context 'When download fails' {
BeforeEach {
Mock -CommandName New-Object -ModuleName PS-GiteaUtilities -MockWith {
$headers = New-Object System.Net.WebHeaderCollection
$webClient = New-Object PSObject
Add-Member -InputObject $webClient -MemberType NoteProperty -Name Headers -Value $headers
Add-Member -InputObject $webClient -MemberType ScriptMethod -Name DownloadFile -Value {
throw "Download failed intentionally"
}
Add-Member -InputObject $webClient -MemberType ScriptMethod -Name Dispose -Value {
return $true
}
return $webClient
}
Mock -CommandName Test-Path -ModuleName PS-GiteaUtilities -MockWith { $false }
Mock -CommandName New-Item -ModuleName PS-GiteaUtilities -MockWith { }
Mock -CommandName Write-Error -ModuleName PS-GiteaUtilities -MockWith { }
}
It 'Should capture error and mark download as failed' {
$result = Invoke-GiteaFileDownload -downloadURL 'https://gitea.example.com/raw/path/to/badfile.txt' -token 'abc123'
$result.Success | Should -Be $false
$result.Error | Should -Match 'Failed to download'
}
}
}

View File

@@ -0,0 +1,64 @@
# Set-GiteaConfiguration.Tests.ps1
Describe 'Set-GiteaConfiguration' {
BeforeAll {
# Import the module dynamically
$modulePath = Join-Path -Path $PSScriptRoot -ChildPath '..\PS-GiteaUtilities.psm1'
Import-Module -Name $modulePath -Force
}
Context 'Parameter Validation' {
It 'Should throw a MissingArgument exception if required parameters are missing' {
{ Set-GiteaConfiguration -giteaURL -token} | Should -Throw
}
}
Context 'Configuration Setup' {
BeforeEach {
Mock -CommandName Test-Path -ModuleName PS-GiteaUtilities -MockWith { $false }
Mock -CommandName New-Item -ModuleName PS-GiteaUtilities -MockWith { return $null }
Mock -CommandName Export-Clixml -ModuleName PS-GiteaUtilities -MockWith { return $null }
}
It 'Should create the configuration directory if it does not exist' {
Set-GiteaConfiguration -giteaURL 'https://gitea.example.com' -token 'ABC123' -defaultOwner 'testuser' -defaultRepo 'testrepo'
Assert-MockCalled -CommandName New-Item -ModuleName PS-GiteaUtilities -Times 1
}
It 'Should export the configuration to an XML file' {
Set-GiteaConfiguration -giteaURL 'https://gitea.example.com' -token 'ABC123' -defaultOwner 'testuser' -defaultRepo 'testrepo'
Assert-MockCalled -CommandName Export-Clixml -ModuleName PS-GiteaUtilities -Times 1
}
It 'Should set defaultBranch to "main" if not specified' {
# Important: override the Export-Clixml mock to capture the config
Mock -CommandName Export-Clixml -ModuleName PS-GiteaUtilities -MockWith {
param($Path, $InputObject)
$script:CapturedConfig = $InputObject
}
Set-GiteaConfiguration -giteaURL 'https://gitea.example.com' -token 'ABC123' -defaultOwner 'testuser' -defaultRepo 'testrepo'
$script:CapturedConfig.defaultBranch | Should -Be 'main'
}
}
Context 'When directory already exists' {
BeforeEach {
Mock -CommandName Test-Path -MockWith { $true }
Mock -CommandName New-Item -MockWith { throw "Should not be called" }
Mock -CommandName Export-Clixml -MockWith { return $null }
}
It 'Should not attempt to create the directory' {
Set-GiteaConfiguration -giteaURL 'https://gitea.example.com' -token 'ABC123' -defaultOwner 'testuser' -defaultRepo 'testrepo'
Assert-MockCalled -CommandName New-Item -Times 0
}
}
}