Skip to content

fix(scripts): centralize ISO 8601 timestamp regex in CIHelpers#1343

Open
MauroDruwel wants to merge 5 commits intomicrosoft:mainfrom
MauroDruwel:fix/centralize-timestamp-regex
Open

fix(scripts): centralize ISO 8601 timestamp regex in CIHelpers#1343
MauroDruwel wants to merge 5 commits intomicrosoft:mainfrom
MauroDruwel:fix/centralize-timestamp-regex

Conversation

@MauroDruwel
Copy link
Copy Markdown

Pull Request

Description

Add Get-StandardTimestampPattern to CIHelpers.psm1 returning the canonical
regex for validating Get-StandardTimestamp output. Replace all standalone
hardcoded ISO 8601 timestamp patterns across five test files with calls to this
function, eliminating duplication and drift.

Three distinct regex variants had already emerged across the test suite:

Variant Where Risk
\.\d+Z$ CIHelpers, SecurityHelpers, FrontmatterValidation Canonical
\.\d{3,7}Z$ SecurityClasses Silently rejects valid timestamps with >7 fractional digits
.*Z$ Invoke-YamlLint Matches anything before Z, defeats validation purpose

The new function returns a single source of truth so all assertions stay in sync
automatically if the timestamp format changes.

Relationship to #1314

This PR was born from the review discussion on #1314, filed separately as #1333.

#1314 This PR
What Renames JSON keys timestampTimestamp Centralizes the regex that validates timestamp values
Scope 4 linting scripts + 5 test files CIHelpers.psm1 + 5 test files
File overlap None None
Merge order Independent Independent

Both PRs are needed — they address different layers of the same standardization
effort (#1003). #1314 fixes how timestamps are named in JSON output; this PR
fixes how timestamps are validated in tests. Neither blocks the other and they
can merge in any order without conflicts.

Note: FrontmatterValidation.Tests.ps1 still uses lowercase
$hash['timestamp'] — that key casing is #1314's scope, intentionally left
untouched here.

Related Issue(s)

Closes #1333

Type of Change

Code & Documentation:

  • Bug fix (non-breaking change fixing an issue)

Other:

  • Script/automation (.ps1, .sh, .py)

Testing

  • Replaced regex strings with (Get-StandardTimestampPattern) calls in 5 test files
  • Added 2 new verification tests in CIHelpers.Tests.ps1:
    • Get-StandardTimestampPattern returns a non-empty string
    • Pattern matches actual Get-StandardTimestamp output (cross-validation)
  • Added explicit CIHelpers module imports to 3 test files that lacked direct
    access (SecurityClasses, SecurityHelpers, FrontmatterValidation)

Files Changed

Module (new function + export):

  • scripts/lib/Modules/CIHelpers.psm1Get-StandardTimestampPattern function and Export-ModuleMember entry

Tests (hardcoded regex → function call):

  • scripts/tests/lib/CIHelpers.Tests.ps1 — replaced regex; added Describe 'Get-StandardTimestampPattern' block
  • scripts/tests/linting/FrontmatterValidation.Tests.ps1 — replaced 2 regexes; added CIHelpers import
  • scripts/tests/linting/Invoke-YamlLint.Tests.ps1 — replaced 1 regex (already imported CIHelpers)
  • scripts/tests/security/SecurityClasses.Tests.ps1 — replaced 1 regex; added CIHelpers import
  • scripts/tests/security/SecurityHelpers.Tests.ps1 — replaced 1 regex; added CIHelpers import

Checklist

Required Checks

  • Documentation is updated (if applicable)
  • Files follow existing naming conventions
  • Changes are backwards compatible (if applicable)
  • Tests added for new functionality (if applicable)

Required Automated Checks

  • Markdown linting: npm run lint:md
  • Spell checking: npm run spell-check
  • Frontmatter validation: npm run lint:frontmatter
  • Skill structure validation: npm run validate:skills
  • Link validation: npm run lint:md-links
  • PowerShell analysis: npm run lint:ps
  • Plugin freshness: npm run plugin:generate
  • Docusaurus tests: npm run docs:test

Security Considerations

  • This PR does not contain any sensitive or NDA information
  • Any new dependencies have been reviewed for security issues
  • Security-related scripts follow the principle of least privilege

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MauroDruwel MauroDruwel requested a review from a team as a code owner April 11, 2026 18:50
Copy link
Copy Markdown
Contributor

@katriendg katriendg left a comment

Choose a reason for hiding this comment

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

Lovely to have you pick up an issue from the backlog @MauroDruwel 🙏 It's looking all good to me!

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 13, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 87.65%. Comparing base (d907526) to head (81778f5).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1343      +/-   ##
==========================================
- Coverage   87.66%   87.65%   -0.01%     
==========================================
  Files          61       61              
  Lines        9328     9329       +1     
==========================================
  Hits         8177     8177              
- Misses       1151     1152       +1     
Flag Coverage Δ
pester 85.22% <100.00%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
scripts/lib/Modules/CIHelpers.psm1 97.90% <100.00%> (+0.01%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@rezatnoMsirhC rezatnoMsirhC left a comment

Choose a reason for hiding this comment

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

Thank you for your contribution! Overall things look pretty neat, but I left a few comments to ensure we maintain regression detection.

Comment on lines +574 to +597
function Get-StandardTimestampPattern {
<#
.SYNOPSIS
Returns the regex pattern that matches Get-StandardTimestamp output.

.DESCRIPTION
Returns a single-source regex anchored to the ISO 8601 round-trip format
produced by Get-StandardTimestamp (e.g. "2025-01-15T18:30:00.0000000Z").
Use this function in tests instead of hard-coding the pattern so that all
assertions stay in sync when the timestamp format changes.

.OUTPUTS
System.String - Anchored regex pattern for ISO 8601 UTC timestamps.
#>
[CmdletBinding()]
[OutputType([string])]
param()

return '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z$'
}

Export-ModuleMember -Function @(
'Get-StandardTimestamp',
'Get-StandardTimestampPattern',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nitpick: Does this need to be a function? There are no parameters and no logic in this function, so the StandardTimestampPattern could be exposed as an exported module variable instead. Something like this could work:

# Alternative: module-scoped readonly variable
$script:StandardTimestampPattern = '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z$'
...
Export-ModuleMember -Variable @('StandardTimestampPattern')
# Callers use: $StandardTimestampPattern

Comment thread scripts/tests/lib/CIHelpers.Tests.ps1 Outdated

It 'Matches ISO 8601 UTC format ending in Z' {
Get-StandardTimestamp | Should -Match '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z$'
Get-StandardTimestamp | Should -Match (Get-StandardTimestampPattern)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is an anti-pattern. While code-reuse is usually elegant and highly-encouraged, it can also be counterproductive to reuse non-test code/constants in tests.

This article provide a nice example of how things can go wrong.

For example, if Get-StandardTimestampPattern were updated to return a more generous pattern like .*Z, this test would still pass, and therefore we wouldn't be able to rely on this unit test case to help us detect the regression.

I recommend reverting this so we can better detect behavior drifts for Get-StandardTimestamp, especially since this is the module which owns the pattern's correctness. It's probably fine for other tests like those for SecurityHelpers to keep using Get-StandardTimestampPattern:

Suggested change
Get-StandardTimestamp | Should -Match (Get-StandardTimestampPattern)
Get-StandardTimestamp | Should -Match '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z$'

Comment on lines +45 to +56
Describe 'Get-StandardTimestampPattern' -Tag 'Unit' {
It 'Returns a non-empty string' {
Get-StandardTimestampPattern | Should -Not -BeNullOrEmpty
}

It 'Pattern matches Get-StandardTimestamp output' {
$timestamp = Get-StandardTimestamp
$pattern = Get-StandardTimestampPattern
$timestamp | Should -Match $pattern
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Neither test will catch if the pattern is changed to something too permissive or too restrictive. A proper test suite for Get-StandardTimestampPattern should include positive and negative cases using inline literals because this is the function that owns the pattern's correctness.

Suggested change
Describe 'Get-StandardTimestampPattern' -Tag 'Unit' {
It 'Returns a non-empty string' {
Get-StandardTimestampPattern | Should -Not -BeNullOrEmpty
}
It 'Pattern matches Get-StandardTimestamp output' {
$timestamp = Get-StandardTimestamp
$pattern = Get-StandardTimestampPattern
$timestamp | Should -Match $pattern
}
}
Describe 'Get-StandardTimestampPattern' -Tag 'Unit' {
It 'Returns a non-empty string' {
Get-StandardTimestampPattern | Should -Not -BeNullOrEmpty
}
It 'Matches a valid ISO 8601 UTC timestamp' {
'2026-04-16T14:55:00.0000000Z' | Should -Match (Get-StandardTimestampPattern)
}
It 'Does not match a timestamp missing fractional seconds' {
'2026-04-16T14:55:00Z' | Should -Not -Match (Get-StandardTimestampPattern)
}
It 'Does not match a local time without Z suffix' {
'2026-04-16T14:55:00.0000000+00:00' | Should -Not -Match (Get-StandardTimestampPattern)
}
It 'Pattern matches Get-StandardTimestamp output' {
$timestamp = Get-StandardTimestamp
$pattern = Get-StandardTimestampPattern
$timestamp | Should -Match $pattern
}
}

@MauroDruwel
Copy link
Copy Markdown
Author

Thanks so much for the careful review, @rezatnoMsirhC. Really appreciate the feedback here.

I pushed an update that addresses your points:

  1. I changed the Get-StandardTimestamp test back to an inline literal regex, so it can catch regressions independently.
  2. I expanded Get-StandardTimestampPattern tests with explicit positive and negative literal cases.

Would love a second look when you have time.

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.

Centralize ISO 8601 timestamp regex pattern in CIHelpers

5 participants