From d9bf508abebf62a7b88f25ea501cb8115e6c203b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Sun, 21 Sep 2025 12:11:11 +0200 Subject: [PATCH 01/13] fea(Build-Node): add level assigment to node --- public/buildTree.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/public/buildTree.ps1 b/public/buildTree.ps1 index 1402c61..ca94e94 100644 --- a/public/buildTree.ps1 +++ b/public/buildTree.ps1 @@ -39,6 +39,7 @@ function Build-Node { ## Manager # Set to null the manager for the CEO where manager == him self $node.manager = ($node.manager -ne $nlogin) ? $hubbers.$($node.manager) : $null + $node.level = $node.manager ? $node.manager.level + 1 : 0 ## Employees From e73a4271198c466142ebe417cd26c9fa64b9a627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 22 Sep 2025 06:57:54 +0200 Subject: [PATCH 02/13] feat(helper): add module helper functions for database and invoke command management --- Test/helper/module.helper.ps1 | 171 +++++++++++++++++++ Test/include/database.mock.ps1 | 41 +++++ Test/include/invokeCommand.mock.ps1 | 255 ++++++++++++++++++++++++++++ helper/invokeCommand.helper.ps1 | 41 +++++ helper/module.helper.ps1 | 171 +++++++++++++++++++ include/databaseV2.ps1 | 165 ++++++++++++++++++ 6 files changed, 844 insertions(+) create mode 100644 Test/helper/module.helper.ps1 create mode 100644 Test/include/database.mock.ps1 create mode 100644 Test/include/invokeCommand.mock.ps1 create mode 100644 helper/invokeCommand.helper.ps1 create mode 100644 helper/module.helper.ps1 create mode 100644 include/databaseV2.ps1 diff --git a/Test/helper/module.helper.ps1 b/Test/helper/module.helper.ps1 new file mode 100644 index 0000000..7fd1786 --- /dev/null +++ b/Test/helper/module.helper.ps1 @@ -0,0 +1,171 @@ +# Helper for module variables + +function Find-ModuleRootPath{ + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline,Position = 0)] + [string]$Path + ) + + $path = Convert-Path -Path $Path + + while (-not [string]::IsNullOrWhiteSpace($Path)){ + $psd1 = Get-ChildItem -Path $Path -Filter *.psd1 | Select-Object -First 1 + + if ($psd1 | Test-Path) { + + if($psd1.BaseName -eq "Test"){ + #foudn testing module. Continue + $path = $path | Split-Path -Parent + continue + } + + # foudn module + return $path + } + # folder without psd1 file + $path = $path | Split-Path -Parent + } + + # Path is null. Reached driver root. Module not found + return $null +} + +$MODULE_ROOT_PATH = $PSScriptRoot | Find-ModuleRootPath +$MODULE_NAME = (Get-ChildItem -Path $MODULE_ROOT_PATH -Filter *.psd1 | Select-Object -First 1).BaseName + +# Helper for module variables + + +$VALID_FOLDER_NAMES = @('Include', 'Private', 'Public', 'Root', 'TestInclude', 'TestPrivate', 'TestPublic', 'TestRoot', 'Tools', 'DevContainer', 'WorkFlows', 'GitHub', 'Helper', 'Config', 'TestHelper', 'TestConfig') + +class ValidFolderNames : System.Management.Automation.IValidateSetValuesGenerator { + [String[]] GetValidValues() { + return $script:VALID_FOLDER_NAMES + } +} + +function Get-Ps1FullPath{ + [CmdletBinding()] + param( + [Parameter(Mandatory,Position = 0)][string]$Name, + [Parameter(Position = 1)][ValidateSet([ValidFolderNames])][string]$FolderName, + [Parameter(Position = 0)][string]$ModuleRootPath + ) + + # If folderName is not empty + if($FolderName -ne $null){ + $folder = Get-ModuleFolder -FolderName $FolderName -ModuleRootPath $ModuleRootPath + $path = $folder | Join-Path -ChildPath $Name + } else { + $path = $Name + } + + # Check if file exists + if(-Not (Test-Path $path)){ + throw "File $path not found" + } + + # Get Path item + $item = Get-item -Path $path + + return $item +} +function Get-ModuleRootPath{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$ModuleRootPath + ) + + # if ModuleRootPath is not provided, default to local module path + if([string]::IsNullOrWhiteSpace($ModuleRootPath)){ + $ModuleRootPath = $MODULE_ROOT_PATH + } + + # Convert to full path + $ModuleRootPath = Convert-Path -Path $ModuleRootPath + + return $ModuleRootPath +} + +function Get-ModuleName{ + [CmdletBinding()] + param( + [Parameter(Position = 0)] [string]$ModuleRootPath + ) + + $ModuleRootPath = Get-ModuleRootPath -ModuleRootPath $ModuleRootPath + + $MODULE_NAME = (Get-ChildItem -Path $MODULE_ROOT_PATH -Filter *.psd1 | Select-Object -First 1).BaseName + + + return $MODULE_NAME +} + +function Get-ModuleFolder{ + [CmdletBinding()] + param( + [Parameter(Mandatory,Position = 0)][ValidateSet([ValidFolderNames])][string]$FolderName, + [Parameter(Position = 1)][string]$ModuleRootPath + ) + + $ModuleRootPath = Get-ModuleRootPath -ModuleRootPath $ModuleRootPath + + # TestRootPath + $testRootPath = $ModuleRootPath | Join-Path -ChildPath "Test" + + switch ($FolderName){ + 'Public'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "public" + } + 'Private'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "private" + } + 'Include'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "include" + } + 'TestInclude'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "include" + } + 'TestPrivate'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "private" + } + 'TestPublic'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "public" + } + 'Root'{ + $moduleFolder = $ModuleRootPath + } + 'TestRoot'{ + $moduleFolder = $testRootPath + } + 'Tools'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "tools" + } + 'DevContainer'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath ".devcontainer" + } + 'WorkFlows'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath ".github/workflows" + } + 'GitHub'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath ".github" + } + 'Helper'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "helper" + } + 'Config'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "config" + } + 'TestHelper'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "helper" + } + 'TestConfig'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "config" + } + default{ + throw "Folder [$FolderName] is unknown" + } + } + return $moduleFolder +} Export-ModuleMember -Function Get-ModuleFolder diff --git a/Test/include/database.mock.ps1 b/Test/include/database.mock.ps1 new file mode 100644 index 0000000..85ff32c --- /dev/null +++ b/Test/include/database.mock.ps1 @@ -0,0 +1,41 @@ +# DATABASE MOCK +# +# This file is used to mock the database path and the database file +# for the tests. It creates a mock database path and a mock database file +# and sets the database path to the mock database path. +# +# THIS INCLUDE REQURED module.helper.ps1 +if(-not $MODULE_NAME){ throw "Missing MODULE_NAME varaible initialization. Check for module.helerp.ps1 file." } + +$DB_INVOKE_GET_ROOT_PATH_CMD = "Invoke-$($MODULE_NAME)GetDbRootPath" +$MOCK_DATABASE_PATH = "test_database_path" + +function Mock_Database([switch]$ResetDatabase){ + + MockCallToString $DB_INVOKE_GET_ROOT_PATH_CMD -OutString $MOCK_DATABASE_PATH + + $dbstore = Invoke-MyCommand -Command $DB_INVOKE_GET_ROOT_PATH_CMD + Assert-AreEqual -Expected $MOCK_DATABASE_PATH -Presented $dbstore + + if($ResetDatabase){ + Reset-DatabaseStore + } + +} + +function Get-Mock_DatabaseStore{ + $dbstore = Invoke-MyCommand -Command $DB_INVOKE_GET_ROOT_PATH_CMD + return $dbstore +} + +function Reset-DatabaseStore{ + [CmdletBinding()] + param() + + $databaseRoot = Invoke-MyCommand -Command $DB_INVOKE_GET_ROOT_PATH_CMD + + Remove-Item -Path $databaseRoot -Recurse -Force -ErrorAction SilentlyContinue + + New-Item -Path $databaseRoot -ItemType Directory + +} diff --git a/Test/include/invokeCommand.mock.ps1 b/Test/include/invokeCommand.mock.ps1 new file mode 100644 index 0000000..f5ee15d --- /dev/null +++ b/Test/include/invokeCommand.mock.ps1 @@ -0,0 +1,255 @@ + +# INVOKE COMMAND MOCK +# +# This includes help commands to mock invokes in a test module +# +# THIS INCLUDE REQURED module.helper.ps1 +if(-not $MODULE_NAME){ throw "Missing MODULE_NAME varaible initialization. Check for module.helerp.ps1 file." } +if(-not $MODULE_ROOT_PATH){ throw "Missing MODULE_ROOT_PATH varaible initialization. Check for module.helerp.ps1 file." } + + +$testRootPath = $MODULE_ROOT_PATH | Join-Path -ChildPath 'Test' +$MOCK_PATH = $testRootPath | Join-Path -ChildPath 'private' -AdditionalChildPath 'mocks' + +$MODULE_INVOKATION_TAG = "$($MODULE_NAME)Module" +$MODULE_INVOKATION_TAG_MOCK = "$($MODULE_INVOKATION_TAG)_Mock" + +function Set-InvokeCommandMock{ + [CmdletBinding()] + param( + [Parameter(Mandatory,Position=0)][string]$Alias, + [Parameter(Mandatory,Position=1)][string]$Command + ) + + InvokeHelper\Set-InvokeCommandAlias -Alias $Alias -Command $Command -Tag $MODULE_INVOKATION_TAG_MOCK +} + +function Reset-InvokeCommandMock{ + [CmdletBinding()] + param() + + # Remove all mocks + InvokeHelper\Reset-InvokeCommandAlias -Tag $MODULE_INVOKATION_TAG_MOCK + + # Disable all dependecies of the library + Disable-InvokeCommandAlias -Tag $MODULE_INVOKATION_TAG + + # Clear Enviroment variables used + Get-Variable -scope Global -Name "$($MODULE_INVOKATION_TAG_MOCK)_*" | Remove-Variable -Force -Scope Global + +} Export-ModuleMember -Function Reset-InvokeCommandMock + +function Enable-InvokeCommandAliasModule{ + [CmdletBinding()] + param() + + Enable-InvokeCommandAlias -Tag $MODULE_INVOKATION_TAG +} Export-ModuleMember -Function Enable-InvokeCommandAliasModule + +function MockCall{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][string] $filename + ) + + Assert-MockFileNotfound $fileName + + Set-InvokeCommandMock -Alias $command -Command "Get-MockFileContent -filename $filename" +} + +function MockCallAsync{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][string] $filename + ) + + Assert-MockFileNotfound $fileName + + $moduleTest = $PSScriptRoot | Split-Path -Parent | Convert-Path + + Set-InvokeCommandMock -Alias $command -Command "Import-Module $moduleTest ; Get-MockFileContent -filename $filename" +} + +function MockCallJson{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][string] $filename, + [Parameter()][switch] $AsHashtable + + ) + + Assert-MockFileNotfound $fileName + + $has = $AsHashtable ? '$true' : '$false' + + Set-InvokeCommandMock -Alias $command -Command "Get-MockFileContentJson -filename $filename -AsHashtable:$has" +} + +function MockCallJsonAsync{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][string] $filename + + ) + + Assert-MockFileNotfound $fileName + + $moduleTest = $PSScriptRoot | Split-Path -Parent | Convert-Path + + Set-InvokeCommandMock -Alias $command -Command "Import-Module $moduleTest ; Get-MockFileContentJson -filename $filename" +} + +function Get-MockFileFullPath{ + param( + [parameter(Mandatory,Position=0)][string] $fileName + ) + + $filePath = $MOCK_PATH | Join-Path -ChildPath $fileName + + return $filePath +} Export-ModuleMember -Function Get-MockFileFullPath + +function Get-MockFileContent{ + param( + [parameter(Mandatory,Position=0)][string] $fileName + ) + + Assert-MockFileNotfound $FileName + + $filePath = Get-MockFileFullPath -fileName $fileName + + $content = Get-Content -Path $filePath | Out-String + + return $content +} Export-ModuleMember -Function Get-MockFileContent + +function Get-MockFileContentJson{ + param( + [parameter(Mandatory,Position=0)][string] $fileName, + [Parameter()][switch] $AsHashtable + ) + + Assert-MockFileNotfound $FileName + + $content = Get-MockFileContent -fileName $filename | ConvertFrom-Json -AsHashtable:$AsHashtable -Depth 10 + + return $content +} Export-ModuleMember -Function Get-MockFileContentJson + +function MockCallToString{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][string] $OutString + ) + + $outputstring = 'echo "{output}"' + $outputstring = $outputstring -replace "{output}", $OutString + + Set-InvokeCommandMock -Alias $command -Command $outputstring +} + + +function MockCallToObject{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][object] $OutObject + ) + + $random = [System.Guid]::NewGuid().ToString() + $varName = "$MODULE_INVOKATION_TAG_MOCK" + "_$random" + + Set-Variable -Name $varName -Value $OutObject -Scope Global + + Set-InvokeCommandMock -Alias $command -Command "(Get-Variable -Name $varName -Scope Global).Value" +} + +function MockCallToNull{ + param( + [Parameter(Position=0)][string] $command + ) + + Set-InvokeCommandMock -Alias $command -Command 'return $null' +} + +function MockCallThrow{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][string] $ExceptionMessage + + ) + + $mockCommand = 'throw "{message}"' + $mockCommand = $mockCommand -replace "{message}", $exceptionMessage + + Set-InvokeCommandMock -Alias $command -Command $mockCommand +} + +function MockCallExpression{ + param( + [Parameter(Position=0)][string] $command, + [Parameter(Position=1)][string] $expression + ) + + $mockCommand = @' + Invoke-Expression -Command '{expression}' +'@ + $mockCommand = $mockCommand -replace "{expression}", $expression + + Set-InvokeCommandMock -Alias $command -Command $expression +} + + +function Save-InvokeAsMockFile{ + param( + [Parameter(Mandatory=$true)] [string]$Command, + [Parameter(Mandatory=$true)] [string]$FileName, + [Parameter(Mandatory=$false)] [switch]$Force + ) + + $filePath = Get-MockFileFullPath -fileName $fileName + + $result = Invoke-Expression -Command $Command + + $json = $result | ConvertTo-Json -Depth 100 + + $json | Out-File -FilePath $filePath + + Write-Host $FileName +} Export-ModuleMember -Function Save-InvokeAsMockFile + +function Save-InvokeAsMockFileJson{ + param( + [Parameter(Mandatory=$true)] [string]$Command, + [Parameter(Mandatory=$true)] [string]$FileName + ) + + $filePath = Get-MockFileFullPath -fileName $fileName + + $result = Invoke-Expression -Command $Command + + $result | Out-File -FilePath $filePath + + Write-Host $FileName +} Export-ModuleMember -Function Save-InvokeAsMockFileJson + +function Assert-MockFileNotfound{ + param( + [Parameter(Mandatory=$true,Position=0)] [string]$FileName + ) + + $filePath = Get-MockFileFullPath -fileName $fileName + + if(-Not (Test-Path -Path $filePath)){ + throw "File not found: $fileName" + } + + # Throw if $file.name and the $filename parameter have different case + # We need to check this to avoid test bugs for mock files not found on linux that the FS is case sensitive + $file = Get-ChildItem -Path $MOCK_PATH | Where-Object { $_.Name.ToLower() -eq $fileName.ToLower() } + if($file.name -cne $fileName){ + Write-host "Wait-Debugger - File not found or wrong case - $($file.name)" + Wait-Debugger + throw "File not found or wrong case name. Expected[ $filename ] - Found[$( $file.name )]" + } +} + diff --git a/helper/invokeCommand.helper.ps1 b/helper/invokeCommand.helper.ps1 new file mode 100644 index 0000000..7bbe0bb --- /dev/null +++ b/helper/invokeCommand.helper.ps1 @@ -0,0 +1,41 @@ + +# SET MY INVOKE COMMAND ALIAS +# +# Allows calling constitely InvokeHelper with the module tag +# Need to define a variable called $MODULE_INVOKATION_TAG +# + +$moduleRootPath = $PSScriptRoot | Split-Path -Parent +$MODULE_NAME = (Get-ChildItem -Path $moduleRootPath -Filter *.psd1 | Select-Object -First 1).BaseName +$MODULE_INVOKATION_TAG = "$($MODULE_NAME)Module" + +function Set-MyInvokeCommandAlias{ + [CmdletBinding(SupportsShouldProcess)] + param( + [Parameter(Mandatory,Position=0)][string]$Alias, + [Parameter(Mandatory,Position=1)][string]$Command + ) + + # throw if MODULE_INVOKATION_TAG is not set or is "MyModuleModule" + if (-not $MODULE_INVOKATION_TAG) { + throw "MODULE_INVOKATION_TAG is not set. Please set it to a unique value before calling Set-MyInvokeCommandAlias." + } + + if ($PSCmdlet.ShouldProcess("InvokeCommandAliasList", ("Add Command Alias [{0}] = [{1}]" -f $Alias, $Command))) { + InvokeHelper\Set-InvokeCommandAlias -Alias $Alias -Command $Command -Tag $MODULE_INVOKATION_TAG + } +} + +function Reset-MyInvokeCommandAlias{ + [CmdletBinding(SupportsShouldProcess)] + param() + + # throw if MODULE_INVOKATION_TAG is not set or is "MyModuleModule" + if (-not $MODULE_INVOKATION_TAG) { + throw "MODULE_INVOKATION_TAG is not set. Please set it to a unique value before calling Set-MyInvokeCommandAlias." + } + InvokeHelper\Reset-InvokeCommandAlias -Tag $MODULE_INVOKATION_TAG +} + +# Reset all aliases for this module on each refresh +Reset-MyInvokeCommandAlias diff --git a/helper/module.helper.ps1 b/helper/module.helper.ps1 new file mode 100644 index 0000000..7fd1786 --- /dev/null +++ b/helper/module.helper.ps1 @@ -0,0 +1,171 @@ +# Helper for module variables + +function Find-ModuleRootPath{ + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline,Position = 0)] + [string]$Path + ) + + $path = Convert-Path -Path $Path + + while (-not [string]::IsNullOrWhiteSpace($Path)){ + $psd1 = Get-ChildItem -Path $Path -Filter *.psd1 | Select-Object -First 1 + + if ($psd1 | Test-Path) { + + if($psd1.BaseName -eq "Test"){ + #foudn testing module. Continue + $path = $path | Split-Path -Parent + continue + } + + # foudn module + return $path + } + # folder without psd1 file + $path = $path | Split-Path -Parent + } + + # Path is null. Reached driver root. Module not found + return $null +} + +$MODULE_ROOT_PATH = $PSScriptRoot | Find-ModuleRootPath +$MODULE_NAME = (Get-ChildItem -Path $MODULE_ROOT_PATH -Filter *.psd1 | Select-Object -First 1).BaseName + +# Helper for module variables + + +$VALID_FOLDER_NAMES = @('Include', 'Private', 'Public', 'Root', 'TestInclude', 'TestPrivate', 'TestPublic', 'TestRoot', 'Tools', 'DevContainer', 'WorkFlows', 'GitHub', 'Helper', 'Config', 'TestHelper', 'TestConfig') + +class ValidFolderNames : System.Management.Automation.IValidateSetValuesGenerator { + [String[]] GetValidValues() { + return $script:VALID_FOLDER_NAMES + } +} + +function Get-Ps1FullPath{ + [CmdletBinding()] + param( + [Parameter(Mandatory,Position = 0)][string]$Name, + [Parameter(Position = 1)][ValidateSet([ValidFolderNames])][string]$FolderName, + [Parameter(Position = 0)][string]$ModuleRootPath + ) + + # If folderName is not empty + if($FolderName -ne $null){ + $folder = Get-ModuleFolder -FolderName $FolderName -ModuleRootPath $ModuleRootPath + $path = $folder | Join-Path -ChildPath $Name + } else { + $path = $Name + } + + # Check if file exists + if(-Not (Test-Path $path)){ + throw "File $path not found" + } + + # Get Path item + $item = Get-item -Path $path + + return $item +} +function Get-ModuleRootPath{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$ModuleRootPath + ) + + # if ModuleRootPath is not provided, default to local module path + if([string]::IsNullOrWhiteSpace($ModuleRootPath)){ + $ModuleRootPath = $MODULE_ROOT_PATH + } + + # Convert to full path + $ModuleRootPath = Convert-Path -Path $ModuleRootPath + + return $ModuleRootPath +} + +function Get-ModuleName{ + [CmdletBinding()] + param( + [Parameter(Position = 0)] [string]$ModuleRootPath + ) + + $ModuleRootPath = Get-ModuleRootPath -ModuleRootPath $ModuleRootPath + + $MODULE_NAME = (Get-ChildItem -Path $MODULE_ROOT_PATH -Filter *.psd1 | Select-Object -First 1).BaseName + + + return $MODULE_NAME +} + +function Get-ModuleFolder{ + [CmdletBinding()] + param( + [Parameter(Mandatory,Position = 0)][ValidateSet([ValidFolderNames])][string]$FolderName, + [Parameter(Position = 1)][string]$ModuleRootPath + ) + + $ModuleRootPath = Get-ModuleRootPath -ModuleRootPath $ModuleRootPath + + # TestRootPath + $testRootPath = $ModuleRootPath | Join-Path -ChildPath "Test" + + switch ($FolderName){ + 'Public'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "public" + } + 'Private'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "private" + } + 'Include'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "include" + } + 'TestInclude'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "include" + } + 'TestPrivate'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "private" + } + 'TestPublic'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "public" + } + 'Root'{ + $moduleFolder = $ModuleRootPath + } + 'TestRoot'{ + $moduleFolder = $testRootPath + } + 'Tools'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "tools" + } + 'DevContainer'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath ".devcontainer" + } + 'WorkFlows'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath ".github/workflows" + } + 'GitHub'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath ".github" + } + 'Helper'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "helper" + } + 'Config'{ + $moduleFolder = $ModuleRootPath | Join-Path -ChildPath "config" + } + 'TestHelper'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "helper" + } + 'TestConfig'{ + $moduleFolder = $testRootPath | Join-Path -ChildPath "config" + } + default{ + throw "Folder [$FolderName] is unknown" + } + } + return $moduleFolder +} Export-ModuleMember -Function Get-ModuleFolder diff --git a/include/databaseV2.ps1 b/include/databaseV2.ps1 new file mode 100644 index 0000000..3b6c88d --- /dev/null +++ b/include/databaseV2.ps1 @@ -0,0 +1,165 @@ +# DATABASE V2 +# +# Database driver to store the cache +# +# Include design description +# This is the function ps1. This file is the same for all modules. +# Create a public psq with variables, Set-MyInvokeCommandAlias call and Invoke public function. +# Invoke function will call back `GetDatabaseRootPath` to use production root path +# Mock this Invoke function with Set-MyInvokeCommandAlias to set the Store elsewhere +# This ps1 has function `GetDatabaseFile` that will call `Invoke-MyCommand -Command $DB_INVOKE_GET_ROOT_PATH_ALIAS` +# to use the store path, mocked or not, to create the final store file name. +# All functions of this ps1 will depend on `GetDatabaseFile` for functionality. +# + +$MODULE_ROOT_PATH = $PSScriptRoot | Split-Path -Parent +$MODULE_NAME = (Get-ChildItem -Path $MODULE_ROOT_PATH -Filter *.psd1 | Select-Object -First 1).BaseName +$DATABASE_ROOT = [System.Environment]::GetFolderPath('UserProfile') | Join-Path -ChildPath ".helpers" -AdditionalChildPath $MODULE_NAME, "databaseCache" + +$DB_INVOKE_GET_ROOT_PATH_ALIAS = "$($MODULE_NAME)GetDbRootPath" + +$function = "Invoke-$($MODULE_NAME)GetDbRootPath" +if(-not (Test-Path -Path function:$function)){ + + # PUBLIC FUNCTION + function Invoke-MyModuleGetDbRootPath{ + [CmdletBinding()] + param() + + $databaseRoot = GetDatabaseRootPath + return $databaseRoot + + } + Rename-Item -path Function:Invoke-MyModuleGetDbRootPath -NewName $function + Export-ModuleMember -Function $function + Set-MyInvokeCommandAlias -Alias $DB_INVOKE_GET_ROOT_PATH_ALIAS -Command $function +} + +# Extra functions not needed by INCLUDE DATABASE V2 +$function = "Reset-$($MODULE_NAME)DatabaseStore" +if(-not (Test-Path -Path function:$function)){ + function Reset-MyModuleDatabaseStore{ + [CmdletBinding()] + param() + + $databaseRoot = Invoke-MyCommand -Command $DB_INVOKE_GET_ROOT_PATH_ALIAS + + Remove-Item -Path $databaseRoot -Recurse -Force -ErrorAction SilentlyContinue + + New-Item -Path $databaseRoot -ItemType Directory + + } + + Rename-Item -path Function:Reset-MyModuleDatabaseStore -NewName $function + Export-ModuleMember -Function $function +} + +# PRIVATE FUNCTIONS +function GetDatabaseRootPath { + [CmdletBinding()] + param() + + $databaseRoot = $DATABASE_ROOT + return $databaseRoot +} + +function GetDatabaseFile{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key, + [Parameter(Position = 1)][ValidateSet("JSON","XML")][string]$DBFormat = "JSON" + ) + + $databaseRoot = Invoke-MyCommand -Command $DB_INVOKE_GET_ROOT_PATH_ALIAS + + $ext = GetFileExtension -DbFormat $DBFormat + + $path = $databaseRoot | Join-Path -ChildPath "$Key$ext" + + return $path +} + +function GetFileExtension{ + [CmdletBinding()] + param( + [string]$DbFormat = "JSON" + ) + + switch ($DbFormat.ToUpper()){ + "JSON" { $ret = ".json" ; Break } + "XML" { $ret = ".xml" ; Break } + default { throw "Unsupported database format $DbFormat" } + } + return $ret +} + +function Get-DatabaseKey{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key, + [Parameter(Position = 1)][ValidateSet("JSON","XML")][string]$DBFormat = "JSON" + ) + + if(-Not (Test-DatabaseKey $Key -DBFormat $DBFormat)){ + return $null + } + + $path = GetDatabaseFile $Key -DBFormat $DBFormat + + switch ($DBFormat) { + "JSON" { $ret = Get-Content $path | ConvertFrom-Json ; Break } + "XML" { $ret = Import-Clixml -Path $path ; Break } + default { throw "Unsupported database format $DbFormat" } + } + + return $ret +} + +function Reset-DatabaseKey{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key, + [Parameter(Position = 1)][ValidateSet("JSON","XML")][string]$DBFormat = "JSON" + ) + $path = GetDatabaseFile -Key $Key -DBFormat $DBFormat + Remove-Item -Path $path -Force -ErrorAction SilentlyContinue + return +} + +function Save-DatabaseKey{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key, + [Parameter(Position = 2)][Object]$Value, + [Parameter(Position = 3)][ValidateSet("JSON","XML")][string]$DbFormat = "JSON" + ) + + $path = GetDatabaseFile -Key $Key -DBFormat $DbFormat + + switch ($DbFormat) { + "JSON" { $Value | ConvertTo-Json -Depth 10 | Set-Content $path -Encoding UTF8 -Force ; Break } + "XML" { $Value | Export-Clixml -Path $path ; Break } + default { throw "Unsupported database format $DbFormat" + } + } +} + +function Test-DatabaseKey{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key, + [Parameter(Position = 1)][ValidateSet("JSON","XML")][string]$DBFormat = "JSON" + ) + + $path = GetDatabaseFile -Key $Key -DBFormat $DBFormat + + # Key file not exists + if(-Not (Test-Path $path)){ + return $false + } + + # TODO: Return $false if cache has expired + + return $true +} + From 3e4c860874eb36df9d1e4eae3d125cb33cf3aef1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 18:15:14 +0200 Subject: [PATCH 03/13] fix(database): ensure XML export forces overwrite in Save-DatabaseKey function --- include/databaseV2.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/databaseV2.ps1 b/include/databaseV2.ps1 index 3b6c88d..79c2855 100644 --- a/include/databaseV2.ps1 +++ b/include/databaseV2.ps1 @@ -138,7 +138,7 @@ function Save-DatabaseKey{ switch ($DbFormat) { "JSON" { $Value | ConvertTo-Json -Depth 10 | Set-Content $path -Encoding UTF8 -Force ; Break } - "XML" { $Value | Export-Clixml -Path $path ; Break } + "XML" { $Value | Export-Clixml -Path $path -Force; Break } default { throw "Unsupported database format $DbFormat" } } From a8944757d20f69050c2494ec23f32b9cf95fffb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 18:37:45 +0200 Subject: [PATCH 04/13] fix(database): ensure database root directory is created if it does not exist --- include/databaseV2.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/databaseV2.ps1 b/include/databaseV2.ps1 index 79c2855..1201c9d 100644 --- a/include/databaseV2.ps1 +++ b/include/databaseV2.ps1 @@ -72,6 +72,10 @@ function GetDatabaseFile{ $databaseRoot = Invoke-MyCommand -Command $DB_INVOKE_GET_ROOT_PATH_ALIAS + if(-not (Test-Path -Path $databaseRoot)){ + New-Item -Path $databaseRoot -ItemType Directory -Force | Out-Null + } + $ext = GetFileExtension -DbFormat $DBFormat $path = $databaseRoot | Join-Path -ChildPath "$Key$ext" From af68166d25b62686a28ffa48a844bdd3be11a670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 18:38:42 +0200 Subject: [PATCH 05/13] refactor(getHubber): replace direct JSON file access with command invocation --- public/getHubber.ps1 | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/public/getHubber.ps1 b/public/getHubber.ps1 index a86645b..fc5773f 100644 --- a/public/getHubber.ps1 +++ b/public/getHubber.ps1 @@ -1,3 +1,6 @@ + +Set-MyInvokeCommandAlias -Alias GetHubbers -Command "Invoke-GetHubbers" + function Get-Hubber{ [CmdletBinding()] param ( @@ -5,7 +8,7 @@ function Get-Hubber{ [Parameter()][switch]$AsHashtable ) - $hubbers = Get-Content -Path "~/hubbers.json" | ConvertFrom-Json -Depth 10 -AsHashtable + $hubbers = Invoke-MyCommand -Command GetHubbers if(! [string]::IsNullOrWhiteSpace($Handle)){ return [pscustomobject] $hubbers.$Handle @@ -23,9 +26,7 @@ function Get-HubberByCountry { [string]$Country ) - $path = Get-HubberJsonPath - - $hubbers = Get-Content -Path $path | ConvertFrom-Json -Depth 10 -AsHashtable + $hubbers = Invoke-MyCommand -Command GetHubbers $ret = $hubbers.Values | Where-Object { $_.country -eq $Country } @@ -33,8 +34,14 @@ function Get-HubberByCountry { } Export-ModuleMember -Function Get-HubberByCountry -function Get-HubberJsonPath() { +function Invoke-GetHubbers { + [CmdletBinding()] + param() # Downloaded from https://thehub.github.com/assets/org-data.json - return "~/hubbers.json" -} \ No newline at end of file + $path = "~/hubbers.json" + + $hubbers = Get-Content -Path $path | ConvertFrom-Json -Depth 10 -AsHashtable + + return $hubbers +} Export-ModuleMember -Function Invoke-GetHubbers \ No newline at end of file From f73a4624145fa513664cf2239f5bc3a376a9896a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 18:39:03 +0200 Subject: [PATCH 06/13] feat(database): add functions to save and retrieve hubbers list and tree --- public/exporthubbers.ps1 | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 public/exporthubbers.ps1 diff --git a/public/exporthubbers.ps1 b/public/exporthubbers.ps1 new file mode 100644 index 0000000..fce3f1f --- /dev/null +++ b/public/exporthubbers.ps1 @@ -0,0 +1,33 @@ +function Save-HubbersListDb { + param( + [Parameter(Mandatory)][object]$Hubbers + ) + + Save-DatabaseKey -Key HubbersList -Value $Hubbers -DBFormat XML + +} + +function Save-HubbersTreeDb { + param( + [Parameter(Mandatory)][object]$Hubbers + ) + + Save-DatabaseKey -Key HubbersTree -Value $Hubbers -DBFormat XML + +} + +function Get-HubbersList { + param() + + $hubbers = Get-DatabaseKey -Key HubbersList -DBFormat XML + + return $hubbers +} Export-ModuleMember -Function Get-HubbersList + +function Get-HubbersTree { + param() + + $tree = Get-DatabaseKey -Key HubbersTree -DBFormat XML + + return $tree +} Export-ModuleMember -Function Get-HubbersTree \ No newline at end of file From b65024719330cc217f40f4a7754ee9075ea7c229 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 18:39:11 +0200 Subject: [PATCH 07/13] feat(tests): update Build-HubbersTree tests and add mock data --- Test/private/mocks/hubbers.json | 151 ++++++++++++++++++++++++++++++++ Test/public/buildTree.test.ps1 | 20 +++-- public/buildTree.ps1 | 46 ++++++---- 3 files changed, 191 insertions(+), 26 deletions(-) create mode 100644 Test/private/mocks/hubbers.json diff --git a/Test/private/mocks/hubbers.json b/Test/private/mocks/hubbers.json new file mode 100644 index 0000000..e0667b9 --- /dev/null +++ b/Test/private/mocks/hubbers.json @@ -0,0 +1,151 @@ +{ + "user3": { + "manager": "user0", + "employment_type": "employee", + "github_login": "user3", + "msft_alias": "user3MSFT", + "name": "User Name 1", + "email": "user3@github.com", + "title": "Chief Operating Officer", + "cost_center": "Strategic Operations", + "country": "United States of America", + "state": "Connecticut" + }, + "user1": { + "manager": "user0", + "employment_type": "employee", + "github_login": "user1", + "msft_alias": "user1MSFT", + "name": "User Name 1", + "email": "user1@github.com", + "title": "Chief Operating Officer", + "cost_center": "Strategic Operations", + "country": "United States of America", + "state": "Connecticut" + }, + "user2": { + "manager": "user0", + "employment_type": "microsoft_employee", + "github_login": "user2", + "msft_alias": "user2MSFT", + "name": "User Name 2", + "email": "user2@github.com", + "title": "Chief Revenue Officer", + "cost_center": "Office of the CEO", + "country": "United States of America", + "state": "Maryland" + }, + "user0": { + "manager": "user0", + "employment_type": "microsoft_employee", + "github_login": "user0", + "msft_alias": "user0MSFT", + "name": "User Name 0", + "email": "user0@github.com", + "title": "CEO", + "cost_center": "Office of the CEO", + "country": "United States of America\"", + "state": "Washington" + }, + "user4": { + "manager": "user2", + "employment_type": "employee", + "github_login": "user4", + "msft_alias": "user4MSFT", + "name": "User Name 4", + "email": "user4@github.com", + "title": "Chief Product Officer", + "cost_center": "Product Management", + "country": "United States of America", + "state": "North Carolina" + }, + "user5": { + "manager": "user1", + "employment_type": "microsoft_employee", + "github_login": "user5", + "msft_alias": "user5MSFT", + "name": "User Name 5", + "email": "user5@github.com", + "title": "VP, Advisor to the CEO", + "cost_center": "Office of the CEO", + "country": "United States of America" + }, + "user6": { + "manager": "user3", + "employment_type": "employee", + "github_login": "user6", + "msft_alias": "user6MSFT", + "name": "User Name 6", + "email": "user6@github.com", + "title": "VP, COS to the CEO", + "cost_center": "Office of the CEO", + "country": "United States of America", + "state": "North Carolina" + }, + "user7": { + "manager": "user1", + "employment_type": "microsoft_employee", + "github_login": "user7", + "msft_alias": "user7MSFT", + "name": "User Name 7", + "email": "user7@github.com", + "title": "Chief Legal Officer", + "cost_center": "Legal", + "country": "United States of America" + }, + "user8": { + "manager": "user2", + "employment_type": "employee", + "github_login": "user8", + "msft_alias": "user8MSFT", + "name": "User Name 8", + "email": "user8@github.com", + "title": "CISO", + "cost_center": "Security", + "country": "United States of America", + "state": "Virginia" + }, + "user9": { + "manager": "user3", + "employment_type": "microsoft_employee", + "github_login": "user9", + "name": "Jay Parikh", + "user9MSFT": "jayparikh@github.com", + "title": "User Name 9", + "cost_center": "user9 of the CEO", + "country": "United States of America" + }, + "user10": { + "manager": "user2", + "employment_type": "microsoft_employee", + "github_login": "user10", + "msft_alias": "user10MSFT", + "name": "User Name 10", + "email": "user10@github.com", + "title": "Chief People Officer", + "cost_center": "Office of the CEO", + "country": "United States of America" + }, + "user11": { + "manager": "user1", + "employment_type": "microsoft_employee", + "github_login": "user11", + "msft_alias": "user11MSFT", + "name": "User Name 11", + "email": "user11h@github.com", + "title": "Chief Technology Officer", + "cost_center": "Office of the CEO", + "country": "United States of America" + }, + "user12": { + "manager": "user3", + "employment_type": "microsoft_employee", + "github_login": "user12", + "msft_alias": "user12MSFT", + "name": "User Name 12", + "email": "user12@github.com", + "title": "Chief Financial Officer", + "cost_center": "Office of the CEO", + "country": "United States of America" + } +} \ No newline at end of file diff --git a/Test/public/buildTree.test.ps1 b/Test/public/buildTree.test.ps1 index 7e5c1bc..933cc2b 100644 --- a/Test/public/buildTree.test.ps1 +++ b/Test/public/buildTree.test.ps1 @@ -1,11 +1,17 @@ -function Test_Build-Tree{ - - $hubbers,$tree = Build-Tree +function Test_Build_HubbersTree{ - Assert-AreEqual -Expected ashtom -Presented $hubbers.rulasg.manager.manager.manager.manager.github_login + Reset-InvokeCommandMock + Mock_Database + + MockCallJson -Command "Invoke-GetHubbers" -Filename "hubbers.json" -AsHashtable + + $result = Build-HubbersTree + + Assert-AreEqual -Expected user0 -Presented $result.HubbersList.user12.manager.manager.github_login # total employeed - Assert-Count -Expected 4410 -Presented $hubbers - Assert-AreEqual -Expected 4410 -Presented $tree.totalEmployees + Assert-AreEqual -Expected 13 -Presented $result.totalHubbers + Assert-AreEqual -Expected 13 -Presented $result.HubbersList.count + +} -} \ No newline at end of file diff --git a/public/buildTree.ps1 b/public/buildTree.ps1 index ca94e94..000dfc4 100644 --- a/public/buildTree.ps1 +++ b/public/buildTree.ps1 @@ -1,4 +1,4 @@ -function Build-Tree { +function Build-HubbersTree { [cmdletbinding()] param ( @@ -10,12 +10,18 @@ function Build-Tree { $tree = Build-Node $hubbers $ceo - $global:hubbers = $hubbers - $global:tree = $tree + Save-HubbersListDb -Hubbers $hubbers + Save-HubbersTreeDb -Hubbers $tree - return $hubbers, $tree + $global:ResultHubbersBuild = @{ + totalHubbers = $hubbers.count + "HubbersList" = $hubbers + "HubbersTree" = $tree + } + + return $global:ResultHubbersBuild -} Export-ModuleMember -Function Build-Tree +} Export-ModuleMember -Function Build-HubbersTree function Build-Node { @@ -26,7 +32,9 @@ function Build-Node { try { - Write-Host "." -NoNewline + if ($null -eq $script:count -or ($script:count % 10) -eq 0) { + Write-Host "." -NoNewline + } # Stop after X nodes if (! (Test-Continue)) { @@ -38,22 +46,22 @@ function Build-Node { ## Manager # Set to null the manager for the CEO where manager == him self - $node.manager = ($node.manager -ne $nlogin) ? $hubbers.$($node.manager) : $null + $node.manager = ($node.manager -eq $nlogin) ? $null : $hubbers.$($node.manager) $node.level = $node.manager ? $node.manager.level + 1 : 0 - ## Employees + ## Reports - $employeesList = $hubbers.Values | Where-Object { ($_.manager -eq $nlogin) -and ($_.github_login -ne $nlogin) } + $reportsList = $hubbers.Values | Where-Object { ($_.manager -eq $nlogin) -and ($_.github_login -ne $nlogin) } - $Node.totalEmployees = 0 + $Node.totalReports = 0 # Recurse employ - if ($employeesList.count -ne 0) { + if ($reportsList.count -ne 0) { - $Node.employees = @{} + $Node.reports = @{} # recurse call - foreach ($employee in $employeesList) { + foreach ($employee in $reportsList) { $elogin = $employee.github_login @@ -63,15 +71,15 @@ function Build-Node { continue } - $Node.employees.$elogin = Build-Node $hubbers $employee + $Node.reports.$elogin = Build-Node $hubbers $employee } - # Count the number of employees under this node - foreach ($employee in $Node.employees.Values) { - if ($null -ne $employee.totalEmployees) { - $Node.totalEmployees += $employee.totalEmployees + # Count the number of reports under this node + foreach ($employee in $Node.reports.Values) { + if ($null -ne $employee.totalReports) { + $Node.totalReports += $employee.totalReports } - $Node.totalEmployees++ + $Node.totalReports++ } } From 97fe6486d2943591a5f493b7875536420f5e3119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 18:56:53 +0200 Subject: [PATCH 08/13] refactor(tests): rename and update Load-HubbersList and Build-HubbersTree functions --- Test/public/buildTree.test.ps1 | 34 ++++++++++++++++++++++++++++---- public/buildTree.ps1 | 36 +++++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/Test/public/buildTree.test.ps1 b/Test/public/buildTree.test.ps1 index 933cc2b..5c7ac53 100644 --- a/Test/public/buildTree.test.ps1 +++ b/Test/public/buildTree.test.ps1 @@ -1,17 +1,43 @@ -function Test_Build_HubbersTree{ +function Test_Load_HubbersList{ Reset-InvokeCommandMock Mock_Database - MockCallJson -Command "Invoke-GetHubbers" -Filename "hubbers.json" -AsHashtable - - $result = Build-HubbersTree + $filePath = Get-MockFileFullPath -fileName "hubbers.json" + $result = Load-HubbersList -Path $filePath + Assert-AreEqual -Expected user0 -Presented $result.HubbersList.user12.manager.manager.github_login # total employeed Assert-AreEqual -Expected 13 -Presented $result.totalHubbers Assert-AreEqual -Expected 13 -Presented $result.HubbersList.count +} + +function Test_GetHubbersList{ + Reset-InvokeCommandMock + Mock_Database + + $filePath = Get-MockFileFullPath -fileName "hubbers.json" + + $result = Load-HubbersList -Path $filePath + + $result = Get-HubbersList + + Assert-AreEqual -Expected user0 -Presented $result.user12.manager.manager.github_login +} + +function Test_GetHubbersTree{ + Reset-InvokeCommandMock + Mock_Database + + $filePath = Get-MockFileFullPath -fileName "hubbers.json" + + $result = Load-HubbersList -Path $filePath + + $result = Get-HubbersTree + Assert-AreEqual -Expected user0 -Presented $result.github_login + Assert-AreEqual -Expected user12 -Presented $result.reports.user3.reports.user12.github_login } diff --git a/public/buildTree.ps1 b/public/buildTree.ps1 index 000dfc4..ff0cbc0 100644 --- a/public/buildTree.ps1 +++ b/public/buildTree.ps1 @@ -1,18 +1,40 @@ -function Build-HubbersTree { + +function Load-HubbersList { [cmdletbinding()] param ( - + #path to hubber list + [Parameter(Mandatory, Position = 1)][string]$Path ) - $hubbers = Get-Hubber -AsHashtable + try{ + $hubbersRawList = Get-Content $Path -Raw | ConvertFrom-Json -AsHashtable + } catch { + Write-Error -Message "Failed to read or parse JSON file: $_" + return $null + } + + # Calculate the tree + $result = Build-HubbersTree -Hubbers $hubbersRawList + + # Save to local cache + Save-HubbersListDb -Hubbers $result.HubbersList + Save-HubbersTreeDb -Hubbers $result.HubbersTree + + return $result + +} Export-ModuleMember -Function Load-HubbersList + + +function Build-HubbersTree { + [cmdletbinding()] + param ( + [Parameter(mandatory)][hashtable]$Hubbers + ) $ceo = $hubbers.Values | Where-Object { $_.manager -eq $_.github_login } $tree = Build-Node $hubbers $ceo - Save-HubbersListDb -Hubbers $hubbers - Save-HubbersTreeDb -Hubbers $tree - $global:ResultHubbersBuild = @{ totalHubbers = $hubbers.count "HubbersList" = $hubbers @@ -21,7 +43,7 @@ function Build-HubbersTree { return $global:ResultHubbersBuild -} Export-ModuleMember -Function Build-HubbersTree +} function Build-Node { From 5a60cd8e448fdc074d3db59f268723b3f373e0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 19:06:56 +0200 Subject: [PATCH 09/13] refactor(helper): remove unused Export-ModuleMember for Get-ModuleFolder --- helper/module.helper.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helper/module.helper.ps1 b/helper/module.helper.ps1 index 7fd1786..6363b03 100644 --- a/helper/module.helper.ps1 +++ b/helper/module.helper.ps1 @@ -168,4 +168,4 @@ function Get-ModuleFolder{ } } return $moduleFolder -} Export-ModuleMember -Function Get-ModuleFolder +} From 934f598e5b4a3e45190a823d6f315c9196dde43f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 19:07:10 +0200 Subject: [PATCH 10/13] refactor(getHubber): simplify Get-Hubber function and remove unused Invoke-GetHubbers --- Test/public/gethubber.test.ps1 | 16 ++++++++++++++++ public/getHubber.ps1 | 27 +++++---------------------- 2 files changed, 21 insertions(+), 22 deletions(-) create mode 100644 Test/public/gethubber.test.ps1 diff --git a/Test/public/gethubber.test.ps1 b/Test/public/gethubber.test.ps1 new file mode 100644 index 0000000..a581ca6 --- /dev/null +++ b/Test/public/gethubber.test.ps1 @@ -0,0 +1,16 @@ +function Test_Gethubber{ + + Reset-InvokeCommandMock + Mock_Database + $filePath = Get-MockFileFullPath -fileName "hubbers.json" + $result = Load-HubbersList -Path $filePath + + $user = "user3" + $manager = "user0" + + $result = Get-Hubber -Handle $user + + Assert-AreEqual -Expected $user -Presented $result.github_login + Assert-AreEqual -Expected $manager -Presented $result.manager.github_login + +} \ No newline at end of file diff --git a/public/getHubber.ps1 b/public/getHubber.ps1 index fc5773f..a8ed2a9 100644 --- a/public/getHubber.ps1 +++ b/public/getHubber.ps1 @@ -8,17 +8,12 @@ function Get-Hubber{ [Parameter()][switch]$AsHashtable ) - $hubbers = Invoke-MyCommand -Command GetHubbers + $hubbers = Get-HubbersList + + $ret = $hubbers.$Handle - if(! [string]::IsNullOrWhiteSpace($Handle)){ - return [pscustomobject] $hubbers.$Handle - } + return $ret - if($AsHashtable){ - return $hubbers - } else { - return $hubbers.Values - } } Export-ModuleMember -Function Get-Hubber function Get-HubberByCountry { @@ -32,16 +27,4 @@ function Get-HubberByCountry { return $ret -} Export-ModuleMember -Function Get-HubberByCountry - -function Invoke-GetHubbers { - [CmdletBinding()] - param() - - # Downloaded from https://thehub.github.com/assets/org-data.json - $path = "~/hubbers.json" - - $hubbers = Get-Content -Path $path | ConvertFrom-Json -Depth 10 -AsHashtable - - return $hubbers -} Export-ModuleMember -Function Invoke-GetHubbers \ No newline at end of file +} Export-ModuleMember -Function Get-HubberByCountry \ No newline at end of file From 6a9e6840e4955b1d551979c880f5e0944edf4497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 19:15:38 +0200 Subject: [PATCH 11/13] refactor(tests): rename Load-HubbersList to Import-HubbersList in test files --- Test/public/SampleFunctionTests.ps1 | 29 ----------------------------- Test/public/buildTree.test.ps1 | 6 +++--- Test/public/gethubber.test.ps1 | 2 +- public/buildTree.ps1 | 4 ++-- public/getHubber.ps1 | 17 +---------------- public/samplePublicFunction.ps1 | 8 -------- 6 files changed, 7 insertions(+), 59 deletions(-) delete mode 100644 Test/public/SampleFunctionTests.ps1 delete mode 100644 public/samplePublicFunction.ps1 diff --git a/Test/public/SampleFunctionTests.ps1 b/Test/public/SampleFunctionTests.ps1 deleted file mode 100644 index 49f32da..0000000 --- a/Test/public/SampleFunctionTests.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -$TESTED_MODULE_PATH = $PSScriptRoot | split-path -Parent | split-path -Parent - -function Test_GetPublicString{ - - $sampleString = "this is a sample string" - - $result = Get-PublicString -Param1 $sampleString - - Assert-AreEqual -Expected ("Public string [{0}]" -f $samplestring) -presented $result -Comment "Sample test failed" - -} - -function Test_GetPrivateString { - - $testedModulePath = $TESTED_MODULE_PATH | Join-Path -ChildPath "HubbersHelper.psd1" - $testedModule = Import-Module -Name $testedModulePath -Force -PassThru - - $sampleString = "this is a sample string" - - $result = & $testedModule { - $sampleString = "this is a sample string" - Get-PrivateString -Param1 $sampleString - } - - Assert-AreEqual -Expected ("Private string [{0}]" -f $samplestring) -presented $result -Comment "Sample test failed" - -} - -Export-ModuleMember -Function Test_* diff --git a/Test/public/buildTree.test.ps1 b/Test/public/buildTree.test.ps1 index 5c7ac53..c86292e 100644 --- a/Test/public/buildTree.test.ps1 +++ b/Test/public/buildTree.test.ps1 @@ -5,7 +5,7 @@ function Test_Load_HubbersList{ $filePath = Get-MockFileFullPath -fileName "hubbers.json" - $result = Load-HubbersList -Path $filePath + $result = Import-HubbersList -Path $filePath Assert-AreEqual -Expected user0 -Presented $result.HubbersList.user12.manager.manager.github_login @@ -20,7 +20,7 @@ function Test_GetHubbersList{ $filePath = Get-MockFileFullPath -fileName "hubbers.json" - $result = Load-HubbersList -Path $filePath + $result = Import-HubbersList -Path $filePath $result = Get-HubbersList @@ -33,7 +33,7 @@ function Test_GetHubbersTree{ $filePath = Get-MockFileFullPath -fileName "hubbers.json" - $result = Load-HubbersList -Path $filePath + $result = Import-HubbersList -Path $filePath $result = Get-HubbersTree diff --git a/Test/public/gethubber.test.ps1 b/Test/public/gethubber.test.ps1 index a581ca6..f5b9dec 100644 --- a/Test/public/gethubber.test.ps1 +++ b/Test/public/gethubber.test.ps1 @@ -3,7 +3,7 @@ function Test_Gethubber{ Reset-InvokeCommandMock Mock_Database $filePath = Get-MockFileFullPath -fileName "hubbers.json" - $result = Load-HubbersList -Path $filePath + $result = Import-HubbersList -Path $filePath $user = "user3" $manager = "user0" diff --git a/public/buildTree.ps1 b/public/buildTree.ps1 index ff0cbc0..f73c6e5 100644 --- a/public/buildTree.ps1 +++ b/public/buildTree.ps1 @@ -1,5 +1,5 @@ -function Load-HubbersList { +function Import-HubbersList { [cmdletbinding()] param ( #path to hubber list @@ -22,7 +22,7 @@ function Load-HubbersList { return $result -} Export-ModuleMember -Function Load-HubbersList +} Export-ModuleMember -Function Import-HubbersList function Build-HubbersTree { diff --git a/public/getHubber.ps1 b/public/getHubber.ps1 index a8ed2a9..f1d12eb 100644 --- a/public/getHubber.ps1 +++ b/public/getHubber.ps1 @@ -1,6 +1,4 @@ -Set-MyInvokeCommandAlias -Alias GetHubbers -Command "Invoke-GetHubbers" - function Get-Hubber{ [CmdletBinding()] param ( @@ -14,17 +12,4 @@ function Get-Hubber{ return $ret -} Export-ModuleMember -Function Get-Hubber - -function Get-HubberByCountry { - param ( - [string]$Country - ) - - $hubbers = Invoke-MyCommand -Command GetHubbers - - $ret = $hubbers.Values | Where-Object { $_.country -eq $Country } - - return $ret - -} Export-ModuleMember -Function Get-HubberByCountry \ No newline at end of file +} Export-ModuleMember -Function Get-Hubber \ No newline at end of file diff --git a/public/samplePublicFunction.ps1 b/public/samplePublicFunction.ps1 deleted file mode 100644 index ad678be..0000000 --- a/public/samplePublicFunction.ps1 +++ /dev/null @@ -1,8 +0,0 @@ - -function Get-PublicString { - param ( - [Parameter()][string]$Param1 - ) - - return ("Public string [{0}]" -f $param1) -} Export-ModuleMember -Function Get-PublicString From 88fa69fe0756767365e4431cbb615cabead315e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 19:28:11 +0200 Subject: [PATCH 12/13] update include files --- Test/include/invokeCommand.mock.ps1 | 4 +- helper/invokeCommand.helper.ps1 | 24 ++++----- helper/module.helper.ps1 | 2 +- include/MyWrite.ps1 | 78 +++++++++++++++++++++++++++++ include/databaseV2.ps1 | 29 ++++++----- 5 files changed, 105 insertions(+), 32 deletions(-) create mode 100644 include/MyWrite.ps1 diff --git a/Test/include/invokeCommand.mock.ps1 b/Test/include/invokeCommand.mock.ps1 index f5ee15d..6af5c68 100644 --- a/Test/include/invokeCommand.mock.ps1 +++ b/Test/include/invokeCommand.mock.ps1 @@ -80,9 +80,7 @@ function MockCallJson{ Assert-MockFileNotfound $fileName - $has = $AsHashtable ? '$true' : '$false' - - Set-InvokeCommandMock -Alias $command -Command "Get-MockFileContentJson -filename $filename -AsHashtable:$has" + Set-InvokeCommandMock -Alias $command -Command "Get-MockFileContentJson -filename $filename -AsHashtable:$AsHashtable" } function MockCallJsonAsync{ diff --git a/helper/invokeCommand.helper.ps1 b/helper/invokeCommand.helper.ps1 index 7bbe0bb..6781ef1 100644 --- a/helper/invokeCommand.helper.ps1 +++ b/helper/invokeCommand.helper.ps1 @@ -16,26 +16,20 @@ function Set-MyInvokeCommandAlias{ [Parameter(Mandatory,Position=1)][string]$Command ) - # throw if MODULE_INVOKATION_TAG is not set or is "MyModuleModule" - if (-not $MODULE_INVOKATION_TAG) { - throw "MODULE_INVOKATION_TAG is not set. Please set it to a unique value before calling Set-MyInvokeCommandAlias." - } - if ($PSCmdlet.ShouldProcess("InvokeCommandAliasList", ("Add Command Alias [{0}] = [{1}]" -f $Alias, $Command))) { InvokeHelper\Set-InvokeCommandAlias -Alias $Alias -Command $Command -Tag $MODULE_INVOKATION_TAG } } -function Reset-MyInvokeCommandAlias{ - [CmdletBinding(SupportsShouldProcess)] - param() +function Invoke-MyCommand{ + [CmdletBinding()] + param( + [Parameter(Mandatory,ValueFromPipeline,Position=0)][string]$Command, + [Parameter(Position=1)][hashtable]$Parameters + ) - # throw if MODULE_INVOKATION_TAG is not set or is "MyModuleModule" - if (-not $MODULE_INVOKATION_TAG) { - throw "MODULE_INVOKATION_TAG is not set. Please set it to a unique value before calling Set-MyInvokeCommandAlias." - } - InvokeHelper\Reset-InvokeCommandAlias -Tag $MODULE_INVOKATION_TAG + Write-Debug "[invoke] $Command" $Parameters + + return InvokeHelper\Invoke-MyCommand -Command $Command -Parameters $Parameters } -# Reset all aliases for this module on each refresh -Reset-MyInvokeCommandAlias diff --git a/helper/module.helper.ps1 b/helper/module.helper.ps1 index 6363b03..7fd1786 100644 --- a/helper/module.helper.ps1 +++ b/helper/module.helper.ps1 @@ -168,4 +168,4 @@ function Get-ModuleFolder{ } } return $moduleFolder -} +} Export-ModuleMember -Function Get-ModuleFolder diff --git a/include/MyWrite.ps1 b/include/MyWrite.ps1 new file mode 100644 index 0000000..24d039c --- /dev/null +++ b/include/MyWrite.ps1 @@ -0,0 +1,78 @@ + +$ERROR_COLOR = "Red" +$WARNING_COLOR = "Yellow" +$OUTPUT_COLOR = "DarkCyan" +$DEBUG_COLOR = "DarkGray" + +function Write-MyError { + param( + [Parameter(Mandatory, ValueFromPipeline)][string]$Message + ) + # Write-Host "Error: $message" -ForegroundColor $ERROR_COLOR + Write-ToConsole "Error: $message" -Color $ERROR_COLOR +} + +function Write-MyWarning { + param( + [Parameter(Mandatory, ValueFromPipeline)][string]$Message + ) + # Write-Host "Error: $message" -ForegroundColor $WARNING_COLOR + Write-ToConsole $message -Color $WARNING_COLOR +} + +function Write-MyVerbose { + param( + [Parameter(ValueFromPipeline)][string]$Message + ) + Write-Verbose -Message $message +} + +function Write-MyHost { + param( + [Parameter(ValueFromPipeline)][string]$Message, + #NoNewLine + [Parameter()][switch]$NoNewLine + ) + # Write-Host $message -ForegroundColor $OUTPUT_COLOR + Write-ToConsole $message -Color $OUTPUT_COLOR -NoNewLine:$NoNewLine +} + +function Write-Debug { + param( + [Parameter(Position = 0)][string]$section, + [Parameter(Position = 1, ValueFromPipeline)][string]$Message, + [Parameter(Position = 2)][object]$Object + ) + + $flag = $env:ProjectHelper_DEBUG + + # Enable debug + if ([string]::IsNullOrWhiteSpace( $flag )) { + return + } + + $trace = ($flag -like '*all*') -or ( $section -like "*$flag*") + + # Write-Host $message -ForegroundColor $DEBUG_COLOR + if ($trace) { + + if ($Object) { + $objJson = $Object | ConvertTo-Json -Depth 10 -ErrorAction SilentlyContinue + $message = $message + " - " + $objJson + } + + $message = "[DEBUG][$section] " + $message + Write-ToConsole $message -Color $DEBUG_COLOR + } +} + +function Write-ToConsole { + param( + [Parameter(ValueFromPipeline)][string]$Color, + [Parameter(ValueFromPipeline, Position = 0)][string]$Message, + [Parameter()][switch]$NoNewLine + + ) + Microsoft.PowerShell.Utility\Write-Host $message -ForegroundColor $Color -NoNewLine:$NoNewLine +} + diff --git a/include/databaseV2.ps1 b/include/databaseV2.ps1 index 1201c9d..8f74a6b 100644 --- a/include/databaseV2.ps1 +++ b/include/databaseV2.ps1 @@ -66,8 +66,8 @@ function GetDatabaseRootPath { function GetDatabaseFile{ [CmdletBinding()] param( - [Parameter(Position = 0)][string]$Key, - [Parameter(Position = 1)][ValidateSet("JSON","XML")][string]$DBFormat = "JSON" + [Parameter(Mandatory, Position = 0)][string]$Key, + [Parameter(Position = 1)][ValidateSet("JSON","XML","TXT")][string]$DBFormat = "JSON" ) $databaseRoot = Invoke-MyCommand -Command $DB_INVOKE_GET_ROOT_PATH_ALIAS @@ -86,12 +86,13 @@ function GetDatabaseFile{ function GetFileExtension{ [CmdletBinding()] param( - [string]$DbFormat = "JSON" + [Parameter(Mandatory)][string]$DbFormat ) switch ($DbFormat.ToUpper()){ "JSON" { $ret = ".json" ; Break } "XML" { $ret = ".xml" ; Break } + "TXT" { $ret = ".txt" ; Break } default { throw "Unsupported database format $DbFormat" } } return $ret @@ -100,8 +101,8 @@ function GetFileExtension{ function Get-DatabaseKey{ [CmdletBinding()] param( - [Parameter(Position = 0)][string]$Key, - [Parameter(Position = 1)][ValidateSet("JSON","XML")][string]$DBFormat = "JSON" + [Parameter(Mandatory, Position = 0)][string]$Key, + [Parameter(Position = 1)][ValidateSet("JSON","XML","TXT")][string]$DBFormat = "JSON" ) if(-Not (Test-DatabaseKey $Key -DBFormat $DBFormat)){ @@ -113,6 +114,7 @@ function Get-DatabaseKey{ switch ($DBFormat) { "JSON" { $ret = Get-Content $path | ConvertFrom-Json ; Break } "XML" { $ret = Import-Clixml -Path $path ; Break } + "TXT" { $ret = Get-Content $path ; Break } default { throw "Unsupported database format $DbFormat" } } @@ -122,8 +124,8 @@ function Get-DatabaseKey{ function Reset-DatabaseKey{ [CmdletBinding()] param( - [Parameter(Position = 0)][string]$Key, - [Parameter(Position = 1)][ValidateSet("JSON","XML")][string]$DBFormat = "JSON" + [Parameter(Mandatory, Position = 0)][string]$Key, + [Parameter(Position = 1)][ValidateSet("JSON","XML","TXT")][string]$DBFormat = "JSON" ) $path = GetDatabaseFile -Key $Key -DBFormat $DBFormat Remove-Item -Path $path -Force -ErrorAction SilentlyContinue @@ -133,16 +135,17 @@ function Reset-DatabaseKey{ function Save-DatabaseKey{ [CmdletBinding()] param( - [Parameter(Position = 0)][string]$Key, - [Parameter(Position = 2)][Object]$Value, - [Parameter(Position = 3)][ValidateSet("JSON","XML")][string]$DbFormat = "JSON" + [Parameter(Mandatory, Position = 0)][string]$Key, + [Parameter(Mandatory, Position = 2)][Object]$Value, + [Parameter(Position = 3)][ValidateSet("JSON","XML","TXT")][string]$DbFormat = "JSON" ) $path = GetDatabaseFile -Key $Key -DBFormat $DbFormat switch ($DbFormat) { "JSON" { $Value | ConvertTo-Json -Depth 10 | Set-Content $path -Encoding UTF8 -Force ; Break } - "XML" { $Value | Export-Clixml -Path $path -Force; Break } + "XML" { $Value | Export-Clixml -Path $path -Force ; Break } + "TXT" { $Value | Set-Content -Path $path -Encoding UTF8 -Force ; Break } default { throw "Unsupported database format $DbFormat" } } @@ -151,8 +154,8 @@ function Save-DatabaseKey{ function Test-DatabaseKey{ [CmdletBinding()] param( - [Parameter(Position = 0)][string]$Key, - [Parameter(Position = 1)][ValidateSet("JSON","XML")][string]$DBFormat = "JSON" + [Parameter(Mandatory, Position = 0)][string]$Key, + [Parameter(Position = 1)][ValidateSet("JSON","XML","TXT")][string]$DBFormat = "JSON" ) $path = GetDatabaseFile -Key $Key -DBFormat $DBFormat From 98dfa990e5b4f4026c90ab881c82302706692e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Tue, 30 Sep 2025 19:37:23 +0200 Subject: [PATCH 13/13] fix(manifest): correct RequiredModules declaration in HubbersHelper.psd1 --- HubbersHelper.psd1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HubbersHelper.psd1 b/HubbersHelper.psd1 index 353444b..06e26d2 100644 --- a/HubbersHelper.psd1 +++ b/HubbersHelper.psd1 @@ -54,7 +54,7 @@ Copyright = '(c) rulasg. All rights reserved.' # RequiredModules = @() # Assemblies that must be loaded prior to importing this module -# RequiredAssemblies = @() +RequiredModules = @(@{ModuleName="InvokeHelper"; ModuleVersion="1.2.4"}) # Script files (.ps1) that are run in the caller's environment prior to importing this module. # ScriptsToProcess = @()