From 14b4b78d4dab25d7eac6b10a1b5155377d7c40a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 24 Nov 2025 07:52:35 +0100 Subject: [PATCH 1/8] feat(datehelper): add Get-DaysBetweenDates PowerShell helper --- include/datehelper.ps1 | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 include/datehelper.ps1 diff --git a/include/datehelper.ps1 b/include/datehelper.ps1 new file mode 100644 index 0000000..41d8573 --- /dev/null +++ b/include/datehelper.ps1 @@ -0,0 +1,14 @@ +function Get-DaysBetweenDates { + [CmdletBinding()] + param( + [Parameter(Position = 0)][string]$StartDate = (Get-Date -Format 'yyyy-MM-dd'), + [Parameter(Mandatory, Position = 1)][string]$EndDate + ) + + $start = [DateTime]::ParseExact($StartDate, 'yyyy-MM-dd', $null) + $end = [DateTime]::ParseExact($EndDate, 'yyyy-MM-dd', $null) + + $timeSpan = $end - $start + + return [Math]::Abs($timeSpan.Days) +} Export-ModuleMember -Function Get-DaysBetweenDates \ No newline at end of file From adf95fc81908922389bdfb438c9f67283e5250a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 24 Nov 2025 07:57:18 +0100 Subject: [PATCH 2/8] docs(copilot-instructions): update and expand Copilot instructions for IncludeHelper --- .github/copilot-instructions.md | 94 +++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 27 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9c4a524..2e0c7b4 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,44 +1,84 @@ -# Copilot Instructions +# Copilot Instructions for IncludeHelper -## Powershell Modules Code Instructions +IncludeHelper is a PowerShell module that provides shared utility functions and a framework for distributing reusable code components ("includes") across other modules. -### PowerShell Functions Instructions +## Architecture Overview -Every powershell function will contain the `CmdletBinding` attribute and the `parm`iven if there are no parameters. -If the function is on the public folder of the module, we will add the Èxport-ModuleFunction` statement in the same line as the closing `}` of the function +**Module Structure:** +- `config/`: Configuration utilities and module initialization +- `helper/`: Core module helpers (module path resolution, folder management) +- `include/`: Shared utility functions (logging, API calls, database, config management) +- `private/`: Internal functions not exported +- `public/`: Exported functions (the public API) +- `Test/`: Mirrored structure with unit tests and mocks -Sample of function will be: +**Loading Order** (in `IncludeHelper.psm1`): `config` → `helper` → `include` → `private` → `public` -```powershell +## PowerShell Function Conventions -function Get-UserName{ - [CmdletBinding()] - param() +### All Functions +- Must include `[CmdletBinding()]` attribute +- Must include `param()` block (even if empty) +- Use proper script documentation with `<# .SYNOPSIS #>` blocks - #Logic of the function -} Export-ModuleFunction -FunctionName 'Get-UserName' -``` +### Public Functions (`public/` folder) +- Add `Export-ModuleMember -Function 'FunctionName'` on the closing `}` line +- Example: + ```powershell + function Add-IncludeToWorkspace { + [CmdletBinding()] + param([Parameter(Mandatory)][string]$Name) + # Logic + } Export-ModuleMember -Function 'Add-IncludeToWorkspace' + ``` -### PowerShell Test Instructions +### Helper Functions (`helper/` folder) +- Available module-wide; follow naming convention: verbs that clearly indicate utility purpose +- Key helpers: `Find-ModuleRootPath`, `Get-ModuleFolder`, `Get-Ps1FullPath` -Every public function on the Test module will have the following format +## Logging and Debugging -- Name will start with `Test_` will follow the name of the function that we are testing with no '-'. It will follow the intention of the test splited with a '_' -- Every time we create a new function with no body we will add the `Assert-NotImplemented` statement at the end -- We will add the 3 sections as with comments `Arrange`, `Act` and `Assert` to make the test more readable. -- Sample of a test function to test `Get-UserName` function will be `Test_GetUserName_UserNotFound` +- Use `MyWrite.ps1` functions: `Write-MyError`, `Write-MyWarning`, `Write-MyVerbose`, `Write-MyDebug`, `Write-MyHost` +- Control verbosity via environment variables: `$env:ModuleHelper_VERBOSE="all"` or specific function names +- Control debugging via `$env:ModuleHelper_DEBUG="all"` or specific sections +- Test verbosity/debug state with `Test-MyVerbose` and `Test-MyDebug` -Full sample will be as follows +## Test Conventions +Tests use `TestingHelper` module and follow pattern: `function Test_FunctionName_Scenario` + +**Test Structure:** ```powershell -function Test_GetUserName_UserNotFound{ +function Test_AddIncludeToWorkspace { + # Arrange - Setup test data and mocks + Import-Module -Name TestingHelper + New-ModuleV3 -Name TestModule + + # Act - Execute the function being tested + Add-IncludeToWorkspace -Name "getHashCode.ps1" -FolderName "Include" -DestinationModulePath "TestModule" + + # Assert - Verify results + Assert-ItemExist -path (Join-Path $folderPath "getHashCode.ps1") +} +``` + +- Use `Assert-NotImplemented` for unfinished tests +- Test files in `Test/public/` mirror functions in `public/` +- Run tests with `./test.ps1` (uses `TestingHelper` module) + +## Core Patterns + +**Module Discovery:** Use `Find-ModuleRootPath` to locate module root by searching up from current path for `*.psd1` files (skips Test.psd1). + +**Folder Management:** `Get-ModuleFolder` maps logical names (`Include`, `Public`, `TestPrivate`, etc.) to filesystem paths. Valid names defined in `helper/module.helper.ps1` `$VALID_FOLDER_NAMES`. - # Arrange +**Configuration:** JSON-based, stored in `~/.helpers/{ModuleName}/config/`. Use `Get-Configuration`, `Save-Configuration`, `Test-Configuration` from `include/config.ps1`. - # Act +**Command Aliasing:** Use `Set-MyInvokeCommandAlias` and `Invoke-MyCommand` for mockable external commands (database calls, API invocations). - # Assert +## Development Commands - Assert-NotImplemented -} -``` \ No newline at end of file +- `./test.ps1` - Run all unit tests +- `./sync.ps1` - Sync includes to workspace/module +- `./deploy.ps1` - Deploy module +- `./release.ps1` - Release module version \ No newline at end of file From 41cf6a2ef8618a4185b76fde9997a4090346225c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 24 Nov 2025 08:05:15 +0100 Subject: [PATCH 3/8] feat(datehelper): add Get-DaysBetweenDates and epoch time conversion functions --- Test/public/datehelper.test.ps1 | 256 ++++++++++++++++++++++++++++++++ include/datehelper.ps1 | 42 +++++- 2 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 Test/public/datehelper.test.ps1 diff --git a/Test/public/datehelper.test.ps1 b/Test/public/datehelper.test.ps1 new file mode 100644 index 0000000..139fb22 --- /dev/null +++ b/Test/public/datehelper.test.ps1 @@ -0,0 +1,256 @@ +function Test_GetDaysBetweenDates_SameDates { + # Arrange + $startDate = "2025-01-15" + $endDate = "2025-01-15" + + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + + # Assert + Assert-AreEqual -Expected 0 -Presented $result +} + +function Test_GetDaysBetweenDates_PositiveRange { + # Arrange + $startDate = "2025-01-01" + $endDate = "2025-01-10" + + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + + # Assert + Assert-AreEqual -Expected 9 -Presented $result +} + +function Test_GetDaysBetweenDates_NegativeRange { + # Arrange + $startDate = "2025-01-10" + $endDate = "2025-01-01" + + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + + # Assert + Assert-AreEqual -Expected 9 -Presented $result +} + +function Test_GetDaysBetweenDates_OneYear { + # Arrange + $startDate = "2025-01-01" + $endDate = "2026-01-01" + + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + + # Assert + Assert-AreEqual -Expected 365 -Presented $result +} + +function Test_GetDaysBetweenDates_DefaultStartDate { + # Arrange + $today = Get-Date -Format 'yyyy-MM-dd' + $tomorrow = (Get-Date).AddDays(1) | Get-Date -Format 'yyyy-MM-dd' + + # Act + $result = Get-DaysBetweenDates -EndDate $tomorrow + + # Assert + Assert-AreEqual -Expected 1 -Presented $result +} + +function Test_GetDaysBetweenDates_LeapYear { + # Arrange + $startDate = "2024-02-29" # Leap day + $endDate = "2024-03-01" + + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + + # Assert + Assert-AreEqual -Expected 1 -Presented $result +} + +function Test_GetDaysBetweenDates_DecadeRange { + # Arrange + $startDate = "2015-01-01" + $endDate = "2025-01-01" + + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + + # Assert + # 10 years with 3 leap years (2016, 2020, 2024) = 7*365 + 3*366 = 3653 days + Assert-AreEqual -Expected 3653 -Presented $result +} + +function Test_GetEpochTime_ReturnsLong { + # Arrange & Act + $result = Get-EpochTime + + # Assert + Assert-IsTrue -Condition ($result -is [long]) +} + +function Test_GetEpochTime_PositiveValue { + # Arrange + $unixEpoch = [datetime]::UnixEpoch + $now = [datetime]::UtcNow + $expectedMinimum = ($now - $unixEpoch).TotalSeconds - 10 # Allow 10 second margin + + # Act + $result = Get-EpochTime + + # Assert + Assert-IsTrue -Condition ($result -ge $expectedMinimum) +} + +function Test_GetEpochTime_ReturnsCurrentTime { + # Arrange + $before = Get-EpochTime + Start-Sleep -Seconds 1 + + # Act + $after = Get-EpochTime + + # Assert + Assert-IsTrue -Condition ($after -gt $before) +} + +function Test_ConvertFromEpochTime_ValidEpochTime { + # Arrange + $epochTime = 0 # Unix epoch: 1970-01-01 00:00:00 + + # Act + $result = ConvertFrom-EpochTime -EpochTime $epochTime + + # Assert + Assert-AreEqual -Expected ([datetime]::UnixEpoch) -Presented $result +} + +function Test_ConvertFromEpochTime_OneDay { + # Arrange + $epochTime = 86400 # One day in seconds + + # Act + $result = ConvertFrom-EpochTime -EpochTime $epochTime + + # Assert + $expected = [datetime]::UnixEpoch.AddSeconds(86400) + Assert-AreEqual -Expected $expected -Presented $result +} + +function Test_ConvertFromEpochTime_LargeValue { + # Arrange + $epochTime = 1609459200 # 2021-01-01 00:00:00 UTC + + # Act + $result = ConvertFrom-EpochTime -EpochTime $epochTime + + # Assert + Assert-IsTrue -Condition ($result -is [datetime]) + Assert-AreEqual -Expected 2021 -Presented $result.Year + Assert-AreEqual -Expected 1 -Presented $result.Month + Assert-AreEqual -Expected 1 -Presented $result.Day +} + +function Test_ConvertFromEpochTime_ZeroEpochTime { + # Arrange + $epochTime = 0 + + # Act + $result = ConvertFrom-EpochTime -EpochTime $epochTime + + # Assert + Assert-AreEqual -Expected 1970 -Presented $result.Year +} + +function Test_ConvertToEpochTime_UnixEpoch { + # Arrange + $dateTime = [datetime]::UnixEpoch + + # Act + $result = ConvertTo-EpochTime -DateTime $dateTime + + # Assert + Assert-AreEqual -Expected 0 -Presented $result +} + +function Test_ConvertToEpochTime_OneDay { + # Arrange + $dateTime = [datetime]::UnixEpoch.AddDays(1) + + # Act + $result = ConvertTo-EpochTime -DateTime $dateTime + + # Assert + Assert-AreEqual -Expected 86400 -Presented $result +} + +function Test_ConvertToEpochTime_RecentDate { + # Arrange + $dateTime = [datetime]::new(2021, 1, 1, 0, 0, 0, [System.DateTimeKind]::Utc) + + # Act + $result = ConvertTo-EpochTime -DateTime $dateTime + + # Assert + Assert-AreEqual -Expected 1609459200 -Presented $result +} + +function Test_ConvertToEpochTime_ReturnsLong { + # Arrange + $dateTime = [datetime]::UtcNow + + # Act + $result = ConvertTo-EpochTime -DateTime $dateTime + + # Assert + Assert-IsTrue -Condition ($result -is [long]) +} + +function Test_ConvertToEpochTime_RoundTrip { + # Arrange + $original = [datetime]::new(2020, 6, 15, 12, 30, 45, [System.DateTimeKind]::Utc) + + # Act + $epochTime = ConvertTo-EpochTime -DateTime $original + $restored = ConvertFrom-EpochTime -EpochTime $epochTime + + # Assert + Assert-AreEqual -Expected $original -Presented $restored +} + +function Test_GetDaysBetweenDates_CrossYears { + # Arrange + $startDate = "2024-12-31" + $endDate = "2025-01-01" + + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + + # Assert + Assert-AreEqual -Expected 1 -Presented $result +} + +function Test_ConvertFromEpochTime_NegativeValue { + # Arrange + $epochTime = -86400 # One day before epoch + + # Act + $result = ConvertFrom-EpochTime -EpochTime $epochTime + + # Assert + $expected = [datetime]::UnixEpoch.AddSeconds(-86400) + Assert-AreEqual -Expected $expected -Presented $result +} + +function Test_ConvertToEpochTime_BeforeEpoch { + # Arrange + $dateTime = [datetime]::UnixEpoch.AddDays(-1) + + # Act + $result = ConvertTo-EpochTime -DateTime $dateTime + + # Assert + Assert-AreEqual -Expected -86400 -Presented $result +} diff --git a/include/datehelper.ps1 b/include/datehelper.ps1 index 41d8573..c26547c 100644 --- a/include/datehelper.ps1 +++ b/include/datehelper.ps1 @@ -11,4 +11,44 @@ function Get-DaysBetweenDates { $timeSpan = $end - $start return [Math]::Abs($timeSpan.Days) -} Export-ModuleMember -Function Get-DaysBetweenDates \ No newline at end of file +} Export-ModuleMember -Function Get-DaysBetweenDates + +function Get-EpochTime { + [CmdletBinding()] + [OutputType([long])] + param() + + $epoch = [datetime]::UnixEpoch + $now = [datetime]::UtcNow + $timeSpan = $now - $epoch + + return [long]$timeSpan.TotalSeconds +} Export-ModuleMember -Function Get-EpochTime + +function ConvertFrom-EpochTime { + [CmdletBinding()] + [OutputType([datetime])] + param( + [Parameter(Mandatory, Position = 0)] + [long]$EpochTime + ) + + $epoch = [datetime]::UnixEpoch + $dateTime = $epoch.AddSeconds($EpochTime) + + return $dateTime +} Export-ModuleMember -Function ConvertFrom-EpochTime + +function ConvertTo-EpochTime { + [CmdletBinding()] + [OutputType([long])] + param( + [Parameter(Mandatory, Position = 0)] + [datetime]$DateTime + ) + + $epoch = [datetime]::UnixEpoch + $timeSpan = $DateTime.ToUniversalTime() - $epoch + + return [long]$timeSpan.TotalSeconds +} Export-ModuleMember -Function ConvertTo-EpochTime From 174c82d36f2017e02b556d97a69c893c83891c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 24 Nov 2025 08:50:42 +0100 Subject: [PATCH 4/8] refactor(datehelper): improve date handling and test structure --- Test/public/datehelper.test.ps1 | 301 +++++++++++++------------------- include/datehelper.ps1 | 30 +++- 2 files changed, 141 insertions(+), 190 deletions(-) diff --git a/Test/public/datehelper.test.ps1 b/Test/public/datehelper.test.ps1 index 139fb22..1709bb8 100644 --- a/Test/public/datehelper.test.ps1 +++ b/Test/public/datehelper.test.ps1 @@ -1,40 +1,45 @@ function Test_GetDaysBetweenDates_SameDates { - # Arrange - $startDate = "2025-01-15" - $endDate = "2025-01-15" + Invoke-PrivateContext { + # Arrange + $startDate = "2025-01-15" + $endDate = "2025-01-15" - # Act - $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate - # Assert - Assert-AreEqual -Expected 0 -Presented $result + # Assert + Assert-AreEqual -Expected 0 -Presented $result + } } function Test_GetDaysBetweenDates_PositiveRange { - # Arrange - $startDate = "2025-01-01" - $endDate = "2025-01-10" + Invoke-PrivateContext { + # Arrange + $startDate = "2025-01-01" + $endDate = "2025-01-10" - # Act - $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + # Act + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate - # Assert - Assert-AreEqual -Expected 9 -Presented $result + # Assert + Assert-AreEqual -Expected 9 -Presented $result + } } function Test_GetDaysBetweenDates_NegativeRange { - # Arrange - $startDate = "2025-01-10" - $endDate = "2025-01-01" - - # Act - $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + # Act & Assert + Invoke-PrivateContext { + # Arrange + $startDate = "2025-01-10" + $endDate = "2025-01-01" - # Assert - Assert-AreEqual -Expected 9 -Presented $result + $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate + Assert-AreEqual -Expected 9 -Presented $result + } } function Test_GetDaysBetweenDates_OneYear { + Invoke-PrivateContext { # Arrange $startDate = "2025-01-01" $endDate = "2026-01-01" @@ -44,213 +49,141 @@ function Test_GetDaysBetweenDates_OneYear { # Assert Assert-AreEqual -Expected 365 -Presented $result + } } function Test_GetDaysBetweenDates_DefaultStartDate { # Arrange - $today = Get-Date -Format 'yyyy-MM-dd' - $tomorrow = (Get-Date).AddDays(1) | Get-Date -Format 'yyyy-MM-dd' - - # Act - $result = Get-DaysBetweenDates -EndDate $tomorrow - - # Assert - Assert-AreEqual -Expected 1 -Presented $result -} - -function Test_GetDaysBetweenDates_LeapYear { - # Arrange - $startDate = "2024-02-29" # Leap day - $endDate = "2024-03-01" - - # Act - $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate - - # Assert - Assert-AreEqual -Expected 1 -Presented $result -} - -function Test_GetDaysBetweenDates_DecadeRange { - # Arrange - $startDate = "2015-01-01" - $endDate = "2025-01-01" - - # Act - $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate - - # Assert - # 10 years with 3 leap years (2016, 2020, 2024) = 7*365 + 3*366 = 3653 days - Assert-AreEqual -Expected 3653 -Presented $result -} - -function Test_GetEpochTime_ReturnsLong { - # Arrange & Act - $result = Get-EpochTime - - # Assert - Assert-IsTrue -Condition ($result -is [long]) -} - -function Test_GetEpochTime_PositiveValue { - # Arrange - $unixEpoch = [datetime]::UnixEpoch - $now = [datetime]::UtcNow - $expectedMinimum = ($now - $unixEpoch).TotalSeconds - 10 # Allow 10 second margin - - # Act - $result = Get-EpochTime - - # Assert - Assert-IsTrue -Condition ($result -ge $expectedMinimum) -} - -function Test_GetEpochTime_ReturnsCurrentTime { - # Arrange - $before = Get-EpochTime - Start-Sleep -Seconds 1 - - # Act - $after = Get-EpochTime - - # Assert - Assert-IsTrue -Condition ($after -gt $before) -} + Reset-InvokeCommandMock -function Test_ConvertFromEpochTime_ValidEpochTime { - # Arrange - $epochTime = 0 # Unix epoch: 1970-01-01 00:00:00 + $now = Get-Date -Format 'yyyy-MM-dd' + MockCallToObject -Command "GetNow" -OutObject $now - # Act - $result = ConvertFrom-EpochTime -EpochTime $epochTime + # Act & Assert + Invoke-PrivateContext { + $futureDate = (Get-Date).AddDays(10) | Get-Date -Format 'yyyy-MM-dd' - # Assert - Assert-AreEqual -Expected ([datetime]::UnixEpoch) -Presented $result + $result = Get-DaysBetweenDates -EndDate $futureDate + + # Result should be approximately 10 days (allowing for same-day test execution) + Assert-AreEqual -Expected 10 -Presented $result + } } function Test_ConvertFromEpochTime_OneDay { - # Arrange - $epochTime = 86400 # One day in seconds - - # Act - $result = ConvertFrom-EpochTime -EpochTime $epochTime + # Act & Assert + Invoke-PrivateContext { + # Arrange + $epochTime = 86400 # One day in seconds - # Assert - $expected = [datetime]::UnixEpoch.AddSeconds(86400) - Assert-AreEqual -Expected $expected -Presented $result + $result = ConvertFrom-EpochTime -EpochTime $epochTime + $expected = [datetime]::UnixEpoch.AddSeconds(86400) + Assert-AreEqual -Expected $expected -Presented $result + } } function Test_ConvertFromEpochTime_LargeValue { - # Arrange - $epochTime = 1609459200 # 2021-01-01 00:00:00 UTC - - # Act - $result = ConvertFrom-EpochTime -EpochTime $epochTime + # Act & Assert + Invoke-PrivateContext { + # Arrange + $epochTime = 1609459200 # 2021-01-01 00:00:00 UTC - # Assert - Assert-IsTrue -Condition ($result -is [datetime]) - Assert-AreEqual -Expected 2021 -Presented $result.Year - Assert-AreEqual -Expected 1 -Presented $result.Month - Assert-AreEqual -Expected 1 -Presented $result.Day + $result = ConvertFrom-EpochTime -EpochTime $epochTime + Assert-IsTrue -Condition ($result -is [datetime]) + Assert-AreEqual -Expected 2021 -Presented $result.Year + Assert-AreEqual -Expected 1 -Presented $result.Month + Assert-AreEqual -Expected 1 -Presented $result.Day + } } function Test_ConvertFromEpochTime_ZeroEpochTime { - # Arrange - $epochTime = 0 + # Act & Assert + Invoke-PrivateContext { + # Arrange + $epochTime = 0 - # Act - $result = ConvertFrom-EpochTime -EpochTime $epochTime - - # Assert - Assert-AreEqual -Expected 1970 -Presented $result.Year -} + $result = ConvertFrom-EpochTime -EpochTime $epochTime + Assert-AreEqual -Expected 1970 -Presented $result.Year + } -function Test_ConvertToEpochTime_UnixEpoch { - # Arrange - $dateTime = [datetime]::UnixEpoch - - # Act - $result = ConvertTo-EpochTime -DateTime $dateTime + # Act & Assert + Invoke-PrivateContext { + # Arrange + $dateTime = [datetime]::UnixEpoch - # Assert - Assert-AreEqual -Expected 0 -Presented $result + $result = ConvertTo-EpochTime -DateTime $dateTime + Assert-AreEqual -Expected 0 -Presented $result + } } function Test_ConvertToEpochTime_OneDay { - # Arrange - $dateTime = [datetime]::UnixEpoch.AddDays(1) - - # Act - $result = ConvertTo-EpochTime -DateTime $dateTime + # Act & Assert + Invoke-PrivateContext { + # Arrange + $dateTime = [datetime]::UnixEpoch.AddDays(1) - # Assert - Assert-AreEqual -Expected 86400 -Presented $result + $result = ConvertTo-EpochTime -DateTime $dateTime + Assert-AreEqual -Expected 86400 -Presented $result + } } function Test_ConvertToEpochTime_RecentDate { - # Arrange - $dateTime = [datetime]::new(2021, 1, 1, 0, 0, 0, [System.DateTimeKind]::Utc) + # Act & Assert + Invoke-PrivateContext { + # Arrange + $dateTime = [datetime]::new(2021, 1, 1, 0, 0, 0, [System.DateTimeKind]::Utc) - # Act - $result = ConvertTo-EpochTime -DateTime $dateTime - - # Assert - Assert-AreEqual -Expected 1609459200 -Presented $result + $result = ConvertTo-EpochTime -DateTime $dateTime + Assert-AreEqual -Expected 1609459200 -Presented $result + } } function Test_ConvertToEpochTime_ReturnsLong { - # Arrange - $dateTime = [datetime]::UtcNow + # Act & Assert + Invoke-PrivateContext { + # Arrange + $dateTime = [datetime]::UtcNow - # Act - $result = ConvertTo-EpochTime -DateTime $dateTime - - # Assert - Assert-IsTrue -Condition ($result -is [long]) + $result = ConvertTo-EpochTime -DateTime $dateTime + Assert-IsTrue -Condition ($result -is [long]) + } } function Test_ConvertToEpochTime_RoundTrip { - # Arrange - $original = [datetime]::new(2020, 6, 15, 12, 30, 45, [System.DateTimeKind]::Utc) + # Act & Assert + Invoke-PrivateContext { + # Arrange + $original = [datetime]::new(2020, 6, 15, 12, 30, 45, [System.DateTimeKind]::Utc) - # Act - $epochTime = ConvertTo-EpochTime -DateTime $original - $restored = ConvertFrom-EpochTime -EpochTime $epochTime + $epochTime = ConvertTo-EpochTime -DateTime $original + $restored = ConvertFrom-EpochTime -EpochTime $epochTime - # Assert - Assert-AreEqual -Expected $original -Presented $restored -} - -function Test_GetDaysBetweenDates_CrossYears { - # Arrange - $startDate = "2024-12-31" - $endDate = "2025-01-01" - - # Act - $result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate - - # Assert - Assert-AreEqual -Expected 1 -Presented $result + Assert-AreEqual -Expected $original -Presented $restored + } } function Test_ConvertFromEpochTime_NegativeValue { - # Arrange - $epochTime = -86400 # One day before epoch + # Act & Assert + Invoke-PrivateContext { + # Arrange + $epochTime = -86400 # One day before epoch - # Act - $result = ConvertFrom-EpochTime -EpochTime $epochTime - - # Assert - $expected = [datetime]::UnixEpoch.AddSeconds(-86400) - Assert-AreEqual -Expected $expected -Presented $result + $result = ConvertFrom-EpochTime -EpochTime $epochTime + $expected = [datetime]::UnixEpoch.AddSeconds(-86400) + Assert-AreEqual -Expected $expected -Presented $result + } } function Test_ConvertToEpochTime_BeforeEpoch { - # Arrange - $dateTime = [datetime]::UnixEpoch.AddDays(-1) + # Act & Assert + Invoke-PrivateContext { + # Arrange + $dateTime = [datetime]::UnixEpoch.AddDays(-1) - # Act - $result = ConvertTo-EpochTime -DateTime $dateTime + $result = ConvertTo-EpochTime -DateTime $dateTime + - # Assert - Assert-AreEqual -Expected -86400 -Presented $result + # Assert + Assert-AreEqual -Expected -86400 -Presented $result + } } diff --git a/include/datehelper.ps1 b/include/datehelper.ps1 index c26547c..9e826bd 100644 --- a/include/datehelper.ps1 +++ b/include/datehelper.ps1 @@ -1,17 +1,35 @@ + +# DATE HELPER +# +# Date and time utility functions including epoch time conversion +# +# This module provides functions for: +# - Calculating days between dates +# - Converting to/from Unix epoch time +# - Getting current date and time (with Invoke-MyCommand pattern for testability) +# + +Set-MyInvokeCommandAlias -Alias GetNow -Command "Get-Date -Format 'yyyy-MM-dd'" +Set-MyInvokeCommandAlias -Alias GetUtcNow -Command "Get-Date -AsUTC" + function Get-DaysBetweenDates { [CmdletBinding()] param( - [Parameter(Position = 0)][string]$StartDate = (Get-Date -Format 'yyyy-MM-dd'), + [Parameter(Position = 0)][string]$StartDate, [Parameter(Mandatory, Position = 1)][string]$EndDate ) + if ([string]::IsNullOrWhiteSpace($StartDate)) { + $StartDate = Invoke-MyCommand -Command GetNow + } + $start = [DateTime]::ParseExact($StartDate, 'yyyy-MM-dd', $null) $end = [DateTime]::ParseExact($EndDate, 'yyyy-MM-dd', $null) $timeSpan = $end - $start return [Math]::Abs($timeSpan.Days) -} Export-ModuleMember -Function Get-DaysBetweenDates +} function Get-EpochTime { [CmdletBinding()] @@ -19,11 +37,11 @@ function Get-EpochTime { param() $epoch = [datetime]::UnixEpoch - $now = [datetime]::UtcNow + $now = Invoke-MyCommand -Command GetUtcNow $timeSpan = $now - $epoch return [long]$timeSpan.TotalSeconds -} Export-ModuleMember -Function Get-EpochTime +} function ConvertFrom-EpochTime { [CmdletBinding()] @@ -37,7 +55,7 @@ function ConvertFrom-EpochTime { $dateTime = $epoch.AddSeconds($EpochTime) return $dateTime -} Export-ModuleMember -Function ConvertFrom-EpochTime +} function ConvertTo-EpochTime { [CmdletBinding()] @@ -51,4 +69,4 @@ function ConvertTo-EpochTime { $timeSpan = $DateTime.ToUniversalTime() - $epoch return [long]$timeSpan.TotalSeconds -} Export-ModuleMember -Function ConvertTo-EpochTime +} From 9b444ce356c18604f244347865e7f966b646022e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 24 Nov 2025 09:24:27 +0100 Subject: [PATCH 5/8] refactor(config): rename Test-Configuration to Test-ConfigurationFile and improve caching logic --- Test/public/config.test.ps1 | 36 +--------- include/config.ps1 | 128 +++++++++++++++--------------------- 2 files changed, 55 insertions(+), 109 deletions(-) diff --git a/Test/public/config.test.ps1 b/Test/public/config.test.ps1 index 453c8a4..1a1ac91 100644 --- a/Test/public/config.test.ps1 +++ b/Test/public/config.test.ps1 @@ -1,44 +1,12 @@ function Test_ConfigInclude{ - Reset-InvokeCommandMock Mock_Config - # Emput config - $result = Get-IncludeHelperConfig - Assert-IsNull -Object $result + Set-IncludeHelperConfigValue -Name "config_name" -Value "test_config_value" - # Add acount - Add-IncludeHelperConfigAttribute -objectType "Account" -Attribute "acountattribute" - $result = Get-IncludeHelperConfig - Assert-Count -Expected 1 -Presented $result.account_attributes - Assert-Contains -Expected "acountattribute" -Presented $result.account_attributes - Add-IncludeHelperConfigAttribute -objectType "Account" -Attribute "acountattribute2" $result = Get-IncludeHelperConfig - Assert-Count -Expected 2 -Presented $result.account_attributes - Assert-Contains -Expected "acountattribute" -Presented $result.account_attributes - Assert-Contains -Expected "acountattribute2" -Presented $result.account_attributes - # Add user - Add-IncludeHelperConfigAttribute -objectType "User" -Attribute "userattribute" - $result = Get-IncludeHelperConfig - Assert-Count -Expected 1 -Presented $result.user_attributes - Assert-Contains -Expected "userattribute" -Presented $result.user_attributes - Add-IncludeHelperConfigAttribute -objectType "User" -Attribute "userattribute2" - $result = Get-IncludeHelperConfig - Assert-Count -Expected 2 -Presented $result.user_attributes - Assert-Contains -Expected "userattribute" -Presented $result.user_attributes - Assert-Contains -Expected "userattribute2" -Presented $result.user_attributes - - # Add Opportunity - Add-IncludeHelperConfigAttribute -objectType "Opportunity" -Attribute "opportunityattribute" - $result = Get-IncludeHelperConfig - Assert-Count -Expected 1 -Presented $result.opportunity_attributes - Assert-Contains -Expected "opportunityattribute" -Presented $result.opportunity_attributes - Add-IncludeHelperConfigAttribute -objectType "Opportunity" -Attribute "opportunityattribute2" - $result = Get-IncludeHelperConfig - Assert-Count -Expected 2 -Presented $result.opportunity_attributes - Assert-Contains -Expected "opportunityattribute" -Presented $result.opportunity_attributes - Assert-Contains -Expected "opportunityattribute2" -Presented $result.opportunity_attributes + Assert-AreEqual -Expected "test_config_value" -Presented $result.config_name } diff --git a/include/config.ps1 b/include/config.ps1 index c2ecd07..d7679e0 100644 --- a/include/config.ps1 +++ b/include/config.ps1 @@ -42,7 +42,7 @@ function GetConfigFile { return $path } -function Test-Configuration { +function Test-ConfigurationFile { [CmdletBinding()] param( [Parameter(Position = 0)][string]$Key = "config" @@ -59,9 +59,16 @@ function Get-Configuration { [Parameter(Position = 0)][string]$Key = "config" ) + # Check for cached configuration + $configVar = Get-Variable -scope Script -Name "config-$Key" -ErrorAction SilentlyContinue + if($configVar){ + return $configVar + } + + # No cached configuration; read from file $path = GetConfigFile -Key $Key - if(-Not (Test-Configuration -Key $Key)){ + if(-Not (Test-ConfigurationFile -Key $Key)){ return $null } @@ -78,7 +85,7 @@ function Get-Configuration { function Save-Configuration { [CmdletBinding()] param( - [Parameter(Position = 0)][string]$Key = "config", + [Parameter()][string]$Key = "config", [Parameter(Mandatory = $true, Position = 1)][Object]$Config ) @@ -91,6 +98,9 @@ function Save-Configuration { Write-Warning "Error saving configuration ($Key) to file: $($path). $($_.Exception.Message)" return $false } + finally{ + Remove-Variable -Scope Script -Name "config-$Key" -ErrorAction SilentlyContinue + } return $true } @@ -98,7 +108,7 @@ function Save-Configuration { ############ -# Define unique aliases for "MyModule" +# Define unique aliases for "ModuleName" $CONFIG_INVOKE_GET_ROOT_PATH_ALIAS = "$($MODULE_NAME)GetConfigRootPath" $CONFIG_INVOKE_GET_ROOT_PATH_CMD = "Invoke-$($MODULE_NAME)GetConfigRootPath" @@ -106,106 +116,74 @@ $CONFIG_INVOKE_GET_ROOT_PATH_CMD = "Invoke-$($MODULE_NAME)GetConfigRootPath" Set-MyInvokeCommandAlias -Alias $CONFIG_INVOKE_GET_ROOT_PATH_ALIAS -Command $CONFIG_INVOKE_GET_ROOT_PATH_CMD # Define the function to get the configuration root path -function Invoke-MyModuleGetConfigRootPath { +function Invoke-ModuleNameGetConfigRootPath { [CmdletBinding()] param() $configRoot = GetConfigRootPath return $configRoot } -$function = "Invoke-MyModuleGetConfigRootPath" -$NewName = $function -Replace "MyModule", $MODULE_NAME -Rename-Item -path Function:$Function -NewName $NewName -Export-ModuleMember -Function $NewName +$function = "Invoke-ModuleNameGetConfigRootPath" +$destFunction = $function -replace "ModuleName", $MODULE_NAME +if( -not (Test-Path function:$destFunction )){ + Copy-Item -path Function:$function -Destination Function:$destFunction + Export-ModuleMember -Function $destFunction +} # Extra functions not needed by INCLUDE CONFIG -function Get-MyModuleConfig{ +function Get-ModuleNameConfig{ [CmdletBinding()] param() $config = Get-Configuration return $config -} -$function = "Get-MyModuleConfig" -$NewName = $function -Replace "MyModule", $MODULE_NAME -Rename-Item -path Function:$Function -NewName $NewName -Export-ModuleMember -Function $NewName - -function Save-MyModuleConfig{ - [CmdletBinding()] - param( - [Parameter(Mandatory, ValueFromPipeline, Position = 0)][Object]$Config - ) - - return Save-Configuration -Config $Config -} $function = "Save-MyModuleConfig" -$NewName = $function -Replace "MyModule", $MODULE_NAME -Rename-Item -path Function:$Function -NewName $NewName -Export-ModuleMember -Function $NewName +} +$function = "Get-ModuleNameConfig" +$destFunction = $function -replace "ModuleName", $MODULE_NAME +if( -not (Test-Path function:$destFunction )){ + Copy-Item -path Function:$function -Destination Function:$destFunction + Export-ModuleMember -Function $destFunction +} -function Open-MyModuleConfig{ +function Open-ModuleNameConfig{ [CmdletBinding()] param() $path = GetConfigFile -Key "config" code $path +} +$function = "Open-ModuleNameConfig" +$destFunction = $function -replace "ModuleName", $MODULE_NAME +if( -not (Test-Path function:$destFunction )){ + Copy-Item -path Function:$function -Destination Function:$destFunction + Export-ModuleMember -Function $destFunction +} -} $function = "Open-MyModuleConfig" -$NewName = $function -Replace "MyModule", $MODULE_NAME -Rename-Item -path Function:$Function -NewName $NewName -Export-ModuleMember -Function $NewName - -function Add-MyModuleConfigAttribute{ +function Set-ModuleNameConfigValue{ [CmdletBinding()] param( - [Parameter(Mandatory,Position=0)][ValidateSet("Account", "User", "Opportunity")][string]$objectType, - - [Parameter(Mandatory, ValueFromPipeline, Position = 1)][string]$Attribute - + [Parameter(Mandatory = $true)][string]$Name, + [Parameter(Mandatory = $true)][object]$Value ) - begin{ - $config = Get-Configuration - $configAttribute = ($objectType + "_attributes").ToLower() - - if(-Not $config){ - $config = @{} - } - - if(-Not $config.$configAttribute){ - $config.$configAttribute = @() - } - } + $config = Get-Configuration - process{ - $config.$configAttribute += $Attribute - } - - End{ - $ret = Save-Configuration -Config $config - if(-Not $ret){ - throw "Error saving configuration" - } - - $config = Get-Configuration - Write-Output $config.$configAttribute - + if(-Not $config){ + $config = @{} } -} $function = "Add-MyModuleConfigAttribute" -$NewName = $function -Replace "MyModule", $MODULE_NAME -Rename-Item -path Function:$Function -NewName $NewName -Export-ModuleMember -Function $NewName - - - - - - - - + $config.$Name = $Value + Save-Configuration -Key "config" -Config $config +} +$function = "Set-ModuleNameConfigValue" +$destFunction = $function -replace "ModuleName", $MODULE_NAME +if( -not (Test-Path function:$destFunction )){ + # Rename-Item -path Function:$function -NewName $destFunction + Copy-Item -path Function:$function -Destination Function:$destFunction + Export-ModuleMember -Function $destFunction +} From 77702872a0b7a045cd2228e04984b5c5875601de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 24 Nov 2025 09:25:24 +0100 Subject: [PATCH 6/8] refactor(MyWrite): replace Rename-Item with Copy-Item for verbose and debug functions --- include/MyWrite.ps1 | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/MyWrite.ps1 b/include/MyWrite.ps1 index fe5460e..522a9e5 100644 --- a/include/MyWrite.ps1 +++ b/include/MyWrite.ps1 @@ -126,7 +126,7 @@ function Enable-ModuleNameVerbose{ $moduleDebugVarName = $MODULE_NAME + "_VERBOSE" [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $flag) } -Rename-Item -path Function:Enable-ModuleNameVerbose -NewName "Set-$($MODULE_NAME)Verbose" +Copy-Item -path Function:Enable-ModuleNameVerbose -Destination Function:"Set-$($MODULE_NAME)Verbose" Export-ModuleMember -Function "Set-$($MODULE_NAME)Verbose" function Disable-ModuleNameVerbose{ @@ -135,7 +135,7 @@ function Disable-ModuleNameVerbose{ $moduleDebugVarName = $MODULE_NAME + "_VERBOSE" [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $null) } -Rename-Item -path Function:Disable-ModuleNameVerbose -NewName "Clear-$($MODULE_NAME)Verbose" +Copy-Item -path Function:Disable-ModuleNameVerbose -Destination Function:"Clear-$($MODULE_NAME)Verbose" Export-ModuleMember -Function "Clear-$($MODULE_NAME)Verbose" function Test-MyDebug { @@ -173,7 +173,7 @@ function Enable-ModuleNameDebug{ $moduleDebugVarName = $MODULE_NAME + "_DEBUG" [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $flag) } -Rename-Item -path Function:Enable-ModuleNameDebug -NewName "Enable-$($MODULE_NAME)Debug" +Copy-Item -path Function:Enable-ModuleNameDebug -Destination Function:"Enable-$($MODULE_NAME)Debug" Export-ModuleMember -Function "Enable-$($MODULE_NAME)Debug" function Disable-ModuleNameDebug { @@ -182,7 +182,7 @@ function Disable-ModuleNameDebug { $moduleDebugVarName = $MODULE_NAME + "_DEBUG" [System.Environment]::SetEnvironmentVariable($moduleDebugVarName, $null) } -Rename-Item -path Function:Disable-ModuleNameDebug -NewName "Disable-$($MODULE_NAME)Debug" +Copy-Item -path Function:Disable-ModuleNameDebug -Destination Function:"Disable-$($MODULE_NAME)Debug" Export-ModuleMember -Function "Disable-$($MODULE_NAME)Debug" function Get-ObjetString { @@ -207,3 +207,5 @@ function Get-ObjetString { + + From d5092f3b932c6409e4d1f41d93205aca2e5f6761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 24 Nov 2025 09:25:58 +0100 Subject: [PATCH 7/8] chore(MyWrite): remove unnecessary blank lines --- include/MyWrite.ps1 | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/MyWrite.ps1 b/include/MyWrite.ps1 index 522a9e5..94460f0 100644 --- a/include/MyWrite.ps1 +++ b/include/MyWrite.ps1 @@ -207,5 +207,3 @@ function Get-ObjetString { - - From bd6303af5a17fb8441ed7673aad83f1bafa727df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20=28Dibildos=29=20Gonz=C3=A1lez?= Date: Mon, 24 Nov 2025 09:26:07 +0100 Subject: [PATCH 8/8] refactor(dependencies): replace Rename-Item with Copy-Item for root path function --- include/dependencies.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/dependencies.ps1 b/include/dependencies.ps1 index cea9df4..c8746fc 100644 --- a/include/dependencies.ps1 +++ b/include/dependencies.ps1 @@ -44,7 +44,7 @@ function Invoke-MODULE_NAME_RootPath{ return $root } -Rename-Item -path Function:Invoke-MODULE_NAME_RootPath -NewName $DEPENDENCY_GETMYMODULEROOTPATH_INVOKE_FUNCTION_NAME +Copy-Item -path Function:Invoke-MODULE_NAME_RootPath -Destination Function:$DEPENDENCY_GETMYMODULEROOTPATH_INVOKE_FUNCTION_NAME Export-ModuleMember -Function $DEPENDENCY_GETMYMODULEROOTPATH_INVOKE_FUNCTION_NAME function Import-Dependency{