Skip to content

Commit

Permalink
tests and change behavior to throw instead of warn
Browse files Browse the repository at this point in the history
  • Loading branch information
mhoeger committed Jun 17, 2019
1 parent 9254d96 commit 1561699
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 48 deletions.
29 changes: 17 additions & 12 deletions src/WorkerChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,24 @@ export class WorkerChannel implements IWorkerChannel {
invocationId: msg.invocationId,
result: this.getStatus(err)
}
if (result) {
if (result.return) {
let returnBinding = info.getReturnBinding();
response.returnValue = returnBinding ? returnBinding.converter(result.return) : toTypedData(result.return);
}
if (result.bindings) {
response.outputData = Object.keys(info.outputBindings)
.filter(key => result.bindings[key] !== undefined)
.map(key => <rpc.IParameterBinding>{
name: key,
data: info.outputBindings[key].converter(result.bindings[key])
});

try {
if (result) {
if (result.return) {
let returnBinding = info.getReturnBinding();
response.returnValue = returnBinding ? returnBinding.converter(result.return) : toTypedData(result.return);
}
if (result.bindings) {
response.outputData = Object.keys(info.outputBindings)
.filter(key => result.bindings[key] !== undefined)
.map(key => <rpc.IParameterBinding>{
name: key,
data: info.outputBindings[key].converter(result.bindings[key])
});
}
}
} catch (e) {
response.result = this.getStatus(e)
}

this._eventStream.write({
Expand Down
39 changes: 31 additions & 8 deletions src/converters/RpcConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
INullableDouble,
INullableTimestamp
} from '../../azure-functions-language-worker-protobuf/src/rpc';
import { systemWarn } from '../utils/Logger';

/**
* Converts 'ITypedData' input from the RPC layer to JavaScript types.
Expand Down Expand Up @@ -65,8 +64,10 @@ export function toNullableBool(nullable: boolean | undefined, propertyName: stri
return <INullableBool>{
value: nullable
};
} else if (nullable != null) {
systemWarn(`A 'boolean' type was expected instead of a '${typeof nullable}' type. Cannot parse value for '${propertyName}'.`);
}

if (nullable != null) {
throw new Error(`A 'boolean' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.`);
}

return undefined;
Expand All @@ -90,13 +91,33 @@ export function toNullableDouble(nullable: number | string | undefined, property
value: parsedNumber
};
}
} else if (nullable != null) {
systemWarn(`A 'number' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.`);
}

if (nullable != null) {
throw new Error(`A 'number' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.`);
}

return undefined;
}

/**
* Converts string input to an 'INullableString' to be sent through the RPC layer.
* Input that is not a string but is also not null or undefined logs a function app level warning.
* @param nullable Input to be converted to an INullableString if it is a valid string
* @param propertyName The name of the property that the caller will assign the output to. Used for debugging.
*/
export function toRpcString(nullable: string | undefined, propertyName: string): string {
if (typeof nullable === 'string') {
return <string>nullable;
}

if (nullable != null) {
throw new Error(`A 'string' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.`);
}

return "";
}

/**
* Converts string input to an 'INullableString' to be sent through the RPC layer.
* Input that is not a string but is also not null or undefined logs a function app level warning.
Expand All @@ -108,8 +129,10 @@ export function toNullableString(nullable: string | undefined, propertyName: str
return <INullableString>{
value: nullable
};
} else if (nullable != null) {
systemWarn(`A 'string' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.`);
}

if (nullable != null) {
throw new Error(`A 'string' type was expected instead of a '${typeof nullable}' type. Cannot parse value of '${propertyName}'.`);
}

return undefined;
Expand All @@ -134,7 +157,7 @@ export function toNullableTimestamp(dateTime: Date | number | undefined, propert
}
}
} catch(e) {
systemWarn(`A 'number' or 'Date' input was expected instead of a '${typeof dateTime}'. Cannot parse value of '${propertyName}'.`, e);
throw new Error(`A 'number' or 'Date' input was expected instead of a '${typeof dateTime}'. Cannot parse value of '${propertyName}'.`);
}
}
return undefined;
Expand Down
19 changes: 10 additions & 9 deletions src/converters/RpcHttpConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Dict } from '../Context';
import {
fromTypedData,
toTypedData,
toRpcString,
toNullableString,
toNullableBool,
toNullableDouble,
Expand Down Expand Up @@ -63,7 +64,7 @@ function toRpcHttpHeaders(inputHeaders: rpc.ITypedData) {
* Convert HTTP 'Cookie' array to an array of 'IRpcHttpCookie' objects to be sent through the RPC layer
* @param inputCookies array of 'Cookie' objects representing options for the 'Set-Cookie' response header
*/
function toRpcHttpCookieList(inputCookies: Cookie[]): rpc.IRpcHttpCookie[] {
export function toRpcHttpCookieList(inputCookies: Cookie[]): rpc.IRpcHttpCookie[] {
let rpcCookies: rpc.IRpcHttpCookie[] = [];
inputCookies.forEach(cookie => {
rpcCookies.push(toRpcHttpCookie(cookie));
Expand All @@ -88,15 +89,15 @@ function toRpcHttpCookie(inputCookie: Cookie): rpc.IRpcHttpCookie {
}

const rpcCookie: rpc.IRpcHttpCookie = {
name: inputCookie && inputCookie.name,
value: inputCookie && inputCookie.value,
domain: toNullableString(inputCookie && inputCookie.domain, "domain"),
path: toNullableString(inputCookie && inputCookie.path, "path"),
expires: toNullableTimestamp(inputCookie && inputCookie.expires, "expires"),
secure: toNullableBool(inputCookie && inputCookie.secure, "secure"),
httpOnly: toNullableBool(inputCookie && inputCookie.httpOnly, "httpOnly"),
name: inputCookie && toRpcString(inputCookie.name, "cookie.name"),
value: inputCookie && toRpcString(inputCookie.value, "cookie.value"),
domain: toNullableString(inputCookie && inputCookie.domain, "cookie.domain"),
path: toNullableString(inputCookie && inputCookie.path, "cookie.path"),
expires: toNullableTimestamp(inputCookie && inputCookie.expires, "cookie.expires"),
secure: toNullableBool(inputCookie && inputCookie.secure, "cookie.secure"),
httpOnly: toNullableBool(inputCookie && inputCookie.httpOnly, "cookie.httpOnly"),
sameSite: rpcSameSite,
maxAge: toNullableDouble(inputCookie && inputCookie.maxAge, "maxAge")
maxAge: toNullableDouble(inputCookie && inputCookie.maxAge, "cookie.maxAge")
};

return rpcCookie;
Expand Down
33 changes: 19 additions & 14 deletions test/RpcConvertersTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ describe('Rpc Converters', () => {
expect(nullable && nullable.value).to.equal(false);
});

it('does not converts string to NullableBool', () => {
let nullable = toNullableBool(<any>"true", "test");
expect(nullable && nullable.value).to.be.undefined;
it('throws and does not converts string to NullableBool', () => {
expect(() => {
toNullableBool(<any>"true", "test");
}).to.throw("A 'boolean' type was expected instead of a 'string' type. Cannot parse value of 'test'.")
});

it('does not converts null to NullableBool', () => {
Expand All @@ -39,13 +40,14 @@ describe('Rpc Converters', () => {
expect(nullable && nullable.value).to.equal(input);
});

it('does not convert number to NullableString', () => {
let nullable = toNullableBool(<any>123, "test");
expect(nullable && nullable.value).to.be.undefined;
it('throws and does not convert number to NullableString', () => {
expect(() => {
toNullableString(<any>123, "test");
}).to.throw("A 'string' type was expected instead of a 'number' type. Cannot parse value of 'test'.");
});

it('does not convert null to NullableString', () => {
let nullable = toNullableBool(<any>null, "test");
let nullable = toNullableString(<any>null, "test");
expect(nullable && nullable.value).to.be.undefined;
});

Expand Down Expand Up @@ -80,9 +82,10 @@ describe('Rpc Converters', () => {
expect(nullable && nullable.value).to.equal(1234567.002);
});

it('does not convert non-number string to NullableDouble', () => {
let nullable = toNullableDouble(<any>"123hellohello!!111", "test");
expect(nullable && nullable.value).to.be.undefined;
it('throws and does not convert non-number string to NullableDouble', () => {
expect(() => {
toNullableDouble(<any>"123hellohello!!111", "test");
}).to.throw("A 'number' type was expected instead of a 'string' type. Cannot parse value of 'test'.");
});

it('does not convert undefined to NullableDouble', () => {
Expand Down Expand Up @@ -113,13 +116,15 @@ describe('Rpc Converters', () => {
});

it('does not convert string to NullableTimestamp', () => {
let nullable = toNullableTimestamp(<any>"1/2/3 2014", "test");
expect(nullable && nullable.value && nullable.value.seconds).to.be.undefined;
expect(() => {
toNullableTimestamp(<any>"1/2/3 2014", "test");
}).to.throw("A 'number' or 'Date' input was expected instead of a 'string'. Cannot parse value of 'test'.");
});

it('does not convert object to NullableTimestamp', () => {
let nullable = toNullableTimestamp(<any>{ time: 100 }, "test");
expect(nullable && nullable.value).to.be.undefined;
expect(() => {
toNullableTimestamp(<any>{ time: 100 }, "test");
}).to.throw("A 'number' or 'Date' input was expected instead of a 'object'. Cannot parse value of 'test'.");
});

it('does not convert undefined to NullableTimestamp', () => {
Expand Down
75 changes: 75 additions & 0 deletions test/RpcHttpConverters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { toRpcHttpCookieList } from '../src/converters';
import { Cookie } from "../types/public/Interfaces";
import { expect } from 'chai';
import * as sinon from 'sinon';
import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc';
import 'mocha';

describe('Rpc Converters', () => {
/** NullableBool */
it('converts http cookies', () => {
let cookieInputs =
[
{
name: "mycookie",
value: "myvalue",
maxAge: 200000
},
{
name: "mycookie2",
value: "myvalue2",
path: "/",
maxAge: "200000"
},
{
name: "mycookie3-expires",
value: "myvalue3-expires",
expires: new Date('December 17, 1995 03:24:00')
}
];

let rpcCookies = toRpcHttpCookieList(<Cookie[]>cookieInputs);
expect(rpcCookies[0].name).to.equal("mycookie");
expect(rpcCookies[0].value).to.equal("myvalue");
expect((<any>rpcCookies[0].maxAge).value).to.equal(200000);

expect(rpcCookies[1].name).to.equal("mycookie2");
expect(rpcCookies[1].value).to.equal("myvalue2");
expect((<any>rpcCookies[1].path).value).to.equal("/");
expect((<any>rpcCookies[1].maxAge).value).to.equal(200000);

expect(rpcCookies[2].name).to.equal("mycookie3-expires");
expect(rpcCookies[2].value).to.equal("myvalue3-expires");
expect((<any>rpcCookies[2].expires).value.seconds).to.equal(819199440);
});

it('throws on invalid input', () => {
expect(() => {
let cookieInputs = [
{
name: 123,
value: "myvalue",
maxAge: 200000
},
{
name: "mycookie2",
value: "myvalue2",
path: "/",
maxAge: "200000"
},
{
name: "mycookie3-expires",
value: "myvalue3-expires",
expires: new Date('December 17, 1995 03:24:00')
},
{
name: "mycookie3-expires",
value: "myvalue3-expires",
expires: new Date("")
}
];

toRpcHttpCookieList(<Cookie[]>cookieInputs);
}).to.throw("");
});
})
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,10 @@ public async Task HttpTriggerTests(string functionName, string queryString, Http
Assert.True(await Utilities.InvokeHttpTrigger(functionName, queryString, expectedStatusCode, expectedMessage));
}

[Fact]
[Fact(Skip = "Not yet enabled.")]
public async Task HttpTriggerWithCookieTests()
{
// No cookies set
Assert.True(await Utilities.InvokeHttpTrigger("HttpTriggerSetsCookie", "", HttpStatusCode.OK, ""));
// Cookies set by previous
Assert.True(await Utilities.InvokeHttpTrigger("HttpTriggerSetsCookie", "", HttpStatusCode.OK, "mycookie=myvalue, mycookie2=myvalue2"));
Assert.True(await Utilities.InvokeHttpTriggerSettingCookies("HttpTriggerSetsCookie", "", HttpStatusCode.OK, "mycookie=myvalue, mycookie2=myvalue2"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,35 @@ public static async Task<bool> InvokeHttpTrigger(string functionName, string que
}
return true;
}

public static async Task<bool> InvokeHttpTriggerSettingCookies(string functionName, string queryString, HttpStatusCode expectedStatusCode, string expectedMessage, int expectedCode = 0)
{
string uri = $"api/{functionName}{queryString}";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, uri);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/plain"));
// CookieContainer cookies = new CookieContainer();
// HttpClientHandler handler = new HttpClientHandler();
// handler.CookieContainer = cookies;

// var httpClient = new HttpClient(handler);
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(Constants.FunctionsHostUrl);
var response1 = await httpClient.SendAsync(request);
// var responseCookies = cookies.GetCookies(new Uri($"{Constants.FunctionsHostUrl}/{uri}"));
HttpRequestMessage request2 = new HttpRequestMessage(HttpMethod.Get, uri);
var response = await httpClient.SendAsync(request2);

if (expectedStatusCode != response.StatusCode && expectedCode != (int)response.StatusCode)
{
return false;
}

if (!string.IsNullOrEmpty(expectedMessage))
{
string actualMessage = await response.Content.ReadAsStringAsync();
return actualMessage.Contains(expectedMessage);
}
return true;
}
}
}

0 comments on commit 1561699

Please sign in to comment.