Skip to content

Commit

Permalink
feat(rpc/handler): add initial implementation Handler class and basic…
Browse files Browse the repository at this point in the history
… positive tests
  • Loading branch information
CheerlessCloud committed Sep 28, 2018
1 parent ad8b109 commit 69ee40b
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 0 deletions.
77 changes: 77 additions & 0 deletions src/rpc/Handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable no-empty-function,no-unused-vars */
// @flow
import { type IMessage } from '../AMQPMessage';
import RpcService from '../Service';
import errorToObject from './errorToObject';

export default class RpcHandler {
+_service: RpcService;
+_message: IMessage;
+context: Object = {};

get action(): string {
return 'default';
}

get payload(): $PropertyType<IMessage, 'payload'> {
return this._message.payload;
}

constructor({ service, message }: { service: RpcService, message: IMessage }) {
this._service = service;
this._message = message;

if (this.handle === RpcHandler.prototype.handle) {
throw new Error('You must override handle method');
}
}

async reply({ payload, error }: { payload?: ?Object, error?: Error }) {
const { messageId, correlationId, replyTo } = this._message._props;
if (!replyTo) {
return;
}

const adapter = this._service._getAdapter();
await adapter.send(
replyTo,
{
error: errorToObject(error),
payload,
},
{ messageId, correlationId },
);
}

async beforeHandle() {}

async handle(): ?Object {}

handleFail(err: Error) {
// reply
// reject
}

async handleSuccess(replyPayload: ?Object) {
await this.reply({ payload: replyPayload });
await this._message.ack();
}

async onSuccess() {}
async onFail(error: Error) {}
async afterHandle() {}

async execute() {
try {
await this.beforeHandle();
const replyPayload = await this.handle();
await this.handleSuccess(replyPayload);
await this.onSuccess();
} catch (err) {
await this.handleFail(err);
await this.onFail(err);
} finally {
await this.afterHandle();
}
}
}
116 changes: 116 additions & 0 deletions src/rpc/Handler.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/* eslint-disable no-param-reassign */
// @flow
import test from 'ava';
import { spy, stub } from 'sinon';
import uuid from 'uuid/v4';
import Handler from './Handler';

test.beforeEach(t => {
t.context = {};
t.context.reply = { foo: 42 };
t.context.AwesomeHandler = class AwesomeHandler extends Handler {
async handle() {
return t.context.reply;
}
};
t.context.adapterSendStub = stub().resolves(undefined);
t.context.serviceStub = {
_getAdapter: () => ({
send: t.context.adapterSendStub,
}),
};
t.context.messageStub = {
id: uuid(),
_props: {
messageId: uuid(),
correlationId: uuid(),
replyTo: uuid(),
},
payload: { foo: 'bar' },
ack: stub().resolves(undefined),
reject: stub().resolves(undefined),
};
});

test('construct handler', async t => {
const { AwesomeHandler, serviceStub, messageStub } = t.context;
t.notThrows(
() =>
new AwesomeHandler({
service: serviceStub,
message: messageStub,
}),
);
});

test('positive execute case', async t => {
const { AwesomeHandler, serviceStub, messageStub } = t.context;
const handler = new AwesomeHandler({
service: serviceStub,
message: messageStub,
});
handler.handleFail = err => t.fail(err);

const beforeHandleSpy = spy(handler, 'beforeHandle');
const handleSpy = spy(handler, 'handle');
const afterHandleSpy = spy(handler, 'afterHandle');
const handleFailSpy = spy(handler, 'handleFail');
const handleSuccessSpy = spy(handler, 'handleSuccess');
const onFailSpy = spy(handler, 'onFail');
const onSuccessSpy = spy(handler, 'onSuccess');

await t.notThrows(handler.execute());

t.true(beforeHandleSpy.calledOnce);
t.true(beforeHandleSpy.calledBefore(handleSpy));

t.true(handleSpy.calledOnce);
t.true(handleSpy.calledBefore(handleSuccessSpy));

t.false(handleFailSpy.called);
t.false(onFailSpy.called);

t.true(handleSuccessSpy.calledOnce);
t.true(handleSuccessSpy.calledBefore(onSuccessSpy));
t.true(onSuccessSpy.calledOnce);
t.true(onSuccessSpy.calledBefore(afterHandleSpy));

t.true(messageStub.ack.calledOnce);
t.true(messageStub.ack.calledBefore(onSuccessSpy));
t.false(messageStub.reject.called);

t.true(afterHandleSpy.calledOnce);
});

test('correct reply at positive execute case', async t => {
const { AwesomeHandler, serviceStub, messageStub } = t.context;
const handler = new AwesomeHandler({
service: serviceStub,
message: messageStub,
});
handler.handleFail = err => t.fail(err);

const handleSuccessSpy = spy(handler, 'handleSuccess');
const replySpy = spy(handler, 'reply');

await t.notThrows(handler.execute());

t.true(handleSuccessSpy.calledOnceWith(t.context.reply));
t.true(replySpy.calledOnceWith({ payload: t.context.reply }));
t.true(t.context.adapterSendStub.calledOnce);
t.true(messageStub.ack.calledOnce);
t.false(messageStub.reject.called);
});

test('must override handle method', async t => {
const { serviceStub, messageStub } = t.context;
const HandlerClass = class AwesomeHandler2 extends Handler {};
t.throws(
() =>
new HandlerClass({
service: serviceStub,
message: messageStub,
}),
'You must override handle method',
);
});

0 comments on commit 69ee40b

Please sign in to comment.