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 @@ -431,11 +431,12 @@ message RpcException {

// Http cookie type. Note that only name and value are used for Http requests
message RpcHttpCookie {
// Enum that lets servers require that a cookie shouoldn't be sent with cross-site requests
// Enum that lets servers require that a cookie shouldn't be sent with cross-site requests
enum SameSite {
None = 0;
Lax = 1;
Strict = 2;
ExplicitNone = 3;
}

// Cookie name
Expand Down
14 changes: 9 additions & 5 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ jobs:
IMAGE_TYPE: 'vs2017-win2016'
NODE_VERSION: $(NODE_12)
MAC_NODE8:
IMAGE_TYPE: 'macos-10.13'
IMAGE_TYPE: 'macos-10.14'
NODE_VERSION: $(NODE_8)
MAC_NODE10:
IMAGE_TYPE: 'macos-10.13'
IMAGE_TYPE: 'macos-10.14'
NODE_VERSION: $(NODE_10)
MAC_NODE12:
IMAGE_TYPE: 'macos-10.13'
IMAGE_TYPE: 'macos-10.14'
NODE_VERSION: $(NODE_12)
pool:
vmImage: $(IMAGE_TYPE)
Expand All @@ -63,8 +63,6 @@ jobs:
NODE_VERSION: $(NODE_8)
NODE10:
NODE_VERSION: $(NODE_10)
NODE12:
NODE_VERSION: $(NODE_12)
pool:
vmImage: 'vs2017-win2016'
steps:
Expand All @@ -81,12 +79,18 @@ jobs:
displayName: 'setup tests'
- powershell: |
.\run-e2e-tests.ps1
displayName: 'run tests'
env:
AzureWebJobsStorage: $(AzureWebJobsStorage)
AzureWebJobsEventHubSender: $(AzureWebJobsEventHubSender)
AzureWebJobsCosmosDBConnectionString: $(AzureWebJobsCosmosDBConnectionString)
FUNCTIONS_WORKER_RUNTIME: 'node'
languageWorkers:node:workerDirectory: $(System.DefaultWorkingDirectory)
- task: PublishTestResults@2
condition: always()
inputs:
testRunner: VSTest
testResultsFiles: '**/*.trx'

- job: BuildArtifacts
condition: and(succeeded(), or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(variables['Build.SourceBranch'], 'refs/heads/v2.x')))
Expand Down
2 changes: 1 addition & 1 deletion run-e2e-tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ function RunTest([string] $project, [string] $description,[bool] $skipBuild = $f
Write-Host "-----------------------------------------------------------------------------" -ForegroundColor DarkCyan
Write-Host

$cmdargs = "test", "$project", "-v", "q", "-l", "trx", "-r","..\..\..\testResults"
$cmdargs = "test", "$project", "-v", "q", "-l", "trx", "-r",".\testResults"

if ($filter) {
$cmdargs += "--filter", "$filter"
Expand Down
11 changes: 7 additions & 4 deletions src/converters/RpcHttpConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,13 @@ function toRpcHttpCookie(inputCookie: Cookie): rpc.IRpcHttpCookie {
// Resolve SameSite enum, a one-off
let rpcSameSite: rpc.RpcHttpCookie.SameSite = rpc.RpcHttpCookie.SameSite.None;
if (inputCookie && inputCookie.sameSite) {
if (inputCookie.sameSite.toLocaleLowerCase() === "lax") {
rpcSameSite = rpc.RpcHttpCookie.SameSite.Lax;
} else if (inputCookie.sameSite.toLocaleLowerCase() === "strict") {
rpcSameSite = rpc.RpcHttpCookie.SameSite.Strict;
let sameSite = inputCookie.sameSite.toLocaleLowerCase();
if (sameSite === "lax") {
rpcSameSite = rpc.RpcHttpCookie.SameSite.Lax;
} else if (sameSite === "strict") {
rpcSameSite = rpc.RpcHttpCookie.SameSite.Strict;
} else if (sameSite === "none") {
rpcSameSite = rpc.RpcHttpCookie.SameSite.ExplicitNone;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/public/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export interface Cookie {
httpOnly?: boolean;

/** Can restrict the cookie to not be sent with cross-site requests */
sameSite?: "Strict" | "Lax" | undefined;
sameSite?: "Strict" | "Lax" | "None" | undefined;

/** Number of seconds until the cookie expires. A zero or negative number will expire the cookie immediately. */
maxAge?: number;
Expand Down
38 changes: 38 additions & 0 deletions test/RpcHttpConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,44 @@ describe('Rpc Converters', () => {
expect((<any>rpcCookies[2].expires).value.seconds).to.equal(819199440);
});

it('converts http cookie SameSite', () => {
let cookieInputs: Cookie[] =
[
{
name: "none-cookie",
value: "myvalue",
sameSite: "None"
},
{
name: "lax-cookie",
value: "myvalue",
sameSite: "Lax"
},
{
name: "strict-cookie",
value: "myvalue",
sameSite: "Strict"
},
{
name: "default-cookie",
value: "myvalue"
}
];

let rpcCookies = toRpcHttpCookieList(<Cookie[]>cookieInputs);
expect(rpcCookies[0].name).to.equal("none-cookie");
expect(rpcCookies[0].sameSite).to.equal(rpc.RpcHttpCookie.SameSite.ExplicitNone);

expect(rpcCookies[1].name).to.equal("lax-cookie");
expect(rpcCookies[1].sameSite).to.equal(rpc.RpcHttpCookie.SameSite.Lax);

expect(rpcCookies[2].name).to.equal("strict-cookie");
expect(rpcCookies[2].sameSite).to.equal(rpc.RpcHttpCookie.SameSite.Strict);

expect(rpcCookies[3].name).to.equal("default-cookie");
expect(rpcCookies[3].sameSite).to.equal(rpc.RpcHttpCookie.SameSite.None);
});

it('throws on invalid input', () => {
expect(() => {
let cookieInputs = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ await Utilities.RetryAsync(async () =>
{
try
{
retrievedDocument = await _docDbClient.ReadDocumentAsync(docUri);
retrievedDocument = await _docDbClient.ReadDocumentAsync(docUri, new RequestOptions { PartitionKey = new PartitionKey(docId) });
return true;
}
catch (DocumentClientException ex) when (ex.Error.Code == "NotFound")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace Azure.Functions.NodeJs.Tests.E2E
{
class HttpHelpers
{
public static async Task<HttpResponseMessage> InvokeHttpTrigger(string functionName, string queryString = "")
{
// Basic http request
HttpRequestMessage request = GetTestRequest(functionName, queryString);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
return await GetResponseMessage(request);
}

public static async Task<HttpResponseMessage> InvokeHttpTriggerWithBody(string functionName, string body, HttpStatusCode expectedStatusCode, string mediaType, int expectedCode = 0)
{
HttpRequestMessage request = GetTestRequest(functionName);
request.Content = new StringContent(body);
request.Content.Headers.ContentType = new MediaTypeHeaderValue(mediaType);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(mediaType));
return await GetResponseMessage(request);
}

private static HttpRequestMessage GetTestRequest(string functionName, string queryString = "")
{
return new HttpRequestMessage
{
RequestUri = new Uri($"{Constants.FunctionsHostUrl}/api/{functionName}{queryString}"),
Method = HttpMethod.Post
};
}

private static async Task<HttpResponseMessage> GetResponseMessage(HttpRequestMessage request)
{
HttpResponseMessage response = null;
using (var httpClient = new HttpClient())
{
response = await httpClient.SendAsync(request);
}

return response;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Xunit;

Expand All @@ -25,7 +31,15 @@ public HttpEndToEndTests(FunctionAppFixture fixture)
public async Task HttpTriggerTests(string functionName, string queryString, HttpStatusCode expectedStatusCode, string expectedMessage)
{
// TODO: Verify exception on 500 after https://github.com/Azure/azure-functions-host/issues/3589
Assert.True(await Utilities.InvokeHttpTrigger(functionName, queryString, expectedStatusCode, expectedMessage));
HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger(functionName, queryString);
string actualMessage = await response.Content.ReadAsStringAsync();

Assert.Equal(expectedStatusCode, response.StatusCode);

if (!string.IsNullOrEmpty(expectedMessage)) {
Assert.False(string.IsNullOrEmpty(actualMessage));
Assert.True(actualMessage.Contains(expectedMessage));
}
}

[Theory]
Expand All @@ -34,15 +48,65 @@ public async Task HttpTriggerTests(string functionName, string queryString, Http
[InlineData("HttpTriggerBodyAndRawBody", "{\"a\":1}", "application/octet-stream", HttpStatusCode.OK)]
[InlineData("HttpTriggerBodyAndRawBody", "abc", "text/plain", HttpStatusCode.OK)]

public async Task HttpTriggerTestsWithCustomMediaType(string functionName, string queryString, string mediaType, HttpStatusCode expectedStatusCode)
public async Task HttpTriggerTestsWithCustomMediaType(string functionName, string body, string mediaType, HttpStatusCode expectedStatusCode)
{
Assert.True(await Utilities.InvokeHttpTriggerWithBody(functionName, queryString, expectedStatusCode, mediaType));
HttpResponseMessage response = await HttpHelpers.InvokeHttpTriggerWithBody(functionName, body, expectedStatusCode, mediaType);
JObject responseBody = JObject.Parse(await response.Content.ReadAsStringAsync());

Assert.Equal(expectedStatusCode, response.StatusCode);
VerifyBodyAndRawBody(responseBody, body, mediaType);
}

[Fact(Skip = "Not yet enabled.")]
[Fact]
public async Task HttpTriggerWithCookieTests()
{
Assert.True(await Utilities.InvokeHttpTrigger("HttpTriggerSetsCookie", "", HttpStatusCode.OK, "mycookie=myvalue, mycookie2=myvalue2"));
HttpResponseMessage response = await HttpHelpers.InvokeHttpTrigger("HttpTriggerSetsCookie");
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
List<string> cookies = response.Headers.SingleOrDefault(header => header.Key == "Set-Cookie").Value.ToList();
Assert.Equal(5, cookies.Count);
Assert.Equal("mycookie=myvalue; max-age=200000; path=/", cookies[0]);
Assert.Equal("mycookie2=myvalue; max-age=200000; path=/", cookies[1]);
Assert.Equal("mycookie3-expires=myvalue3-expires; max-age=0; path=/", cookies[2]);
Assert.Equal("mycookie4-samesite-lax=myvalue; path=/; samesite=lax", cookies[3]);
Assert.Equal("mycookie5-samesite-strict=myvalue; path=/; samesite=strict", cookies[4]);
// Assert.Equal("mycookie4-samesite-none=myvalue; path=/; samesite=none", cookies[5]);
}

private static void VerifyBodyAndRawBody(JObject result, string input, string mediaType)
{
if (mediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase))
{
try
{
Assert.Equal(input, (string)result["reqRawBody"]);
Assert.True(JToken.DeepEquals((JObject)result["reqBody"], JObject.Parse(input)));
}
catch (InvalidCastException) // Invalid JSON
{
Assert.Equal(input, (string)result["reqRawBody"]);
Assert.Equal(input, (string)result["reqBody"]);
}
}
else if (IsMediaTypeOctetOrMultipart(mediaType))
{
JObject reqBody = (JObject)result["reqBody"];
byte[] responseBytes = reqBody["data"].ToObject<byte[]>();
Assert.True(responseBytes.SequenceEqual(Encoding.UTF8.GetBytes(input)));
Assert.Equal(input, (string)result["reqRawBody"]);
}
else if (mediaType.Equals("text/plain", StringComparison.OrdinalIgnoreCase))
{
Assert.Equal(input, (string)result["reqRawBody"]);
Assert.Equal(input, (string)result["reqBody"]);
} else {
Assert.Equal("Supported media types are 'text/plain' 'application/octet-stream', 'multipart/*', and 'application/json'", $"Found mediaType '{mediaType}'");
}
}

private static bool IsMediaTypeOctetOrMultipart(string mediaType)
{
return mediaType != null && (string.Equals(mediaType, "application/octet-stream", StringComparison.OrdinalIgnoreCase)
|| mediaType.IndexOf("multipart/", StringComparison.OrdinalIgnoreCase) >= 0);
}
}
}
Loading