Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
62c651e
Add Path parameter to Test-Json cmdlet
ArmaanMcleod Jan 26, 2023
517c2a7
Fix parameter sets
ArmaanMcleod Jan 27, 2023
88d9dba
Add tests
ArmaanMcleod Jan 27, 2023
5b27f83
Use -Path in tests
ArmaanMcleod Jan 27, 2023
f8edd3a
Remove empty line between try/catch
ArmaanMcleod Jan 27, 2023
4e0e6ea
Fixing more codefactor issues
ArmaanMcleod Jan 27, 2023
2a6889f
Make added code consistent with code factor changes
ArmaanMcleod Jan 27, 2023
9570bc3
Put asset test paths at the start of BeforeAll block
ArmaanMcleod Jan 27, 2023
8c62387
Adding more tests
ArmaanMcleod Jan 28, 2023
9627832
Fix comment in tests
ArmaanMcleod Jan 28, 2023
ad5ab60
Put assets path into variable
ArmaanMcleod Jan 28, 2023
2973df5
Adding test for value from pipeline
ArmaanMcleod Jan 29, 2023
c2847c3
Fixing up value from pipeline tests
ArmaanMcleod Jan 30, 2023
0cd4ee6
Fix schema param in test
ArmaanMcleod Jan 30, 2023
e90b3d2
Remove extra whitespace
ArmaanMcleod Jan 31, 2023
73b1187
Use TestDrive and remove asset files
ArmaanMcleod Jan 31, 2023
a153f68
Remove Aggregate exception read resolved path
ArmaanMcleod Jan 31, 2023
fe29158
Use TestDrive variable
ArmaanMcleod Jan 31, 2023
dbb2c76
Adding -LiteralPath parameter
ArmaanMcleod Feb 2, 2023
6501253
Update test name and add another alias test
ArmaanMcleod Feb 2, 2023
7ac8cfd
Setting isLiteralPath to false by default
ArmaanMcleod Feb 2, 2023
1de49ad
Remove JsonFile alias
ArmaanMcleod Feb 4, 2023
6709394
Add error checking for resolved path not being found
ArmaanMcleod Feb 4, 2023
1200d68
Use simpler ItemNotFoundException constructor
ArmaanMcleod Feb 4, 2023
9eb1603
Remove unnecessary exception handling
ArmaanMcleod Feb 4, 2023
c7348e6
Check if file exists before reading
ArmaanMcleod Feb 4, 2023
3189ae6
Remove null check since File.Exists can handle null
ArmaanMcleod Feb 5, 2023
64d5c56
Add regions to collapse code parts
ArmaanMcleod Feb 6, 2023
dc0dc9b
Updated from feedback
ArmaanMcleod Feb 14, 2023
a1e6d79
Undo Codefactor fix for unrelated change
ArmaanMcleod Feb 15, 2023
cd3c920
Remove blank line from unrelated codefactor fix
ArmaanMcleod Feb 15, 2023
1114dfd
Exit early with WriteObject with null or whitespace json
ArmaanMcleod Feb 15, 2023
c94b462
Remove blank line before else if
ArmaanMcleod Feb 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,63 @@ namespace Microsoft.PowerShell.Commands
/// <summary>
/// This class implements Test-Json command.
/// </summary>
[Cmdlet(VerbsDiagnostic.Test, "Json", DefaultParameterSetName = ParameterAttribute.AllParameterSets, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096609")]
[Cmdlet(VerbsDiagnostic.Test, "Json", DefaultParameterSetName = JsonStringParameterSet, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096609")]
[OutputType(typeof(bool))]
public class TestJsonCommand : PSCmdlet
{
private const string SchemaFileParameterSet = "SchemaFile";
private const string SchemaStringParameterSet = "SchemaString";
#region Parameter Set Names

private const string JsonStringParameterSet = "JsonString";
private const string JsonStringWithSchemaStringParameterSet = "JsonStringWithSchemaString";
private const string JsonStringWithSchemaFileParameterSet = "JsonStringWithSchemaFile";
private const string JsonPathParameterSet = "JsonPath";
private const string JsonPathWithSchemaStringParameterSet = "JsonPathWithSchemaString";
private const string JsonPathWithSchemaFileParameterSet = "JsonPathWithSchemaFile";
Comment on lines +28 to +30
Copy link
Contributor

Choose a reason for hiding this comment

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

"JsonPath" is really unfortunate naming as it suggests JSON Path, the query syntax.

Copy link
Contributor Author

@ArmaanMcleod ArmaanMcleod May 2, 2023

Choose a reason for hiding this comment

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

@gregsdennis Agree, we could change parameter set names to something more suitable.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please wait until my #18141 merges, or I can do it afterward. That change almost erased these due to a bad rebase.

private const string JsonLiteralPathParameterSet = "JsonLiteralPath";
private const string JsonLiteralPathWithSchemaStringParameterSet = "JsonLiteralPathWithSchemaString";
private const string JsonLiteralPathWithSchemaFileParameterSet = "JsonLiteralPathWithSchemaFile";

#endregion

#region Parameters

/// <summary>
/// Gets or sets JSON string to be validated.
/// </summary>
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true)]
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = JsonStringParameterSet)]
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = JsonStringWithSchemaStringParameterSet)]
[Parameter(Position = 0, Mandatory = true, ValueFromPipeline = true, ParameterSetName = JsonStringWithSchemaFileParameterSet)]
public string Json { get; set; }

/// <summary>
/// Gets or sets JSON file path to be validated.
/// </summary>
[Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonPathParameterSet)]
[Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonPathWithSchemaStringParameterSet)]
[Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonPathWithSchemaFileParameterSet)]
public string Path { get; set; }

/// <summary>
/// Gets or sets JSON literal file path to be validated.
/// </summary>
[Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonLiteralPathParameterSet)]
[Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonLiteralPathWithSchemaStringParameterSet)]
[Parameter(Position = 0, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = JsonLiteralPathWithSchemaFileParameterSet)]
[Alias("PSPath", "LP")]
public string LiteralPath
{
get
{
return _isLiteralPath ? Path : null;
}

set
{
_isLiteralPath = true;
Path = value;
}
}

/// <summary>
/// Gets or sets schema to validate the JSON against.
/// This is optional parameter.
Expand All @@ -37,18 +81,27 @@ public class TestJsonCommand : PSCmdlet
/// then validates the JSON against the schema. Before testing the JSON string,
/// the cmdlet parses the schema doing implicitly check the schema too.
/// </summary>
[Parameter(Position = 1, ParameterSetName = SchemaStringParameterSet)]
[Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonStringWithSchemaStringParameterSet)]
[Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonPathWithSchemaStringParameterSet)]
[Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonLiteralPathWithSchemaStringParameterSet)]
[ValidateNotNullOrEmpty]
public string Schema { get; set; }

/// <summary>
/// Gets or sets path to the file containing schema to validate the JSON string against.
/// This is optional parameter.
/// </summary>
[Parameter(Position = 1, ParameterSetName = SchemaFileParameterSet)]
[Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonStringWithSchemaFileParameterSet)]
[Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonPathWithSchemaFileParameterSet)]
[Parameter(Position = 1, Mandatory = true, ParameterSetName = JsonLiteralPathWithSchemaFileParameterSet)]
[ValidateNotNullOrEmpty]
public string SchemaFile { get; set; }

#endregion

#region Private Members

private bool _isLiteralPath = false;
private JsonSchema _jschema;

/// <summary>
Expand All @@ -72,6 +125,10 @@ private static bool UnwrapException(Exception e)
return true;
}

#endregion

#region Protected Members

/// <summary>
/// Prepare a JSON schema.
/// </summary>
Expand Down Expand Up @@ -137,10 +194,38 @@ e is SecurityException
protected override void ProcessRecord()
{
bool result = true;
string jsonToParse = string.Empty;

if (Json != null)
{
jsonToParse = Json;
}
else if (Path != null)
{
string resolvedPath = PathUtils.ResolveFilePath(Path, this, _isLiteralPath);

if (!File.Exists(resolvedPath))
{
ItemNotFoundException exception = new(
Path,
"PathNotFound",
SessionStateStrings.PathNotFound);

ThrowTerminatingError(exception.ErrorRecord);
}

jsonToParse = File.ReadAllText(resolvedPath);
}

if (string.IsNullOrWhiteSpace(jsonToParse))
{
WriteObject(false);
return;
}

try
{
var parsedJson = JToken.Parse(Json);
var parsedJson = JToken.Parse(jsonToParse);

if (_jschema != null)
{
Expand All @@ -165,10 +250,12 @@ protected override void ProcessRecord()
result = false;

Exception exception = new(TestJsonCmdletStrings.InvalidJson, exc);
WriteError(new ErrorRecord(exception, "InvalidJson", ErrorCategory.InvalidData, Json));
WriteError(new ErrorRecord(exception, "InvalidJson", ErrorCategory.InvalidData, jsonToParse));
}

WriteObject(result);
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@

Describe "Test-Json" -Tags "CI" {
BeforeAll {
$validSchemaJsonPath = Join-Path -Path (Join-Path $PSScriptRoot -ChildPath assets) -ChildPath valid_schema_reference.json

$invalidSchemaJsonPath = Join-Path -Path (Join-Path $PSScriptRoot -ChildPath assets) -ChildPath invalid_schema_reference.json

$missingSchemaJsonPath = Join-Path -Path (Join-Path $PSScriptRoot -ChildPath assets) -ChildPath no_such_file.json
$assetsPath = Join-Path $PSScriptRoot -ChildPath assets
$validSchemaJsonPath = Join-Path -Path $assetsPath -ChildPath valid_schema_reference.json
$invalidSchemaJsonPath = Join-Path -Path $assetsPath -ChildPath invalid_schema_reference.json
$missingSchemaJsonPath = Join-Path -Path $assetsPath -ChildPath no_such_file.json
$missingJsonPath = Join-Path -Path $assetsPath -ChildPath no_such_file.json

$validSchemaJson = @"
{
Expand Down Expand Up @@ -65,48 +65,139 @@ Describe "Test-Json" -Tags "CI" {
errorNode
}
"@

$validJsonPath = Join-Path -Path $TestDrive -ChildPath 'validJson.json'
$validLiteralJsonPath = Join-Path -Path $TestDrive -ChildPath "[valid]Json.json"
$invalidNodeInJsonPath = Join-Path -Path $TestDrive -ChildPath 'invalidNodeInJson.json'
$invalidTypeInJsonPath = Join-Path -Path $TestDrive -ChildPath 'invalidTypeInJson.json'
$invalidTypeInJson2Path = Join-Path -Path $TestDrive -ChildPath 'invalidTypeInJson2.json'
$invalidEmptyJsonPath = Join-Path -Path $TestDrive -ChildPath 'emptyJson.json'

Set-Content -Path $validJsonPath -Value $validJson
Set-Content -LiteralPath $validLiteralJsonPath -Value $validJson
Set-Content -Path $invalidNodeInJsonPath -Value $invalidNodeInJson
Set-Content -Path $invalidTypeInJsonPath -Value $invalidTypeInJson
Set-Content -Path $invalidTypeInJson2Path -Value $invalidTypeInJson2
New-Item -Path $invalidEmptyJsonPath -ItemType File
}

It "Missing JSON schema file doesn't exist" {
Test-Path -LiteralPath $missingSchemaJsonPath | Should -BeFalse
}

It "Missing JSON file doesn't exist" {
Test-Path -LiteralPath $missingJsonPath | Should -BeFalse
}

It "Json is valid" {
Test-Json -Json $validJson | Should -BeTrue
($validJson | Test-Json) | Should -BeTrue
}

It "Json is valid against a valid schema from string" {
Test-Json -Json $validJson -Schema $validSchemaJson | Should -BeTrue
($validJson | Test-Json -Schema $validSchemaJson) | Should -BeTrue
}

It "Json is valid against a valid schema from file" {
Test-Json -Json $validJson -SchemaFile $validSchemaJsonPath | Should -BeTrue
($validJson | Test-Json -SchemaFile $validSchemaJsonPath) | Should -BeTrue
}

It "Json file specified using -Path is valid" {
Test-Json -Path $validJsonPath | Should -BeTrue
}

It "Json file specified using -LiteralPath is valid" {
Test-Json -LiteralPath $validLiteralJsonPath | Should -BeTrue
}

It "Json file specified using LiteralPath aliases -PSPath and -LP is valid" {
Test-Json -PSPath $validLiteralJsonPath | Should -BeTrue
Test-Json -LP $validLiteralJsonPath | Should -BeTrue
}

It "Json file specified using -Path from pipeline is valid" {
(Get-ChildItem -Path $validJsonPath -File | Test-Json) | Should -BeTrue
}

It "Json file specified using -LiteralPath from pipeline is valid" {
(Get-ChildItem -LiteralPath $validLiteralJsonPath -File | Test-Json) | Should -BeTrue
}

It "Json file is valid against a valid schema from string" {
Test-Json -Path $validJsonPath -Schema $validSchemaJson | Should -BeTrue
}

It "Json file is valid against a valid schema from file" {
Test-Json -Path $validJsonPath -SchemaFile $validSchemaJsonPath | Should -BeTrue
}

It "Json is invalid" {
Test-Json -Json $invalidNodeInJson -ErrorAction SilentlyContinue | Should -BeFalse
($invalidNodeInJson | Test-Json -ErrorAction SilentlyContinue) | Should -BeFalse
}

It "Json is invalid against a valid schema from string" {
Test-Json -Json $invalidTypeInJson2 -Schema $validSchemaJson -ErrorAction SilentlyContinue | Should -BeFalse
($invalidTypeInJson2 | Test-Json -Schema $validSchemaJson -ErrorAction SilentlyContinue) | Should -BeFalse

Test-Json -Json $invalidNodeInJson -Schema $validSchemaJson -ErrorAction SilentlyContinue | Should -BeFalse
($invalidNodeInJson | Test-Json -Schema $validSchemaJson -ErrorAction SilentlyContinue) | Should -BeFalse
}

It "Json is invalid against a valid schema from file" {
Test-Json -Json $invalidTypeInJson2 -SchemaFile $validSchemaJsonPath -ErrorAction SilentlyContinue | Should -BeFalse
($invalidTypeInJson2 | Test-Json -SchemaFile $validSchemaJsonPath -ErrorAction SilentlyContinue) | Should -BeFalse

Test-Json -Json $invalidNodeInJson -SchemaFile $validSchemaJsonPath -ErrorAction SilentlyContinue | Should -BeFalse
($invalidNodeInJson | Test-Json -SchemaFile $validSchemaJsonPath -ErrorAction SilentlyContinue) | Should -BeFalse
}

It "Json file is invalid against a valid schema from file" {
Test-Json -Path $invalidTypeInJson2Path -SchemaFile $validSchemaJsonPath -ErrorAction SilentlyContinue | Should -BeFalse
Test-Json -Path $invalidNodeInJsonPath -SchemaFile $validSchemaJsonPath -ErrorAction SilentlyContinue | Should -BeFalse
}

It "Json file is invalid" {
Test-Json -Path $invalidNodeInJsonPath -ErrorAction SilentlyContinue | Should -BeFalse
}

It "Json file is invalid against a valid schema from string" {
Test-Json -Path $invalidTypeInJson2Path -Schema $validSchemaJson -ErrorAction SilentlyContinue | Should -BeFalse
Test-Json -Path $invalidNodeInJsonPath -Schema $validSchemaJson -ErrorAction SilentlyContinue | Should -BeFalse
}

It "Json file is invalid against an empty file" {
Test-Json -Path $invalidEmptyJsonPath -ErrorAction SilentlyContinue | Should -BeFalse
}

It "Test-Json throw if a schema from string is invalid" {
{ Test-Json -Json $validJson -Schema $invalidSchemaJson -ErrorAction Stop } | Should -Throw -ErrorId "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
{ Test-Json -Path $validJsonPath -Schema $invalidSchemaJson -ErrorAction Stop } | Should -Throw -ErrorId "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Test-Json throw if a schema from file is invalid" {
{ Test-Json -Json $validJson -SchemaFile $invalidSchemaJsonPath -ErrorAction Stop } | Should -Throw -ErrorId "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
{ Test-Json -Path $validJsonPath -SchemaFile $invalidSchemaJsonPath -ErrorAction Stop } | Should -Throw -ErrorId "InvalidJsonSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Test-Json throw if a path to a schema from file is invalid" {
{ Test-Json -Json $validJson -SchemaFile $missingSchemaJsonPath -ErrorAction Stop } | Should -Throw -ErrorId "JsonSchemaFileOpenFailure,Microsoft.PowerShell.Commands.TestJsonCommand"
{ Test-Json -Path $validJsonPath -SchemaFile $missingSchemaJsonPath -ErrorAction Stop } | Should -Throw -ErrorId "JsonSchemaFileOpenFailure,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Test-Json throw if a path from file is invalid" {
{ Test-Json -Path $missingJsonPath -ErrorAction Stop } | Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Test-Json throw if a path from file using -Path is a literal path" {
{ Test-Json -Path $validLiteralJsonPath -ErrorAction Stop } | Should -Throw -ErrorId "FileOpenFailure,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Json file throw if a path from file using -LiteralPath is a wildcard or regular expression" {
{ Test-Json -LiteralPath (Join-Path -Path $TestDrive -ChildPath "*Json.json") -ErrorAction Stop } | Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.TestJsonCommand"
{ Test-Json -LiteralPath (Join-Path -Path $TestDrive -ChildPath "[a-z]Json.json") -ErrorAction Stop } | Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Test-Json write an error on invalid (<name>) Json against a valid schema from string" -TestCases @(
Expand All @@ -133,6 +224,30 @@ Describe "Test-Json" -Tags "CI" {
$errorVar.FullyQualifiedErrorId | Should -BeExactly $errorId
}

It "Test-Json write an error on invalid (<name>) Json file against a valid schema from string" -TestCases @(
@{ name = "type"; json = $invalidTypeInJsonPath; errorId = "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand" }
@{ name = "node"; json = $invalidNodeInJsonPath; errorId = "InvalidJson,Microsoft.PowerShell.Commands.TestJsonCommand" }
) {
param ($json, $errorId)

$errorVar = $null
Test-Json -Path $json -Schema $validSchemaJson -ErrorVariable errorVar -ErrorAction SilentlyContinue

$errorVar.FullyQualifiedErrorId | Should -BeExactly $errorId
}

It "Test-Json write an error on invalid (<name>) Json file against a valid schema from file" -TestCases @(
@{ name = "type"; json = $invalidTypeInJsonPath; errorId = "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand" }
@{ name = "node"; json = $invalidNodeInJsonPath; errorId = "InvalidJson,Microsoft.PowerShell.Commands.TestJsonCommand" }
) {
param ($json, $errorId)

$errorVar = $null
Test-Json -Path $json -SchemaFile $validSchemaJsonPath -ErrorVariable errorVar -ErrorAction SilentlyContinue

$errorVar.FullyQualifiedErrorId | Should -BeExactly $errorId
}

It "Test-Json return all errors when check invalid Json against a valid schema from string" {
$errorVar = $null
Test-Json -Json $invalidTypeInJson2 -Schema $validSchemaJson -ErrorVariable errorVar -ErrorAction SilentlyContinue
Expand All @@ -153,6 +268,26 @@ Describe "Test-Json" -Tags "CI" {
$errorVar[1].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Test-Json return all errors when check invalid Json file against a valid schema from string" {
$errorVar = $null
Test-Json -Path $invalidTypeInJson2Path -Schema $validSchemaJson -ErrorVariable errorVar -ErrorAction SilentlyContinue

# '$invalidTypeInJson2Path' contains two errors in property types.
$errorVar.Count | Should -Be 2
$errorVar[0].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
$errorVar[1].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It "Test-Json return all errors when check invalid Json file against a valid schema from file" {
$errorVar = $null
Test-Json -Path $invalidTypeInJson2Path -SchemaFile $validSchemaJsonPath -ErrorVariable errorVar -ErrorAction SilentlyContinue

# '$invalidTypeInJson2Path' contains two errors in property types.
$errorVar.Count | Should -Be 2
$errorVar[0].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
$errorVar[1].FullyQualifiedErrorId | Should -BeExactly "InvalidJsonAgainstSchema,Microsoft.PowerShell.Commands.TestJsonCommand"
}

It 'Test-Json recognizes non-object types: <name>' -TestCases @(
@{ name = 'number'; value = 1; expected = 'number' }
@{ name = '"true"'; value = '"true"'; expected = 'string' }
Expand Down