From 1bb4b4e5100ea801f3a5ca27ac590bdef40e7d0a Mon Sep 17 00:00:00 2001 From: Noj Vek Date: Thu, 21 Jul 2016 10:51:27 -0700 Subject: [PATCH] Adding initial tests and coverage --- .coveralls.yml | 2 + .gitignore | 3 +- .travis.yml | 8 ++- README.md | 5 +- lib/json-rpc2.d.ts | 92 ++++++++++++++++++++++++++++ lib/json-rpc2.js | 1 + lib/noice-json-rpc.d.ts | 17 ++--- lib/noice-json-rpc.js | 7 +++ package.json | 8 ++- src/{json-rpc2.d.ts => json-rpc2.ts} | 0 src/noice-json-rpc.ts | 28 ++++++--- tests/client-server.mock.ts | 20 ++++++ tests/client-server.test.ts | 22 +++++++ {src => tests}/example.ts | 6 +- {test => tests}/tsconfig.json | 2 +- 15 files changed, 194 insertions(+), 27 deletions(-) create mode 100644 .coveralls.yml create mode 100644 lib/json-rpc2.d.ts create mode 100644 lib/json-rpc2.js rename src/{json-rpc2.d.ts => json-rpc2.ts} (100%) create mode 100644 tests/client-server.mock.ts create mode 100644 tests/client-server.test.ts rename {src => tests}/example.ts (95%) rename {test => tests}/tsconfig.json (100%) diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 0000000..ba3ffc7 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,2 @@ +service_name: travis-pro +repo_token: 93ms0A7jcSsudOFUEZ7DpVGpDEdR1isO2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3d10042..dffd592 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules typings -out \ No newline at end of file +out +coverage \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index a33390b..83882d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,10 @@ language: node_js node_js: - - "node" + - node install: - npm install -g typescript@next - - npm install -g mocha - npm install -g typings - typings install - npm install @@ -15,4 +14,7 @@ before_script: script: - npm run build - - npm run test + - npm test + +after_success: + - npm run coveralls \ No newline at end of file diff --git a/README.md b/README.md index 91f6fe6..eebd00b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # Noice Json Rpc -[![build status](https://travis-ci.org/nojvek/noice-json-rpc.svg?branch=master)](https://travis-ci.org/nojvek/noice-json-rpc) +[![Build Status](https://travis-ci.org/nojvek/noice-json-rpc.svg?branch=master)](https://travis-ci.org/nojvek/noice-json-rpc) +[![Coverage Status](https://coveralls.io/repos/github/nojvek/noice-json-rpc/badge.svg?branch=master)](https://coveralls.io/github/nojvek/noice-json-rpc?branch=master) +[![npm version](https://badge.fury.io/js/noice-json-rpc.svg)](https://badge.fury.io/js/noice-json-rpc) + Client and Server helpers to implement a clean function based Api for Json Rpc. diff --git a/lib/json-rpc2.d.ts b/lib/json-rpc2.d.ts new file mode 100644 index 0000000..de0525f --- /dev/null +++ b/lib/json-rpc2.d.ts @@ -0,0 +1,92 @@ +/** + * Interface according to spec from http://www.jsonrpc.org/specification + * JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol. + * Primarily this specification defines several data structures. + * It is transport agnostic in that the concepts can be used within the same process, + * over sockets, over http, or in many various message passing environments. I + * It uses JSON (RFC 4627) as data format. + */ +export declare namespace JsonRpc2 { + /** + * Request object representation of a rpc call. + * Server always replies with a Response object having the same id. + */ + interface Request extends Notification { + /** An identifier established by the Client */ + id: number; + } + /** + * Client can send a request with no expectation of a response. + * Server can send a notification without an explicit request by a client. + */ + interface Notification { + /** Name of the method to be invoked. */ + method: string; + /** Parameter values to be used during the invocation of the method. */ + params?: any; + /** Version of the JSON-RPC protocol. MUST be exactly "2.0". */ + jsonrpc?: '2.0'; + } + /** + * Response object representation of a rpc call. + * Response will always contain a result property unless an error occured. + * In which case, an error property is present. + */ + interface Response { + /** An identifier established by the Client. */ + id: number; + /** Result object from the Server if method invocation was successful. */ + result?: any; + /** Error object from Server if method invocation resulted in an error. */ + error?: Error; + /** Version of the JSON-RPC protocol. MUST be exactly "2.0". */ + jsonrpc?: '2.0'; + } + /** + * Error object representation when a method invocation fails. + */ + interface Error { + /** Indicates the error type that occurred. */ + code: ErrorCode; + /** A short description of the error. */ + message: string; + /** Additional information about the error */ + data?: any; + } + const enum ErrorCode { + /** Parse error Invalid JSON was received by the Server. */ + ParseError = -32700, + /** Invalid Request The JSON sent is not a valid Request object. */ + InvalidRequest = -32600, + /** The method does not exist / is not available. */ + MethodNotFound = -32601, + /** Invalid method parameter(s). */ + InvalidParams = 32602, + /** Internal JSON-RPC error. */ + InternalError = -32603, + } + type PromiseOrNot = Promise | T; + /** A JsonRPC Client that abstracts the transportation of messages to and from the Server. */ + interface Client { + /** Creates a Request object and sends to the Server. Returns the Response from the Server as a Promise. */ + call: (method: string, params: any) => Promise; + /** Invokes the handler function when Server sends a notification. */ + on: (method: string, handler: (params: any) => void) => void; + /** Sends a notification to the Server. */ + notify: (method: string, params?: any) => void; + } + /** A JsonRPC Server that abstracts the transportation of messages to and from the Client */ + interface Server { + /** + * Invokes the handler function when Client sends a Request and sends the Response back. + * If handler function returns a Promise, then it waits for the promise to be resolved or rejected before returning. + * It also wraps the handler in a trycatch so it can send an error response when an exception is thrown. + */ + expose: (method: string, handler: (params: any) => Promise) => void; + /** Invokes the handler function when Client sends a notification. */ + on: (method: string, handler: (params: any) => void) => void; + /** Sends a notification to the Client. */ + notify: (method: string, params?: any) => void; + } +} +export default JsonRpc2; diff --git a/lib/json-rpc2.js b/lib/json-rpc2.js new file mode 100644 index 0000000..3918c74 --- /dev/null +++ b/lib/json-rpc2.js @@ -0,0 +1 @@ +"use strict"; diff --git a/lib/noice-json-rpc.d.ts b/lib/noice-json-rpc.d.ts index 76c1262..dd1fce8 100644 --- a/lib/noice-json-rpc.d.ts +++ b/lib/noice-json-rpc.d.ts @@ -1,17 +1,18 @@ +/// import { JsonRpc2 } from './json-rpc2'; import { EventEmitter } from 'events'; export interface LikeSocket { send(message: string): void; - on(event: string, cb: Function): this; - removeListener(event: string, cb: Function): this; - on(event: 'open', cb: (ws: LikeSocket) => void): this; - on(event: 'message', cb: (data: string) => void): this; - on(event: 'error', cb: (err: Error) => void): this; + on(event: string, cb: Function): any; + removeListener(event: string, cb: Function): any; + on(event: 'open', cb: (ws: LikeSocket) => void): any; + on(event: 'message', cb: (data: string) => void): any; + on(event: 'error', cb: (err: Error) => void): any; } export interface LikeSocketServer { - on(event: string, cb: Function): this; - on(event: 'connection', cb: (ws: LikeSocket) => void): this; - on(event: 'error', cb: (err: Error) => void): this; + on(event: string, cb: Function): any; + on(event: 'connection', cb: (ws: LikeSocket) => void): any; + on(event: 'error', cb: (err: Error) => void): any; clients?: LikeSocket[]; } export interface LogOpts { diff --git a/lib/noice-json-rpc.js b/lib/noice-json-rpc.js index d8a9183..4095528 100644 --- a/lib/noice-json-rpc.js +++ b/lib/noice-json-rpc.js @@ -1,3 +1,4 @@ +/// "use strict"; const events_1 = require('events'); /** @@ -14,6 +15,9 @@ class Client extends events_1.EventEmitter { this._emitLog = false; this._consoleLog = false; this.setLogging(opts); + if (!socket) { + throw new TypeError("socket cannot be undefined or null"); + } this._connectedPromise = new Promise((resolve, reject) => { this._socket = socket; socket.on('error', reject); @@ -147,6 +151,9 @@ class Server extends events_1.EventEmitter { this._emitLog = false; this._consoleLog = false; this.setLogging(opts); + if (!server) { + throw new TypeError("server cannot be undefined or null"); + } this._socketServer = server; server.on('error', (e) => this.emit('error', e)); server.on('connection', socket => { diff --git a/package.json b/package.json index 9597ad0..76137b8 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,10 @@ ], "scripts": { "build": "tsc -p src", - "clean": "rm -rf lib", - "test": "echo 'test'" + "clean": "rm -rf lib && rm -rf test", + "pretest": "tsc -p tests", + "test": "istanbul cover node_modules/.bin/_mocha -- -R landing out/*.test.js", + "coveralls": "cat coverage/lcov.info | node_modules/.bin/coveralls" }, "repository": { "type": "git", @@ -37,6 +39,8 @@ "devDependencies": { "chai": "^3.5.0", "chrome-remote-debug-protocol": "^1.0.1", + "coveralls": "^2.11.11", + "mocha": "^2.5.3", "ws": "^1.1.1" } } diff --git a/src/json-rpc2.d.ts b/src/json-rpc2.ts similarity index 100% rename from src/json-rpc2.d.ts rename to src/json-rpc2.ts diff --git a/src/noice-json-rpc.ts b/src/noice-json-rpc.ts index 800ab32..60a5cf2 100644 --- a/src/noice-json-rpc.ts +++ b/src/noice-json-rpc.ts @@ -1,20 +1,22 @@ +/// + import {JsonRpc2} from './json-rpc2' import {EventEmitter} from 'events' export interface LikeSocket { send(message: string): void - on(event: string, cb: Function): this; - removeListener(event: string, cb: Function): this; + on(event: string, cb: Function): any; + removeListener(event: string, cb: Function): any; - on(event: 'open', cb: (ws: LikeSocket) => void ): this - on(event: 'message', cb: (data: string) => void): this; - on(event: 'error', cb: (err: Error) => void ): this + on(event: 'open', cb: (ws: LikeSocket) => void ): any + on(event: 'message', cb: (data: string) => void): any; + on(event: 'error', cb: (err: Error) => void ): any } export interface LikeSocketServer { - on(event: string, cb: Function): this; - on(event: 'connection', cb: (ws: LikeSocket) => void ): this - on(event: 'error', cb: (err: Error) => void ): this + on(event: string, cb: Function): any; + on(event: 'connection', cb: (ws: LikeSocket) => void ): any + on(event: 'error', cb: (err: Error) => void ): any clients?: LikeSocket[] } @@ -52,6 +54,10 @@ export class Client extends EventEmitter implements JsonRpc2.Client{ super() this.setLogging(opts) + if (!socket) { + throw new TypeError("socket cannot be undefined or null") + } + this._connectedPromise = new Promise((resolve, reject) => { this._socket = socket @@ -192,6 +198,12 @@ export class Server extends EventEmitter implements JsonRpc2.Server { constructor (server: LikeSocketServer, opts?:ServerOpts) { super() this.setLogging(opts) + + if (!server) { + throw new TypeError("server cannot be undefined or null") + } + + this._socketServer = server server.on('error', (e) => this.emit('error', e)) diff --git a/tests/client-server.mock.ts b/tests/client-server.mock.ts new file mode 100644 index 0000000..dde3e74 --- /dev/null +++ b/tests/client-server.mock.ts @@ -0,0 +1,20 @@ +import {LikeSocket, LikeSocketServer} from '../lib/noice-json-rpc' + +export class MockSocket implements LikeSocket { + send(message: string) { + } + + on(event: string, cb: Function) { + } + + removeListener(event: string, cb: Function) { + } +} + +export class MockSocketServer implements LikeSocketServer { + on(event: string, cb: Function) { + } + + removeListener(event: string, cb: Function) { + } +} \ No newline at end of file diff --git a/tests/client-server.test.ts b/tests/client-server.test.ts new file mode 100644 index 0000000..7cb2c2e --- /dev/null +++ b/tests/client-server.test.ts @@ -0,0 +1,22 @@ +import {Client, Server} from '../lib/noice-json-rpc' +import {assert} from 'chai' + +describe('Client', () => { + let client: Client + + it('fails on null socket', ()=> { + assert.throws(() => { + client = new Client(null) + }, TypeError) + }) +}) + +describe('Server', () => { + let server: Server + + it('fails on null server', ()=> { + assert.throws(() => { + server = new Server(null) + }, TypeError) + }) +}) \ No newline at end of file diff --git a/src/example.ts b/tests/example.ts similarity index 95% rename from src/example.ts rename to tests/example.ts index a80855a..c8bd354 100644 --- a/src/example.ts +++ b/tests/example.ts @@ -5,7 +5,7 @@ import * as WebSocket from 'ws' import WebSocketServer = WebSocket.Server import * as http from 'http' import Crdp from 'chrome-remote-debug-protocol' -import * as rpc from './noice-json-rpc' +import * as rpc from '../lib/noice-json-rpc' async function setupClient() { try { @@ -60,5 +60,5 @@ function setupServer() { } -setupServer() -setupClient() \ No newline at end of file +//setupServer() +//setupClient() \ No newline at end of file diff --git a/test/tsconfig.json b/tests/tsconfig.json similarity index 100% rename from test/tsconfig.json rename to tests/tsconfig.json index 2eb60f0..e021c7d 100644 --- a/test/tsconfig.json +++ b/tests/tsconfig.json @@ -5,8 +5,8 @@ "removeComments": false, "sourceMap": false, "target": "ES6", - "outDir": "../out", "declaration": false, + "outDir": "../out", "moduleResolution": "node" } } \ No newline at end of file