To Introduction

Microservice Facade

Step 6. Testing of operations

Before we integrate our new facade with the actual system, we should put it through its paces and thoroughly test it. So let’s develop a set of tests and helper elements for testing all of the operations registered in the facade. We’ll start off by creating a set of helper classes. One will test our dependencies, another will test how well the facade works with users, and the last one will contain a set of test users. All of these files will be placed in the folder /test/fixtures.

The file for testing dependencies will be called TestReferences.ts and will allow us to test how well the facade is able to work with the microservices it depends on. This file’s code is listed below:

/test/fixture/TestReferences.ts

let _ = require('lodash');

import { ConfigParams } from 'pip-services3-commons-node';
import { Descriptor } from 'pip-services3-commons-node';

import { CompositeFactory } from 'pip-services3-components-node';
import { ManagedReferences } from 'pip-services3-container-node';

import { IAccountsClientV1, AccountV1 } from 'pip-clients-accounts-node';
import { ISessionsClientV1 } from 'pip-clients-sessions-node';

import { TestUsers } from './TestUsers';import { ClientFacadeFactory } from '../../src/build/ClientFacadeFactory';
import { ServiceFacadeFactory } from '../../src/build/ServiceFacadeFactory';
import { HttpEndpoint } from 'pip-services3-rpc-node';
import { DefaultRpcFactory } from 'pip-services3-rpc-node';
import { FacadeServiceV1 } from '../../src/services/version1/FacadeServiceV1';
import { AccountsMemoryClientV1 } from 'pip-clients-accounts-node';
import { SessionsMemoryClientV1 } from 'pip-clients-sessions-node';
import { BeaconsMemoryClientV1 } from 'pip-clients-beacons-node';

export class TestReferences extends ManagedReferences {
    private _factory = new CompositeFactory();

    public constructor() {
        super();

        this.setupFactories();
        this.appendDependencies();
        this.configureService();
        this.createUsersAndSessions();
    }

    private setupFactories() {
        this._factory.add(new ClientFacadeFactory());
        this._factory.add(new ServiceFacadeFactory());
        this._factory.add(new DefaultRpcFactory());
    }

    public append(descriptor: Descriptor): void {
        let component = this._factory.create(descriptor);
        this.put(descriptor, component);
    }

    private appendDependencies() {
        // Add factories
        this.put(null, this._factory);

        // Add service
        this.put(null, new FacadeServiceV1());

        // Add user management services
        this.put(new Descriptor('pip-services-accounts', 'client', 'memory', 'default', '*'), new AccountsMemoryClientV1());
        this.put(new Descriptor('pip-services-sessions', 'client', 'memory', 'default', '*'), new SessionsMemoryClientV1());
        // Add content management services
        // Beacons
        this.put(new Descriptor('pip-services-beacons', 'client', 'memory', 'default', '*'), new BeaconsMemoryClientV1());
    }

    private configureService(): void {
        // Configure Facade service
        let service = this.getOneRequired<HttpEndpoint>(
            new Descriptor('pip-services', 'endpoint', 'http', 'default', '*')
        );
        service.configure(ConfigParams.fromTuples(
            'root_path', '', //'/api/v1',
            'connection.protocol', 'http',
            'connection.host', '0.0.0.0',
            'connection.port', 3000
        ));
    }

    private createUsersAndSessions(): void {
        // Create accounts
        let accountsClient = this.getOneRequired<IAccountsClientV1>(
            new Descriptor('pip-services-accounts', 'client', '*', '*', '*')
        );

        let adminUserAccount = <AccountV1>{
            id: TestUsers.AdminUserId,
             login: TestUsers.AdminUserLogin,
             name: TestUsers.AdminUserName,
            active: true,
            create_time: new Date()
        };
        accountsClient.createAccount(null, adminUserAccount, () => {});

        let user1Account = <AccountV1>{
            id: TestUsers.User1Id,
            login: TestUsers.User1Login,
            name: TestUsers.User1Name,
            active: true,
            create_time: new Date()
        };
        accountsClient.createAccount(null, user1Account, () => {});

        let user2Account = <AccountV1>{
            id: TestUsers.User2Id,
            login: TestUsers.User2Login,
            name: TestUsers.User2Name,
            active: true,
            create_time: new Date()
        };

        accountsClient.createAccount(null, user2Account, () => {});

        // Create opened sessions
        let sessionsClient = this.getOneRequired<ISessionsClientV1>(
            new Descriptor('pip-services-sessions', 'client', '*', '*', '*')
        );

        let adminUserData = _.clone(adminUserAccount);
        adminUserData.roles = ['admin'];
        sessionsClient.openSession(
            null, TestUsers.AdminUserId, TestUsers.AdminUserName,
            null, null, adminUserData, null,
            (err, session) => { session.id = TestUsers.AdminUserSessionId });

        let user1Data = _.clone(user1Account);
        user1Data.roles = [];
        sessionsClient.openSession(
            null, TestUsers.User1Id, TestUsers.User1Name,
            null, null, user1Data, null,
            (err, session) => { session.id = TestUsers.User1SessionId });

        let user2Data = _.clone(user2Account);
        user2Data.roles = [];
        sessionsClient.openSession(
            null, TestUsers.User2Id, TestUsers.User2Name,
            null, null, user2Data, null,
            (err, session) => { session.id = TestUsers.User2SessionId });
    }
}

Now, let’s create a file with a test client, which will help us test our user and session related operations. Place the code below into a file named TestRestClient.ts:

/test/fixture/TestRestClient.ts

let restify = require('restify-clients');

export class TestRestClient {
    private _rest: any;

    public constructor() {
        let url = 'http://localhost:3000';
        this._rest = restify.createJsonClient({ url: url, version: '*' });
    }

    public get(path: string,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        delete this._rest.headers['x-session-id'];
        this._rest.get(path, callback);
    }

    public head(path: string,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        delete this._rest.headers['x-session-id'];
        this._rest.head(path, callback)
    }

    public post(path: string, params: any,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        delete this._rest.headers['x-session-id'];        this._rest.post(path, params, callback);
    }

    public put(path: string, params: any,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        delete this._rest.headers['x-session-id'];
        this._rest.put(path, params, callback);
    }

    public del(path: string,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        delete this._rest.headers['x-session-id'];
        this._rest.del(path, callback);
    }

    public getAsUser(sessionId: string, path: string,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        this._rest.headers['x-session-id'] = sessionId;
        this._rest.get(path, callback);
    }

    public headAsUser(sessionId: string, path: string,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        this._rest.headers['x-session-id'] = sessionId;
        this._rest.head(path, callback)
    }

    public postAsUser(sessionId: string, path: string, params: any,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        this._rest.headers['x-session-id'] = sessionId;
        this._rest.post(path, params, callback);
    }

    public putAsUser(sessionId: string, path: string, params: any,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        this._rest.headers['x-session-id'] = sessionId;
        this._rest.put(path, params, callback);
    }

    public delAsUser(sessionId: string, path: string,
        callback: (err: any, req: any, res: any, result: any) => void): void {
        this._rest.headers['x-session-id'] = sessionId;
        this._rest.del(path, callback);
    }
}

Lastly, define some test users in a file named TestUsers.ts, as shown below:

/test/fixture/TestUsers.ts

export class TestUsers {
    public static readonly AdminUserId: string = '1';
    public static readonly AdminUserName: string = 'Admin User';
    public static readonly AdminUserLogin: string = 'admin';
    public static readonly AdminUserPassword: string = 'pwd123';
    public static readonly AdminUserSessionId: string = '111';

    public static readonly User1Id: string = '2';
    public static readonly User1Name: string = 'User #1';
    public static readonly User1Login: string = 'user1';
    public static readonly User1Password: string = 'pwd123';
    public static readonly User1SessionId: string = '222';

    public static readonly User2Id: string = '3';
    public static readonly User2Name: string = 'User #2';
    public static readonly User2Login: string = 'user2';
    public static readonly User2Password: string = 'pwd123';
    public static readonly User2SessionId: string = '333';
};

Now we can move on to the tests themselves. Create the following files in the folder test/operations:

BeaconsRoutesV1.test.ts - for testing business logic operations of the Beacons microservice:

/test/operations/BeaconsRoutesV1.test.ts

let _ = require('lodash');
let async = require('async');
let assert = require('chai').assert;

import { TestReferences } from '../../fixtures/TestReferences';
import { TestUsers } from '../../fixtures/TestUsers';
import { TestRestClient } from '../../fixtures/TestRestClient';
import { BeaconV1, BeaconTypeV1 } from 'pip-clients-beacons-node';

const BEACON1: BeaconV1 = {
    id: '1',
    udi: '00001',
    type: BeaconTypeV1.AltBeacon,
    org_id: '1',
    label: 'TestBeacon1',
    center: { type: 'Point', coordinates: [ 0, 0 ] },
    radius: 50};
const BEACON2: BeaconV1 = {
    id: '2',
    udi: '00002',
    type: BeaconTypeV1.iBeacon,
    org_id: '1',
    label: 'TestBeacon2',
    center: { type: 'Point', coordinates: [ 2, 2 ] },
    radius: 70
};

suite('BeaconsRoutesV1', () => {

    let references: TestReferences;
    let rest: TestRestClient;

    setup((done) => {
        rest = new TestRestClient();
        references = new TestReferences();
        references.open(null, done);
    });

    teardown((done) => {
        references.close(null, done);
    });

    test('CRUD Operations', (done) => {
        let beacon1: BeaconV1;

        async.series([
            // Create the first beacon
            (callback) => {
                rest.postAsUser(TestUsers.AdminUserSessionId, '/api/v1/beacons',
                    BEACON1,
                    (err, req, res, beacon) => {
                        assert.isNull(err);

                        assert.isObject(beacon);
                        assert.equal(BEACON1.udi, beacon.udi);
                        assert.equal(BEACON1.org_id, beacon.org_id);
                        assert.equal(BEACON1.type, beacon.type);
                        assert.equal(BEACON1.label, beacon.label);
                        assert.isNotNull(beacon.center);

                        callback();
                    }
                );
            },
            // Create the second beacon
            (callback) => {
                rest.postAsUser(TestUsers.AdminUserSessionId, '/api/v1/beacons',
                    BEACON2,
                    (err, req, res, beacon) => {
                        assert.isNull(err);

                        assert.isObject(beacon);
                        assert.equal(BEACON2.udi, beacon.udi);
                        assert.equal(BEACON2.org_id, beacon.org_id);
                        assert.equal(BEACON2.type, beacon.type);
                        assert.equal(BEACON2.label, beacon.label);
                        assert.isNotNull(beacon.center);
                        callback();
                    }
                );
            },
            // Get all beacons
            (callback) => {
                rest.getAsUser(TestUsers.AdminUserSessionId, '/api/v1/beacons',
                    (err, req, res, page) => {
                        assert.isNull(err);

                        assert.isObject(page);
                        assert.lengthOf(page.data, 2);

                        beacon1 = page.data[0];

                        callback();
                    }
                )
            },
            // Update the beacon
            (callback) => {
                beacon1.label = 'ABC';

                rest.putAsUser(TestUsers.AdminUserSessionId, '/api/v1/beacons',
                    beacon1,
                    (err, req, res, beacon) => {
                        assert.isNull(err);

                        assert.isObject(beacon);
                        assert.equal(beacon1.id, beacon.id);
                        assert.equal('ABC', beacon.label);

                        callback();
                    }
                )
            },
            // Get beacon by udi
            (callback) => {
                rest.getAsUser(TestUsers.User1SessionId, '/api/v1/beacons/udi/' + beacon1.udi + '?user_id=' + TestUsers.User1Id,
                    (err, req, res, beacon) => {
                        assert.isNull(err);

                        assert.isObject(beacon);
                        assert.equal(beacon1.id, beacon.id);

                        callback();
                    }
                )
            },
            // Calculate position for one beacon
            (callback) => {
                rest.postAsUser(TestUsers.User1SessionId, '/api/v1/beacons/position',
                    {
                        org_id: '1',
                        udis: ['00001']
                    },
                    (err, req, res, position) => {
                        assert.isNull(err);

                        assert.isObject(position);
                        assert.equal('Point', position.type);
                        assert.lengthOf(position.coordinates, 2);
                        assert.equal(0, position.coordinates[0]);
                        assert.equal(0, position.coordinates[1]);

                        callback();
                    }
                )
            },
            // Delete the beacon
            (callback) => {
                rest.delAsUser(TestUsers.AdminUserSessionId, '/api/v1/beacons/' + beacon1.id,
                    (err, req, res, beacon) => {
                        assert.isNull(err);

                        assert.isObject(beacon);
                        assert.equal(beacon1.id, beacon.id);

                        callback();
                    }
                )
            },
            // Try to get deleted beacon
            (callback) => {
                rest.getAsUser(TestUsers.User1SessionId, '/api/v1/beacons/' + beacon1.id + '?user_id=' + TestUsers.User1Id,
                    (err, req, res, beacon) => {
                        assert.isNull(err);

                        //assert.isEmpty(beacon || null);

                        callback();
                    }
                )
            }
        ], done);
    });
});

SessionsRoutesV1.test.ts - for testing user and session related operations:

/test/operations/SessionsRoutesV1.test.ts

let _ = require('lodash');
let async = require('async');
let assert = require('chai').assert;

import { TestReferences } from '../../fixtures/TestReferences';
import { TestRestClient } from '../../fixtures/TestRestClient';

suite('SessionRoutesV1', () => {
    let USER = {
        login: 'test',
        name: 'Test User',
        email: 'test@conceptual.vision',
        password: 'test123'
    };

    let references: TestReferences;
    let rest: TestRestClient;

    setup((done) => {
        rest = new TestRestClient();
        references = new TestReferences();
        references.open(null, done);
    });

    teardown((done) => {
        references.close(null, done);
    });

    test('should signup new user', (done) => {
        rest.post('/api/v1/users/signup',
            USER,
            (err, req, res, session) => {
                assert.isNull(err);

                assert.isDefined(session);
                assert.isDefined(session.id);
                assert.equal(session.user_name, USER.name);

                done();
            }
        );
    });

    test('should not signup with the same email', (done) => {
        async.series([
        // Sign up
            (callback) => {
                rest.post('/api/v1/users/signup',
                    USER,
                    (err, req, res, session) => {
                        assert.isNull(err);
                        callback();
                    }
                );
            },
        // Try to sign up again
            (callback) => {
                rest.post('/api/v1/users/signup',
                    USER,
                    (err, req, res, session) => {
                        assert.isNotNull(err);
                        callback();
                    }
                );
            }
        ], done);
    });

    test('should signout', (done) => {
        rest.post('/api/v1/users/signout',
            null,
            (err, req, res, result) => {
                assert.isNull(err);
                done();
            }
        );
    });

    test('should signin with email and password', (done) => {
        async.series([
        // Sign up
            (callback) => {
                rest.post('/api/v1/users/signup',
                    USER,
                    (err, req, res, session) => {
                        assert.isNull(err);
                        callback();
                    }
                );
            },
        // Sign in with username
            (callback) => {
                rest.post('/api/v1/users/signin',
                    {
                        login: USER.login,
                        password: USER.password
                    },
                    (err, req, res, session) => {
                        assert.isNull(err);
                        callback();
                    }
                );
            }
        ], done);
    });
});

Run the tests using the following command:

npm test

Once all the tests pass successfully, you can move on to Step 7 - Running the facade - to learn how to deploy all of these microservices using Docker.