diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 082b64f..37f9402 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,8 @@ { "name": "PowerShell", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/powershell:lts-debian-11", + "image": "mcr.microsoft.com/powershell:lts-ubuntu-22.04", + "features": { "ghcr.io/devcontainers/features/common-utils:2": {}, "ghcr.io/devcontainers/features/powershell:1": {}, diff --git a/SfHelper.psm1 b/SfHelper.psm1 index f3e72cc..4d6908e 100644 --- a/SfHelper.psm1 +++ b/SfHelper.psm1 @@ -9,11 +9,12 @@ $START = Get-ChildItem -Path $MODULE_PATH -Filter start.ps1 -Recurse if($START | Test-Path){ . $($START | Get-Item).FullName } #Get public and private function definition files. +$Include = @( Get-ChildItem -Path $MODULE_PATH\include\*.ps1 -Recurse -ErrorAction SilentlyContinue ) $Public = @( Get-ChildItem -Path $MODULE_PATH\public\*.ps1 -Recurse -ErrorAction SilentlyContinue ) $Private = @( Get-ChildItem -Path $MODULE_PATH\private\*.ps1 -Recurse -ErrorAction SilentlyContinue ) #Dot source the files -Foreach($import in @($Public + $Private)) +Foreach($import in @($Include + $Public + $Private)) { Try { diff --git a/Test/Test.psm1 b/Test/Test.psm1 index 14c5c6a..6fc3d0b 100644 --- a/Test/Test.psm1 +++ b/Test/Test.psm1 @@ -4,11 +4,12 @@ Write-Information -Message ("Loading {0} ..." -f ($PSCommandPath | Split-Path -L $MODULE_PATH = $PSScriptRoot #Get public and private function definition files. +$Include = @( Get-ChildItem -Path $MODULE_PATH\include\*.ps1 -Recurse -ErrorAction SilentlyContinue ) $Public = @( Get-ChildItem -Path $MODULE_PATH\public\*.ps1 -Recurse -ErrorAction SilentlyContinue ) $Private = @( Get-ChildItem -Path $MODULE_PATH\private\*.ps1 -Recurse -ErrorAction SilentlyContinue ) #Dot source the files -Foreach($import in @($Public + $Private)) +Foreach($import in @($Include + $Public + $Private)) { Try { diff --git a/Test/include/config.mock.ps1 b/Test/include/config.mock.ps1 new file mode 100644 index 0000000..53536fe --- /dev/null +++ b/Test/include/config.mock.ps1 @@ -0,0 +1,28 @@ + + +function Mock_Config{ + param( + [Parameter(Position=0)][string] $key = "config", + [Parameter(Position=1)][object] $Config + ) + + $MOCK_CONFIG_PATH = "test_config_path" + + # Remove mock config path if exists + if(Test-Path $MOCK_CONFIG_PATH){ + Remove-Item -Path $MOCK_CONFIG_PATH -ErrorAction SilentlyContinue -Recurse -Force + } + + # create mock config path + New-Item -Path $MOCK_CONFIG_PATH -ItemType Directory -Force + + # if $config is not null save it to a file + if($null -ne $Config){ + $configfile = Join-Path -Path $MOCK_CONFIG_PATH -ChildPath "$key.json" + $Config | ConvertTo-Json -Depth 10 | Set-Content $configfile + } + + # Mock invoke call + MockCallToString "Invoke-GetConfigRootPath" -OutString $MOCK_CONFIG_PATH + +} \ No newline at end of file diff --git a/Test/include/database.mock.ps1 b/Test/include/database.mock.ps1 new file mode 100644 index 0000000..3a0e927 --- /dev/null +++ b/Test/include/database.mock.ps1 @@ -0,0 +1,9 @@ +function Mock_Database([switch]$ResetDatabase){ + + MockCallToString "Invoke-GetDatabaseStorePath" -OutString "test_database_path" + + if($ResetDatabase){ + Reset-DatabaseStore + } + +} \ No newline at end of file diff --git a/Test/private/InvokeCommandMock.ps1 b/Test/private/InvokeCommandMock.ps1 index ce78ff0..9762ab4 100644 --- a/Test/private/InvokeCommandMock.ps1 +++ b/Test/private/InvokeCommandMock.ps1 @@ -1,6 +1,6 @@ # Managing dependencies -$MODULE_INVOKATION_TAG = "project-migration-module" -$MODULE_INVOKATION_TAG_MOCK = "project-migration-module_Mock" +$MODULE_INVOKATION_TAG = "SfHelperModule" +$MODULE_INVOKATION_TAG_MOCK = "SfHelperModule-Mock" $ROOT = $PSScriptRoot | Split-Path -Parent $MOCK_PATH = $ROOT | Join-Path -ChildPath 'private' -AdditionalChildPath 'mocks' @@ -136,11 +136,28 @@ function Save-InvokeAsMockFile{ $result = Invoke-Expression -Command $Command - $result | ConvertTo-Json -Depth 100 | Out-File -FilePath $filePath + $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 diff --git a/Test/private/mocks/sf-data-query-account.json b/Test/private/mocks/sf-data-query-account.json new file mode 100644 index 0000000..9c83d05 --- /dev/null +++ b/Test/private/mocks/sf-data-query-account.json @@ -0,0 +1,31 @@ +{ + "status": 0, + "result": { + "records": [ + { + "attributes": { + "type": "Account", + "url": "/services/data/v63.0/sobjects/Account/0010V00002KIWkaQAH" + }, + "Id": "0010V00002KIWkaQAH", + "Name": "Contoso", + "OwnerId": "0053o000008Skv3AAC", + "Industry": "Retail", + "Account_Owner__c": "Oana Dinca [Change]", + "Account_Segment__c": "Enterprise", + "Account_Owner_Role__c": "EMEA - Enterprise Sales Manager - South EMEA", + "Account_Tier__c": "Tier 1", + "Potential_Seats__c": 998, + "Country_Name__c": "Spain", + "Current_Seats__c": 600, + "Current_ARR_10__c": 116928, + "Salesforce_Record_URL__c": "https://github.my.salesforce.com/0010V00002KIWkaQAH", + "Potential_Seats_Manual__c": 1500, + "Website": "www.contos.es", + "PhotoUrl": "/services/images/photo/0010V00002KIWkaQAH" + } + ], + "totalSize": 1, + "done": true + } +} diff --git a/Test/public/Get-SfObjectIdFromUrl.test.ps1 b/Test/public/Get-SfObjectIdFromUrl.test.ps1 index 565c5e1..dca5d81 100644 --- a/Test/public/Get-SfObjectIdFromUrl.test.ps1 +++ b/Test/public/Get-SfObjectIdFromUrl.test.ps1 @@ -1,4 +1,4 @@ -function Test_GetSfAccount{ +function Test_GetSfAccountFromUrl{ . $PSScriptRoot/../../private/auxiliarfunctions.ps1 diff --git a/Test/public/sfDataQuery.test.ps1 b/Test/public/sfDataQuery.test.ps1 new file mode 100644 index 0000000..54dd671 --- /dev/null +++ b/Test/public/sfDataQuery.test.ps1 @@ -0,0 +1,37 @@ +function Test_GetSfAccount{ + + Reset-InvokeCommandMock + Mock_Database -ResetDatabase + $mockAttrib = @{attributes = @("Potential_Seats_Manual__c","Website","PhotoUrl")} + Mock_Config -Config $mockAttrib + + $dbstore = Invoke-MyCommand -Command GetDatabaseStorePath + Assert-AreEqual -Expected "test_database_path" -Presented $dbstore + + $attrib = "Id,Name,OwnerId,Industry,Account_Owner__c,Account_Segment__c,Account_Owner_Role__c,Account_Tier__c,Potential_Seats__c,Country_Name__c,Current_Seats__c,Current_ARR_10__c,Salesforce_Record_URL__c,Potential_Seats_Manual__c,Website,PhotoUrl" + $type = "Account" + $id = "0010V00002KIWkaQAH" + + $command = 'sf data query --query "SELECT {attributes} FROM {type} WHERE Id=''{id}''" -r=json' + $command = $command -replace "{attributes}", $attrib + $command = $command -replace "{type}", $type + $command = $command -replace "{id}", $id + MockCall -Command $command -filename "sf-data-query-account.json" + + # Act with out cache + $result = get-sfAccount https://github.lightning.force.com/lightning/r/Account/0010V00002KIWkaQAH/view + Assert-AreEqual -Expected $Id -Presented $result.Id + $dbfiles = Get-ChildItem $dbstore + Assert-Count -Expected 1 -Presented $dbfiles + Assert-IsTrue -Condition ($dbfiles[0].Name -like "sfDataQuery*-$type-$id-*.json") + + # Remove sf data + Reset-InvokeCommandMock + Mock_Database + Mock_Config -Config $mockAttrib + + # Act with cache + $result = Get-SfAccount https://github.lightning.force.com/lightning/r/Account/0010V00002KIWkaQAH/view + Assert-AreEqual -Expected $Id -Presented $result.Id + +} \ No newline at end of file diff --git a/include/config.ps1 b/include/config.ps1 new file mode 100644 index 0000000..19d2d30 --- /dev/null +++ b/include/config.ps1 @@ -0,0 +1,93 @@ +# Configuration management module + +Set-MyInvokeCommandAlias -Alias GetConfigRootPath -Command "Invoke-GetConfigRootPath" + +$moduleName = Get-ModuleName +$CONFIG_ROOT = [System.Environment]::GetFolderPath('UserProfile') | Join-Path -ChildPath ".helpers" -AdditionalChildPath $moduleName, "config" + +# Create the config root if it does not exist +if(-Not (Test-Path $CONFIG_ROOT)){ + New-Item -Path $CONFIG_ROOT -ItemType Directory +} + +function Invoke-GetConfigRootPath { + [CmdletBinding()] + param() + + $configRoot = $CONFIG_ROOT + return $configRoot +} Export-ModuleMember -Function Invoke-GetConfigRootPath + +function GetConfigFile { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true, Position = 0)][string]$Key + ) + + $configRoot = Invoke-MyCommand -Command GetConfigRootPath + $path = Join-Path -Path $configRoot -ChildPath "$Key.json" + return $path +} + +function Test-Configuration { + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key = "config" + ) + + $path = GetConfigFile -Key $Key + + return Test-Path $path +} + +function Get-Configuration { + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key = "config" + ) + + $path = GetConfigFile -Key $Key + + if(-Not (Test-Configuration -Key $Key)){ + return $null + } + + try{ + $ret = Get-Content $path | ConvertFrom-Json -ErrorAction Stop + return $ret + } + catch{ + Write-Warning "Error reading configuration ($Key) file: $($path). $($_.Exception.Message)" + return $null + } +} + +function Save-Configuration { + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key = "config", + [Parameter(Mandatory = $true, Position = 1)][Object]$Config + ) + + $path = GetConfigFile -Key $Key + + try { + $Config | ConvertTo-Json -Depth 10 | Set-Content $path -ErrorAction Stop + } + catch { + Write-Warning "Error saving configuration ($Key) to file: $($path). $($_.Exception.Message)" + return $false + } + + return $true +} + + + + + + + + + + diff --git a/private/dependencies/databaseV2.ps1 b/private/dependencies/databaseV2.ps1 new file mode 100644 index 0000000..305ae37 --- /dev/null +++ b/private/dependencies/databaseV2.ps1 @@ -0,0 +1,113 @@ +# Database driver to store the cache + +# Invoke to allow mockig the store path on testing +Set-MyInvokeCommandAlias -Alias GetDatabaseStorePath -Command "Invoke-GetDatabaseStorePath" + +$moduleName = Get-ModuleName +$DATABASE_ROOT = [System.Environment]::GetFolderPath('UserProfile') | Join-Path -ChildPath ".helpers" -AdditionalChildPath $moduleName, "databaseCache" + +# Create the database root if it does not exist +if(-Not (Test-Path $DATABASE_ROOT)){ + New-Item -Path $DATABASE_ROOT -ItemType Directory +} + +function Reset-DatabaseStore{ + [CmdletBinding()] + param() + + $databaseRoot = Invoke-MyCommand -Command GetDatabaseStorePath + + Remove-Item -Path $databaseRoot -Recurse -Force -ErrorAction SilentlyContinue + + New-Item -Path $databaseRoot -ItemType Directory + +} Export-ModuleMember -Function Reset-DatabaseStore + +function Get-DatabaseStore{ + [CmdletBinding()] + param() + + $databaseRoot = Invoke-MyCommand -Command GetDatabaseStorePath + + return $databaseRoot + +} Export-ModuleMember -Function Get-DatabaseStore + +function Get-Database{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key + ) + + if(-Not (Test-Database $Key)){ + return $null + } + + $path = GetDatabaseFile $Key + + $ret = Get-Content $path | ConvertFrom-Json -Depth 10 -AsHashtable + + return $ret +} + +function Reset-Database{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key + ) + $path = GetDatabaseFile -Key $Key + Remove-Item -Path $path -Force -ErrorAction SilentlyContinue + return +} + +function Save-Database{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key, + [Parameter(Position = 2)][Object]$Database + ) + + $path = GetDatabaseFile -Key $Key + + $Database | ConvertTo-Json -Depth 10 | Set-Content $path +} + +function Test-Database{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key + ) + + $path = GetDatabaseFile -Key $Key + + # Key file not exists + if(-Not (Test-Path $path)){ + return $false + } + + # TODO: Return $false if cache has expired + + return $true +} + +function GetDatabaseFile{ + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$Key + ) + + $databaseRoot = Invoke-MyCommand -Command GetDatabaseStorePath + + $path = $databaseRoot | Join-Path -ChildPath "$Key.json" + + return $path +} + +function Invoke-GetDatabaseStorePath{ + [CmdletBinding()] + param() + + $databaseRoot = $DATABASE_ROOT + + return $databaseRoot +} Export-ModuleMember -Function Invoke-GetDatabaseStorePath \ No newline at end of file diff --git a/private/start/START.ps1 b/private/start/START.ps1 index ca671d7..8a7366f 100644 --- a/private/start/START.ps1 +++ b/private/start/START.ps1 @@ -9,5 +9,11 @@ if (! $LOADED_EARLYLOADED){ # Load Invoke helper functions . $(($PSScriptRoot | Join-Path -ChildPath SetMyInvokeCommandAlias.ps1 | Get-Item).FullName) -} + function Get-ModuleName{ + $local = $PSScriptRoot | Split-Path -Parent | Split-Path -Parent + $moduleName = (Get-ChildItem -Path $local -Filter *.psd1 | Select-Object -First 1).BaseName + + return $moduleName + } +} diff --git a/private/start/SetMyInvokeCommandAlias.ps1 b/private/start/SetMyInvokeCommandAlias.ps1 index 65fc3e1..57cfcbc 100644 --- a/private/start/SetMyInvokeCommandAlias.ps1 +++ b/private/start/SetMyInvokeCommandAlias.ps1 @@ -4,7 +4,7 @@ if (!$SET_MY_INVOKECOMMANDALIAS_LOADED){ $SET_MY_INVOKECOMMANDALIAS_LOADED = $true - $MODULE_INVOKATION_TAG = "sfhelper-module" + $MODULE_INVOKATION_TAG = "SfHelperModule" function Set-MyInvokeCommandAlias{ [CmdletBinding(SupportsShouldProcess)] diff --git a/public/getsfaccount.ps1 b/public/getsfaccount.ps1 index 481c5e5..7cce9b5 100644 --- a/public/getsfaccount.ps1 +++ b/public/getsfaccount.ps1 @@ -59,10 +59,11 @@ function Get-SfAccount{ } ## Add attributes from file - if (Test-Path $PROFILE_ATTRIBUTES_FILE_PATH) { - $attributesFromFile = Get-Content $PROFILE_ATTRIBUTES_FILE_PATH - "addring attributes from file $attributesFromFile" | Write-Verbose - $attributes += $attributesFromFile | Select-Object -Unique + if (Test-Configuration ) { + $config = Get-Configuration + $attributesFromConfig = $config.attributes + "adding attributes from config $($attributesFromConfig -join ',' )" | Write-Verbose + $attributes += $attributesFromConfig | Select-Object -Unique } # Get object diff --git a/public/sfDataQuery.ps1 b/public/sfDataQuery.ps1 index 50eaa92..7cf0b07 100644 --- a/public/sfDataQuery.ps1 +++ b/public/sfDataQuery.ps1 @@ -8,6 +8,12 @@ function Get-SfDataQuery{ [Parameter(Mandatory)][string[]]$Attributes ) + # Testcache first + $cacheKey = getcacheKey -Type $Type -Id $Id -Attributes $Attributes + if(Test-Database -Key $cacheKey){ + return Get-Database -Key $cacheKey + } + $params = @{ id = $Id type = $Type @@ -35,8 +41,31 @@ function Get-SfDataQuery{ $ret | Add-Member -MemberType NoteProperty -Name "QueryDate" -Value (Get-Date) + Save-Database -Key $cacheKey -Database $ret + return $ret } +function getcacheKey{ + [CmdletBinding()] + param( + [Parameter(Mandatory)][string]$Type, + [Parameter(Mandatory)][string]$Id, + [Parameter(Mandatory)][string[]]$Attributes + ) + + # Add hash of attributes to key + $attribString = $Attributes -join "," + "Attributes : $attribString" | Write-Verbose + $attributesHash = $attribString.GetHashCode() + "AttributesHash : $attributesHash" | Write-Verbose + + $cacheKey = "sfDataQuery-$Type-$Id-$attributesHash" + + "CacheKey : $cacheKey" | Write-Verbose + + return $cacheKey +} +