Public/New-HDTClock.ps1
|
function New-HDTClock { <# .SYNOPSIS Creates the real IClock adapter over the system clock. .DESCRIPTION The one place in HDT that reads the wall clock or waits. PROJECT constraint 4 forbids engine logic from doing either directly, so the log writer, the state document and the retry policy all receive this object and can be swapped for New-HDTFakeClock in a test - which is what lets a backoff of several minutes be proven in microseconds. Two methods: GetUtcNow() [datetime], always Kind = Utc Sleep([int] $Millisecond) blocks for that long, returns nothing It is a [pscustomobject] carrying ScriptMethod members rather than a PowerShell class: classes dot-sourced into the module are the known flaky path across -Force re-imports (see 01-03). Every call is recorded in $Operations, before it can throw, exactly as the fakes record (tests/helpers/README.md section 4), so an ordered-operation assertion in a contract file holds against either implementation. This is the only file in the engine permitted to name [datetime]::UtcNow. Everything else asks an IClock. .PARAMETER Journal The shared cross-service operation journal. When supplied, every recorded call is appended to it in addition to $Operations, numbered globally across services, so a test can assert one ordered list spanning the filesystem, the clock and the registry. .OUTPUTS System.Management.Automation.PSCustomObject with GetUtcNow and Sleep ScriptMethods. Note that Get-Member -MemberType Method does NOT list a ScriptMethod - use -MemberType Method, ScriptMethod. .EXAMPLE $clock = New-HDTClock $clock.GetUtcNow() The current instant, in UTC, through the interface the engine uses. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Builds a stateless service adapter object; it changes no state.')] [CmdletBinding()] [OutputType([object])] param( [Parameter()] [AllowNull()] [System.Collections.ArrayList] $Journal ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $service = [pscustomobject] @{ Operations = [System.Collections.ArrayList]::new() Journal = $Journal ServiceName = 'Clock' } $service | Add-Member -MemberType ScriptMethod -Name Record -Value { param([string] $Operation, [object[]] $Argument) [void] $this.Operations.Add([pscustomobject] @{ Sequence = $this.Operations.Count + 1 Operation = $Operation Arguments = $Argument }) if ($null -ne $this.Journal) { [void] $this.Journal.Add([pscustomobject] @{ Sequence = $this.Journal.Count + 1 Service = $this.ServiceName Operation = $Operation Arguments = $Argument }) } } $service | Add-Member -MemberType ScriptMethod -Name GetOperationName -Value { return , ([string[]] @($this.Operations | ForEach-Object { $_.Operation })) } $service | Add-Member -MemberType ScriptMethod -Name GetUtcNow -Value { $this.Record('GetUtcNow', @()) return [datetime]::UtcNow } $service | Add-Member -MemberType ScriptMethod -Name Sleep -Value { param([int] $Millisecond) $this.Record('Sleep', @($Millisecond)) Start-Sleep -Milliseconds $Millisecond } return $service } |