aspnetcore/client-ts/FunctionalTests/ts/HubConnectionTests.ts

574 lines
22 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 { HubConnection, JsonHubProtocol, LogLevel, TransportType } from "@aspnet/signalr";
import { MessagePackHubProtocol } from "@aspnet/signalr-protocol-msgpack";
import { eachTransport, eachTransportAndProtocol } from "./Common";
const TESTHUBENDPOINT_URL = "/testhub";
const TESTHUB_NOWEBSOCKETS_ENDPOINT_URL = "/testhub-nowebsockets";
describe("hubConnection", () => {
eachTransportAndProtocol((transportType, protocol) => {
describe("using " + protocol.name + " over " + TransportType[transportType] + " transport", () => {
it("can invoke server method and receive result", (done) => {
const message = "你好,世界!";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
});
hubConnection.start().then(() => {
hubConnection.invoke("Echo", message).then((result) => {
expect(result).toBe(message);
}).catch((e) => {
fail(e);
}).then(() => {
hubConnection.stop();
});
}).catch((e) => {
fail(e);
done();
});
});
it("can invoke server method non-blocking and not receive result", (done) => {
const message = "你好,世界!";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
});
hubConnection.start().then(() => {
hubConnection.send("Echo", message).catch((e) => {
fail(e);
}).then(() => {
hubConnection.stop();
});
}).catch((e) => {
fail(e);
done();
});
});
it("can invoke server method structural object and receive structural result", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.on("CustomObject", (customObject) => {
expect(customObject.Name).toBe("test");
expect(customObject.Value).toBe(42);
hubConnection.stop();
});
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
});
hubConnection.start().then(() => {
hubConnection.send("SendCustomObject", { Name: "test", Value: 42 });
}).catch((e) => {
fail(e);
done();
});
});
it("can stream server method and receive result", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
});
const received = [];
hubConnection.start().then(() => {
hubConnection.stream("Stream").subscribe({
complete: function complete() {
expect(received).toEqual(["a", "b", "c"]);
hubConnection.stop();
},
error: function error(err) {
fail(err);
hubConnection.stop();
},
next: function next(item) {
received.push(item);
},
});
}).catch((e) => {
fail(e);
done();
});
});
it("rethrows an exception from the server when invoking", (done) => {
const errorMessage = "An unexpected error occurred invoking 'ThrowException' on the server. InvalidOperationException: An error occurred.";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.start().then(() => {
hubConnection.invoke("ThrowException", "An error occurred.").then(() => {
// exception expected but none thrown
fail();
}).catch((e) => {
expect(e.message).toBe(errorMessage);
}).then(() => {
return hubConnection.stop();
}).then(() => {
done();
});
}).catch((e) => {
fail(e);
done();
});
});
it("throws an exception when invoking streaming method with invoke", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.start().then(() => {
hubConnection.invoke("EmptyStream").then(() => {
// exception expected but none thrown
fail();
}).catch((e) => {
expect(e.message).toBe("The client attempted to invoke the streaming 'EmptyStream' method in a non-streaming fashion.");
}).then(() => {
return hubConnection.stop();
}).then(() => {
done();
});
}).catch((e) => {
fail(e);
done();
});
});
it("throws an exception when receiving a streaming result for method called with invoke", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.start().then(() => {
hubConnection.invoke("Stream").then(() => {
// exception expected but none thrown
fail();
}).catch((e) => {
expect(e.message).toBe("The client attempted to invoke the streaming 'Stream' method in a non-streaming fashion.");
}).then(() => {
return hubConnection.stop();
}).then(() => {
done();
});
}).catch((e) => {
fail(e);
done();
});
});
it("rethrows an exception from the server when streaming", (done) => {
const errorMessage = "An unexpected error occurred invoking 'StreamThrowException' on the server. InvalidOperationException: An error occurred.";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.start().then(() => {
hubConnection.stream("StreamThrowException", "An error occurred.").subscribe({
complete: function complete() {
hubConnection.stop();
fail();
},
error: function error(err) {
expect(err.message).toEqual(errorMessage);
hubConnection.stop();
done();
},
next: function next(item) {
hubConnection.stop();
fail();
},
});
}).catch((e) => {
fail(e);
done();
});
});
it("throws an exception when invoking hub method with stream", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.start().then(() => {
hubConnection.stream("Echo", "42").subscribe({
complete: function complete() {
hubConnection.stop();
fail();
},
error: function error(err) {
expect(err.message).toEqual("The client attempted to invoke the non-streaming 'Echo' method in a streaming fashion.");
hubConnection.stop();
done();
},
next: function next(item) {
hubConnection.stop();
fail();
},
});
}).catch((e) => {
fail(e);
done();
});
});
it("can receive server calls", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
const message = "你好 SignalR";
// client side method names are case insensitive
let methodName = "message";
const idx = Math.floor(Math.random() * (methodName.length - 1));
methodName = methodName.substr(0, idx) + methodName[idx].toUpperCase() + methodName.substr(idx + 1);
hubConnection.on(methodName, (msg) => {
expect(msg).toBe(message);
done();
});
hubConnection.start()
.then(() => {
return hubConnection.invoke("InvokeWithString", message);
})
.then(() => {
return hubConnection.stop();
})
.catch((e) => {
fail(e);
done();
});
});
it("can receive server calls without rebinding handler when restarted", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
const message = "你好 SignalR";
// client side method names are case insensitive
let methodName = "message";
const idx = Math.floor(Math.random() * (methodName.length - 1));
methodName = methodName.substr(0, idx) + methodName[idx].toUpperCase() + methodName.substr(idx + 1);
let closeCount = 0;
let invocationCount = 0;
hubConnection.onclose((e) => {
expect(e).toBeUndefined();
closeCount += 1;
if (closeCount === 1) {
// Reconnect
hubConnection.start()
.then(() => {
return hubConnection.invoke("InvokeWithString", message);
})
.then(() => {
return hubConnection.stop();
})
.catch((error) => {
fail(error);
done();
});
} else {
expect(invocationCount).toBe(2);
done();
}
});
hubConnection.on(methodName, (msg) => {
expect(msg).toBe(message);
invocationCount += 1;
});
hubConnection.start()
.then(() => {
return hubConnection.invoke("InvokeWithString", message);
})
.then(() => {
return hubConnection.stop();
})
.catch((e) => {
fail(e);
done();
});
});
it("closed with error if hub cannot be created", (done) => {
const errorRegex = {
LongPolling: "Internal Server Error",
ServerSentEvents: "Error occurred",
WebSockets: "1011|1005", // Message is browser specific (e.g. 'Websocket closed with status code: 1011'), Edge and IE report 1005 even though the server sent 1011
};
const hubConnection = new HubConnection("http://" + document.location.host + "/uncreatable", {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.onclose((error) => {
expect(error.message).toMatch(errorRegex[TransportType[transportType]]);
done();
});
hubConnection.start();
});
it("can handle different types", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
});
const complexObject = {
ByteArray: protocol.name === "json"
? "aGVsbG8="
: new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]),
GUID: protocol.name === "json"
? "00010203-0405-0607-0706-050403020100"
: new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00]),
IntArray: [0x01, 0x02, 0x03, 0xff],
String: "Hello, World!",
};
hubConnection.start()
.then(() => {
return hubConnection.invoke("EchoComplexObject", complexObject);
})
.then((value) => {
if (protocol.name === "messagepack") {
// msgpack creates a Buffer for byte arrays and jasmine fails to compare a Buffer
// and a Uint8Array even though Buffer instances are also Uint8Array instances
value.ByteArray = new Uint8Array(value.ByteArray);
// GUIDs are serialized as Buffer as well.
value.GUID = new Uint8Array(value.GUID);
}
expect(value).toEqual(complexObject);
})
.then(() => {
hubConnection.stop();
})
.catch((e) => {
fail(e);
done();
});
});
it("can be restarted", (done) => {
const message = "你好,世界!";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
let closeCount = 0;
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
// Start and invoke again
if (closeCount === 0) {
closeCount += 1;
hubConnection.start().then(() => {
hubConnection.invoke("Echo", message).then((result) => {
expect(result).toBe(message);
}).catch((e) => {
fail(e);
}).then(() => {
hubConnection.stop();
});
}).catch((e) => {
fail(e);
done();
});
} else {
done();
}
});
hubConnection.start().then(() => {
hubConnection.invoke("Echo", message).then((result) => {
expect(result).toBe(message);
}).catch((e) => {
fail(e);
}).then(() => {
hubConnection.stop();
});
}).catch((e) => {
fail(e);
done();
});
});
});
});
eachTransport((transportType) => {
describe("over " + TransportType[transportType] + " transport", () => {
it("can connect to hub with authorization", async (done) => {
const message = "你好,世界!";
try {
const jwtToken = await getJwtToken("http://" + document.location.host + "/generateJwtToken");
const hubConnection = new HubConnection("/authorizedhub", {
accessTokenFactory: () => jwtToken,
logger: LogLevel.Trace,
transport: transportType,
});
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
});
await hubConnection.start();
const response = await hubConnection.invoke("Echo", message);
expect(response).toEqual(message);
await hubConnection.stop();
done();
} catch (err) {
fail(err);
done();
}
});
if (transportType !== TransportType.LongPolling) {
it("terminates if no messages received within timeout interval", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
timeoutInMilliseconds: 100,
transport: transportType,
});
const timeout = setTimeout(200, () => {
fail("Server timeout did not fire within expected interval");
});
hubConnection.start().then(() => {
hubConnection.onclose((error) => {
clearTimeout(timeout);
expect(error).toEqual(new Error("Server timeout elapsed without receiving a message from the server."));
done();
});
});
});
}
});
});
if (typeof EventSource !== "undefined") {
it("allows Server-Sent Events when negotiating for JSON protocol", async (done) => {
const hubConnection = new HubConnection(TESTHUB_NOWEBSOCKETS_ENDPOINT_URL, {
logger: LogLevel.Trace,
protocol: new JsonHubProtocol(),
});
try {
await hubConnection.start();
// Check what transport was used by asking the server to tell us.
expect(await hubConnection.invoke("GetActiveTransportName")).toEqual("ServerSentEvents");
done();
} catch (e) {
fail(e);
}
});
}
it("skips Server-Sent Events when negotiating for MsgPack protocol", async (done) => {
const hubConnection = new HubConnection(TESTHUB_NOWEBSOCKETS_ENDPOINT_URL, {
logger: LogLevel.Trace,
protocol: new MessagePackHubProtocol(),
});
try {
await hubConnection.start();
// Check what transport was used by asking the server to tell us.
expect(await hubConnection.invoke("GetActiveTransportName")).toEqual("LongPolling");
done();
} catch (e) {
fail(e);
}
});
function getJwtToken(url): Promise<string> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.send();
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response || xhr.responseText);
} else {
reject(new Error(xhr.statusText));
}
};
xhr.onerror = () => {
reject(new Error(xhr.statusText));
};
});
}
});