switch tests to 'jest' (#2193)
This commit is contained in:
parent
c009e15b0c
commit
e3e80b957c
|
|
@ -57,14 +57,14 @@
|
|||
</ItemGroup>
|
||||
<Copy SourceFiles="@(MsgPack5Files)" DestinationFolder="$(MSBuildProjectDirectory)/wwwroot/lib/msgpack5" />
|
||||
<ItemGroup>
|
||||
<JasmineFiles Include="$(MSBuildThisFileDirectory)../node_modules/jasmine-core/lib/jasmine-core/*.js" />
|
||||
<JasmineFiles Include="$(MSBuildThisFileDirectory)../node_modules/jasmine-core/lib/jasmine-core/*.css" />
|
||||
<JasmineFiles Include="$(MSBuildThisFileDirectory)node_modules/jasmine-core/lib/jasmine-core/*.js" />
|
||||
<JasmineFiles Include="$(MSBuildThisFileDirectory)node_modules/jasmine-core/lib/jasmine-core/*.css" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(JasmineFiles)" DestinationFolder="$(MSBuildProjectDirectory)/wwwroot/lib/jasmine" />
|
||||
|
||||
<ItemGroup>
|
||||
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)../signalr/dist/browser/*" />
|
||||
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)../signalr-protocol-msgpack/dist/browser/*" />
|
||||
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)node_modules/@aspnet/signalr/dist/browser/*" />
|
||||
<SignalRJSClientFiles Include="$(MSBuildThisFileDirectory)node_modules/@aspnet/signalr-protocol-msgpack/dist/browser/*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(SignalRJSClientFiles)" DestinationFolder="$(MSBuildThisFileDirectory)/wwwroot/lib/signalr" />
|
||||
</Target>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,131 @@
|
|||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@aspnet/signalr": {
|
||||
"version": "file:../signalr",
|
||||
"dependencies": {
|
||||
"es6-promise": {
|
||||
"version": "4.2.2",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@aspnet/signalr-protocol-msgpack": {
|
||||
"version": "file:../signalr-protocol-msgpack",
|
||||
"requires": {
|
||||
"msgpack5": "4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/bl": {
|
||||
"version": "0.8.31",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"@types/node": "8.5.5"
|
||||
}
|
||||
},
|
||||
"@types/msgpack5": {
|
||||
"version": "3.4.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"@types/bl": "0.8.31"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "8.5.5",
|
||||
"bundled": true
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.2.1",
|
||||
"bundled": true
|
||||
},
|
||||
"bl": {
|
||||
"version": "1.2.2",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"readable-stream": "2.3.6",
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.0.8",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"base64-js": "1.2.1",
|
||||
"ieee754": "1.1.8"
|
||||
}
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.8",
|
||||
"bundled": true
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"msgpack5": {
|
||||
"version": "4.0.2",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"bl": "1.2.2",
|
||||
"inherits": "2.0.3",
|
||||
"readable-stream": "2.3.6",
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.0",
|
||||
"bundled": true
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.6",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"core-util-is": "1.0.2",
|
||||
"inherits": "2.0.3",
|
||||
"isarray": "1.0.0",
|
||||
"process-nextick-args": "2.0.0",
|
||||
"safe-buffer": "5.1.1",
|
||||
"string_decoder": "1.1.1",
|
||||
"util-deprecate": "1.0.2"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.1.1",
|
||||
"bundled": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/debug": {
|
||||
"version": "0.0.30",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-0.0.30.tgz",
|
||||
"integrity": "sha512-orGL5LXERPYsLov6CWs3Fh6203+dXzJkR7OnddIr2514Hsecwc8xRpzCapshBbKFImCsvS/mk6+FWiN5LyZJAQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/jasmine": {
|
||||
"version": "2.8.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.6.tgz",
|
||||
"integrity": "sha512-clg9raJTY0EOo5pVZKX3ZlMjlYzVU73L71q5OV1jhE2Uezb7oF94jh4CvwrW6wInquQAdhOxJz5VDF2TLUGmmA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "9.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.6.tgz",
|
||||
|
|
@ -52,6 +171,22 @@
|
|||
"integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
|
||||
"dev": true
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
|
||||
|
|
@ -78,6 +213,12 @@
|
|||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
|
|
@ -117,6 +258,26 @@
|
|||
"integrity": "sha1-LUH1Y+H+QA7Uli/hpNXGp1Od9/Y=",
|
||||
"dev": true
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
|
||||
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "1.0.0",
|
||||
"inflight": "1.0.6",
|
||||
"inherits": "2.0.3",
|
||||
"minimatch": "3.0.4",
|
||||
"once": "1.4.0",
|
||||
"path-is-absolute": "1.0.1"
|
||||
}
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
|
||||
|
|
@ -132,6 +293,38 @@
|
|||
"parse-passwd": "1.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "1.4.0",
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"dev": true
|
||||
},
|
||||
"jasmine": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.1.0.tgz",
|
||||
"integrity": "sha1-K9Wf1+xuwOistk4J9Fpo7SrRlSo=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "7.1.2",
|
||||
"jasmine-core": "3.1.0"
|
||||
}
|
||||
},
|
||||
"jasmine-core": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.1.0.tgz",
|
||||
"integrity": "sha1-pHheE11d9lAk38kiSVPfWFvSdmw=",
|
||||
"dev": true
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz",
|
||||
|
|
@ -148,6 +341,15 @@
|
|||
"integrity": "sha512-j3dZCri3cCd23wgPqK/0/KvTN8R+W6fXDqQe8BNLbTpONjbA8SPaRr+q0BQq9bx3Q/+g68/gDIh9FW3by702Tg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "1.1.11"
|
||||
}
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
||||
|
|
@ -187,12 +389,27 @@
|
|||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1.0.2"
|
||||
}
|
||||
},
|
||||
"parse-passwd": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
|
||||
"integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=",
|
||||
"dev": true
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
|
||||
|
|
@ -308,6 +525,12 @@
|
|||
"homedir-polyfill": "1.0.1"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -4,12 +4,17 @@
|
|||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"dependencies": {},
|
||||
"dependencies": {
|
||||
"@aspnet/signalr": "file:../signalr",
|
||||
"@aspnet/signalr-protocol-msgpack": "file:../signalr-protocol-msgpack"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/debug": "0.0.30",
|
||||
"@types/jasmine": "^2.8.6",
|
||||
"@types/node": "^9.4.6",
|
||||
"debug": "^3.1.0",
|
||||
"es6-promise": "^4.2.2",
|
||||
"jasmine": "^3.1.0",
|
||||
"tap-parser": "^7.0.0",
|
||||
"tee": "^0.2.0",
|
||||
"ts-node": "^4.1.0"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
|||
import resolve from 'rollup-plugin-node-resolve'
|
||||
|
||||
export default {
|
||||
input: path.join(__dirname, "obj", "js", "FunctionalTests", "ts", "index.js"),
|
||||
input: path.join(__dirname, "obj", "js", "index.js"),
|
||||
output: {
|
||||
file: path.join(__dirname, "wwwroot", "dist", "signalr-functional-tests.js"),
|
||||
format: "iife",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { eachTransport, ECHOENDPOINT_URL } from "./Common";
|
|||
import { TestLogger } from "./TestLogger";
|
||||
|
||||
// We want to continue testing HttpConnection, but we don't export it anymore. So just pull it in directly from the source file.
|
||||
import { HttpConnection } from "../../signalr/src/HttpConnection";
|
||||
import { HttpConnection } from "@aspnet/signalr/dist/esm/HttpConnection";
|
||||
|
||||
const commonOptions: IHttpConnectionOptions = {
|
||||
logMessageContent: true,
|
||||
|
|
|
|||
|
|
@ -116,9 +116,9 @@ describe("hubConnection", () => {
|
|||
done();
|
||||
});
|
||||
|
||||
const received = [];
|
||||
const received: string[] = [];
|
||||
hubConnection.start().then(() => {
|
||||
hubConnection.stream("Stream").subscribe({
|
||||
hubConnection.stream<string>("Stream").subscribe({
|
||||
complete() {
|
||||
expect(received).toEqual(["a", "b", "c"]);
|
||||
hubConnection.stop();
|
||||
|
|
@ -686,7 +686,7 @@ describe("hubConnection", () => {
|
|||
}
|
||||
});
|
||||
|
||||
function getJwtToken(url): Promise<string> {
|
||||
function getJwtToken(url: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { ILogger, LogLevel } from "@aspnet/signalr";
|
|||
|
||||
// Since JavaScript modules are file-based, we can just pull in utilities from the
|
||||
// main library directly even if they aren't exported.
|
||||
import { ConsoleLogger } from "../../signalr/src/Utils";
|
||||
import { ConsoleLogger } from "@aspnet/signalr/dist/esm/Utils";
|
||||
|
||||
export class TestLog {
|
||||
public messages: Array<[Date, LogLevel, string]> = [];
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
console.log("SignalR Functional Tests Loaded");
|
||||
|
||||
import "es6-promise/dist/es6-promise.auto.js";
|
||||
import "./ConnectionTests";
|
||||
import "./HubConnectionTests";
|
||||
import "./WebDriverReporter";
|
||||
import "./WebSocketTests";
|
||||
|
|
|
|||
|
|
@ -1,27 +1,12 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"noImplicitAny": false,
|
||||
"noEmitOnError": true,
|
||||
"removeComments": false,
|
||||
"sourceMap": true,
|
||||
"target": "es5",
|
||||
"module": "es2015",
|
||||
"outDir": "./obj/js",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@aspnet/signalr": [ "../signalr/dist/esm/index" ],
|
||||
"@aspnet/signalr-protocol-msgpack": [ "../signalr-protocol-msgpack/dist/esm/index" ]
|
||||
},
|
||||
"lib": [ "es2015.promise", "es5", "dom", "es2015.collection" ]
|
||||
"typeRoots": [
|
||||
"./node_modules/@types/"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./ts/**/*",
|
||||
"../signalr/dist/esm/**/*.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"wwwroot",
|
||||
"js"
|
||||
"./ts/**/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,21 +5,47 @@
|
|||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "cd ./signalr && npm run build && cd ../signalr-protocol-msgpack && npm run build",
|
||||
"test": "cd ./signalr && npm run test && cd ../signalr-protocol-msgpack && npm run test"
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "Microsoft",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.8.3",
|
||||
"@types/jest": "^22.2.3",
|
||||
"@types/node": "^8.5.2",
|
||||
"jasmine": "^2.8.0",
|
||||
"jest": "^22.4.3",
|
||||
"rimraf": "^2.6.2",
|
||||
"rollup": "^0.53.4",
|
||||
"rollup-plugin-commonjs": "^8.2.6",
|
||||
"rollup-plugin-node-resolve": "^3.0.2",
|
||||
"rollup-plugin-sourcemaps": "^0.4.2",
|
||||
"ts-jest": "^22.4.4",
|
||||
"tslint": "^5.9.1",
|
||||
"typescript": "^2.7.1",
|
||||
"uglify-js": "^3.3.5"
|
||||
},
|
||||
"jest": {
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"tsConfigFile": "./tsconfig.jest.json",
|
||||
"skipBabel": true,
|
||||
"enableTsDiagnostics": true
|
||||
}
|
||||
},
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
"testEnvironment": "node",
|
||||
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
|
||||
"moduleNameMapper": {
|
||||
"^@aspnet/signalr$": "<rootDir>/signalr/src/index.ts"
|
||||
},
|
||||
"moduleFileExtensions": [
|
||||
"ts",
|
||||
"tsx",
|
||||
"js",
|
||||
"jsx",
|
||||
"json",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,12 +14,11 @@
|
|||
"clean": "node ../node_modules/rimraf/bin.js ./dist",
|
||||
"build": "npm run clean && npm run build:lint && npm run build:esm && npm run build:cjs && npm run build:browser && npm run build:uglify",
|
||||
"build:lint": "node ../node_modules/tslint/bin/tslint -c ../tslint.json -p ./tsconfig.json",
|
||||
"build:esm": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm --target ES5 -d",
|
||||
"build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs --target ES5",
|
||||
"build:esm": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm -d",
|
||||
"build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs",
|
||||
"build:browser": "node ../node_modules/rollup/bin/rollup -c",
|
||||
"build:uglify": "node ../node_modules/uglify-js/bin/uglifyjs --source-map \"url='signalr-protocol-msgpack.min.js.map',content='./dist/browser/signalr-protocol-msgpack.js.map'\" --comments -o ./dist/browser/signalr-protocol-msgpack.min.js ./dist/browser/signalr-protocol-msgpack.js",
|
||||
"pretest": "node ../node_modules/rimraf/bin.js ./spec/obj && node ../node_modules/typescript/bin/tsc --project ./spec/tsconfig.json && cd ./spec/obj && npm init -y && npm install ../../../signalr",
|
||||
"test": "node ../node_modules/jasmine/bin/jasmine.js ./spec/obj/spec/**/*.spec.js"
|
||||
"test": "echo \"Run 'npm test' in the 'clients\\ts' folder to test this package\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"signalr",
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./obj",
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@aspnet/*": [ "../../*" ]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../typings/**/*"
|
||||
]
|
||||
}
|
||||
|
|
@ -18,16 +18,16 @@ describe("Binary Message Formatter", () => {
|
|||
});
|
||||
|
||||
([
|
||||
[[0x80], new Error("Cannot read message size.")],
|
||||
[[0x02, 0x01, 0x80, 0x80], new Error("Cannot read message size.")],
|
||||
[[0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80], new Error("Cannot read message size.")], // the size of the second message is cut
|
||||
[[0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], new Error("Incomplete message.")], // second message has only size
|
||||
[[0xff, 0xff, 0xff, 0xff, 0xff], new Error("Messages bigger than 2GB are not supported.")],
|
||||
[[0x80, 0x80, 0x80, 0x80, 0x08], new Error("Messages bigger than 2GB are not supported.")],
|
||||
[[0x80, 0x80, 0x80, 0x80, 0x80], new Error("Messages bigger than 2GB are not supported.")],
|
||||
[[0x02, 0x00], new Error("Incomplete message.")],
|
||||
[[0xff, 0xff, 0xff, 0xff, 0x07], new Error("Incomplete message.")],
|
||||
] as Array<[number[], Error]>).forEach(([payload, expectedError]) => {
|
||||
[[0x80], "Cannot read message size."],
|
||||
[[0x02, 0x01, 0x80, 0x80], "Cannot read message size."],
|
||||
[[0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80], "Cannot read message size."], // the size of the second message is cut
|
||||
[[0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], "Incomplete message."], // second message has only size
|
||||
[[0xff, 0xff, 0xff, 0xff, 0xff], "Messages bigger than 2GB are not supported."],
|
||||
[[0x80, 0x80, 0x80, 0x80, 0x08], "Messages bigger than 2GB are not supported."],
|
||||
[[0x80, 0x80, 0x80, 0x80, 0x80], "Messages bigger than 2GB are not supported."],
|
||||
[[0x02, 0x00], "Incomplete message."],
|
||||
[[0xff, 0xff, 0xff, 0xff, 0x07], "Incomplete message."],
|
||||
] as Array<[number[], string]>).forEach(([payload, expectedError]) => {
|
||||
it(`should fail to parse '${payload}'`, () => {
|
||||
expect(() => BinaryMessageFormat.parse(new Uint8Array(payload).buffer)).toThrow(expectedError);
|
||||
});
|
||||
|
|
@ -146,16 +146,16 @@ describe("MessageHubProtocol", () => {
|
|||
}));
|
||||
|
||||
([
|
||||
["message with no payload", [0x00], new Error("Invalid payload.")],
|
||||
["message with empty array", [0x01, 0x90], new Error("Invalid payload.")],
|
||||
["message without outer array", [0x01, 0xc2], new Error("Invalid payload.")],
|
||||
["message with invalid headers", [0x03, 0x92, 0x01, 0x05], new Error("Invalid headers.")],
|
||||
["Invocation message with invalid invocation id", [0x03, 0x92, 0x01, 0x80], new Error("Invalid payload for Invocation message.")],
|
||||
["StreamItem message with invalid invocation id", [0x03, 0x92, 0x02, 0x80], new Error("Invalid payload for StreamItem message.")],
|
||||
["Completion message with invalid invocation id", [0x04, 0x93, 0x03, 0x80, 0xa0], new Error("Invalid payload for Completion message.")],
|
||||
["Completion message with missing result", [0x05, 0x94, 0x03, 0x80, 0xa0, 0x01], new Error("Invalid payload for Completion message.")],
|
||||
["Completion message with missing error", [0x05, 0x94, 0x03, 0x80, 0xa0, 0x03], new Error("Invalid payload for Completion message.")],
|
||||
] as Array<[string, number[], Error]>).forEach(([name, payload, expectedError]) =>
|
||||
["message with no payload", [0x00], "Invalid payload."],
|
||||
["message with empty array", [0x01, 0x90], "Invalid payload."],
|
||||
["message without outer array", [0x01, 0xc2], "Invalid payload."],
|
||||
["message with invalid headers", [0x03, 0x92, 0x01, 0x05], "Invalid headers."],
|
||||
["Invocation message with invalid invocation id", [0x03, 0x92, 0x01, 0x80], "Invalid payload for Invocation message."],
|
||||
["StreamItem message with invalid invocation id", [0x03, 0x92, 0x02, 0x80], "Invalid payload for StreamItem message."],
|
||||
["Completion message with invalid invocation id", [0x04, 0x93, 0x03, 0x80, 0xa0], "Invalid payload for Completion message."],
|
||||
["Completion message with missing result", [0x05, 0x94, 0x03, 0x80, 0xa0, 0x01], "Invalid payload for Completion message."],
|
||||
["Completion message with missing error", [0x05, 0x94, 0x03, 0x80, 0xa0, 0x03], "Invalid payload for Completion message."],
|
||||
] as Array<[string, number[], string]>).forEach(([name, payload, expectedError]) =>
|
||||
it("throws for " + name, () => {
|
||||
expect(() => new MessagePackHubProtocol().parseMessages(new Uint8Array(payload).buffer, NullLogger.instance))
|
||||
.toThrow(expectedError);
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"./**/*"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,15 +1,7 @@
|
|||
{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@aspnet/signalr": [
|
||||
"../signalr"
|
||||
]
|
||||
}
|
||||
},
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"../typings/**/*"
|
||||
"./src/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,15 +11,14 @@
|
|||
"test": "spec"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "node ../node_modules/rimraf/bin.js ./dist ./.rpt2_cache",
|
||||
"clean": "node ../node_modules/rimraf/bin.js ./dist",
|
||||
"build": "npm run clean && npm run build:lint && npm run build:esm && npm run build:cjs && npm run build:browser && npm run build:uglify",
|
||||
"build:lint": "node ../node_modules/tslint/bin/tslint -c ../tslint.json -p ./tsconfig.json",
|
||||
"build:esm": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm --target ES5 -d && node ./build/process-dts.js",
|
||||
"build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs --target ES5",
|
||||
"build:esm": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm -d && node ./build/process-dts.js",
|
||||
"build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs",
|
||||
"build:browser": "node ../node_modules/rollup/bin/rollup -c",
|
||||
"build:uglify": "node ../node_modules/uglify-js/bin/uglifyjs --source-map \"url='signalr.min.js.map',content='./dist/browser/signalr.js.map'\" --comments -o ./dist/browser/signalr.min.js ./dist/browser/signalr.js",
|
||||
"pretest": "node ../node_modules/rimraf/bin.js ./spec/obj && node ../node_modules/typescript/bin/tsc --project ./spec/tsconfig.json",
|
||||
"test": "node ../node_modules/jasmine/bin/jasmine.js ./spec/obj/spec/**/*.spec.js"
|
||||
"test": "echo \"Run 'npm test' in the 'clients\\ts' folder to test this package\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -1,931 +0,0 @@
|
|||
// 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 } from "../src/HubConnection";
|
||||
import { IConnection } from "../src/IConnection";
|
||||
import { HubMessage, IHubProtocol, MessageType } from "../src/IHubProtocol";
|
||||
import { ILogger, LogLevel } from "../src/ILogger";
|
||||
import { TransferFormat } from "../src/ITransport";
|
||||
import { JsonHubProtocol } from "../src/JsonHubProtocol";
|
||||
import { NullLogger } from "../src/Loggers";
|
||||
import { IStreamSubscriber } from "../src/Stream";
|
||||
import { TextMessageFormat } from "../src/TextMessageFormat";
|
||||
|
||||
import { asyncit as it, captureException, delay, PromiseSource } from "./Utils";
|
||||
|
||||
function createHubConnection(connection: IConnection, logger?: ILogger, protocol?: IHubProtocol) {
|
||||
return HubConnection.create(connection, logger || NullLogger.instance, protocol || new JsonHubProtocol());
|
||||
}
|
||||
|
||||
describe("HubConnection", () => {
|
||||
|
||||
describe("start", () => {
|
||||
it("sends negotiation message", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
await hubConnection.start();
|
||||
expect(connection.sentData.length).toBe(1);
|
||||
expect(JSON.parse(connection.sentData[0])).toEqual({
|
||||
protocol: "json",
|
||||
version: 1,
|
||||
});
|
||||
await hubConnection.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("send", () => {
|
||||
it("sends a non blocking invocation", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
const invokePromise = hubConnection.send("testMethod", "arg", 42)
|
||||
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
|
||||
|
||||
// Verify the message is sent
|
||||
expect(connection.sentData.length).toBe(1);
|
||||
expect(JSON.parse(connection.sentData[0])).toEqual({
|
||||
arguments: [
|
||||
"arg",
|
||||
42,
|
||||
],
|
||||
target: "testMethod",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
// Close the connection
|
||||
hubConnection.stop();
|
||||
});
|
||||
});
|
||||
|
||||
describe("invoke", () => {
|
||||
it("sends an invocation", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
const invokePromise = hubConnection.invoke("testMethod", "arg", 42)
|
||||
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
|
||||
|
||||
// Verify the message is sent
|
||||
expect(connection.sentData.length).toBe(1);
|
||||
expect(JSON.parse(connection.sentData[0])).toEqual({
|
||||
arguments: [
|
||||
"arg",
|
||||
42,
|
||||
],
|
||||
invocationId: connection.lastInvocationId,
|
||||
target: "testMethod",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
// Close the connection
|
||||
hubConnection.stop();
|
||||
});
|
||||
|
||||
it("can process handshake from text", async () => {
|
||||
let protocolCalled = false;
|
||||
|
||||
const mockProtocol = new TestProtocol(TransferFormat.Text);
|
||||
mockProtocol.onreceive = (d) => {
|
||||
protocolCalled = true;
|
||||
};
|
||||
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection, null, mockProtocol);
|
||||
|
||||
const data = "{}" + TextMessageFormat.RecordSeparator;
|
||||
|
||||
connection.receiveText(data);
|
||||
|
||||
// message only contained handshake response
|
||||
expect(protocolCalled).toEqual(false);
|
||||
});
|
||||
|
||||
it("can process handshake from binary", async () => {
|
||||
let protocolCalled = false;
|
||||
|
||||
const mockProtocol = new TestProtocol(TransferFormat.Binary);
|
||||
mockProtocol.onreceive = (d) => {
|
||||
protocolCalled = true;
|
||||
};
|
||||
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection, null, mockProtocol);
|
||||
|
||||
// handshake response + message separator
|
||||
const data = [0x7b, 0x7d, 0x1e];
|
||||
|
||||
connection.receiveBinary(new Uint8Array(data).buffer);
|
||||
|
||||
// message only contained handshake response
|
||||
expect(protocolCalled).toEqual(false);
|
||||
});
|
||||
|
||||
it("can process handshake and additional messages from binary", async () => {
|
||||
let receivedProcotolData: ArrayBuffer;
|
||||
|
||||
const mockProtocol = new TestProtocol(TransferFormat.Binary);
|
||||
mockProtocol.onreceive = (d) => receivedProcotolData = d as ArrayBuffer;
|
||||
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection, null, mockProtocol);
|
||||
|
||||
// handshake response + message separator + message pack message
|
||||
const data = [
|
||||
0x7b, 0x7d, 0x1e, 0x65, 0x95, 0x03, 0x80, 0xa1, 0x30, 0x01, 0xd9, 0x5d, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6c,
|
||||
0x69, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20,
|
||||
0x69, 0x6e, 0x76, 0x6f, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69,
|
||||
0x6e, 0x67, 0x20, 0x27, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x27, 0x20, 0x6d,
|
||||
0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x73, 0x74, 0x72,
|
||||
0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x61, 0x73, 0x68, 0x69, 0x6f, 0x6e, 0x2e,
|
||||
];
|
||||
|
||||
connection.receiveBinary(new Uint8Array(data).buffer);
|
||||
|
||||
// left over data is the message pack message
|
||||
expect(receivedProcotolData.byteLength).toEqual(102);
|
||||
});
|
||||
|
||||
it("can process handshake and additional messages from text", async () => {
|
||||
let receivedProcotolData: string;
|
||||
|
||||
const mockProtocol = new TestProtocol(TransferFormat.Text);
|
||||
mockProtocol.onreceive = (d) => receivedProcotolData = d as string;
|
||||
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection, null, mockProtocol);
|
||||
|
||||
const data = "{}" + TextMessageFormat.RecordSeparator + "{\"type\":6}" + TextMessageFormat.RecordSeparator;
|
||||
|
||||
connection.receiveText(data);
|
||||
|
||||
expect(receivedProcotolData).toEqual("{\"type\":6}" + TextMessageFormat.RecordSeparator);
|
||||
});
|
||||
|
||||
it("rejects the promise when an error is received", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
|
||||
|
||||
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, error: "foo" });
|
||||
|
||||
const ex = await captureException(async () => invokePromise);
|
||||
expect(ex.message).toBe("foo");
|
||||
});
|
||||
|
||||
it("resolves the promise when a result is received", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
|
||||
|
||||
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, result: "foo" });
|
||||
|
||||
expect(await invokePromise).toBe("foo");
|
||||
});
|
||||
|
||||
it("completes pending invocations when stopped", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const invokePromise = hubConnection.invoke("testMethod");
|
||||
hubConnection.stop();
|
||||
|
||||
const ex = await captureException(async () => await invokePromise);
|
||||
expect(ex.message).toBe("Invocation canceled due to connection being closed.");
|
||||
});
|
||||
|
||||
it("completes pending invocations when connection is lost", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const invokePromise = hubConnection.invoke("testMethod");
|
||||
// Typically this would be called by the transport
|
||||
connection.onclose(new Error("Connection lost"));
|
||||
|
||||
const ex = await captureException(async () => await invokePromise);
|
||||
expect(ex.message).toBe("Connection lost");
|
||||
});
|
||||
});
|
||||
|
||||
describe("on", () => {
|
||||
it("invocations ignored in callbacks not registered", async () => {
|
||||
const warnings: string[] = [];
|
||||
const logger = {
|
||||
log: (logLevel: LogLevel, message: string) => {
|
||||
if (logLevel === LogLevel.Warning) {
|
||||
warnings.push(message);
|
||||
}
|
||||
},
|
||||
} as ILogger;
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection, logger);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
connection.receive({
|
||||
arguments: ["test"],
|
||||
nonblocking: true,
|
||||
target: "message",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(warnings).toEqual(["No client method with the name 'message' found."]);
|
||||
});
|
||||
|
||||
it("invocations ignored in callbacks that have registered then unregistered", async () => {
|
||||
const warnings: string[] = [];
|
||||
const logger = {
|
||||
log: (logLevel: LogLevel, message: string) => {
|
||||
if (logLevel === LogLevel.Warning) {
|
||||
warnings.push(message);
|
||||
}
|
||||
},
|
||||
} as ILogger;
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection, logger);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const handler = () => { };
|
||||
hubConnection.on("message", handler);
|
||||
hubConnection.off("message", handler);
|
||||
|
||||
connection.receive({
|
||||
arguments: ["test"],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "message",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(warnings).toEqual(["No client method with the name 'message' found."]);
|
||||
});
|
||||
|
||||
it("all handlers can be unregistered with just the method name", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
let count = 0;
|
||||
const handler = () => { count++; };
|
||||
const secondHandler = () => { count++; };
|
||||
hubConnection.on("inc", handler);
|
||||
hubConnection.on("inc", secondHandler);
|
||||
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "inc",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
hubConnection.off("inc");
|
||||
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "inc",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(count).toBe(2);
|
||||
});
|
||||
|
||||
it("a single handler can be unregistered with the method name and handler", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
let count = 0;
|
||||
const handler = () => { count++; };
|
||||
const secondHandler = () => { count++; };
|
||||
hubConnection.on("inc", handler);
|
||||
hubConnection.on("inc", secondHandler);
|
||||
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "inc",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
hubConnection.off("inc", handler);
|
||||
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "inc",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(count).toBe(3);
|
||||
});
|
||||
|
||||
it("can't register the same handler multiple times", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
let count = 0;
|
||||
const handler = () => { count++; };
|
||||
hubConnection.on("inc", handler);
|
||||
hubConnection.on("inc", handler);
|
||||
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "inc",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(count).toBe(1);
|
||||
});
|
||||
|
||||
it("callback invoked when servers invokes a method on the client", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
let value = "";
|
||||
hubConnection.on("message", (v) => value = v);
|
||||
|
||||
connection.receive({
|
||||
arguments: ["test"],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "message",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(value).toBe("test");
|
||||
});
|
||||
|
||||
it("stop on handshake error", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
let closeError: Error = null;
|
||||
hubConnection.onclose((e) => closeError = e);
|
||||
|
||||
connection.receiveHandshakeResponse("Error!");
|
||||
|
||||
expect(closeError.message).toEqual("Server returned handshake error: Error!");
|
||||
});
|
||||
|
||||
it("stop on close message", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
let isClosed = false;
|
||||
let closeError: Error = null;
|
||||
hubConnection.onclose((e) => {
|
||||
isClosed = true;
|
||||
closeError = e;
|
||||
});
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
connection.receive({
|
||||
type: MessageType.Close,
|
||||
});
|
||||
|
||||
expect(isClosed).toEqual(true);
|
||||
expect(closeError).toEqual(null);
|
||||
});
|
||||
|
||||
it("stop on error close message", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
let isClosed = false;
|
||||
let closeError: Error = null;
|
||||
hubConnection.onclose((e) => {
|
||||
isClosed = true;
|
||||
closeError = e;
|
||||
});
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
connection.receive({
|
||||
error: "Error!",
|
||||
type: MessageType.Close,
|
||||
});
|
||||
|
||||
expect(isClosed).toEqual(true);
|
||||
expect(closeError.message).toEqual("Server returned an error on close: Error!");
|
||||
});
|
||||
|
||||
it("can have multiple callbacks", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
let numInvocations1 = 0;
|
||||
let numInvocations2 = 0;
|
||||
hubConnection.on("message", () => numInvocations1++);
|
||||
hubConnection.on("message", () => numInvocations2++);
|
||||
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "message",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(numInvocations1).toBe(1);
|
||||
expect(numInvocations2).toBe(1);
|
||||
});
|
||||
|
||||
it("can unsubscribe from on", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
let numInvocations = 0;
|
||||
const callback = () => numInvocations++;
|
||||
hubConnection.on("message", callback);
|
||||
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "message",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
hubConnection.off("message", callback);
|
||||
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "message",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(numInvocations).toBe(1);
|
||||
});
|
||||
|
||||
it("unsubscribing from non-existing callbacks no-ops", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
hubConnection.off("_", () => { });
|
||||
hubConnection.on("message", (t) => { });
|
||||
hubConnection.on("message", () => { });
|
||||
});
|
||||
|
||||
it("using null/undefined for methodName or method no-ops", async () => {
|
||||
const warnings: string[] = [];
|
||||
const logger = {
|
||||
log(logLevel: LogLevel, message: string) {
|
||||
if (logLevel === LogLevel.Warning) {
|
||||
warnings.push(message);
|
||||
}
|
||||
|
||||
},
|
||||
} as ILogger;
|
||||
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection, logger);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
hubConnection.on(null, undefined);
|
||||
hubConnection.on(undefined, null);
|
||||
hubConnection.on("message", null);
|
||||
hubConnection.on("message", undefined);
|
||||
hubConnection.on(null, () => { });
|
||||
hubConnection.on(undefined, () => { });
|
||||
|
||||
// invoke a method to make sure we are not trying to use null/undefined
|
||||
connection.receive({
|
||||
arguments: [],
|
||||
invocationId: "0",
|
||||
nonblocking: true,
|
||||
target: "message",
|
||||
type: MessageType.Invocation,
|
||||
});
|
||||
|
||||
expect(warnings).toEqual(["No client method with the name 'message' found."]);
|
||||
|
||||
hubConnection.off(null, undefined);
|
||||
hubConnection.off(undefined, null);
|
||||
hubConnection.off("message", null);
|
||||
hubConnection.off("message", undefined);
|
||||
hubConnection.off(null, () => { });
|
||||
hubConnection.off(undefined, () => { });
|
||||
});
|
||||
});
|
||||
|
||||
describe("stream", () => {
|
||||
it("sends an invocation", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
const invokePromise = hubConnection.stream("testStream", "arg", 42);
|
||||
|
||||
// Verify the message is sent
|
||||
expect(connection.sentData.length).toBe(1);
|
||||
expect(JSON.parse(connection.sentData[0])).toEqual({
|
||||
arguments: [
|
||||
"arg",
|
||||
42,
|
||||
],
|
||||
invocationId: connection.lastInvocationId,
|
||||
target: "testStream",
|
||||
type: MessageType.StreamInvocation,
|
||||
});
|
||||
|
||||
// Close the connection
|
||||
hubConnection.stop();
|
||||
});
|
||||
|
||||
it("completes with an error when an error is yielded", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const observer = new TestObserver();
|
||||
hubConnection.stream<any>("testMethod", "arg", 42)
|
||||
.subscribe(observer);
|
||||
|
||||
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, error: "foo" });
|
||||
|
||||
const ex = await captureException(async () => await observer.completed);
|
||||
expect(ex.message).toEqual("Error: foo");
|
||||
});
|
||||
|
||||
it("completes the observer when a completion is received", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const observer = new TestObserver();
|
||||
hubConnection.stream<any>("testMethod", "arg", 42)
|
||||
.subscribe(observer);
|
||||
|
||||
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId });
|
||||
|
||||
expect(await observer.completed).toEqual([]);
|
||||
});
|
||||
|
||||
it("completes pending streams when stopped", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
const observer = new TestObserver();
|
||||
hubConnection.stream<any>("testMethod")
|
||||
.subscribe(observer);
|
||||
hubConnection.stop();
|
||||
|
||||
const ex = await captureException(async () => await observer.completed);
|
||||
expect(ex.message).toEqual("Error: Invocation canceled due to connection being closed.");
|
||||
});
|
||||
|
||||
it("completes pending streams when connection is lost", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
const observer = new TestObserver();
|
||||
hubConnection.stream<any>("testMethod")
|
||||
.subscribe(observer);
|
||||
|
||||
// Typically this would be called by the transport
|
||||
connection.onclose(new Error("Connection lost"));
|
||||
|
||||
const ex = await captureException(async () => await observer.completed);
|
||||
expect(ex.message).toEqual("Error: Connection lost");
|
||||
});
|
||||
|
||||
it("yields items as they arrive", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const observer = new TestObserver();
|
||||
hubConnection.stream<any>("testMethod")
|
||||
.subscribe(observer);
|
||||
|
||||
connection.receive({ type: MessageType.StreamItem, invocationId: connection.lastInvocationId, item: 1 });
|
||||
expect(observer.itemsReceived).toEqual([1]);
|
||||
|
||||
connection.receive({ type: MessageType.StreamItem, invocationId: connection.lastInvocationId, item: 2 });
|
||||
expect(observer.itemsReceived).toEqual([1, 2]);
|
||||
|
||||
connection.receive({ type: MessageType.StreamItem, invocationId: connection.lastInvocationId, item: 3 });
|
||||
expect(observer.itemsReceived).toEqual([1, 2, 3]);
|
||||
|
||||
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId });
|
||||
expect(await observer.completed).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it("does not require error function registered", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
const observer = hubConnection.stream("testMethod").subscribe(NullSubscriber.instance);
|
||||
|
||||
// Typically this would be called by the transport
|
||||
// triggers observer.error()
|
||||
connection.onclose(new Error("Connection lost"));
|
||||
});
|
||||
|
||||
it("does not require complete function registered", async () => {
|
||||
const connection = new TestConnection();
|
||||
|
||||
const hubConnection = createHubConnection(connection);
|
||||
const observer = hubConnection.stream("testMethod").subscribe(NullSubscriber.instance);
|
||||
|
||||
// Send completion to trigger observer.complete()
|
||||
// Expectation is connection.receive will not to throw
|
||||
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId });
|
||||
});
|
||||
|
||||
it("can be canceled", () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
|
||||
connection.receiveHandshakeResponse();
|
||||
|
||||
const observer = new TestObserver();
|
||||
const subscription = hubConnection.stream("testMethod")
|
||||
.subscribe(observer);
|
||||
|
||||
connection.receive({ type: MessageType.StreamItem, invocationId: connection.lastInvocationId, item: 1 });
|
||||
expect(observer.itemsReceived).toEqual([1]);
|
||||
|
||||
subscription.dispose();
|
||||
|
||||
connection.receive({ type: MessageType.StreamItem, invocationId: connection.lastInvocationId, item: 2 });
|
||||
// Observer should no longer receive messages
|
||||
expect(observer.itemsReceived).toEqual([1]);
|
||||
|
||||
// Verify the cancel is sent
|
||||
expect(connection.sentData.length).toBe(2);
|
||||
expect(JSON.parse(connection.sentData[1])).toEqual({
|
||||
invocationId: connection.lastInvocationId,
|
||||
type: MessageType.CancelInvocation,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("onClose", () => {
|
||||
it("can have multiple callbacks", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
let invocations = 0;
|
||||
hubConnection.onclose((e) => invocations++);
|
||||
hubConnection.onclose((e) => invocations++);
|
||||
// Typically this would be called by the transport
|
||||
connection.onclose();
|
||||
expect(invocations).toBe(2);
|
||||
});
|
||||
|
||||
it("callbacks receive error", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
let error: Error;
|
||||
hubConnection.onclose((e) => error = e);
|
||||
|
||||
// Typically this would be called by the transport
|
||||
connection.onclose(new Error("Test error."));
|
||||
expect(error.message).toBe("Test error.");
|
||||
});
|
||||
|
||||
it("ignores null callbacks", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
hubConnection.onclose(null);
|
||||
hubConnection.onclose(undefined);
|
||||
// Typically this would be called by the transport
|
||||
connection.onclose();
|
||||
// expect no errors
|
||||
});
|
||||
});
|
||||
|
||||
describe("keepAlive", () => {
|
||||
it("can receive ping messages", async () => {
|
||||
// Receive the ping mid-invocation so we can see that the rest of the flow works fine
|
||||
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
|
||||
|
||||
connection.receive({ type: MessageType.Ping });
|
||||
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, result: "foo" });
|
||||
|
||||
expect(await invokePromise).toBe("foo");
|
||||
});
|
||||
|
||||
it("does not terminate if messages are received", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
hubConnection.serverTimeoutInMilliseconds = 100;
|
||||
|
||||
const p = new PromiseSource<Error>();
|
||||
hubConnection.onclose((e) => p.resolve(e));
|
||||
|
||||
await hubConnection.start();
|
||||
|
||||
await connection.receive({ type: MessageType.Ping });
|
||||
await delay(50);
|
||||
await connection.receive({ type: MessageType.Ping });
|
||||
await delay(50);
|
||||
await connection.receive({ type: MessageType.Ping });
|
||||
await delay(50);
|
||||
await connection.receive({ type: MessageType.Ping });
|
||||
await delay(50);
|
||||
|
||||
connection.stop();
|
||||
|
||||
const error = await p.promise;
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not timeout if message was received before HubConnection.start", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
hubConnection.serverTimeoutInMilliseconds = 100;
|
||||
|
||||
const p = new PromiseSource<Error>();
|
||||
hubConnection.onclose((e) => p.resolve(e));
|
||||
|
||||
// send message before start to trigger timeout handler
|
||||
// testing for regression where we didn't cleanup timer if request received before start created a timer
|
||||
await connection.receive({ type: MessageType.Ping });
|
||||
|
||||
await hubConnection.start();
|
||||
|
||||
await connection.receive({ type: MessageType.Ping });
|
||||
await delay(50);
|
||||
await connection.receive({ type: MessageType.Ping });
|
||||
await delay(50);
|
||||
await connection.receive({ type: MessageType.Ping });
|
||||
await delay(50);
|
||||
|
||||
connection.stop();
|
||||
|
||||
const error = await p.promise;
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
});
|
||||
|
||||
it("terminates if no messages received within timeout interval", async () => {
|
||||
const connection = new TestConnection();
|
||||
const hubConnection = createHubConnection(connection);
|
||||
hubConnection.serverTimeoutInMilliseconds = 100;
|
||||
|
||||
const p = new PromiseSource<Error>();
|
||||
hubConnection.onclose((e) => p.resolve(e));
|
||||
|
||||
await hubConnection.start();
|
||||
|
||||
const error = await p.promise;
|
||||
|
||||
expect(error).toEqual(new Error("Server timeout elapsed without receiving a message from the server."));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class TestConnection implements IConnection {
|
||||
public readonly features: any = {};
|
||||
|
||||
public start(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public send(data: any): Promise<void> {
|
||||
const invocation = TextMessageFormat.parse(data)[0];
|
||||
const invocationId = JSON.parse(invocation).invocationId;
|
||||
if (invocationId) {
|
||||
this.lastInvocationId = invocationId;
|
||||
}
|
||||
if (this.sentData) {
|
||||
this.sentData.push(invocation);
|
||||
} else {
|
||||
this.sentData = [invocation];
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public stop(error?: Error): Promise<void> {
|
||||
if (this.onclose) {
|
||||
this.onclose(error);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public receiveHandshakeResponse(error?: string): void {
|
||||
this.receive({ error });
|
||||
}
|
||||
|
||||
public receive(data: any): void {
|
||||
const payload = JSON.stringify(data);
|
||||
this.onreceive(TextMessageFormat.write(payload));
|
||||
}
|
||||
|
||||
public receiveText(data: string) {
|
||||
this.onreceive(data);
|
||||
}
|
||||
|
||||
public receiveBinary(data: ArrayBuffer) {
|
||||
this.onreceive(data);
|
||||
}
|
||||
|
||||
public onreceive: (data: string | ArrayBuffer) => void;
|
||||
public onclose: (error?: Error) => void;
|
||||
public sentData: any[];
|
||||
public lastInvocationId: string;
|
||||
}
|
||||
|
||||
class TestProtocol implements IHubProtocol {
|
||||
public readonly name: string = "TestProtocol";
|
||||
public readonly version: number = 1;
|
||||
|
||||
public readonly transferFormat: TransferFormat;
|
||||
|
||||
public onreceive: (data: string | ArrayBuffer) => void;
|
||||
|
||||
constructor(transferFormat: TransferFormat) {
|
||||
this.transferFormat = transferFormat;
|
||||
}
|
||||
|
||||
public parseMessages(input: any): HubMessage[] {
|
||||
if (this.onreceive) {
|
||||
this.onreceive(input);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public writeMessage(message: HubMessage): any {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class TestObserver implements IStreamSubscriber<any> {
|
||||
public readonly closed: boolean;
|
||||
public itemsReceived: [any];
|
||||
private itemsSource: PromiseSource<[any]>;
|
||||
|
||||
get completed(): Promise<[any]> {
|
||||
return this.itemsSource.promise;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.itemsReceived = [] as [any];
|
||||
this.itemsSource = new PromiseSource<[any]>();
|
||||
}
|
||||
|
||||
public next(value: any) {
|
||||
this.itemsReceived.push(value);
|
||||
}
|
||||
|
||||
public error(err: any) {
|
||||
this.itemsSource.reject(new Error(err));
|
||||
}
|
||||
|
||||
public complete() {
|
||||
this.itemsSource.resolve(this.itemsReceived);
|
||||
}
|
||||
}
|
||||
|
||||
class NullSubscriber<T> implements IStreamSubscriber<T> {
|
||||
public static instance: NullSubscriber<any> = new NullSubscriber();
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public next(value: T): void {
|
||||
}
|
||||
public error(err: any): void {
|
||||
}
|
||||
public complete(): void {
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es2016",
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./obj",
|
||||
"lib": [ "es2016", "dom" ]
|
||||
},
|
||||
"include": [
|
||||
"./**/*",
|
||||
"../../typings/**/*"
|
||||
]
|
||||
}
|
||||
|
|
@ -17,14 +17,14 @@ const enum ConnectionState {
|
|||
Disconnected,
|
||||
}
|
||||
|
||||
interface INegotiateResponse {
|
||||
connectionId: string;
|
||||
availableTransports: IAvailableTransport[];
|
||||
url: string;
|
||||
accessToken: string;
|
||||
export interface INegotiateResponse {
|
||||
connectionId?: string;
|
||||
availableTransports?: IAvailableTransport[];
|
||||
url?: string;
|
||||
accessToken?: string;
|
||||
}
|
||||
|
||||
interface IAvailableTransport {
|
||||
export interface IAvailableTransport {
|
||||
transport: keyof typeof HttpTransportType;
|
||||
transferFormats: Array<keyof typeof TransferFormat>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { AbortController } from "../src/AbortController";
|
||||
import { asyncit as it } from "./Utils";
|
||||
|
||||
describe("AbortSignal", () => {
|
||||
describe("aborted", () => {
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
import { HttpRequest } from "../src/HttpClient";
|
||||
import { TestHttpClient } from "./TestHttpClient";
|
||||
import { asyncit as it } from "./Utils";
|
||||
|
||||
describe("HttpClient", () => {
|
||||
describe("get", () => {
|
||||
|
|
@ -2,30 +2,44 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
import { HttpResponse } from "../src/HttpClient";
|
||||
import { HttpConnection } from "../src/HttpConnection";
|
||||
import { HttpConnection, INegotiateResponse } from "../src/HttpConnection";
|
||||
import { IHttpConnectionOptions } from "../src/IHttpConnectionOptions";
|
||||
import { HttpTransportType, ITransport, TransferFormat } from "../src/ITransport";
|
||||
|
||||
import { HttpError } from "../src/Errors";
|
||||
import { LogLevel } from "../src/ILogger";
|
||||
import { eachEndpointUrl, eachTransport } from "./Common";
|
||||
import { TestHttpClient } from "./TestHttpClient";
|
||||
import { PromiseSource } from "./Utils";
|
||||
|
||||
const commonOptions: IHttpConnectionOptions = {
|
||||
logger: null,
|
||||
};
|
||||
|
||||
const defaultConnectionId = "abc123";
|
||||
const defaultNegotiateResponse: INegotiateResponse = {
|
||||
availableTransports: [
|
||||
{ transport: "WebSockets", transferFormats: ["Text", "Binary"] },
|
||||
{ transport: "ServerSentEvents", transferFormats: ["Text"] },
|
||||
{ transport: "LongPolling", transferFormats: ["Text", "Binary"] },
|
||||
],
|
||||
connectionId: defaultConnectionId,
|
||||
};
|
||||
|
||||
describe("HttpConnection", () => {
|
||||
it("cannot be created with relative url if document object is not present", () => {
|
||||
expect(() => new HttpConnection("/test", commonOptions))
|
||||
.toThrow(new Error("Cannot resolve '/test'."));
|
||||
.toThrow("Cannot resolve '/test'.");
|
||||
});
|
||||
|
||||
it("cannot be created with relative url if window object is not present", () => {
|
||||
(global as any).window = {};
|
||||
expect(() => new HttpConnection("/test", commonOptions))
|
||||
.toThrow(new Error("Cannot resolve '/test'."));
|
||||
.toThrow("Cannot resolve '/test'.");
|
||||
delete (global as any).window;
|
||||
});
|
||||
|
||||
it("starting connection fails if getting id fails", async (done) => {
|
||||
it("starting connection fails if getting id fails", async () => {
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
|
|
@ -35,45 +49,48 @@ describe("HttpConnection", () => {
|
|||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
expect(e).toBe("error");
|
||||
done();
|
||||
}
|
||||
expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("error");
|
||||
});
|
||||
|
||||
it("cannot start a running connection", async (done) => {
|
||||
it("cannot start a running connection", async () => {
|
||||
const negotiating = new PromiseSource();
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", (r) => {
|
||||
connection.start(TransferFormat.Text)
|
||||
.then(() => {
|
||||
fail();
|
||||
done();
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
expect(error.message).toBe("Cannot start a connection that is not in the 'Disconnected' state.");
|
||||
done();
|
||||
});
|
||||
return Promise.reject("error");
|
||||
negotiating.resolve();
|
||||
return defaultNegotiateResponse;
|
||||
}),
|
||||
transport: {
|
||||
connect(url: string, transferFormat: TransferFormat) {
|
||||
return Promise.resolve();
|
||||
},
|
||||
send(data: any) {
|
||||
return Promise.resolve();
|
||||
},
|
||||
stop() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
onclose: null,
|
||||
onreceive: null,
|
||||
},
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
} catch (e) {
|
||||
// This exception is thrown after the actual verification is completed.
|
||||
// The connection is not setup to be running so just ignore the error.
|
||||
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Cannot start a connection that is not in the 'Disconnected' state.");
|
||||
} finally {
|
||||
await connection.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("can start a stopped connection", async (done) => {
|
||||
it("can start a stopped connection", async () => {
|
||||
let negotiateCalls = 0;
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
|
|
@ -87,22 +104,16 @@ describe("HttpConnection", () => {
|
|||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
} catch (e) {
|
||||
expect(e).toBe("reached negotiate");
|
||||
}
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("reached negotiate");
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
} catch (e) {
|
||||
expect(e).toBe("reached negotiate");
|
||||
}
|
||||
|
||||
done();
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("reached negotiate");
|
||||
});
|
||||
|
||||
it("can stop a starting connection", async (done) => {
|
||||
it("can stop a starting connection", async () => {
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
|
|
@ -118,22 +129,15 @@ describe("HttpConnection", () => {
|
|||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
done();
|
||||
} catch (e) {
|
||||
fail();
|
||||
done();
|
||||
}
|
||||
await connection.start(TransferFormat.Text);
|
||||
});
|
||||
|
||||
it("can stop a non-started connection", async (done) => {
|
||||
it("can stop a non-started connection", async () => {
|
||||
const connection = new HttpConnection("http://tempuri.org", commonOptions);
|
||||
await connection.stop();
|
||||
done();
|
||||
});
|
||||
|
||||
it("start throws after all transports fail", async (done) => {
|
||||
it("start throws after all transports fail", async () => {
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
|
|
@ -142,25 +146,21 @@ describe("HttpConnection", () => {
|
|||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org?q=myData", options);
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Unable to initialize any of the available transports.");
|
||||
}
|
||||
done();
|
||||
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Unable to initialize any of the available transports.");
|
||||
});
|
||||
|
||||
it("preserves user's query string", async (done) => {
|
||||
let connectUrl: string;
|
||||
it("preserves user's query string", async () => {
|
||||
const connectUrl = new PromiseSource<string>();
|
||||
const fakeTransport: ITransport = {
|
||||
connect(url: string): Promise<void> {
|
||||
connectUrl = url;
|
||||
return Promise.reject("");
|
||||
connectUrl.resolve(url);
|
||||
return Promise.resolve();
|
||||
},
|
||||
send(data: any): Promise<void> {
|
||||
return Promise.reject("");
|
||||
return Promise.resolve();
|
||||
},
|
||||
stop(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
|
|
@ -178,60 +178,50 @@ describe("HttpConnection", () => {
|
|||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org?q=myData", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
}
|
||||
const startPromise = connection.start(TransferFormat.Text);
|
||||
|
||||
expect(connectUrl).toBe("http://tempuri.org?q=myData&id=42");
|
||||
done();
|
||||
expect(await connectUrl).toBe("http://tempuri.org?q=myData&id=42");
|
||||
|
||||
await startPromise;
|
||||
} finally {
|
||||
await connection.stop();
|
||||
}
|
||||
});
|
||||
|
||||
eachEndpointUrl((givenUrl: string, expectedUrl: string) => {
|
||||
it("negotiate request puts 'negotiate' at the end of the path", async (done) => {
|
||||
let negotiateUrl: string;
|
||||
let connection: HttpConnection;
|
||||
it(`negotiate request for '${givenUrl}' puts 'negotiate' at the end of the path`, async () => {
|
||||
const negotiateUrl = new PromiseSource<string>();
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", (r) => {
|
||||
negotiateUrl = r.url;
|
||||
connection.stop();
|
||||
return "{}";
|
||||
negotiateUrl.resolve(r.url);
|
||||
throw new HttpError("We don't care how this turns out", 500);
|
||||
})
|
||||
.on("GET", (r) => {
|
||||
connection.stop();
|
||||
return "";
|
||||
}),
|
||||
return new HttpResponse(204);
|
||||
})
|
||||
.on("DELETE", (r) => new HttpResponse(202)),
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
connection = new HttpConnection(givenUrl, options);
|
||||
|
||||
const connection = new HttpConnection(givenUrl, options);
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
done();
|
||||
} catch (e) {
|
||||
fail();
|
||||
done();
|
||||
}
|
||||
const startPromise = connection.start(TransferFormat.Text);
|
||||
|
||||
expect(negotiateUrl).toBe(expectedUrl);
|
||||
expect(await negotiateUrl).toBe(expectedUrl);
|
||||
|
||||
await expect(startPromise).rejects;
|
||||
} finally {
|
||||
await connection.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
eachTransport((requestedTransport: HttpTransportType) => {
|
||||
it(`cannot be started if requested ${HttpTransportType[requestedTransport]} transport not available on server`, async () => {
|
||||
const negotiateResponse = {
|
||||
availableTransports: [
|
||||
{ transport: "WebSockets", transferFormats: [ "Text", "Binary" ] },
|
||||
{ transport: "ServerSentEvents", transferFormats: [ "Text" ] },
|
||||
{ transport: "LongPolling", transferFormats: [ "Text", "Binary" ] },
|
||||
],
|
||||
connectionId: "abc123",
|
||||
};
|
||||
// Clone the default response
|
||||
const negotiateResponse = { ...defaultNegotiateResponse };
|
||||
|
||||
// Remove the requested transport from the response
|
||||
negotiateResponse.availableTransports = negotiateResponse.availableTransports
|
||||
|
|
@ -247,12 +237,9 @@ describe("HttpConnection", () => {
|
|||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail("Expected connection.start to throw!");
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Unable to initialize any of the available transports.");
|
||||
}
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Unable to initialize any of the available transports.");
|
||||
});
|
||||
|
||||
for (const [val, name] of [[null, "null"], [undefined, "undefined"], [0, "0"]]) {
|
||||
|
|
@ -316,7 +303,7 @@ describe("HttpConnection", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("cannot be started if no transport available on server and no transport requested", async (done) => {
|
||||
it("cannot be started if no transport available on server and no transport requested", async () => {
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient()
|
||||
|
|
@ -325,17 +312,12 @@ describe("HttpConnection", () => {
|
|||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Unable to initialize any of the available transports.");
|
||||
done();
|
||||
}
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Unable to initialize any of the available transports.");
|
||||
});
|
||||
|
||||
it("does not send negotiate request if WebSockets transport requested explicitly and skipNegotiation is true", async (done) => {
|
||||
it("does not send negotiate request if WebSockets transport requested explicitly and skipNegotiation is true", async () => {
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient(),
|
||||
|
|
@ -344,19 +326,12 @@ describe("HttpConnection", () => {
|
|||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
// WebSocket is created when the transport is connecting which happens after
|
||||
// negotiate request would be sent. No better/easier way to test this.
|
||||
expect(e.message).toBe("'WebSocket' is not supported in your environment.");
|
||||
done();
|
||||
}
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("'WebSocket' is not supported in your environment.");
|
||||
});
|
||||
|
||||
it("does not start non WebSockets transport requested explicitly and skipNegotiation is true", async (done) => {
|
||||
it("does not start non WebSockets transport requested explicitly and skipNegotiation is true", async () => {
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
httpClient: new TestHttpClient(),
|
||||
|
|
@ -365,19 +340,12 @@ describe("HttpConnection", () => {
|
|||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
// WebSocket is created when the transport is connecting which happens after
|
||||
// negotiate request would be sent. No better/easier way to test this.
|
||||
expect(e.message).toBe("Negotiation can only be skipped when using the WebSocket transport directly.");
|
||||
done();
|
||||
}
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Negotiation can only be skipped when using the WebSocket transport directly.");
|
||||
});
|
||||
|
||||
it("redirects to url when negotiate returns it", async (done) => {
|
||||
it("redirects to url when negotiate returns it", async () => {
|
||||
let firstNegotiate = true;
|
||||
let firstPoll = true;
|
||||
const httpClient = new TestHttpClient()
|
||||
|
|
@ -397,7 +365,8 @@ describe("HttpConnection", () => {
|
|||
return "";
|
||||
}
|
||||
return new HttpResponse(204, "No Content", "");
|
||||
});
|
||||
})
|
||||
.on("DELETE", (r) => new HttpResponse(202));
|
||||
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
|
|
@ -405,23 +374,21 @@ describe("HttpConnection", () => {
|
|||
transport: HttpTransportType.LongPolling,
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
try {
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
await connection.start(TransferFormat.Text);
|
||||
} catch (e) {
|
||||
fail(e);
|
||||
done();
|
||||
}
|
||||
|
||||
expect(httpClient.sentRequests.length).toBe(4);
|
||||
expect(httpClient.sentRequests[0].url).toBe("http://tempuri.org/negotiate");
|
||||
expect(httpClient.sentRequests[1].url).toBe("https://another.domain.url/chat/negotiate");
|
||||
expect(httpClient.sentRequests[2].url).toMatch(/^https:\/\/another\.domain\.url\/chat\?id=0rge0d00-0040-0030-0r00-000q00r00e00/i);
|
||||
expect(httpClient.sentRequests[3].url).toMatch(/^https:\/\/another\.domain\.url\/chat\?id=0rge0d00-0040-0030-0r00-000q00r00e00/i);
|
||||
done();
|
||||
expect(httpClient.sentRequests.length).toBe(4);
|
||||
expect(httpClient.sentRequests[0].url).toBe("http://tempuri.org/negotiate");
|
||||
expect(httpClient.sentRequests[1].url).toBe("https://another.domain.url/chat/negotiate");
|
||||
expect(httpClient.sentRequests[2].url).toMatch(/^https:\/\/another\.domain\.url\/chat\?id=0rge0d00-0040-0030-0r00-000q00r00e00/i);
|
||||
expect(httpClient.sentRequests[3].url).toMatch(/^https:\/\/another\.domain\.url\/chat\?id=0rge0d00-0040-0030-0r00-000q00r00e00/i);
|
||||
} finally {
|
||||
await connection.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("fails to start if negotiate redirects more than 100 times", async (done) => {
|
||||
it("fails to start if negotiate redirects more than 100 times", async () => {
|
||||
const httpClient = new TestHttpClient()
|
||||
.on("POST", /negotiate$/, (r) => ({ url: "https://another.domain.url/chat" }));
|
||||
|
||||
|
|
@ -431,17 +398,13 @@ describe("HttpConnection", () => {
|
|||
transport: HttpTransportType.LongPolling,
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
try {
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
} catch (e) {
|
||||
expect(e.message).toBe("Negotiate redirection limit exceeded.");
|
||||
done();
|
||||
}
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Negotiate redirection limit exceeded.");
|
||||
});
|
||||
|
||||
it("redirects to url when negotiate returns it with access token", async (done) => {
|
||||
it("redirects to url when negotiate returns it with access token", async () => {
|
||||
let firstNegotiate = true;
|
||||
let firstPoll = true;
|
||||
const httpClient = new TestHttpClient()
|
||||
|
|
@ -475,7 +438,8 @@ describe("HttpConnection", () => {
|
|||
return "";
|
||||
}
|
||||
return new HttpResponse(204, "No Content", "");
|
||||
});
|
||||
})
|
||||
.on("DELETE", (r) => new HttpResponse(202));
|
||||
|
||||
const options: IHttpConnectionOptions = {
|
||||
...commonOptions,
|
||||
|
|
@ -484,23 +448,21 @@ describe("HttpConnection", () => {
|
|||
transport: HttpTransportType.LongPolling,
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
try {
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
await connection.start(TransferFormat.Text);
|
||||
} catch (e) {
|
||||
fail(e);
|
||||
done();
|
||||
}
|
||||
|
||||
expect(httpClient.sentRequests.length).toBe(4);
|
||||
expect(httpClient.sentRequests[0].url).toBe("http://tempuri.org/negotiate");
|
||||
expect(httpClient.sentRequests[1].url).toBe("https://another.domain.url/chat/negotiate");
|
||||
expect(httpClient.sentRequests[2].url).toMatch(/^https:\/\/another\.domain\.url\/chat\?id=0rge0d00-0040-0030-0r00-000q00r00e00/i);
|
||||
expect(httpClient.sentRequests[3].url).toMatch(/^https:\/\/another\.domain\.url\/chat\?id=0rge0d00-0040-0030-0r00-000q00r00e00/i);
|
||||
done();
|
||||
expect(httpClient.sentRequests.length).toBe(4);
|
||||
expect(httpClient.sentRequests[0].url).toBe("http://tempuri.org/negotiate");
|
||||
expect(httpClient.sentRequests[1].url).toBe("https://another.domain.url/chat/negotiate");
|
||||
expect(httpClient.sentRequests[2].url).toMatch(/^https:\/\/another\.domain\.url\/chat\?id=0rge0d00-0040-0030-0r00-000q00r00e00/i);
|
||||
expect(httpClient.sentRequests[3].url).toMatch(/^https:\/\/another\.domain\.url\/chat\?id=0rge0d00-0040-0030-0r00-000q00r00e00/i);
|
||||
} finally {
|
||||
await connection.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("authorization header removed when token factory returns null and using LongPolling", async (done) => {
|
||||
it("authorization header removed when token factory returns null and using LongPolling", async () => {
|
||||
const availableTransport = { transport: "LongPolling", transferFormats: ["Text"] };
|
||||
|
||||
let httpClientGetCount = 0;
|
||||
|
|
@ -535,23 +497,22 @@ describe("HttpConnection", () => {
|
|||
}
|
||||
throw new Error("fail");
|
||||
}
|
||||
}),
|
||||
})
|
||||
.on("DELETE", (r) => new HttpResponse(202)),
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
|
||||
expect(httpClientGetCount).toBeGreaterThanOrEqual(2);
|
||||
expect(accessTokenFactoryCount).toBeGreaterThanOrEqual(2);
|
||||
done();
|
||||
} catch (e) {
|
||||
fail(e);
|
||||
done();
|
||||
} finally {
|
||||
await connection.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("sets inherentKeepAlive feature when using LongPolling", async (done) => {
|
||||
it("sets inherentKeepAlive feature when using LongPolling", async () => {
|
||||
const availableTransport = { transport: "LongPolling", transferFormats: ["Text"] };
|
||||
|
||||
let httpClientGetCount = 0;
|
||||
|
|
@ -567,22 +528,20 @@ describe("HttpConnection", () => {
|
|||
} else {
|
||||
throw new Error("fail");
|
||||
}
|
||||
}),
|
||||
})
|
||||
.on("DELETE", (r) => new HttpResponse(202)),
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
expect(connection.features.inherentKeepAlive).toBe(true);
|
||||
done();
|
||||
} catch (e) {
|
||||
fail(e);
|
||||
done();
|
||||
} finally {
|
||||
await connection.stop();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not select ServerSentEvents transport when not available in environment", async (done) => {
|
||||
it("does not select ServerSentEvents transport when not available in environment", async () => {
|
||||
const serverSentEventsTransport = { transport: "ServerSentEvents", transferFormats: ["Text"] };
|
||||
|
||||
const options: IHttpConnectionOptions = {
|
||||
|
|
@ -593,19 +552,12 @@ describe("HttpConnection", () => {
|
|||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
// ServerSentEvents is only transport returned from server but is not selected
|
||||
// because there is no support in the environment, leading to the following error
|
||||
expect(e.message).toBe("Unable to initialize any of the available transports.");
|
||||
done();
|
||||
}
|
||||
await expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Unable to initialize any of the available transports.");
|
||||
});
|
||||
|
||||
it("does not select WebSockets transport when not available in environment", async (done) => {
|
||||
it("does not select WebSockets transport when not available in environment", async () => {
|
||||
const webSocketsTransport = { transport: "WebSockets", transferFormats: ["Text"] };
|
||||
|
||||
const options: IHttpConnectionOptions = {
|
||||
|
|
@ -616,16 +568,9 @@ describe("HttpConnection", () => {
|
|||
|
||||
const connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start(TransferFormat.Text);
|
||||
fail();
|
||||
done();
|
||||
} catch (e) {
|
||||
// WebSockets is only transport returned from server but is not selected
|
||||
// because there is no support in the environment, leading to the following error
|
||||
expect(e.message).toBe("Unable to initialize any of the available transports.");
|
||||
done();
|
||||
}
|
||||
expect(connection.start(TransferFormat.Text))
|
||||
.rejects
|
||||
.toThrow("Unable to initialize any of the available transports.");
|
||||
});
|
||||
|
||||
describe(".constructor", () => {
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -11,7 +11,7 @@ import { HttpTransportType, TransferFormat } from "../src/ITransport";
|
|||
import { NullLogger } from "../src/Loggers";
|
||||
|
||||
import { TestHttpClient } from "./TestHttpClient";
|
||||
import { asyncit as it, PromiseSource } from "./Utils";
|
||||
import { PromiseSource } from "./Utils";
|
||||
|
||||
const allTransportsNegotiateResponse = {
|
||||
availableTransports: [
|
||||
|
|
@ -37,17 +37,17 @@ describe("HubConnectionBuilder", () => {
|
|||
eachMissingValue((val, name) => {
|
||||
it(`configureLogging throws if logger is ${name}`, () => {
|
||||
const builder = new HubConnectionBuilder();
|
||||
expect(() => builder.configureLogging(val)).toThrow(new Error("The 'logging' argument is required."));
|
||||
expect(() => builder.configureLogging(val)).toThrow("The 'logging' argument is required.");
|
||||
});
|
||||
|
||||
it(`withUrl throws if url is ${name}`, () => {
|
||||
const builder = new HubConnectionBuilder();
|
||||
expect(() => builder.withUrl(val)).toThrow(new Error("The 'url' argument is required."));
|
||||
expect(() => builder.withUrl(val)).toThrow("The 'url' argument is required.");
|
||||
});
|
||||
|
||||
it(`withHubProtocol throws if protocol is ${name}`, () => {
|
||||
const builder = new HubConnectionBuilder();
|
||||
expect(() => builder.withHubProtocol(val)).toThrow(new Error("The 'protocol' argument is required."));
|
||||
expect(() => builder.withHubProtocol(val)).toThrow("The 'protocol' argument is required.");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -149,16 +149,16 @@ describe("JsonHubProtocol", () => {
|
|||
|
||||
([
|
||||
["message with empty payload", `{}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload.")],
|
||||
["Invocation message with invalid invocation id", `{"type":1,"invocationId":1,"target":"method"}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Invocation message.")],
|
||||
["Invocation message with empty string invocation id", `{"type":1,"invocationId":"","target":"method"}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Invocation message.")],
|
||||
["Invocation message with invalid target", `{"type":1,"invocationId":"1","target":1}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Invocation message.")],
|
||||
["StreamItem message with missing invocation id", `{"type":2}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for StreamItem message.")],
|
||||
["StreamItem message with invalid invocation id", `{"type":2,"invocationId":1}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for StreamItem message.")],
|
||||
["Completion message with missing invocation id", `{"type":3}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Completion message.")],
|
||||
["Completion message with invalid invocation id", `{"type":3,"invocationId":1}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Completion message.")],
|
||||
["Completion message with result and error", `{"type":3,"invocationId":"1","result":2,"error":"error"}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Completion message.")],
|
||||
["Completion message with non-string error", `{"type":3,"invocationId":"1","error":21}${TextMessageFormat.RecordSeparator}`, new Error("Invalid payload for Completion message.")],
|
||||
] as Array<[string, string, Error]>).forEach(([name, payload, expectedError]) =>
|
||||
["Invocation message with invalid invocation id", `{"type":1,"invocationId":1,"target":"method"}${TextMessageFormat.RecordSeparator}`, "Invalid payload for Invocation message."],
|
||||
["Invocation message with empty string invocation id", `{"type":1,"invocationId":"","target":"method"}${TextMessageFormat.RecordSeparator}`, "Invalid payload for Invocation message."],
|
||||
["Invocation message with invalid target", `{"type":1,"invocationId":"1","target":1}${TextMessageFormat.RecordSeparator}`, "Invalid payload for Invocation message."],
|
||||
["StreamItem message with missing invocation id", `{"type":2}${TextMessageFormat.RecordSeparator}`, "Invalid payload for StreamItem message."],
|
||||
["StreamItem message with invalid invocation id", `{"type":2,"invocationId":1}${TextMessageFormat.RecordSeparator}`, "Invalid payload for StreamItem message."],
|
||||
["Completion message with missing invocation id", `{"type":3}${TextMessageFormat.RecordSeparator}`, "Invalid payload for Completion message."],
|
||||
["Completion message with invalid invocation id", `{"type":3,"invocationId":1}${TextMessageFormat.RecordSeparator}`, "Invalid payload for Completion message."],
|
||||
["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);
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import { HttpError, TimeoutError } from "../src/Errors";
|
||||
import { HttpResponse } from "../src/HttpClient";
|
||||
import { LogLevel } from "../src/ILogger";
|
||||
import { TransferFormat } from "../src/ITransport";
|
||||
|
|
@ -6,9 +7,7 @@ import { LongPollingTransport } from "../src/LongPollingTransport";
|
|||
import { ConsoleLogger } from "../src/Utils";
|
||||
|
||||
import { TestHttpClient } from "./TestHttpClient";
|
||||
import { asyncit as it, PromiseSource, delay } from "./Utils";
|
||||
import { HttpError, TimeoutError } from "../src/Errors";
|
||||
import { AbortSignal } from "../src/AbortController";
|
||||
import { delay, PromiseSource } from "./Utils";
|
||||
|
||||
describe("LongPollingTransport", () => {
|
||||
it("shuts down poll after timeout even if server doesn't shut it down on receiving the DELETE", async () => {
|
||||
|
|
@ -70,7 +69,10 @@ describe("LongPollingTransport", () => {
|
|||
});
|
||||
|
||||
for (const result of [200, 204, 300, new HttpError("Boom", 500), new TimeoutError()]) {
|
||||
const resultName = typeof result === "number" ? result.toString() : result.constructor.name;
|
||||
|
||||
// Function has a name property but TypeScript doesn't know about it.
|
||||
const resultName = typeof result === "number" ? result.toString() : (result.constructor as any).name;
|
||||
|
||||
it(`does not fire shutdown timer when poll terminates with ${resultName}`, async () => {
|
||||
let firstPoll = true;
|
||||
const deleteReceived = new PromiseSource();
|
||||
|
|
@ -119,4 +121,4 @@ describe("LongPollingTransport", () => {
|
|||
expect(transport.pollAborted).toBe(false);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { HttpClient, HttpRequest, HttpResponse } from "../src/HttpClient";
|
||||
|
||||
type TestHttpHandlerResult = string | HttpResponse | any;
|
||||
export type TestHttpHandlerResult = string | HttpResponse | any;
|
||||
export type TestHttpHandler = (request: HttpRequest, next?: (request: HttpRequest) => Promise<HttpResponse>) => Promise<TestHttpHandlerResult> | TestHttpHandlerResult;
|
||||
|
||||
export class TestHttpClient extends HttpClient {
|
||||
|
|
@ -17,10 +17,10 @@ describe("TextMessageFormat", () => {
|
|||
});
|
||||
|
||||
([
|
||||
["", new Error("Message is incomplete.")],
|
||||
["ABC", new Error("Message is incomplete.")],
|
||||
["ABC\u001eXYZ", new Error("Message is incomplete.")],
|
||||
] as Array<[string, Error]>).forEach(([payload, expectedError]) => {
|
||||
["", "Message is incomplete."],
|
||||
["ABC", "Message is incomplete."],
|
||||
["ABC\u001eXYZ", "Message is incomplete."],
|
||||
] as Array<[string, string]>).forEach(([payload, expectedError]) => {
|
||||
it(`should fail to parse '${payload}'`, () => {
|
||||
expect(() => TextMessageFormat.parse(payload)).toThrow(expectedError);
|
||||
});
|
||||
|
|
@ -3,42 +3,13 @@
|
|||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
|
||||
|
||||
export function asyncit(expectation: string, assertion?: () => Promise<any> | void, timeout?: number): void {
|
||||
let testFunction: (done: DoneFn) => void;
|
||||
if (assertion) {
|
||||
testFunction = (done) => {
|
||||
const promise = assertion();
|
||||
if (promise) {
|
||||
promise.then(() => done())
|
||||
.catch((err) => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
it(expectation, testFunction, timeout);
|
||||
}
|
||||
|
||||
export async function captureException(fn: () => Promise<any>): Promise<Error> {
|
||||
try {
|
||||
await fn();
|
||||
return null;
|
||||
} catch (e) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
export function delay(durationInMilliseconds: number): Promise<void> {
|
||||
const source = new PromiseSource<void>();
|
||||
setTimeout(() => source.resolve(), durationInMilliseconds);
|
||||
return source.promise;
|
||||
}
|
||||
|
||||
export class PromiseSource<T = void> {
|
||||
export class PromiseSource<T = void> implements Promise<T> {
|
||||
public promise: Promise<T>;
|
||||
|
||||
private resolver: (value?: T | PromiseLike<T>) => void;
|
||||
|
|
@ -58,4 +29,12 @@ export class PromiseSource<T = void> {
|
|||
public reject(reason?: any) {
|
||||
this.rejecter(reason);
|
||||
}
|
||||
|
||||
// Look like a promise so we can be awaited directly;
|
||||
public then<TResult1 = T, TResult2 = never>(onfulfilled?: (value: T) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>): Promise<TResult1 | TResult2> {
|
||||
return this.promise.then(onfulfilled, onrejected);
|
||||
}
|
||||
public catch<TResult = never>(onrejected?: (reason: any) => TResult | PromiseLike<TResult>): Promise<T | TResult> {
|
||||
return this.promise.catch(onrejected);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": [
|
||||
"./**/*"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"extends": "../tsconfig-base.json",
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@
|
|||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"module": "es2015",
|
||||
"target": "es2016",
|
||||
"outDir": "./obj/js",
|
||||
"target": "es5",
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"inlineSources": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
|
|
@ -14,6 +14,15 @@
|
|||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noEmitOnError": true,
|
||||
"stripInternal": true,
|
||||
"lib": [ "es5", "es2015.promise", "es2015.iterable", "dom" ]
|
||||
"lib": [ "es5", "es2015.promise", "es2015.iterable", "dom" ],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@aspnet/signalr": [
|
||||
"./signalr"
|
||||
],
|
||||
"@aspnet/signalr-protocol-msgpack": [
|
||||
"./signalr-protocol-msgpack"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "./tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
},
|
||||
"include": [
|
||||
"./*/tests/**/*"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue