Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -712,33 +712,31 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {
}

It "Validate Invoke-WebRequest returns valid RelationLink property with absolute uris if Link Header is present" {
$command = "Invoke-WebRequest -Uri 'http://localhost:8080/PowerShell?test=linkheader&maxlinks=5'"
$uri = Get-WebListenerUrl -Test 'Link' -Query @{maxlinks = 5; linknumber = 2}
$command = "Invoke-WebRequest -Uri '$uri'"
$result = ExecuteWebCommand -command $command
$result.Output.RelationLink.Count | Should BeExactly 2
$result.Output.RelationLink["next"] | Should BeExactly "http://localhost:8080/PowerShell?test=linkheader&maxlinks=5&linknumber=2"
$result.Output.RelationLink["last"] | Should BeExactly "http://localhost:8080/PowerShell?test=linkheader&maxlinks=5&linknumber=5"

$result.Output.RelationLink.Count | Should BeExactly 5
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why we use BeExactly in the whole file for Count?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it was like that before I didn't change them. Does it really matter though?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd prefer Should Be because BeExactly is used for strings and my first thought was that here Count is a method in RelationLink which return a string.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@iSazonov This appears to happen in more place. Lets leave it as is here and I will address them all as part of #5669 which I have updated.

$baseUri = Get-WebListenerUrl -Test 'Link'
$result.Output.RelationLink["next"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=3&type=default"
$result.Output.RelationLink["last"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=5&type=default"
$result.Output.RelationLink["prev"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=1&type=default"
$result.Output.RelationLink["first"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=1&type=default"
$result.Output.RelationLink["self"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=2&type=default"
}

# Test pending support for multiple header capable server on Linux/macOS see issue #4639
It "Validate Invoke-WebRequest returns valid RelationLink property with absolute uris if Multiple Link Headers are present" -Pending:$(!$IsWindows) {
$Query = @{
body = "ok"
contenttype = 'text/plain'
headers = @{
Link = @(
'<http://localhost:8080/PowerShell?test=linkheader&maxlinks=5&linknumber=1>; rel="self"'
'<http://localhost:8080/PowerShell?test=linkheader&maxlinks=5&linknumber=2>; rel="next"'
'<http://localhost:8080/PowerShell?test=linkheader&maxlinks=5&linknumber=5>; rel="last"'
)
} | ConvertTo-Json -Compress
}
$Uri = Get-WebListenerUrl -Test 'Response' -Query $Query
It "Validate Invoke-WebRequest returns valid RelationLink property with absolute uris if Multiple Link Headers are present" {
$uri = Get-WebListenerUrl -Test 'Link' -Query @{maxlinks = 5; linknumber = 2; type = 'multiple'}
$command = "Invoke-WebRequest -Uri '$uri'"
$result = ExecuteWebCommand -command $command
$result.Output.RelationLink.Count | Should BeExactly 3
$result.Output.RelationLink["self"] | Should BeExactly "http://localhost:8080/PowerShell?test=linkheader&maxlinks=5&linknumber=1"
$result.Output.RelationLink["next"] | Should BeExactly "http://localhost:8080/PowerShell?test=linkheader&maxlinks=5&linknumber=2"
$result.Output.RelationLink["last"] | Should BeExactly "http://localhost:8080/PowerShell?test=linkheader&maxlinks=5&linknumber=5"

$result.Output.RelationLink.Count | Should BeExactly 5
$baseUri = Get-WebListenerUrl -Test 'Link'
$result.Output.RelationLink["next"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=3&type=multiple"
$result.Output.RelationLink["last"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=5&type=multiple"
$result.Output.RelationLink["prev"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=1&type=multiple"
$result.Output.RelationLink["first"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=1&type=multiple"
$result.Output.RelationLink["self"] | Should BeExactly "${baseUri}?maxlinks=5&linknumber=2&type=multiple"
}

It "Validate Invoke-WebRequest quietly ignores invalid Link Headers in RelationLink property: <type>" -TestCases @(
Expand All @@ -747,10 +745,15 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {
@{ type = "noRel" }
) {
param($type)
$command = "Invoke-WebRequest -Uri 'http://localhost:8080/PowerShell?test=linkheader&type=$type'"
$uri = Get-WebListenerUrl -Test 'Link' -Query @{type = $type}
$command = "Invoke-WebRequest -Uri '$uri'"
$result = ExecuteWebCommand -command $command
$result.Output.RelationLink.Count | Should BeExactly 1
$result.Output.RelationLink["last"] | Should BeExactly "http://localhost:8080/PowerShell?test=linkheader&maxlinks=3&linknumber=3"

$result.Output.RelationLink.Count | Should BeExactly 3
$baseUri = Get-WebListenerUrl -Test 'Link'
$result.Output.RelationLink["last"] | Should BeExactly "${baseUri}?maxlinks=3&linknumber=3&type=${type}"
$result.Output.RelationLink["first"] | Should BeExactly "${baseUri}?maxlinks=3&linknumber=1&type=${type}"
$result.Output.RelationLink["self"] | Should BeExactly "${baseUri}?maxlinks=3&linknumber=1&type=${type}"
}

#region Redirect tests
Expand Down Expand Up @@ -1026,9 +1029,7 @@ Describe "Invoke-WebRequest tests" -Tags "Feature" {
$result.Output.RawContent | Should Match ([regex]::Escape('Content-Length: 2'))
}

# Test pending due to limitation on Linux/macOS
# https://github.com/PowerShell/PowerShell/pull/4640
It "Verifies Invoke-WebRequest Supports Multiple response headers with same name" -Pending:$(!$IsWindows) {
It "Verifies Invoke-WebRequest Supports Multiple response headers with same name" {
$query = @{
contenttype = 'text/plain'
body = 'OK'
Expand Down Expand Up @@ -1719,23 +1720,23 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" {

It "Validate Invoke-RestMethod -FollowRelLink correctly follows all the available relation links" {
$maxLinks = 5

$command = "Invoke-RestMethod -Uri 'http://localhost:8081/PowerShell?test=linkheader&maxlinks=$maxlinks' -FollowRelLink"
$uri = Get-WebListenerUrl -Test 'Link' -Query @{maxlinks = $maxLinks}
$command = "Invoke-RestMethod -Uri '$uri' -FollowRelLink"
$result = ExecuteWebCommand -command $command

$result.Output.output.Count | Should BeExactly $maxLinks
1..$maxLinks | ForEach-Object { $result.Output.output[$_ - 1] | Should BeExactly $_ }
$result.Output.Count | Should BeExactly $maxLinks
1..$maxLinks | ForEach-Object { $result.Output[$_ - 1].linknumber | Should BeExactly $_ }
}

It "Validate Invoke-RestMethod -FollowRelLink correctly limits to -MaximumRelLink" {
$maxLinks = 10
$maxLinksToFollow = 6

$command = "Invoke-RestMethod -Uri 'http://localhost:8081/PowerShell?test=linkheader&maxlinks=$maxlinks' -FollowRelLink -MaximumFollowRelLink $maxLinksToFollow"
$uri = Get-WebListenerUrl -Test 'Link' -Query @{maxlinks = $maxLinks}
$command = "Invoke-RestMethod -Uri '$uri' -FollowRelLink -MaximumFollowRelLink $maxLinksToFollow"
$result = ExecuteWebCommand -command $command

$result.Output.output.Count | Should BeExactly $maxLinksToFollow
1..$maxLinksToFollow | ForEach-Object { $result.Output.output[$_ - 1] | Should BeExactly $_ }
$result.Output.Count | Should BeExactly $maxLinksToFollow
1..$maxLinksToFollow | ForEach-Object { $result.Output[$_ - 1].linknumber | Should BeExactly $_ }
}

It "Validate Invoke-RestMethod quietly ignores invalid Link Headers if -FollowRelLink is specified: <type>" -TestCases @(
Expand All @@ -1744,9 +1745,10 @@ Describe "Invoke-RestMethod tests" -Tags "Feature" {
@{ type = "noRel" }
) {
param($type)
$command = "Invoke-RestMethod -Uri 'http://localhost:8081/PowerShell?test=linkheader&type=$type' -FollowRelLink"
$uri = Get-WebListenerUrl -Test 'Link' -Query @{type = $type}
$command = "Invoke-RestMethod -Uri '$uri' -FollowRelLink"
$result = ExecuteWebCommand -command $command
$result.Output.output | Should BeExactly 1
$result.Output.linknumber | Should BeExactly 1
Copy link
Member

Choose a reason for hiding this comment

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

should there be any links in this case?

Copy link
Member

Choose a reason for hiding this comment

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

never mind

}

#region Redirect tests
Expand Down
1 change: 1 addition & 0 deletions test/tools/Modules/WebListener/WebListener.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ function Get-WebListenerUrl {
'Encoding',
'Get',
'Home',
'Link',
'Multipart',
'Patch',
'Post',
Expand Down
6 changes: 6 additions & 0 deletions test/tools/WebListener/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ internal static class Constants
{
public const string HeaderSeparator = ", ";
public const string ApplicationJson = "application/json";
public const string LinkUriTemplate = "<{0}?maxlinks={1}&linknumber={2}&type={3}>; rel=\"{4}\"";
public const string MalformedUrlLinkHeader = "{url}; foo";
public const string NoRelLinkHeader = "<url>; foo=\"bar\"";
public const string NoUrlLinkHeader = "<>; rel=\"next\"";


}

internal static class StatusCodes
Expand Down
102 changes: 102 additions & 0 deletions test/tools/WebListener/Controllers/LinkController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Primitives;
using mvc.Models;

namespace mvc.Controllers
{
public class LinkController : Controller
{
public JsonResult Index()
{
if (!Request.Query.TryGetValue("maxlinks", out StringValues maxLinksSV) || !Int32.TryParse(maxLinksSV.FirstOrDefault(), out int maxLinks) || maxLinks < 1)
{
maxLinks = 3;
}

if (!Request.Query.TryGetValue("linknumber", out StringValues linkNumberSV) || !Int32.TryParse(linkNumberSV.FirstOrDefault(), out int linkNumber) || linkNumber < 1)
{
linkNumber = 1;
}

string baseUri = Regex.Replace(UriHelper.GetDisplayUrl(Request), "\\?.*", String.Empty);
Copy link
Collaborator

Choose a reason for hiding this comment

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

So simple? 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Something wrong with it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, I expected a url parsing 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

heh. So far as I can tell, the only way to get the request url is GetDisplayUrl() which returns a string. I could use UriBuilder on that string.. BUT.. on macOS UriBuilder behaves differently with the query section resulting in invalid urls.. rather than mess with that, the regex works just fine :)


string type = Request.Query.TryGetValue("type", out StringValues typeSV) ? typeSV.FirstOrDefault() : "default";

var linkList = new List<String>();
if (maxLinks > 1 && linkNumber > 1)
{
linkList.Add(GetLink(baseUri: baseUri, maxLinks: maxLinks, linkNumber: linkNumber - 1, type: type, rel: "prev"));
}
linkList.Add(GetLink(baseUri: baseUri, maxLinks: maxLinks, linkNumber: maxLinks, type: type, rel: "last"));
linkList.Add(GetLink(baseUri: baseUri, maxLinks: maxLinks, linkNumber: 1, type: type, rel: "first"));
linkList.Add(GetLink(baseUri: baseUri, maxLinks: maxLinks, linkNumber: linkNumber, type: type, rel: "self"));

bool sendMultipleHeaders = false;
bool skipNextLink = false;
switch (type.ToUpper())
{
case "NOURL":
linkList.Add(Constants.NoUrlLinkHeader);
skipNextLink = true;
break;
case "MALFORMED":
linkList.Add(Constants.MalformedUrlLinkHeader);
skipNextLink = true;
break;
case "NOREL":
linkList.Add(Constants.NoRelLinkHeader);
skipNextLink = true;
break;
case "MULTIPLE":
sendMultipleHeaders = true;
break;
default:
break;
}

if (!skipNextLink && maxLinks > 1 && linkNumber < maxLinks)
{
linkList.Add(GetLink(baseUri: baseUri, maxLinks: maxLinks, linkNumber: linkNumber + 1, type: type, rel: "next"));
}

StringValues linkHeader;
if (sendMultipleHeaders)
{
linkHeader = linkList.ToArray();
}
else
{
linkHeader = String.Join(",", linkList);
}
Response.Headers.Add("Link", linkHeader);

// Generate /Get/ result and append linknumber, maxlinks, and type
var getController = new GetController();
getController.ControllerContext = this.ControllerContext;
var result = getController.Index();
var output = result.Value as Hashtable;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Never null?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It will always return a HashTable unless some uncaught exception prevents it from even getting to this point.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks.

Closed.

output.Add("linknumber", linkNumber);
output.Add("maxlinks", maxLinks);
output.Add("type", type.FirstOrDefault());

return result;
}

public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}

private string GetLink(string baseUri, int maxLinks, int linkNumber, string type, string rel)
{
return String.Format(Constants.LinkUriTemplate, baseUri, maxLinks, linkNumber, type, rel);
}
}
}
61 changes: 61 additions & 0 deletions test/tools/WebListener/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,67 @@ Invoke-RestMethod -Uri $uri -Body @{TestField = 'TestValue'}
}
```

### /Link/

Returns Link response headers to test paginated results. The endpoint accepts 3 query items:

* `linknumber` - The current link number. This determines the current page. If not supplied or less than 1, this will be set to 1.
* `maxlinks` - The maximum number of links. This determines the last page. If not supplied or less than 1, this will be set to 3.
* `type` - The type of link to return. When not supplied or not in the list below, `default` will be used.
* `default` - Does not return any special test links and returns `next` link if one is available.
* `norel` - Returns a Link header that does not include the `rel=` portion. Suppresses `next` link.
* `nourl` - Returns a Link header that does not include the URI portion. Suppresses `next` link.
* `malformed` - Returns a malformed Link header. Suppresses `next` link.
* `multiple` - Returns multiple Link headers instead of a single Link header and returns `next` link if one is available.

The body will contain the same results as `/Get/` with the addition of the `type`, `linknumber`, and `maxlinks` for the current page.

```powershell
$Query = @{
linknumber = 1
maxlinks = 3
type = 'default'
}
$Uri = Get-WebListenerUrl -Test 'Link' -Query $Query
Invoke-RestMethod -Uri $uri -FollowRelLink -MaximumFollowRelLink 1
```

Headers:

```none
HTTP/1.1 200 OK
Date: Sat, 06 Jan 2018 14:27:36 GMT
Content-Type: application/json; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
Link: <http://localhost:8083/Link/?maxlinks=3&linknumber=3>; rel="last",<http://localhost:8083/Link/?maxlinks=3&linknumber=1>; rel="first",<http://localhost:8083/Link/?maxlinks=3&linknumber=1>; rel="self",<http://localhost:8083/Link/?maxlinks=3&linknumber=2>; rel="next"
```

Body:

```json
{
"type": "default",
"url": "http://localhost:8083/Link/?maxlinks=3&linknumber=1&type=default",
"maxlinks": 3,
"linknumber": 1,
"headers": {
"User-Agent": "insomnia/5.12.4",
"Accept": "*/*",
"Content-Length": "0",
"Host": "localhost:8083",
"Content-Type": "application/json"
},
"args": {
"linknumber": "1",
"maxlinks": "3",
"type": "default"
},
"origin": "127.0.0.1",
"method": "GET"
}
```

### /Multipart/

#### GET
Expand Down
3 changes: 2 additions & 1 deletion test/tools/WebListener/Views/Home/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
<li>/Delete/ - returns data from a DELETE request</li>
<li><a href="/Encoding/Utf8/">/Encoding/Utf8/</a> - Returns page containing UTF-8 data.</li>
<li><a href="/Get/">/Get/</a> - Emulates functionality of https://httpbin.org/get by returning GET headers, Arguments, and Request URL</li>
<li><a href="/Multipart/">/Multipart/</a> - Multipart/form-data submission testing</li>
<li><a href="/Get/">/Get/</a> - Emulates functionality of https://httpbin.org/get by returning GET headers, Arguments, and Request URL</li>
<li><a href="/Link/?linknumber=1&maxlinks=3&type=default">/Link/</a> - Link Header (pagination) testing</li>
<li>/Patch/ - returns data from a PATCH request</li>
<li>/Post/ - returns data from a POST request</li>
<li>/Put/ - returns data from a PUT request</li>
Expand Down