Modules/businessdev.ALbuild.Apps/Public/Test-BcCallGraphArchitecture.ps1

function Test-BcCallGraphArchitecture {
    <#
    .SYNOPSIS
        Checks callgraph.architecture.json against a call graph.
 
    .DESCRIPTION
        Reports, by name and with a suggestion where one is close: parts with an unknown parent, kind or no
        objects, patterns that match nothing, descriptions of objects that do not exist, flow steps that name an
        unknown node or are not connected by a call to the step before, and tour stops that are neither part
        nor object. Also counts the objects no part claims (keep it under ~10%) and the ones without a
        description. Never throws for a finding; Valid is false when there are warnings.
 
    .PARAMETER GraphPath
        The call graph folder written by New-BcCallGraph.
 
    .PARAMETER ArchitecturePath
        The architecture file. Default: callgraph.architecture.json in the workspace root.
 
    .PARAMETER WorkspaceRoot
        Repository root. Default: recorded by New-BcCallGraph.
 
    .EXAMPLE
        Test-BcCallGraphArchitecture -GraphPath .albuild/callgraph
 
    .OUTPUTS
        PSCustomObject: Valid, Source, Path, Parts, Objects, Unassigned, Described, Flows, Warnings, UnassignedObjects.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)] [string] $GraphPath,
        [string] $ArchitecturePath,
        [string] $WorkspaceRoot
    )

    $context = Get-BcCallGraphContext -GraphPath $GraphPath -WorkspaceRoot $WorkspaceRoot -ArchitecturePath $ArchitecturePath
    $w = [ALbuild.CallGraph.Analyzer]::Open($context.Graph.GraphPath, $context.Apps, $context.WorkspaceRoot, $context.ArchitecturePath)
    $arch = $w.Architecture
    $unassigned = @($arch.Objects | Where-Object { -not $arch.Assignment.ContainsKey($_.Key) })
    $described = @($arch.Objects | Where-Object { $arch.Descriptions.ContainsKey($_.Label) }).Count
    $warnings = @($arch.Warnings)
    if ($arch.Source -ne 'file') { Write-ALbuildLog -Level Warning "No architecture file$(if ($context.ArchitecturePath) { " at '$($context.ArchitecturePath)'" }); checked the folder-based parts." }
    if ($arch.Objects.Count -gt 0 -and $unassigned.Count / $arch.Objects.Count -gt 0.1) {
        $warnings += "$($unassigned.Count) of $($arch.Objects.Count) objects belong to no part (keep it under 10%)."
    }
    foreach ($m in $warnings) { Write-ALbuildLog -Level Warning "Architecture: $m" }
    Write-ALbuildLog ("Architecture ({0}): {1} parts, {2} objects, {3} unassigned, {4} described, {5} flows, {6} warning(s)." -f
        $arch.Source, $arch.Parts.Count, $arch.Objects.Count, $unassigned.Count, $described, $arch.Flows.Count, $warnings.Count)
    [PSCustomObject]@{
        Valid             = ($warnings.Count -eq 0)
        Source            = $arch.Source
        Path              = $context.ArchitecturePath
        Parts             = $arch.Parts.Count
        Objects           = $arch.Objects.Count
        Unassigned        = $unassigned.Count
        Described         = $described
        Flows             = $arch.Flows.Count
        Warnings          = $warnings
        UnassignedObjects = @($unassigned | Select-Object -First 200 | ForEach-Object { "$($_.Label) ($($_.Path))" })
    }
}