Add VerifyLogger to JS tests (#2472)

This commit is contained in:
BrennanConroy 2018-06-12 12:49:15 -07:00 committed by GitHub
parent 7317762b29
commit cb8264321d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 1672 additions and 1425 deletions

View File

@ -1,6 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { EOL } from "os";
import { ILogger, LogLevel } from "../src/ILogger";
import { HttpTransportType } from "../src/ITransport";
export function eachTransport(action: (transport: HttpTransportType) => void) {
@ -21,3 +23,38 @@ export function eachEndpointUrl(action: (givenUrl: string, expectedUrl: string)
urls.forEach((t) => action(t[0], t[1]));
}
type ErrorMatchFunction = (error: string) => boolean;
export class VerifyLogger implements ILogger {
public unexpectedErrors: string[];
private expectedErrors: ErrorMatchFunction[];
public constructor(...expectedErrors: Array<RegExp | string | ErrorMatchFunction>) {
this.unexpectedErrors = [];
this.expectedErrors = [];
expectedErrors.forEach((element) => {
if (element instanceof RegExp) {
this.expectedErrors.push((e) => element.test(e));
} else if (typeof element === "string") {
this.expectedErrors.push((e) => element === e);
} else {
this.expectedErrors.push(element);
}
}, this);
}
public static async run(fn: (logger: VerifyLogger) => Promise<void>, ...expectedErrors: Array<RegExp | string | ErrorMatchFunction>): Promise<void> {
const logger = new VerifyLogger(...expectedErrors);
await fn(logger);
expect(logger.unexpectedErrors.join(EOL)).toBe("");
}
public log(logLevel: LogLevel, message: string): void {
if (logLevel >= LogLevel.Error) {
if (!this.expectedErrors.some((fn) => fn(message))) {
this.unexpectedErrors.push(message);
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@ import { ILogger, LogLevel } from "../src/ILogger";
import { HttpTransportType, TransferFormat } from "../src/ITransport";
import { NullLogger } from "../src/Loggers";
import { VerifyLogger } from "./Common";
import { TestHttpClient } from "./TestHttpClient";
import { PromiseSource } from "./Utils";
@ -43,29 +44,32 @@ describe("HubConnectionBuilder", () => {
});
it("builds HubConnection with HttpConnection using provided URL", async () => {
const pollSent = new PromiseSource<HttpRequest>();
const pollCompleted = new PromiseSource<HttpResponse>();
const testClient = createTestClient(pollSent, pollCompleted.promise)
.on("POST", "http://example.com?id=abc123", (req) => {
// Respond from the poll with the handshake response
pollCompleted.resolve(new HttpResponse(204, "No Content", "{}"));
return new HttpResponse(202);
});
const connection = createConnectionBuilder()
.withUrl("http://example.com", {
...commonHttpOptions,
httpClient: testClient,
})
.build();
await VerifyLogger.run(async (logger) => {
const pollSent = new PromiseSource<HttpRequest>();
const pollCompleted = new PromiseSource<HttpResponse>();
const testClient = createTestClient(pollSent, pollCompleted.promise)
.on("POST", "http://example.com?id=abc123", (req) => {
// Respond from the poll with the handshake response
pollCompleted.resolve(new HttpResponse(204, "No Content", "{}"));
return new HttpResponse(202);
});
const connection = createConnectionBuilder()
.withUrl("http://example.com", {
...commonHttpOptions,
httpClient: testClient,
logger,
})
.build();
// Start the connection
const closed = makeClosedPromise(connection);
await connection.start();
// Start the connection
const closed = makeClosedPromise(connection);
await connection.start();
const pollRequest = await pollSent.promise;
expect(pollRequest.url).toMatch(/http:\/\/example.com\?id=abc123.*/);
const pollRequest = await pollSent.promise;
expect(pollRequest.url).toMatch(/http:\/\/example.com\?id=abc123.*/);
await closed;
await closed;
});
});
it("can configure transport type", async () => {
@ -78,35 +82,38 @@ describe("HubConnectionBuilder", () => {
});
it("can configure hub protocol", async () => {
const protocol = new TestProtocol();
await VerifyLogger.run(async (logger) => {
const protocol = new TestProtocol();
const pollSent = new PromiseSource<HttpRequest>();
const pollCompleted = new PromiseSource<HttpResponse>();
const negotiateReceived = new PromiseSource<HttpRequest>();
const testClient = createTestClient(pollSent, pollCompleted.promise)
.on("POST", "http://example.com?id=abc123", (req) => {
// Respond from the poll with the handshake response
negotiateReceived.resolve(req);
pollCompleted.resolve(new HttpResponse(204, "No Content", "{}"));
return new HttpResponse(202);
});
const pollSent = new PromiseSource<HttpRequest>();
const pollCompleted = new PromiseSource<HttpResponse>();
const negotiateReceived = new PromiseSource<HttpRequest>();
const testClient = createTestClient(pollSent, pollCompleted.promise)
.on("POST", "http://example.com?id=abc123", (req) => {
// Respond from the poll with the handshake response
negotiateReceived.resolve(req);
pollCompleted.resolve(new HttpResponse(204, "No Content", "{}"));
return new HttpResponse(202);
});
const connection = createConnectionBuilder()
.withUrl("http://example.com", {
...commonHttpOptions,
httpClient: testClient,
})
.withHubProtocol(protocol)
.build();
const connection = createConnectionBuilder()
.withUrl("http://example.com", {
...commonHttpOptions,
httpClient: testClient,
logger,
})
.withHubProtocol(protocol)
.build();
// Start the connection
const closed = makeClosedPromise(connection);
await connection.start();
// Start the connection
const closed = makeClosedPromise(connection);
await connection.start();
const negotiateRequest = await negotiateReceived.promise;
expect(negotiateRequest.content).toBe(`{"protocol":"${protocol.name}","version":1}\x1E`);
const negotiateRequest = await negotiateReceived.promise;
expect(negotiateRequest.content).toBe(`{"protocol":"${protocol.name}","version":1}\x1E`);
await closed;
await closed;
});
});
it("allows logger to be replaced", async () => {

View File

@ -3,63 +3,71 @@
import { CompletionMessage, InvocationMessage, MessageType, StreamItemMessage } from "../src/IHubProtocol";
import { JsonHubProtocol } from "../src/JsonHubProtocol";
import { NullLogger } from "../src/Loggers";
import { TextMessageFormat } from "../src/TextMessageFormat";
import { VerifyLogger } from "./Common";
describe("JsonHubProtocol", () => {
it("can write/read non-blocking Invocation message", () => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {},
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
it("can write/read non-blocking Invocation message", async () => {
await VerifyLogger.run(async (logger) => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {},
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), NullLogger.instance);
expect(parsedMessages).toEqual([invocation]);
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), logger);
expect(parsedMessages).toEqual([invocation]);
});
});
it("can read Invocation message with Date argument", () => {
const invocation = {
arguments: [Date.UTC(2018, 1, 1, 12, 34, 56)],
headers: {},
target: "mymethod",
type: MessageType.Invocation,
} as InvocationMessage;
it("can read Invocation message with Date argument", async () => {
await VerifyLogger.run(async (logger) => {
const invocation = {
arguments: [Date.UTC(2018, 1, 1, 12, 34, 56)],
headers: {},
target: "mymethod",
type: MessageType.Invocation,
} as InvocationMessage;
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), NullLogger.instance);
expect(parsedMessages).toEqual([invocation]);
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), logger);
expect(parsedMessages).toEqual([invocation]);
});
});
it("can write/read Invocation message with headers", () => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {
foo: "bar",
},
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
it("can write/read Invocation message with headers", async () => {
await VerifyLogger.run(async (logger) => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {
foo: "bar",
},
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), NullLogger.instance);
expect(parsedMessages).toEqual([invocation]);
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), logger);
expect(parsedMessages).toEqual([invocation]);
});
});
it("can write/read Invocation message", () => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {},
invocationId: "123",
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
it("can write/read Invocation message", async () => {
await VerifyLogger.run(async (logger) => {
const invocation = {
arguments: [42, true, "test", ["x1", "y2"], null],
headers: {},
invocationId: "123",
target: "myMethod",
type: MessageType.Invocation,
} as InvocationMessage;
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), NullLogger.instance);
expect(parsedMessages).toEqual([invocation]);
const protocol = new JsonHubProtocol();
const parsedMessages = protocol.parseMessages(protocol.writeMessage(invocation), logger);
expect(parsedMessages).toEqual([invocation]);
});
});
([
@ -101,9 +109,11 @@ describe("JsonHubProtocol", () => {
type: MessageType.Completion,
} as CompletionMessage],
] as Array<[string, CompletionMessage]>).forEach(([payload, expectedMessage]) =>
it("can read Completion message", () => {
const messages = new JsonHubProtocol().parseMessages(payload, NullLogger.instance);
expect(messages).toEqual([expectedMessage]);
it("can read Completion message", async () => {
await VerifyLogger.run(async (logger) => {
const messages = new JsonHubProtocol().parseMessages(payload, logger);
expect(messages).toEqual([expectedMessage]);
});
}));
([
@ -122,9 +132,11 @@ describe("JsonHubProtocol", () => {
type: MessageType.StreamItem,
} as StreamItemMessage],
] as Array<[string, StreamItemMessage]>).forEach(([payload, expectedMessage]) =>
it("can read StreamItem message", () => {
const messages = new JsonHubProtocol().parseMessages(payload, NullLogger.instance);
expect(messages).toEqual([expectedMessage]);
it("can read StreamItem message", async () => {
await VerifyLogger.run(async (logger) => {
const messages = new JsonHubProtocol().parseMessages(payload, logger);
expect(messages).toEqual([expectedMessage]);
});
}));
([
@ -138,9 +150,11 @@ describe("JsonHubProtocol", () => {
type: MessageType.StreamItem,
} as StreamItemMessage],
] as Array<[string, StreamItemMessage]>).forEach(([payload, expectedMessage]) =>
it("can read message with headers", () => {
const messages = new JsonHubProtocol().parseMessages(payload, NullLogger.instance);
expect(messages).toEqual([expectedMessage]);
it("can read message with headers", async () => {
await VerifyLogger.run(async (logger) => {
const messages = new JsonHubProtocol().parseMessages(payload, logger);
expect(messages).toEqual([expectedMessage]);
});
}));
([
@ -155,37 +169,43 @@ describe("JsonHubProtocol", () => {
["Completion message with result and error", `{"type":3,"invocationId":"1","result":2,"error":"error"}${TextMessageFormat.RecordSeparator}`, "Invalid payload for Completion message."],
["Completion message with non-string error", `{"type":3,"invocationId":"1","error":21}${TextMessageFormat.RecordSeparator}`, "Invalid payload for Completion message."],
] as Array<[string, string, string]>).forEach(([name, payload, expectedError]) =>
it("throws for " + name, () => {
expect(() => new JsonHubProtocol().parseMessages(payload, NullLogger.instance))
.toThrow(expectedError);
it("throws for " + name, async () => {
await VerifyLogger.run(async (logger) => {
expect(() => new JsonHubProtocol().parseMessages(payload, logger))
.toThrow(expectedError);
});
}));
it("can read multiple messages", () => {
const payload = `{"type":2, "invocationId": "abc", "headers": {}, "item": 8}${TextMessageFormat.RecordSeparator}{"type":3, "invocationId": "abc", "headers": {}, "result": "OK"}${TextMessageFormat.RecordSeparator}`;
const messages = new JsonHubProtocol().parseMessages(payload, NullLogger.instance);
expect(messages).toEqual([
{
headers: {},
invocationId: "abc",
item: 8,
type: MessageType.StreamItem,
} as StreamItemMessage,
{
headers: {},
invocationId: "abc",
result: "OK",
type: MessageType.Completion,
} as CompletionMessage,
]);
it("can read multiple messages", async () => {
await VerifyLogger.run(async (logger) => {
const payload = `{"type":2, "invocationId": "abc", "headers": {}, "item": 8}${TextMessageFormat.RecordSeparator}{"type":3, "invocationId": "abc", "headers": {}, "result": "OK"}${TextMessageFormat.RecordSeparator}`;
const messages = new JsonHubProtocol().parseMessages(payload, logger);
expect(messages).toEqual([
{
headers: {},
invocationId: "abc",
item: 8,
type: MessageType.StreamItem,
} as StreamItemMessage,
{
headers: {},
invocationId: "abc",
result: "OK",
type: MessageType.Completion,
} as CompletionMessage,
]);
});
});
it("can read ping message", () => {
const payload = `{"type":6}${TextMessageFormat.RecordSeparator}`;
const messages = new JsonHubProtocol().parseMessages(payload, NullLogger.instance);
expect(messages).toEqual([
{
type: MessageType.Ping,
},
]);
it("can read ping message", async () => {
await VerifyLogger.run(async (logger) => {
const payload = `{"type":6}${TextMessageFormat.RecordSeparator}`;
const messages = new JsonHubProtocol().parseMessages(payload, logger);
expect(messages).toEqual([
{
type: MessageType.Ping,
},
]);
});
});
});

View File

@ -3,47 +3,56 @@
import { HttpResponse } from "../src/HttpClient";
import { TransferFormat } from "../src/ITransport";
import { NullLogger } from "../src/Loggers";
import { LongPollingTransport } from "../src/LongPollingTransport";
import { VerifyLogger } from "./Common";
import { TestHttpClient } from "./TestHttpClient";
import { PromiseSource, SyncPoint } from "./Utils";
describe("LongPollingTransport", () => {
it("shuts down polling by aborting in-progress request", async () => {
let firstPoll = true;
const pollCompleted = new PromiseSource();
const client = new TestHttpClient()
.on("GET", async (r) => {
if (firstPoll) {
firstPoll = false;
return new HttpResponse(200);
} else {
// Turn 'onabort' into a promise.
const abort = new Promise((resolve, reject) => r.abortSignal!.onabort = resolve);
await abort;
await VerifyLogger.run(async (logger) => {
let firstPoll = true;
const pollCompleted = new PromiseSource();
const client = new TestHttpClient()
.on("GET", async (r) => {
if (firstPoll) {
firstPoll = false;
return new HttpResponse(200);
} else {
// Turn 'onabort' into a promise.
const abort = new Promise((resolve, reject) => {
if (r.abortSignal!.aborted) {
resolve();
} else {
r.abortSignal!.onabort = resolve;
}
});
await abort;
// Signal that the poll has completed.
pollCompleted.resolve();
// Signal that the poll has completed.
pollCompleted.resolve();
return new HttpResponse(200);
}
})
.on("DELETE", () => new HttpResponse(202));
const transport = new LongPollingTransport(client, undefined, NullLogger.instance, false);
return new HttpResponse(200);
}
})
.on("DELETE", () => new HttpResponse(202));
const transport = new LongPollingTransport(client, undefined, logger, false);
await transport.connect("http://example.com", TransferFormat.Text);
const stopPromise = transport.stop();
await transport.connect("http://example.com", TransferFormat.Text);
const stopPromise = transport.stop();
await pollCompleted.promise;
await pollCompleted.promise;
await stopPromise;
await stopPromise;
});
});
it("204 server response stops polling and raises onClose", async () => {
let firstPoll = true;
const client = new TestHttpClient()
.on("GET", async () => {
await VerifyLogger.run(async (logger) => {
let firstPoll = true;
const client = new TestHttpClient()
.on("GET", async () => {
if (firstPoll) {
firstPoll = false;
return new HttpResponse(200);
@ -52,59 +61,62 @@ describe("LongPollingTransport", () => {
return new HttpResponse(204);
}
});
const transport = new LongPollingTransport(client, undefined, NullLogger.instance, false);
const transport = new LongPollingTransport(client, undefined, logger, false);
const stopPromise = makeClosedPromise(transport);
const stopPromise = makeClosedPromise(transport);
await transport.connect("http://example.com", TransferFormat.Text);
await transport.connect("http://example.com", TransferFormat.Text);
// Close will be called on transport because of 204 result from polling
await stopPromise;
// Close will be called on transport because of 204 result from polling
await stopPromise;
});
});
it("sends DELETE on stop after polling has finished", async () => {
let firstPoll = true;
let deleteSent = false;
const pollingPromiseSource = new PromiseSource();
const deleteSyncPoint = new SyncPoint();
const httpClient = new TestHttpClient()
.on("GET", async (r) => {
if (firstPoll) {
firstPoll = false;
return new HttpResponse(200);
} else {
await pollingPromiseSource.promise;
return new HttpResponse(204);
}
})
.on("DELETE", async (r) => {
deleteSent = true;
await deleteSyncPoint.waitToContinue();
return new HttpResponse(202);
});
await VerifyLogger.run(async (logger) => {
let firstPoll = true;
let deleteSent = false;
const pollingPromiseSource = new PromiseSource();
const deleteSyncPoint = new SyncPoint();
const httpClient = new TestHttpClient()
.on("GET", async (r) => {
if (firstPoll) {
firstPoll = false;
return new HttpResponse(200);
} else {
await pollingPromiseSource.promise;
return new HttpResponse(204);
}
})
.on("DELETE", async (r) => {
deleteSent = true;
await deleteSyncPoint.waitToContinue();
return new HttpResponse(202);
});
const transport = new LongPollingTransport(httpClient, undefined, NullLogger.instance, false);
const transport = new LongPollingTransport(httpClient, undefined, logger, false);
await transport.connect("http://tempuri.org", TransferFormat.Text);
await transport.connect("http://tempuri.org", TransferFormat.Text);
// Begin stopping transport
const stopPromise = transport.stop();
// Begin stopping transport
const stopPromise = transport.stop();
// Delete will not be sent until polling is finished
expect(deleteSent).toEqual(false);
// Delete will not be sent until polling is finished
expect(deleteSent).toEqual(false);
// Allow polling to complete
pollingPromiseSource.resolve();
// Allow polling to complete
pollingPromiseSource.resolve();
// Wait for delete to be called
await deleteSyncPoint.waitForSyncPoint();
// Wait for delete to be called
await deleteSyncPoint.waitForSyncPoint();
expect(deleteSent).toEqual(true);
expect(deleteSent).toEqual(true);
deleteSyncPoint.continue();
deleteSyncPoint.continue();
// Wait for stop to complete
await stopPromise;
// Wait for stop to complete
await stopPromise;
});
});
});