Skip to content

Commit b7b0790

Browse files
authored
Merge pull request #70 from /issues/68-ci-add-PSScriptAnalyzer-linting-with-repo-settings
CI quality gates (Pester runner + PSScriptAnalyzer + artifacts + SARIF)
2 parents 26138e2 + 0d6eca9 commit b7b0790

11 files changed

Lines changed: 774 additions & 126 deletions

File tree

.github/workflows/ci.yml

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ jobs:
1717
name: Pester (${{ matrix.os }})
1818
runs-on: ${{ matrix.os }}
1919

20-
# add job-scoped permissions needed for artifact upload
2120
permissions:
2221
contents: read
2322
actions: write
@@ -32,15 +31,49 @@ jobs:
3231

3332
- name: Run Pester
3433
shell: pwsh
35-
run: pwsh -NoProfile -File ./tools/run-tests.ps1 -CI
34+
run: pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1 -CI
3635

37-
- name: Upload test results
36+
- name: Upload Pester artifacts
3837
if: always()
39-
continue-on-error: true
4038
uses: actions/upload-artifact@v6
4139
with:
42-
name: test-results-${{ matrix.os }}
43-
path: artifacts/test-results.xml
40+
name: pester-artifacts-${{ matrix.os }}
41+
if-no-files-found: warn
42+
path: |
43+
artifacts/test-results.xml
44+
artifacts/coverage.xml
45+
46+
lint:
47+
name: PSScriptAnalyzer
48+
runs-on: ubuntu-latest
49+
50+
permissions:
51+
contents: read
52+
actions: read
53+
security-events: write
54+
55+
steps:
56+
- uses: actions/checkout@v6
57+
58+
- name: Run PSScriptAnalyzer
59+
shell: pwsh
60+
run: pwsh -NoProfile -File ./tools/Invoke-IdleScriptAnalyzer.ps1 -CI
61+
62+
- name: Upload PSScriptAnalyzer artifacts
63+
if: always()
64+
uses: actions/upload-artifact@v6
65+
with:
66+
name: psscriptanalyzer-artifacts
67+
if-no-files-found: warn
68+
path: |
69+
artifacts/pssa-results.json
70+
artifacts/pssa-results.sarif
71+
72+
- name: Upload SARIF to GitHub Code Scanning
73+
if: always() && github.event_name == 'push' && github.ref == 'refs/heads/main'
74+
uses: github/codeql-action/upload-sarif@v4
75+
with:
76+
sarif_file: artifacts/pssa-results.sarif
4477

4578
docs-cmdlet-reference:
4679
name: Verify cmdlet reference is up to date
@@ -63,12 +96,11 @@ jobs:
6396
# Ignore if not supported in this environment
6497
}
6598
}
66-
99+
67100
# platyPS is pinned for deterministic Markdown output.
68101
# See CONTRIBUTING.md for upgrade procedure.
69-
70102
Install-Module -Name platyPS -RequiredVersion 0.14.2 -Scope CurrentUser -Force -AllowClobber -ErrorAction Stop
71-
103+
72104
- name: Debug platyPS version
73105
shell: pwsh
74106
run: |

.github/workflows/issue-auto-assign.yml

Lines changed: 0 additions & 32 deletions
This file was deleted.

CONTRIBUTING.md

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,54 @@ Pull Requests must:
123123

124124
A contribution is complete when:
125125

126-
- all tests pass (`Invoke-Pester -Path ./tests`)
127-
- no architecture rules are violated (see `docs/01-architecture.md`)
126+
- all tests pass (`pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1`)
127+
- static analysis passes (`pwsh -NoProfile -File ./tools/Invoke-IdleScriptAnalyzer.ps1`)
128+
- no architecture rules are violated (see `docs/advanced/architecture.md`)
128129
- public APIs are documented (comment-based help for exported functions)
129130
- documentation is updated where required:
130131
- README.md (only high-level overview + pointers)
131132
- docs/ (usage/concepts/examples)
132133
- provider/step module READMEs if behavior/auth changes
133134

135+
## Local quality checks
136+
137+
IdLE provides canonical scripts under `tools/` so you can reproduce the same checks locally that CI runs.
138+
139+
### Run tests (Pester)
140+
141+
Run the test suite:
142+
143+
- `pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1`
144+
145+
To generate CI-like artifacts (test results + coverage) under `artifacts/`:
146+
147+
- `pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1 -CI`
148+
149+
Outputs:
150+
151+
- `artifacts/test-results.xml` (NUnitXml)
152+
- `artifacts/coverage.xml` (coverage report)
153+
154+
### Run static analysis (PSScriptAnalyzer)
155+
156+
Run PSScriptAnalyzer using the repository settings:
157+
158+
- `pwsh -NoProfile -File ./tools/Invoke-IdleScriptAnalyzer.ps1`
159+
160+
To generate CI-like artifacts under `artifacts/` (including SARIF for GitHub Code Scanning):
161+
162+
- `pwsh -NoProfile -File ./tools/Invoke-IdleScriptAnalyzer.ps1 -CI`
163+
164+
Outputs:
165+
166+
- `artifacts/pssa-results.json` (summary)
167+
- `artifacts/pssa-results.sarif` (SARIF)
168+
169+
The rule set is defined in `PSScriptAnalyzerSettings.psd1` at the repository root.
170+
The runner pins tool versions for deterministic CI results; update pins intentionally and document the change in the PR.
171+
172+
> Note: `artifacts/` is a build output folder and should not be committed.
173+
134174
---
135175

136176
## Generated cmdlet reference (platyPS)
@@ -253,11 +293,6 @@ To fix a failing PR:
253293

254294
Repository maintainers should configure branch protection so that required status checks include this workflow.
255295

256-
257-
258-
259-
260-
261296
## Documentation
262297

263298
Keep docs short and linkable:
@@ -269,7 +304,7 @@ Keep docs short and linkable:
269304
Key links:
270305

271306
- Docs map: `docs/00-index.md`
272-
- Architecture: `docs/01-architecture.md`
307+
- Architecture: `docs/advanced/architecture.md`
273308
- Examples: `docs/02-examples.md`
274309
- Coding & in-code documentation rules: `STYLEGUIDE.md`
275310

PSScriptAnalyzerSettings.psd1

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# PSScriptAnalyzer settings for IdentityLifecycleEngine (IdLE)
2+
#
3+
# This file is intentionally data-only (no script blocks, no expressions).
4+
# It is used by CI and can also be referenced from VS Code workspace settings.
5+
#
6+
# Notes:
7+
# - We explicitly list IncludeRules to keep the first rollout focused and low-noise.
8+
# - Formatting rules are enabled to align with STYLEGUIDE.md (4 spaces, consistent whitespace).
9+
10+
@{
11+
Severity = @('Error', 'Warning')
12+
13+
IncludeRules = @(
14+
# Naming / API hygiene
15+
'PSUseApprovedVerbs',
16+
'PSAvoidGlobalVars',
17+
'PSAvoidUsingCmdletAliases',
18+
'PSAvoidUsingPositionalParameters',
19+
'PSUseCorrectCasing',
20+
21+
# Common correctness issues
22+
'PSAvoidUsingEmptyCatchBlock',
23+
'PSReviewUnusedParameter',
24+
'PSUseDeclaredVarsMoreThanAssignments',
25+
'PSAvoidTrailingWhitespace',
26+
27+
# Security / risky constructs
28+
'PSAvoidUsingInvokeExpression',
29+
'PSAvoidUsingPlainTextForPassword',
30+
'PSAvoidUsingConvertToSecureStringWithPlainText',
31+
32+
# Style / formatting (enabled explicitly)
33+
'PSUseConsistentIndentation',
34+
'PSUseConsistentWhitespace'
35+
)
36+
37+
Rules = @{
38+
PSUseConsistentIndentation = @{
39+
Enable = $true
40+
IndentationSize = 4
41+
PipelineIndentation = 'IncreaseIndentationForFirstPipeline'
42+
Kind = 'space'
43+
}
44+
45+
PSUseConsistentWhitespace = @{
46+
Enable = $true
47+
CheckInnerBrace = $true
48+
CheckOpenBrace = $true
49+
CheckOpenParen = $true
50+
CheckOperator = $true
51+
CheckPipe = $true
52+
CheckPipeForRedundantWhitespace = $false
53+
CheckSeparator = $true
54+
CheckParameter = $false
55+
IgnoreAssignmentOperatorInsideHashTable = $false
56+
}
57+
}
58+
}

STYLEGUIDE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,20 @@ Providers:
9999
- No live system calls in unit tests
100100
- Providers require contract tests
101101

102+
## Quality Gates
103+
104+
IdLE uses static analysis and automated tests to keep the codebase consistent and maintainable.
105+
106+
- **PSScriptAnalyzer** is the required linter for PowerShell code.
107+
- Repository policy is defined in `PSScriptAnalyzerSettings.psd1` (repo root).
108+
- Run locally via `pwsh -NoProfile -File ./tools/Invoke-IdleScriptAnalyzer.ps1`.
109+
- CI publishes analyzer outputs under `artifacts/`.
110+
- On default-branch runs, CI also uploads SARIF to GitHub Code Scanning.
111+
112+
- **Pester** is the required test framework.
113+
- Run locally via `pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1`.
114+
- CI publishes test results and coverage under `artifacts/`.
115+
102116
---
103117

104118
## Documentation Responsibilities

docs/advanced/releases.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,19 @@ Pre-release tags do **not** publish to PowerShell Gallery.
6363
pwsh -NoProfile -File ./tools/Set-IdleModuleVersion.ps1 -TargetVersion 1.2.0
6464
```
6565

66-
3. Run tests:
66+
3. Run quality checks locally:
6767

68-
```powershell
69-
pwsh -NoProfile -File ./tools/run-tests.ps1
70-
```
68+
- Pester tests:
69+
70+
```powershell
71+
pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1
72+
```
73+
74+
- Static analysis (PSScriptAnalyzer):
75+
76+
```powershell
77+
pwsh -NoProfile -File ./tools/Invoke-IdleScriptAnalyzer.ps1
78+
```
7179
7280
4. Commit and push the changes.
7381
5. Open a Pull Request to `main` and wait for CI to pass.

docs/advanced/testing.md

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,67 @@
11
# Testing
22

3-
IdLE is designed to be testable in isolation.
3+
IdLE is designed to be testable in isolation. Tests should be deterministic, fast, and runnable on any machine (local or CI) without requiring live systems.
4+
5+
## Running tests locally
6+
7+
Use the canonical test runner:
8+
9+
```powershell
10+
pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1
11+
```
12+
13+
Enable coverage (optional):
14+
15+
```powershell
16+
pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1 -EnableCoverage
17+
```
18+
19+
If you want a specific coverage format:
20+
21+
```powershell
22+
pwsh -NoProfile -File ./tools/Invoke-IdlePesterTests.ps1 -EnableCoverage -CoverageOutputFormat Cobertura
23+
```
424

525
## Unit tests
626

727
Unit tests should:
828

9-
- use Pester
10-
- use mock providers
29+
- use **Pester**
30+
- use **mock providers**
1131
- avoid live system calls
32+
- prefer explicit, committed fixtures over writing ad-hoc temporary files
1233

1334
## Provider contract tests
1435

1536
Provider contract tests verify that an implementation matches the expected contract.
16-
They can run against:
1737

18-
- a mock harness
19-
- a local test double
20-
- a dedicated test tenant (only when explicitly intended)
38+
They should:
39+
40+
- test the *contract behavior* (inputs/outputs, error handling, capability surface)
41+
- run against **mock/file providers** by default
42+
- run against real providers only as an explicit, opt-in scenario (separate pipeline / environment)
43+
44+
## CI artifacts
45+
46+
The CI pipeline produces test artifacts under the `artifacts/` folder and uploads them.
47+
48+
Expected outputs:
49+
50+
- `artifacts/test-results.xml` (NUnitXml test results)
51+
- `artifacts/coverage.xml` (code coverage report; format depends on configuration)
52+
53+
## Static analysis
2154

22-
## Workflow validation in CI
55+
IdLE uses **PSScriptAnalyzer** as a CI quality gate to enforce baseline style and correctness rules.
2356

24-
Validate workflows and step metadata in CI using a dedicated validation command.
57+
Local run:
2558

26-
Principles:
59+
```powershell
60+
pwsh -NoProfile -File ./tools/Invoke-IdleScriptAnalyzer.ps1
61+
```
2762

28-
- fail fast for unknown keys
29-
- fail early for invalid references
30-
- keep configuration data-only (no script blocks)
63+
The analyzer uses the repository settings file:
3164

32-
## Tips
65+
- `PSScriptAnalyzerSettings.psd1` (repo root)
3366

34-
- Prefer deterministic input fixtures
35-
- Keep tests readable and focused
36-
- Treat public cmdlets as stable contracts
67+
In CI, PSScriptAnalyzer emits machine-readable artifacts under `artifacts/` (JSON and optional SARIF) and can publish SARIF findings to GitHub Code Scanning on default-branch runs.

tests/ProviderContracts/EntitlementProvider.Contract.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ function Invoke-IdleEntitlementProviderContractTests {
117117
[void]$script:Provider.RevokeEntitlement($id, $entitlement)
118118
$afterRevoke = @($script:Provider.ListEntitlements($id))
119119

120-
($afterGrant | Where-Object { $_.Kind -eq $entitlement.Kind -and $_.Id -eq $entitlement.Id }).Count | Should -Be 1
121-
($afterRevoke | Where-Object { $_.Kind -eq $entitlement.Kind -and $_.Id -eq $entitlement.Id }).Count | Should -Be 0
120+
@($afterGrant | Where-Object { $_.Kind -eq $entitlement.Kind -and $_.Id -eq $entitlement.Id }).Count | Should -Be 1
121+
@($afterRevoke | Where-Object { $_.Kind -eq $entitlement.Kind -and $_.Id -eq $entitlement.Id }).Count | Should -Be 0
122122

123123
# Sanity: $null is treated as empty.
124124
($before -is [object[]]) | Should -BeTrue

0 commit comments

Comments
 (0)