tests/Client/ShouldProcess.Tests.ps1

<#
    Static Test: -WhatIf / -Confirm coverage
    - Invoke-XrmRequest declares SupportsShouldProcess: every write of the module goes through it.
    - A write cmdlet (Add, Set, Remove, Update, Upsert...) that calls a cmdlet declaring SupportsShouldProcess declares it too,
      so that callers can pass -WhatIf to it (preference variables do not flow from a script into a module).
    - No direct SDK write call (Execute, Create, Update, Delete, Associate, Disassociate) outside Invoke-XrmRequest
      and Invoke-XrmParallelRequests (whose workers run where the module is not loaded).
#>

. "$PSScriptRoot\..\_TestConfig.ps1";

$srcPath = Join-Path $PSScriptRoot "..\..\src" | Resolve-Path;
$writeVerbs = @("Add", "Set", "Remove", "Update", "Upsert", "Import", "Uninstall", "Publish", "Enable", "Disable", "Copy", "Clear", "Join", "Split", "Merge", "Sync", "Start", "Send", "Share", "Revoke", "Apply", "Backup", "Restore", "Invoke");

$functions = @{};
foreach ($file in Get-ChildItem -Path $srcPath -Recurse -Include "*.ps1") {
    $ast = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$null, [ref]$null);
    $ast.FindAll({ $args[0] -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | ForEach-Object {
        $binding = $_.Body.ParamBlock.Attributes | Where-Object { $_.TypeName.Name -eq "CmdletBinding" };
        $supports = $null -ne ($binding.NamedArguments | Where-Object { $_.ArgumentName -eq "SupportsShouldProcess" });
        $functions[$_.Name] = [PSCustomObject]@{ Name = $_.Name; Ast = $_; Supports = $supports; File = $file.FullName.Substring($srcPath.Path.Length + 1) };
    };
}

Write-Section "Invoke-XrmRequest";
Assert-Test "Invoke-XrmRequest declares SupportsShouldProcess" { $functions["Invoke-XrmRequest"].Supports };

Write-Section "Write cmdlets pass -WhatIf on";
$missing = [System.Collections.Generic.List[string]]::new();
foreach ($function in $functions.Values) {
    $verb = $function.Name.Split("-")[0];
    if ($writeVerbs -notcontains $verb -or $function.Supports -or $function.File -like "_Internals*") {
        continue;
    }
    $calledWriters = $function.Ast.FindAll({ $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true) | ForEach-Object { $_.GetCommandName() } | Where-Object { $_ -and $functions.ContainsKey($_) -and $functions[$_].Supports } | Select-Object -Unique;
    if ($calledWriters) {
        $missing.Add("$($function.Name) (calls $($calledWriters -join ', '))");
    }
}
$missing | ForEach-Object { Write-Host " missing SupportsShouldProcess: $_" -ForegroundColor Yellow; };
Assert-Test "Every write cmdlet calling a ShouldProcess cmdlet declares SupportsShouldProcess ($($missing.Count) missing)" { $missing.Count -eq 0 };

Write-Section "No direct SDK write";
$directCalls = [System.Collections.Generic.List[string]]::new();
foreach ($function in $functions.Values) {
    if ($function.Name -in @("Invoke-XrmRequest", "Invoke-XrmParallelRequests")) {
        continue;
    }
    $function.Ast.FindAll({
            $args[0] -is [System.Management.Automation.Language.InvokeMemberExpressionAst] -and
            -not $args[0].Static -and
            $args[0].Member.Value -in @("Execute", "Create", "Update", "Delete", "Associate", "Disassociate")
        }, $true) | ForEach-Object {
        $directCalls.Add("$($function.File):$($_.Extent.StartLineNumber) $($_.Extent.Text)");
    };
}
$directCalls | ForEach-Object { Write-Host " direct SDK call: $_" -ForegroundColor Yellow; };
Assert-Test "No direct SDK write outside Invoke-XrmRequest ($($directCalls.Count) found)" { $directCalls.Count -eq 0 };

Write-TestSummary;