From b6b465f3364507bf670cad6f84316c5d73e6c4de Mon Sep 17 00:00:00 2001 From: Jonathan Date: Tue, 20 Mar 2018 16:54:56 -0600 Subject: [PATCH] Release 1.0.0-alpha.7.2 WIP - Increased test coverage --- README.md | 4 +- package.json | 1 + src/core/app.factory.ts | 4 +- src/core/app.server.ts | 8 +- src/core/app.ts | 16 +- src/core/call.streamer.ts | 4 +- src/core/index.ts | 3 - src/core/rpc.ts | 70 --------- src/index.ts | 8 - src/interfaces/index.ts | 8 +- test/onix.acceptance.ts | 72 ++++++++- test/{core.unit.ts => onixjs.core.unit.ts} | 166 ++++++++++++++++++++- test/todo.shared/todo.component.ts | 23 ++- test/todo3.app/index.ts | 15 ++ 14 files changed, 296 insertions(+), 106 deletions(-) delete mode 100644 src/core/rpc.ts rename test/{core.unit.ts => onixjs.core.unit.ts} (57%) create mode 100644 test/todo3.app/index.ts diff --git a/README.md b/README.md index c2571a5..5816c23 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ $ npm install && npm run test ## Contributors -| [
Jonathan Casarrubias](http://mean.expert/)
[💻](https://github.com/onixjs/core/commits?author=jonathan-casarrubias) | | | | | | -| :---: | :---: | :---: | :---: | :---: | :---: | +| [
Jonathan Casarrubias](http://mean.expert/)
[💻](https://github.com/onixjs/core/commits?author=jonathan-casarrubias) | +| :---: | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome! \ No newline at end of file diff --git a/package.json b/package.json index ed9c29e..aea2780 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "node": ">=8.10.0" }, "dependencies": { + "@onixjs/sdk": "^1.0.0-alpha.4", "reflect-metadata": "^0.1.12", "uuid": "^3.2.1", "uws": "^9.14.0" diff --git a/src/core/app.factory.ts b/src/core/app.factory.ts index 88fb906..f8e7e67 100644 --- a/src/core/app.factory.ts +++ b/src/core/app.factory.ts @@ -8,7 +8,7 @@ import { Constructor, AppConstructor, } from '../interfaces'; -import {OnixRPC, Injector} from '../core'; +import {Injector} from '../core'; import {getObjectMethods} from '../utils'; /** * @class AppFactory @@ -39,7 +39,7 @@ export class AppFactory { */ constructor(private Class: AppConstructor, private config: IAppConfig) { // First of all create a new class instance - if (!this.app) this.app = new this.Class(new OnixRPC(this.Class)); + if (!this.app) this.app = new this.Class(); // Now setup its modules this.setupModules(); // Once finished send the schema back diff --git a/src/core/app.server.ts b/src/core/app.server.ts index 443a015..943597b 100644 --- a/src/core/app.server.ts +++ b/src/core/app.server.ts @@ -161,9 +161,11 @@ export class AppServer { apps.map( (name: string) => new Promise(async (resolve, reject) => { - const result: boolean = await this.factory.app.rpc - .topic(`${name}.isAlive`) - .call(); + const result: boolean = await this.responser.process({ + uuid: '1', + rpc: `${name}.isAlive`, + request: {metadata: {}, payload: {}}, + }); resolve(result); }), ), diff --git a/src/core/app.ts b/src/core/app.ts index bea972a..3d5e6a5 100644 --- a/src/core/app.ts +++ b/src/core/app.ts @@ -1,4 +1,4 @@ -import {IApp, OnixRPC, IAppConfig, IModuleDirectory} from '../index'; +import {IApp, IAppConfig, IModuleDirectory} from '../index'; /** * @class App * @author Jonathan Casarrubias @@ -27,14 +27,14 @@ export class Application implements IApp { * Receives optional configurations as parameter. * Otherwise will use platform default configuration. */ - constructor(public rpc: OnixRPC) {} + constructor() {} /** * @method start * @description Mock start method, this might be replaced * by custom App level functionality */ - async start(): Promise { - return new Promise((resolve, reject) => { + async start(): Promise { + return new Promise((resolve, reject) => { Object.keys(this.modules).forEach((moduleName: string) => { Object.keys(this.modules[moduleName]).forEach( (componentName: string) => { @@ -46,7 +46,7 @@ export class Application implements IApp { }, ); }); - resolve(); + resolve(true); }); } /** @@ -54,8 +54,8 @@ export class Application implements IApp { * @description Mock stop method, this might be replaced * by custom App level functionality */ - async stop(): Promise { - return new Promise((resolve, reject) => { + async stop(): Promise { + return new Promise((resolve, reject) => { Object.keys(this.modules).forEach((moduleName: string) => { Object.keys(this.modules[moduleName]).forEach( (componentName: string) => { @@ -68,7 +68,7 @@ export class Application implements IApp { }, ); }); - resolve(); + resolve(true); }); } } diff --git a/src/core/call.streamer.ts b/src/core/call.streamer.ts index b5ac64e..02bc450 100644 --- a/src/core/call.streamer.ts +++ b/src/core/call.streamer.ts @@ -27,7 +27,9 @@ export class CallStreamer { const segments: string[] = message.rpc.split('.'); // Component level method, RPC Exposed if (segments.length !== 4) { - return handler(new Error(`RPC Call is invalid ${message.rpc}`)); + return handler( + new Error(`OnixJS Error: RPC Call is invalid "${message.rpc}"`), + ); } // Execute main hook, might be app/system or module level. this.factory.app.modules[segments[1]][segments[2]][segments[3]](handler); diff --git a/src/core/index.ts b/src/core/index.ts index 69c9251..c07bcb9 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,7 +1,4 @@ export * from './app'; -export * from './rpc'; -//xport * from './roles'; -//export * from './acl.rule'; export * from './injector'; export * from './lifecycle'; export * from './connection'; diff --git a/src/core/rpc.ts b/src/core/rpc.ts deleted file mode 100644 index e411019..0000000 --- a/src/core/rpc.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {IAppOperation, OperationType, IRequest, AppConstructor} from '../index'; -/** - * @class OnixRPC - * @author Jonathan Casarrubias - * @license MIT - * @description This class provides RPC features for calling - * remote procedures for the given topic. - */ -export class OnixRPC { - /** - * @property _topic - * @description A concatenated string containing information - * about the Application, Module, Component and Method to - * execute. - */ - private _topic: string; - /** - * @constructor - * @param Class - * @description Receives a Constructor class that returns - * a valid IApp object. - */ - constructor(private Class: AppConstructor) {} - /** - * @method topic - * @param topic - * @returns OnixRPC - * @description This method is a setter for the next topic call. - */ - topic(topic: string): OnixRPC { - this._topic = topic; - return this; - } - /** - * @method call - * @param request - * @returns Promise - * @description It executes a RPC Call, it can be either through - * native OS IO events or through gRPC for remote calls. - */ - async call( - request: IRequest = {metadata: {stream: false}, payload: {}}, - ): Promise { - Object.assign(request.metadata, {caller: this.Class.name}); - console.log( - `Onix app ${this.Class.name} making call on topic: ${this._topic}`, - ); - return new Promise((resolve, reject) => { - process.on('message', (operation: IAppOperation) => { - if ( - operation.type === OperationType.ONIX_REMOTE_CALL_PROCEDURE_RESPONSE - ) { - console.log( - `Onix app ${this.Class.name} got call response from server`, - ); - resolve(operation.message); - } - }); - if (process.send) { - process.send({ - type: OperationType.ONIX_REMOTE_CALL_PROCEDURE, - message: { - rpc: this._topic, - request, - }, - }); - } - }); - } -} diff --git a/src/index.ts b/src/index.ts index 150c089..afcf10d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -148,14 +148,6 @@ export class OnixJS { this._apps[name].config = operation.message; resolve(this._apps[name].process); break; - case OperationType.ONIX_REMOTE_CALL_PROCEDURE: - this._apps[name].process.send( - await this.coordinate( - (operation.message).rpc, - (operation.message).request, - ), - ); - break; } }, ); diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 4bfa079..a542bf1 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,5 +1,4 @@ import {ChildProcess} from 'child_process'; -import {OnixRPC} from '../index'; import * as http from 'http'; /** * @interface IAppConfig @@ -20,7 +19,7 @@ export interface IAppConfig extends DomainConfig { * that receives a OnixRPC class. */ export interface AppConstructor { - new (rpc: OnixRPC): IApp; + new (): IApp; } /** * @interface IModuleConfig @@ -111,9 +110,8 @@ export interface IInjectable { */ export interface IApp { modules: IModuleDirectory; - rpc: OnixRPC; - start(): Promise; - stop(): Promise; + start(): Promise; + stop(): Promise; isAlive(): boolean; } /** diff --git a/test/onix.acceptance.ts b/test/onix.acceptance.ts index d891a5d..b5c0ec6 100644 --- a/test/onix.acceptance.ts +++ b/test/onix.acceptance.ts @@ -12,6 +12,8 @@ const pkg = require('../../package.json'); const cwd = path.join(process.cwd(), 'dist', 'test'); import * as WebSocket from 'uws'; import {IAppConfig, ICall} from '../src/interfaces'; +import {OnixClient, AppReference, ComponentReference} from '@onixjs/sdk'; +import {NodeJS} from '@onixjs/sdk/dist/core/node.adapters'; /** * Test Onix Version **/ @@ -62,7 +64,7 @@ test('Onix app greeter', async t => { * Test Onix RPC component methods **/ test('Onix rpc component methods from server', async t => { - const onix: OnixJS = new OnixJS({cwd, port: 8082}); + const onix: OnixJS = new OnixJS({cwd, port: 8085}); await onix.load('TodoApp@todo2.app'); await onix.start(); const todo: TodoModel = new TodoModel(); @@ -86,7 +88,7 @@ test('Onix rpc component methods from server', async t => { * Test Onix RPC component methods **/ test('Onix rpc component methods from client', async t => { - const onix: OnixJS = new OnixJS({cwd, port: 8081}); + const onix: OnixJS = new OnixJS({cwd, port: 8086}); await onix.load('TodoApp@todo.app'); await onix.start(); // Websocket should be available now @@ -122,3 +124,69 @@ test('Onix rpc component methods from client', async t => { ); }); }); + +/** + * Test Onix RPC component stream + ***/ +test('Onix rpc component stream', async t => { + const text: string = 'Hello SDK World'; + const onix: OnixJS = new OnixJS({cwd, port: 8087}); + await onix.load('TodoApp@todo3.app'); + await onix.start(); + // Websocket should be available now + const client: OnixClient = new OnixClient({ + host: 'http://127.0.0.1', + port: 8087, + adapters: { + http: NodeJS.HTTP, + websocket: NodeJS.WebSocket, + }, + }); + // Init SDK + await client.init(); + // Create a TodoApp Reference + const TodoAppRef: AppReference | Error = await client.AppReference('TodoApp'); + // Verify we actually got a Reference and not an Error + if (TodoAppRef instanceof AppReference) { + const componentRef: ComponentReference = TodoAppRef.Module( + 'TodoModule', + ).Component('TodoComponent'); + // Set on create listener + componentRef.Method('onCreate').stream(data => { + console.log('HELLO STREAM: ', data); + t.is(data.text, text); + }); + // Send new todo + const result = await componentRef.Method('addTodo').call({text}); + console.log('HELLO RESULT: ', result); + t.is(result.text, text); + } + console.log('HERE?'); + await onix.stop(); + /* Init SDK + await client.init(); + // Create a TodoApp Reference + const TodoAppRef: AppReference | Error = await client.AppReference('TodoApp'); + // Verify we actually got a Reference and not an Error + if (TodoAppRef instanceof AppReference) { + const componentRef: ComponentReference = TodoAppRef.Module( + 'TodoModule', + ).Component('TodoComponent'); + // Set on create listener + componentRef.Method('onCreate').stream(data => { + console.log('HELLO STREAM: ', data); + t.is(data.text, text); + onix + .stop() + .then(() => { + console.log('STOPPED'); + }) + .catch(() => {}); + }); + console.log('SENDING: '); + // Create a new todo + await componentRef.Method('addTodo').call({text}); + } else { + throw TodoAppRef; + }*/ +}); diff --git a/test/core.unit.ts b/test/onixjs.core.unit.ts similarity index 57% rename from test/core.unit.ts rename to test/onixjs.core.unit.ts index 1edd7f1..18576cb 100644 --- a/test/core.unit.ts +++ b/test/onixjs.core.unit.ts @@ -11,10 +11,12 @@ import { isJsonString, OperationType, IAppOperation, + Component, } from '../src'; import * as path from 'path'; import {CallResponser} from '../src/core/call.responser'; import * as WebSocket from 'uws'; +import {CallStreamer} from '../src/core/call.streamer'; // Test AppFactory test('Core: AppFactory creates an Application.', async t => { class MyApp extends Application {} @@ -23,7 +25,6 @@ test('Core: AppFactory creates an Application.', async t => { t.truthy(instance.app.stop); t.truthy(instance.app.isAlive); t.truthy(instance.app.modules); - t.truthy(instance.app.rpc); }); // Test AppFactory test('Core: AppFactory fails on installing invalid module.', async t => { @@ -165,7 +166,149 @@ test('Core: CallResponser invalid call.', async t => { 'OnixJS Error: RPC Call is invalid "MyApp.MyModule.MyComponent.NotExistingMethod"', ); }); +// Test CallResponser invalid call +test('Core: CallResponser invalid call.', async t => { + class MyComponent { + @RPC() + testRPC() {} + @Stream() + testSTREAM() {} + } + @Module({ + models: [], + services: [], + components: [MyComponent], + }) + class MyModule {} + class MyApp extends Application {} + const factory: AppFactory = new AppFactory(MyApp, { + disableNetwork: true, + modules: [MyModule], + }); + const responser: CallResponser = new CallResponser(factory, MyApp); + const error = await t.throws( + responser.process({ + uuid: '1', + rpc: 'MyApp.MyModule.MyComponent.NotExistingMethod', + request: {}, + }), + ); + t.is( + error.message, + 'OnixJS Error: RPC Call is invalid "MyApp.MyModule.MyComponent.NotExistingMethod"', + ); +}); +// Test CallResponser Hooks +test('Core: CallResponser Hooks.', async t => { + @Component({ + lifecycle: async function(app, metadata, method) { + const methodResult = await method(); + return methodResult; + }, + }) + class MyComponent { + @RPC() + test(payload) { + return payload; + } + } + @Module({ + models: [], + services: [], + components: [MyComponent], + }) + class MyModule {} + class MyApp extends Application {} + const factory: AppFactory = new AppFactory(MyApp, { + disableNetwork: true, + modules: [MyModule], + }); + const responser: CallResponser = new CallResponser(factory, MyApp); + const result = await responser.process({ + uuid: '1', + rpc: 'MyApp.MyModule.MyComponent.test', + request: { + metadata: {stream: false}, + payload: { + text: 'Hello Responser', + }, + }, + }); + t.is(result.text, 'Hello Responser'); +}); + +// Test CallResponser Hooks +test('Core: CallResponser Hooks.', async t => { + @Component({ + lifecycle: async function(app, metadata, method) { + const methodResult = await method(); + return methodResult; + }, + }) + class MyComponent { + @Stream() + test(stream) { + return stream({ + text: 'Hello Streamer', + }); + } + } + @Module({ + models: [], + services: [], + components: [MyComponent], + }) + class MyModule {} + class MyApp extends Application {} + const factory: AppFactory = new AppFactory(MyApp, { + disableNetwork: true, + modules: [MyModule], + }); + const streamer: CallStreamer = new CallStreamer(factory, MyApp); + streamer.register( + { + uuid: '1', + rpc: 'MyApp.MyModule.MyComponent.test', + request: { + metadata: {stream: true}, + payload: {}, + }, + }, + result => { + t.is(result.text, 'Hello Streamer'); + }, + ); +}); +// Test CallStreamer invalid call +test('Core: CallStreamer invalid call.', async t => { + class MyComponent {} + @Module({ + models: [], + services: [], + components: [MyComponent], + }) + class MyModule {} + class MyApp extends Application {} + const factory: AppFactory = new AppFactory(MyApp, { + disableNetwork: true, + modules: [MyModule], + }); + const streamer: CallStreamer = new CallStreamer(factory, MyApp); + streamer.register( + { + uuid: '1', + rpc: 'MyApp.MyModule.MyComponent.invalid.method.rpc.call', + request: {}, + }, + result => { + t.is( + result.message, + 'OnixJS Error: RPC Call is invalid "MyApp.MyModule.MyComponent.invalid.method.rpc.call"', + ); + }, + ); +}); // Test AppServer invalid operation test('Core: AppServer invalid operation.', async t => { class MyApp extends Application {} @@ -201,3 +344,24 @@ test('Core: AppServer invalid operation.', async t => { } }); }); +// Test Application start and stop +test('Core: Application start and stop.', async t => { + class MyComponent { + init() {} + destroy() {} + } + @Module({ + models: [], + services: [], + components: [MyComponent], + }) + class MyModule {} + class MyApp extends Application {} + const appInstance: MyApp = new MyApp(); + appInstance.modules[MyModule.name] = new MyModule(); + appInstance.modules[MyModule.name][MyComponent.name] = new MyComponent(); + const startResult: boolean = await appInstance.start(); + const stopResult: boolean = await appInstance.stop(); + t.true(startResult); + t.true(stopResult); +}); diff --git a/test/todo.shared/todo.component.ts b/test/todo.shared/todo.component.ts index 79f277f..c85b71b 100644 --- a/test/todo.shared/todo.component.ts +++ b/test/todo.shared/todo.component.ts @@ -3,6 +3,8 @@ import {TodoService} from './todo.service'; import {TodoModel} from './todo.model'; import {Component} from '../../src/decorators/component'; import {Inject} from '../../src/decorators/inject'; +import {RPC, Stream} from '../../src/decorators'; +import {EventEmitter} from 'events'; /** * @class TodoComponent * @author Jonathan Casarrubias @@ -24,6 +26,7 @@ import {Inject} from '../../src/decorators/inject'; }, }) export class TodoComponent implements IComponent { + private emmiter = new EventEmitter(); /** * @property service * @description This is a dependency injection example. @@ -44,8 +47,25 @@ export class TodoComponent implements IComponent { * RPC methods that internally might add business logic * or database/services calls. */ + @RPC() async addTodo(todo: TodoModel): Promise { - return this.service.create(todo); + const result = await this.service.create(todo); + this.emmiter.emit('onCreate', result); + return result; + } + /** + * @method onCreate + * @param todo + * @returns Promise + * @description Example method of how to fetch data and + * expose it through RPC methods.. + */ + @Stream() + async onCreate(stream) { + this.emmiter.on('onCreate', todo => { + console.log('STREAMING: ', todo); + stream(todo); + }); } /** * @method getTodo @@ -54,6 +74,7 @@ export class TodoComponent implements IComponent { * @description Example method of how to fetch data and * expose it through RPC methods.. */ + @RPC() async getTodos(): Promise { return this.service.find(); } diff --git a/test/todo3.app/index.ts b/test/todo3.app/index.ts new file mode 100644 index 0000000..3eadac6 --- /dev/null +++ b/test/todo3.app/index.ts @@ -0,0 +1,15 @@ +import {TodoModule} from '../todo.shared/todo.module'; +import {MicroService, Application} from '../../src/index'; +/** + * @class TodoApp + * @author Jonathan Casarrubias + * @license MIT + * @description This example app is used as example + * and for testing purposes. It imports a TodoModule. + */ +@MicroService({ + host: '127.0.0.1', + port: 8078, + modules: [TodoModule], +}) +export class TodoApp extends Application {}