Invoke-RestMethod cmdlet error if the response is neither JSON nor XML#2862
Invoke-RestMethod cmdlet error if the response is neither JSON nor XML#2862
Conversation
|
Hi @2xmax, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution! TTYL, MSBOT; |
|
Hi @2xmax, I'm your friendly neighborhood Microsoft Pull Request Bot (You can call me MSBOT). Thanks for your contribution! TTYL, MSBOT; |
|
Eliminated the duplication caused by platform-specific libs exceptions. Also, locally I wrote some C# unit tests for the JsonObject class related to the problem I was trying to solve but found only a few C# unit tests in the project for some reason. If it makes sense, I can bring in the C# unit tests as well. |
| // JArray is a collection | ||
| else if (obj is JArray) | ||
| // JObject is a IDictionary | ||
| if (obj is JObject) |
There was a problem hiding this comment.
Please avoid double casting.
Use this pattern:
var dictionary = obj as JObject;
if (dictionary != null)
{| } | ||
|
|
||
| // JArray is a collection | ||
| else if (obj is JArray) |
There was a problem hiding this comment.
Please avoid double casting.
There was a problem hiding this comment.
Ok, I'm going to refactor this expression too. However, it will increase the nesting.
|
@daxian-dbw , @iSazonov, it is ready for your review. Would you mind looking this over? |
| // Hence consider the the entire input as a single Json content. | ||
| } | ||
| #if CORECLR | ||
| catch (Newtonsoft.Json.JsonSerializationException) |
There was a problem hiding this comment.
Why we don't want to mask this exception?
There was a problem hiding this comment.
Since it is already handled here. I decided to fix the behavior of JsonObject making them invariant of a platform rather than handling serialization exceptions for every platform. You can also take a look at the C# unit tests I mentioned above.
There was a problem hiding this comment.
I like this change, so when using JsonObject.ConvertFromJson in C#, you don't need to catch different exceptions between FullCLR and CoreCLR.
|
@daxian-dbw I see in JsonObject.cs multiple double castings. Can we ask @2xmax to fix this or better make new PR? JValue theValue = entry.Value as JValue;
result.Properties.Add(new PSNoteProperty(entry.Key, theValue.Value)); in line 184 @2xmax Before contribution you must sign CLA (a contribution license agreement) https://cla.microsoft.com |
| var list = obj as JArray; | ||
| obj = PopulateFromJArray(list, out error); | ||
| // the same as JavaScriptSerializer does | ||
| throw new ArgumentException("input", je); |
There was a problem hiding this comment.
new ArgumentException("input", je);
The first parameter is supposed to be the exception message. You need to add a resource string in WebCmdletStrings.resx for the message. The string could be something like this: Conversion from JSON failed with error: {0}. So the message generated would be var message = string.Format(CurrentCulture, WebCmdletStrings.ResourceStringName, je.Message).
| // JObject is a IDictionary | ||
| var dictionary = obj as JObject; | ||
| obj = PopulateFromJDictionary(dictionary, out error); | ||
| if (dictionary != null) |
There was a problem hiding this comment.
I hope Declaration Expressions can be brought back in C# 7. It will make code like this much more concise.
|
@2xmax we don't support NUnit, but the support to xUnit is on the way, see #2574. |
@iSazonov I think general code refactoring can go to a separate PR. Yes, those double casting should be avoid, but like @2xmax mentioned it will increase nesting, which is also annoying. Declaration Expressions would really help in such cases, if it can make into C# 7.0.
Good catch. I'm not sure if it's really a potential bug, or it's OK because the value can only be one of |
|
Thanks for the comments! I have ported the unit tests to Pester, changed the error message, signed the CLA
According to this doc, there be no other value except JArray, JObject, and JValue but maybe it is still worth to examine the Json.NET unit test suite in details; I haven't found any contradiction, yet I feel like maybe we could beware of a breaking change (e.g. caused by the nuget package update) by writing some tests here.
|
| @@ -0,0 +1,53 @@ | ||
| # Unit tests for JsonObject | ||
| Describe 'JsonObject' -tags "CI" { |
There was a problem hiding this comment.
Please move the comment to Describe
Describe 'Unit tests for JsonObject' -tags "CI" {
| <data name="JsonDeserializationFailed" xml:space="preserve"> | ||
| <value>Conversion from JSON failed with error: {0}</value> | ||
| </data> | ||
| </root> No newline at end of file |
There was a problem hiding this comment.
this file is autogenerated
| # Unit tests for JsonObject | ||
| Describe 'JsonObject' -tags "CI" { | ||
|
|
||
| function ShouldThrow |
There was a problem hiding this comment.
Please use the template with FullyQualifiedErrorId to check for proper errors
it "Get-Item on a nonexisting file should have error PathNotFound" {
try
{
get-item "ThisFileCannotPossiblyExist" -ErrorAction Stop
throw "No Exception!"
}
catch
{
$_.FullyQualifiedErrorId | should be "PathNotFound,Microsoft.PowerShell.Commands.GetItemCommand"
}
}
And see my comment about testcase.
There was a problem hiding this comment.
This man tells that I've got to verify FullyQualifiedErrorId, going to fix this, thx. However, it does not explicitly state that all the expected exception checks should
follow this template. Nevertheless, If I had adhered to the whole template, imho, the test would have been less clear and concise, so I'd prefer to keep the ShouldThrow checker. Moreover, the copypaste is prone to mistakes, just take a look at my quick findings:
grep -Hn -B 4 -ir "FullyQualifiedErrorId.*should" --include *Test*.ps1 --group-separator=$'\n\n---------------------------------\n\n'
Here developers simply forgot to throw before catch:
engine/ParameterBinding/ParameterBinding.Tests.ps1-16- test-PositionalBinding1 1
engine/ParameterBinding/ParameterBinding.Tests.ps1-17- }
engine/ParameterBinding/ParameterBinding.Tests.ps1-18- catch
engine/ParameterBinding/ParameterBinding.Tests.ps1-19- {
engine/ParameterBinding/ParameterBinding.Tests.ps1:20: $_.FullyQualifiedErrorId | should be "AmbiguousPositionalParameterNoName,test-PositionalBinding1"
---
Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1-920- try {
Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1-921- Set-ExecutionPolicy -Scope $policyScope -ExecutionPolicy Restricted
Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1-922- }
Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1-923- catch {
Modules/Microsoft.PowerShell.Security/ExecutionPolicy.Tests.ps1:924: $_.FullyQualifiedErrorId | Should Be "CantSetGroupPolicy,Microsoft.PowerShell.Commands.SetExecutionPolicyCommand"
--
Modules/Microsoft.PowerShell.Security/SecureString.Tests.ps1-15- try {
Modules/Microsoft.PowerShell.Security/SecureString.Tests.ps1-16- ConvertFrom-SecureString -secureString $null -key $null
Modules/Microsoft.PowerShell.Security/SecureString.Tests.ps1-17- }
Modules/Microsoft.PowerShell.Security/SecureString.Tests.ps1-18- catch {
Modules/Microsoft.PowerShell.Security/SecureString.Tests.ps1:19: $_.FullyQualifiedErrorId | should be "ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.ConvertFromSecureStringCommand"
--
Modules/Microsoft.PowerShell.Utility/Export-Alias.Tests.ps1-104- try{
Modules/Microsoft.PowerShell.Utility/Export-Alias.Tests.ps1-105- Export-Alias $fulltestpath abcd02
Modules/Microsoft.PowerShell.Utility/Export-Alias.Tests.ps1-106- }
Modules/Microsoft.PowerShell.Utility/Export-Alias.Tests.ps1-107- catch{
Modules/Microsoft.PowerShell.Utility/Export-Alias.Tests.ps1:108: $_.FullyQualifiedErrorId | Should be "FileOpenFailure,Microsoft.PowerShell.Commands.ExportAliasCommand"
Here instead of err checking we will get a nullref:
Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1-76- It "Call Get-TimeZone using ID param and multiple items, where first and third are invalid ids - expect error" {
Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1-77- $null = Get-TimeZone -Id @("Cape Verde Standard","Morocco Standard Time","Azores Standard") `
Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1-78- -ErrorVariable errVar -ErrorAction SilentlyContinue
Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1-79- $errVar.Count -eq 2 | Should Be $true
Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1:80: $errVar[0].FullyQualifiedErrorID | Should Be "TimeZoneNotFound,Microsoft.PowerShell.Commands.GetTimeZoneCommand"
Perhaps this is the reason why in places like here or here were desided to implement the custom assertions. Of course, all the mistakes I encountered are related to lack of calibration, but I merely revealed you a couple of drawbacks.
All in all, you can still insist on it, persuade me by proposing other arguments and other ideas to refactor it.
There was a problem hiding this comment.
the comment about testcase is valid, thx
There was a problem hiding this comment.
It's totally fine to have custom assertions in your test. I think @iSazonov meant that the try/catch section in ShouldThrow should be better use the template, so it would be like
function ShouldThrow
{
....
try {
& $InputObject
throw "Should throw ArgumentException"
} catch {
$_.FullyQualifiedErrorId | should be "ArgumentException"
}
}
For those tests you found out, they were written before we came up with the guidelines, but we do want future test to follow the guidelines as much as possible 😄
| $errRecord = $null | ||
| { [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson("{a:1", [ref]$errRecord) } | ShouldThrow 'System.ArgumentException' | ||
| } | ||
| } No newline at end of file |
|
|
||
| It 'empty string' { | ||
| $errRecord = $null | ||
| [Microsoft.PowerShell.Commands.JsonObject]::ConvertFromJson("", [ref]$errRecord) |
There was a problem hiding this comment.
All test use the same code with different data so it is recommended to use testcase
Init code must be moved to BeforeAll block. (Use the same link to docs)
There was a problem hiding this comment.
An example of -testcases can be found here. It's helpful to avoid duplication when there are a lot It blocks with same logic but different test data.
Since the number of test cases with the same logic is not many (3 for $errRecord | Should BeNullOrEmpty and 2 for ShouldThrow), I don't insist on changing the existing test cases.
There was a problem hiding this comment.
thank you guys for clarification, the test has been refactored
Yes, my question just is can we hope that this will be not changed in the future ( |
I already responded you in the same sentence you quoted "...we could beware of a breaking change (e.g. caused by the nuget package update) by writing some tests here," perhaps it was too long and convoluted. To put it simply, we can write some tests (a safety net) to prevent this. Yet I still consider this discussion is out of the scope of this PR and it is better to converse about this in a separate issue or a Slack channel. |
| @{ str = "{a:1}" } | ||
| ) | ||
|
|
||
| It 'no error for valid string' -TestCase $validStrings { |
There was a problem hiding this comment.
It's better to make the test case name different for different test cases, so that when one of the test cases failed, you will know which test data hashtable caused the failure. The recommended way is:
$validStrings = @(
@{ name = "empty"; str = "" }
@{ name = "spaces"; str = " " }
@{ name = "{a:1}"; str = "{a:1}" }
)
It "no error for valid string - <name>" -TestCase $validStrings {
....
}
When running, you will see
[+] no error for valid string - empty 407ms
[+] no error for valid string - spaces 75ms
[+] no error for valid string - {a:1} 9ms
| @{ str = "{a:1" } | ||
| ) | ||
|
|
||
| It 'throw ArgumentException for invalid string' -TestCase $invalidStrings { |
|
@2xmax Thanks for your contribution. @daxian-dbw Thanks for your great comments! |
|
Thank you both for the comments too! tried to sign the agreement one more time with no luck. Seems I'm not the only person who is having problems with the cla bot microsoft/MixedRealityToolkit-Unity#370 |
|
@2xmax I changed the label to |
This pull request fixes the Invoke-RestMethod cmdlet behavior if the response is neither XML nor JSON, e.g. just a plain text from a webservice.
Before the change:
After the change: