Skip to content

test(#21): strengthen contract tests (conventions + export integrity, AST-driven) - #65

Merged
Cadacious merged 2 commits into
mainfrom
test/21-strengthen-contracts
Jun 26, 2026
Merged

Cadacious merged 2 commits into
mainfrom
test/21-strengthen-contracts

Conversation

@Cadacious

@Cadacious Cadacious commented Jun 25, 2026 •

Copy link
Copy Markdown
Contributor

Implements #21 — strengthen the Contract test layer so convention drift fails CI everywhere (one AST/introspection check per rule, data-driven over all modules+functions; each failure names the offending file/function).

New contracts

Tests/Contract/Conventions.Tests.ps1 (new): (1) comment-based help (.SYNOPSIS/.DESCRIPTION/.EXAMPLE) on every public function; (2) [CmdletBinding()] + [OutputType()]; (3) one top-level function per file (name == BaseName); (4) SEB-prefix both ways (exported must, private must not); (5) approved verbs (Get-Verb); (6) no aliases in Modules/; (7) no Write-Host in Modules/.
Exports.Tests.ps1 (extended): reverse-export (every export has a Public/*.ps1), root manifest == union of sub-module exports (both ways), every module dir listed in $Modules.

Pre-existing debt — allow-listed (green now, new drift fails)

Like build.ps1's PSSA baseline. Rules 1, CmdletBinding, exported-prefix, verbs, aliases, and all export-integrity are clean with no allow-list. Three rules had real existing violations, documented as Skip-allow-lists:

  • [OutputType()] missing on 23 public functions (Add-SEBMetric, Invoke-SEBRemoteCommand, Write-SEBLog, …).
  • -SEB prefix on 25 private functions (Test-SEBPreFlight, Protect-SEBSecret, New-SEBDiscordEmbed, …) — contradicts CLAUDE.md.
  • one-function-per-file: Test-SEBConfig.ps1 (3 file-scope validators); Write-Host: Write-SEBLog.ps1 (the logger's own console sink).

These two debts (OutputType, private-prefix) are worth a follow-up cleanup; allow-listing avoids touching ~48 files in a test PR.

Validation

Contract tests 1486 passed / 49 skipped (the documented exceptions); whole suite 2189 passed; build.ps1 BUILD OK. All 9 rules spot-checked: each turned RED on a planted violation, then reverted.

Closes #21.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Improved contract test reliability by reusing shared parsing results across the suite.
    • Expanded validation to better catch missing exports, wiring mismatches, parsing issues, and convention violations.
    • Added stronger safeguards so test allow-lists stay current and don’t hide new problems.

…one-function-per-file, SEB-prefix, approved verbs, no aliases/Write-Host, export integrity)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Cadacious
Cadacious requested a review from Copilot June 25, 2026 23:39
@coderabbitai

coderabbitai Bot commented Jun 25, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Contract tests now share a memoized AST helper, and the parse, remote-script, API, export, and convention suites use it while adding export-wiring, naming, verb, alias, and allow-list checks.

Changes

Contract test hardening

Layer / File(s) Summary
Shared AST helper
Tests/Contract/_ContractAst.ps1
Adds memoized AST parsing plus helpers for contract test AST access, function metadata, and command AST enumeration.
Parse, remote, and API tests
Tests/Contract/Parse.Tests.ps1, Tests/Contract/RemoteScriptBlock.Tests.ps1, Tests/Contract/ApiContract.Tests.ps1
Switches these suites to Get-ContractAst for parsing and AST access while keeping their existing assertions.
Export wiring checks
Tests/Contract/Exports.Tests.ps1
Expands export checks to compare root exports, public function files, sub-module manifest exports, and $Modules wiring in SEBackup.psm1.
Convention discovery and allow-lists
Tests/Contract/Conventions.Tests.ps1
Adds discovery data, allow-lists, approved verbs, and alias and Write-Host scan targets.
Convention rules
Tests/Contract/Conventions.Tests.ps1
Adds assertions for comment-based help, CmdletBinding, OutputType, file/function naming, verb usage, aliases, and Write-Host calls.
Convention guardrails
Tests/Contract/Conventions.Tests.ps1
Adds non-vacuity checks and stale allow-list validation for missing OutputType, private SEB prefixes, multi-function files, and Write-Host sites.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I hopped through tests with whiskers bright,
Cached ASTs now parse the night.
Exports and verbs stand neat in rows,
No stray Write-Host rustles toes.
🐇✨ Boing!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Only the reverse-export check is clearly implemented; GUI coverage, RemoteScriptBlock allowlist changes, and docs-lint validation from #21 are not shown. Add GUI to ApiContract, update RemoteScriptBlock to use a parameter allowlist and cover Invoke-SEBRemoteCommand, and add the docs-lint contract.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: strengthening contract tests with convention and export integrity checks.
Out of Scope Changes check ✅ Passed All changes stay within contract-test infrastructure and supporting helpers, with no unrelated production-code or side-scope edits visible.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/21-strengthen-contracts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Strengthens the Contract test layer by expanding export-integrity checks and adding a new AST-driven conventions contract, so convention/export drift fails CI consistently across all modules/functions.

Changes:

  • Extended Exports.Tests.ps1 to enforce reverse-export, root-vs-submodule export-set equality, and $Modules↔Modules/ directory integrity.
  • Added Conventions.Tests.ps1 to enforce CLAUDE.md conventions (help, attributes, naming, verbs, no aliases, no Write-Host) with documented allow-lists for pre-existing debt.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
Tests/Contract/Exports.Tests.ps1 Adds reverse export and module wiring consistency checks (root exports, submodule exports, and $Modules directory list).
Tests/Contract/Conventions.Tests.ps1 Introduces a centralized, AST-based conventions contract with allow-lists for known legacy exceptions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Tests/Contract/Exports.Tests.ps1 Outdated
Comment on lines +8 to +11
# This file also enforces the REVERSE direction and the wider export wiring (issue #21):
# - reverse-export: every root FunctionsToExport entry has a backing Public/*.ps1 file;
# - the root manifest's export set equals the union of every sub-module manifest's exports;
# - every module directory on disk is listed in the root SEBackup.psm1 $Modules array.
Comment thread Tests/Contract/Conventions.Tests.ps1 Outdated
Comment on lines +17 to +40
function script:Get-ContractFunctionInfo {
# Returns one hashtable per function definition found in $Path, with the metadata each
# convention test needs. TopLevel=$true means the function is at file scope (not nested
# inside another function) -- used by the one-function-per-file rule.
param([string]$Path)
$tokens = $null; $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors)
$topLevel = @($ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $false))
$all = @($ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true))
foreach ($fn in $all) {
$attrs = @()
if ($fn.Body.ParamBlock) { $attrs = @($fn.Body.ParamBlock.Attributes.TypeName.FullName) }
$help = $fn.GetHelpContent()
@{
Name = $fn.Name
IsTopLevel = ($topLevel -contains $fn)
HasCmdletBind = ($attrs -contains 'CmdletBinding')
HasOutputType = ($attrs -contains 'OutputType')
HasSynopsis = ($help -and -not [string]::IsNullOrWhiteSpace($help.Synopsis))
HasDescription = ($help -and -not [string]::IsNullOrWhiteSpace($help.Description))
HasExample = ($help -and $help.Examples -and $help.Examples.Count -gt 0)
}
}
}
…sentinels; stale-allow-list guard; top-level naming; memoized AST (8-lens)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Cadacious
Cadacious requested a review from Copilot June 26, 2026 00:21
@Cadacious
Cadacious merged commit 4a31db1 into main Jun 26, 2026
2 of 3 checks passed
@Cadacious
Cadacious deleted the test/21-strengthen-contracts branch June 26, 2026 00:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Comment on lines +20 to +42
# Initialise the cache once. Guard with Get-Variable so re-dot-sourcing (each container dot-sources
# this file again) does NOT wipe the accumulated cache.
if (-not (Get-Variable -Name __ContractAstCache -Scope Script -ErrorAction SilentlyContinue)) {
$script:__ContractAstCache = @{}
}

function Get-ContractAst {
# Parse $Path once and return its ScriptBlockAst; subsequent calls for the same path return
# the cached AST. Tokens/parse-errors are cached alongside so callers that need errors (the
# parse-integrity contract) can read them without re-parsing.
param([Parameter(Mandatory)][string]$Path)
$key = [System.IO.Path]::GetFullPath($Path)
if (-not $script:__ContractAstCache.ContainsKey($key)) {
$tokens = $null; $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($key, [ref]$tokens, [ref]$errors)
$script:__ContractAstCache[$key] = [pscustomobject]@{
Ast = $ast
Tokens = $tokens
Errors = @($errors)
}
}
return $script:__ContractAstCache[$key]
}
Comment on lines 21 to 24
BeforeAll {
. "$PSScriptRoot/_ContractAst.ps1" # memoized Get-ContractAst (shared parse cache)
$repoRoot = (Resolve-Path "$PSScriptRoot/../..").Path
Import-Module "$repoRoot/SEBackup.psd1" -Force -DisableNameChecking 3>$null

$tokens = $null; $errors = $null
$ast = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors)
$ast = (Get-ContractAst -Path $Path).Ast
Comment on lines +3 to +6
# Convention contract: a single layer that guards the CLAUDE.md authoring rules across the
# WHOLE codebase, so convention drift is caught once rather than per function. Each rule below
# corresponds to a section of CLAUDE.md (naming, comment-based help, [CmdletBinding]/[OutputType],
# one-function-per-file, approved verbs, no aliases, no Write-Host in module code).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Strengthen the Contract tests

2 participants