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

545 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, LogLevel, TransportType } from "@aspnet/signalr";
import { eachTransport, eachTransportAndProtocol } from "./Common";
const TESTHUBENDPOINT_URL = "/testhub";
describe("hubConnection", () => {
eachTransportAndProtocol((transportType, protocol) => {
describe(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 error occurred.";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.start().then(() => {
hubConnection.invoke("ThrowException", errorMessage).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 error occurred.";
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
hubConnection.start().then(() => {
hubConnection.stream("StreamThrowException", errorMessage).subscribe({
complete: function complete() {
hubConnection.stop();
fail();
},
error: function error(err) {
expect(err.message).toEqual("An error occurred.");
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 raw type which is a string containing bytes which need to
// be extracted. Note that with msgpack5 the original bytes will be encoded with utf8
// and needs to be decoded. To not go into utf8 encoding intricacies the test uses values
// less than 0x80.
const guidBytes = [];
for (let i = 0; i < value.GUID.length; i++) {
guidBytes.push(value.GUID.charCodeAt(i));
}
value.GUID = new Uint8Array(guidBytes);
}
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 = "你好,世界!";
let hubConnection;
getJwtToken("http://" + document.location.host + "/generateJwtToken")
.then((jwtToken) => {
hubConnection = new HubConnection("/authorizedhub", {
accessTokenFactory: () => jwtToken,
logger: LogLevel.Trace,
transport: transportType,
});
hubConnection.onclose((error) => {
expect(error).toBe(undefined);
done();
});
return hubConnection.start();
})
.then(() => {
return hubConnection.invoke("Echo", message);
})
.then((response) => {
expect(response).toEqual(message);
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();
});
});
});
}
});
});
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));
};
});
}
});