Skip to content

Conversation

@yotsuda
Copy link
Contributor

@yotsuda yotsuda commented Dec 22, 2025

PR Summary

Add compatibility tests for ConvertTo-Json to improve test coverage.

PR Context

This PR adds tests to validate ConvertTo-Json behavior, extracted from #26637 as suggested by @iSazonov.

PR Checklist

Tests Added

The following tests are added to ConvertTo-Json.Tests.ps1:

Test Description
Uri serialization Validates Uri objects serialize correctly
Enum default Validates enums serialize as numbers by default
Enum -EnumsAsStrings Validates -EnumsAsStrings parameter
null Validates null serialization
Arrays Validates array serialization
Hashtable Validates ordered hashtable serialization
Nested objects Validates nested PSCustomObject serialization
Default escaping Validates no escaping by default
-EscapeHandling EscapeHtml Validates HTML character escaping
-EscapeHandling EscapeNonAscii Validates non-ASCII escaping
-Depth Validates Depth parameter
-AsArray Validates AsArray parameter
Multiple pipeline objects Validates array output for multiple inputs
Depth over 100 Validates depth limit enforcement

@iSazonov iSazonov added the CL-Test Indicates that a PR should be marked as a test change in the Change Log label Dec 23, 2025
@iSazonov iSazonov requested a review from Copilot December 23, 2025 06:53
@iSazonov
Copy link
Collaborator

I guess we can add more tests while working on V2 JSON serializer. So I will keep this open some time.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This test-only PR adds compatibility tests for ConvertTo-Json to improve test coverage, extracted from PR #26637. While the intention is good, several of the new tests duplicate existing test coverage.

Key Points:

  • Adds 14 new test cases for ConvertTo-Json cmdlet
  • Several tests provide genuinely new coverage (Uri, enums, nested objects, EscapeNonAscii, depth validation)
  • Multiple tests are redundant with existing test coverage (null handling, default escaping, HTML escaping, AsArray, Depth, multiple pipeline objects)

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

Comment on lines 208 to 213
It 'Should escape HTML with -EscapeHandling EscapeHtml' {
$json = @{ text = "<>&" } | ConvertTo-Json -Compress -EscapeHandling EscapeHtml
$json | Should -Match '\\u003C'
$json | Should -Match '\\u003E'
$json | Should -Match '\\u0026'
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The HTML escape handling test is redundant. Lines 84-92 already test -EscapeHandling EscapeHtml with similar escaping validation. The existing test checks for \u0027 (escaped apostrophe), while this test checks for \u003C, \u003E, and \u0026 (escaped HTML characters), but the mechanism and parameter are already covered.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Kept, but consolidated into TestCases format. Line 87 tests apostrophe escaping ('), while this tests HTML tag characters (<>&). Different character sets require explicit coverage.

Comment on lines 203 to 206
It 'Should not escape by default' {
$json = @{ text = "<>&" } | ConvertTo-Json -Compress
$json | Should -BeExactly '{"text":"<>&"}'
}
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

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

The default escaping test is redundant. Lines 84-92 already test escaping behavior including:

  • Default behavior (no escaping)
  • Explicit -EscapeHandling Default
  • -EscapeHandling EscapeHtml

This test duplicates the existing coverage without adding new scenarios.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Kept with renamed test. Line 85 tests apostrophe default behavior, while this tests HTML tag characters (<>&) default behavior. Since EscapeHtml specifically targets HTML characters, explicitly testing their default (non-escaped) behavior adds value.

@iSazonov
Copy link
Collaborator

If there are duplicates we could simply add comments in existing tests to describe additional scenarios are tested. If we can not create clear comments please keep duplicate tests.

@iSazonov iSazonov self-assigned this Dec 23, 2025
@iSazonov iSazonov added the Blocked blocked on something external to this repo label Dec 23, 2025
@yotsuda
Copy link
Contributor Author

yotsuda commented Dec 23, 2025

@iSazonov Sorry for the initial submission with redundant tests. I've refactored based on your feedback:

Removed (covered by existing tests):

  • null, arrays, hashtable (lines 94-107)
  • Depth, AsArray, Multiple pipeline (lines 43-77)

Consolidated into TestCases format:

  • Enum: default + -EnumsAsStrings
  • EscapeHandling: EscapeHtml + EscapeNonAscii

Kept:

  • Uri, Nested PSCustomObject (renamed), EscapeHandling default (renamed), Depth over 100 error

Added:

  • Special floating-point values (Infinity, -Infinity, NaN)
  • Empty array ($null)
  • SwitchParameter ({"IsPresent":true/false})

Result: +29/-51 lines, removing redundancy while improving edge case coverage.

I'll add more test cases if I find other scenarios worth covering while working on V2 improvements.

yotsuda added a commit to yotsuda/PowerShell that referenced this pull request Dec 24, 2025
@microsoft-github-policy-service microsoft-github-policy-service bot added the Review - Needed The PR is being reviewed label Jan 4, 2026
@iSazonov
Copy link
Collaborator

iSazonov commented Jan 5, 2026

@yotsuda Have we tests to cover all code paths for if (currentDepth > context.MaxDepth)? Notice, we don't add extended/adapted properties if isPurePSObj = true; and add otherwise.

@microsoft-github-policy-service microsoft-github-policy-service bot removed the Review - Needed The PR is being reviewed label Jan 5, 2026
@yotsuda
Copy link
Contributor Author

yotsuda commented Jan 6, 2026

@iSazonov Added tests covering both code paths for currentDepth > context.MaxDepth (b10f8be):

  1. Pure PSObject (isPurePSObj = true): AddPsProperties returns early without adding ETS properties. The ToString() result is used as-is.
  2. Non-pure PSObject (isPurePSObj = false): AddPsProperties processes ETS properties on the string-converted value.
# Pure PSObject - depth exceeded (isPurePSObj = true)
$pso = [PSCustomObject]@{ Name = 'test' }
$pso | Add-Member -NotePropertyName ETSProp -NotePropertyValue 'ets'
@{ inner = $pso } | ConvertTo-Json -Depth 0 -Compress
# Output: {"inner":"@{Name=test; ETSProp=ets}"}

# Non-pure PSObject (Version) - depth exceeded (isPurePSObj = false)
$version = [version]'1.2.3'
$version | Add-Member -NotePropertyName ETSProp -NotePropertyValue 'ets'
@{ inner = $version } | ConvertTo-Json -Depth 0 -Compress
# Output: {"inner":"1.2.3"}

Also added a comparison test for pure PSObject serialization within depth limit.

Also fixed the DateTime ETS test (f3025f7) to use UTC, avoiding timezone conversion issues that caused intermittent failures.

@iSazonov
Copy link
Collaborator

iSazonov commented Jan 6, 2026

@yotsuda Thanks! I see if object is pure PSObject LanguagePrimitives.ConvertTo() does all work and add ETS properties so we skipp AddPSProperty(). But I wonder why these properties are not added if isPurePSObj == false - I'd expect AddPSProperty() should add. Thoughts?

@yotsuda yotsuda force-pushed the add-convertto-json-compatibility-tests branch from 78befa8 to 13e911d Compare January 6, 2026 07:23
@yotsuda
Copy link
Contributor Author

yotsuda commented Jan 6, 2026

@iSazonov Let me trace through the code flow (referring to V1/JsonObject.cs).

When isPurePSObj == false and currentDepth > MaxDepth:

  1. The object is converted to string via LanguagePrimitives.ConvertTo(obj, typeof(string), ...) (line 618-619)
  2. AddPsProperties is called with the converted string
  3. Since isPurePSObj == false, we don't early-return at line 671-673
  4. The string is wrapped in a new Dictionary with key "value" (line 680-682)
  5. AppendPsProperties is called (line 685)

In AppendPsProperties:

  • Line 710-712 checks if the original psObj.BaseObject is string or DateTime - if so, it returns early (no ETS properties added)
  • Line 716-719: Since isCustomObj == false, only Extended properties are searched (not Adapted)
  • Any Extended properties found are added to the dictionary

After AppendPsProperties returns:

  • Line 687-690: If the original rv was not a dictionary and no properties were added (dict.Count == 1), the original string is returned instead of the dictionary wrapper

So the ETS properties are added for isPurePSObj == false when:

  • The original BaseObject is not string/DateTime, AND
  • Extended properties exist on the PSObject

The properties are not added when:

  • BaseObject is string/DateTime (intentional, these types use their natural serialization)
  • No Extended properties exist

Note for #26654 (V2/STJ): While testing this, I found an issue in WriteProperty where MaxDepth == 0 directly called ToString() without going through WriteDepthExceeded, so ETS properties on nested PSObjects were not included. I've fixed this in #26654 (commit 67a1bbd) and added a test to cover this case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Blocked blocked on something external to this repo CL-Test Indicates that a PR should be marked as a test change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants