Skip to content
Merged
94 changes: 67 additions & 27 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -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
}
```
- `./test.ps1` - Run all unit tests
- `./sync.ps1` - Sync includes to workspace/module
- `./deploy.ps1` - Deploy module
- `./release.ps1` - Release module version
36 changes: 2 additions & 34 deletions Test/public/config.test.ps1
Original file line number Diff line number Diff line change
@@ -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

}

189 changes: 189 additions & 0 deletions Test/public/datehelper.test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
function Test_GetDaysBetweenDates_SameDates {
Invoke-PrivateContext {
# 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 {
Invoke-PrivateContext {
# 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 {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$startDate = "2025-01-10"
$endDate = "2025-01-01"

$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"

# Act
$result = Get-DaysBetweenDates -StartDate $startDate -EndDate $endDate

# Assert
Assert-AreEqual -Expected 365 -Presented $result
}
}

function Test_GetDaysBetweenDates_DefaultStartDate {
# Arrange
Reset-InvokeCommandMock

$now = Get-Date -Format 'yyyy-MM-dd'
MockCallToObject -Command "GetNow" -OutObject $now

# Act & Assert
Invoke-PrivateContext {
$futureDate = (Get-Date).AddDays(10) | Get-Date -Format 'yyyy-MM-dd'

$result = Get-DaysBetweenDates -EndDate $futureDate

Check notice

Code scanning / PSScriptAnalyzer

Line has trailing whitespace Note

Line has trailing whitespace
# Result should be approximately 10 days (allowing for same-day test execution)
Assert-AreEqual -Expected 10 -Presented $result
}
}

function Test_ConvertFromEpochTime_OneDay {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$epochTime = 86400 # One day in seconds

$result = ConvertFrom-EpochTime -EpochTime $epochTime
$expected = [datetime]::UnixEpoch.AddSeconds(86400)
Assert-AreEqual -Expected $expected -Presented $result
}
}

function Test_ConvertFromEpochTime_LargeValue {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$epochTime = 1609459200 # 2021-01-01 00:00:00 UTC

$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 {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$epochTime = 0

$result = ConvertFrom-EpochTime -EpochTime $epochTime
Assert-AreEqual -Expected 1970 -Presented $result.Year
}

# Act & Assert
Invoke-PrivateContext {
# Arrange
$dateTime = [datetime]::UnixEpoch

$result = ConvertTo-EpochTime -DateTime $dateTime
Assert-AreEqual -Expected 0 -Presented $result
}
}

function Test_ConvertToEpochTime_OneDay {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$dateTime = [datetime]::UnixEpoch.AddDays(1)

$result = ConvertTo-EpochTime -DateTime $dateTime
Assert-AreEqual -Expected 86400 -Presented $result
}
}

function Test_ConvertToEpochTime_RecentDate {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$dateTime = [datetime]::new(2021, 1, 1, 0, 0, 0, [System.DateTimeKind]::Utc)

$result = ConvertTo-EpochTime -DateTime $dateTime
Assert-AreEqual -Expected 1609459200 -Presented $result
}
}

function Test_ConvertToEpochTime_ReturnsLong {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$dateTime = [datetime]::UtcNow

$result = ConvertTo-EpochTime -DateTime $dateTime
Assert-IsTrue -Condition ($result -is [long])
}
}

function Test_ConvertToEpochTime_RoundTrip {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$original = [datetime]::new(2020, 6, 15, 12, 30, 45, [System.DateTimeKind]::Utc)

$epochTime = ConvertTo-EpochTime -DateTime $original
$restored = ConvertFrom-EpochTime -EpochTime $epochTime

Assert-AreEqual -Expected $original -Presented $restored
}
}

function Test_ConvertFromEpochTime_NegativeValue {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$epochTime = -86400 # One day before epoch

$result = ConvertFrom-EpochTime -EpochTime $epochTime
$expected = [datetime]::UnixEpoch.AddSeconds(-86400)
Assert-AreEqual -Expected $expected -Presented $result
}
}

function Test_ConvertToEpochTime_BeforeEpoch {
# Act & Assert
Invoke-PrivateContext {
# Arrange
$dateTime = [datetime]::UnixEpoch.AddDays(-1)

$result = ConvertTo-EpochTime -DateTime $dateTime

Check notice

Code scanning / PSScriptAnalyzer

Line has trailing whitespace Note

Line has trailing whitespace

# Assert
Assert-AreEqual -Expected -86400 -Presented $result
}
}
8 changes: 4 additions & 4 deletions include/MyWrite.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
Loading
Loading