Skip to content

Commit ccc2b4f

Browse files
committed
feat: add more tests to improve coverage
1 parent 719bd4f commit ccc2b4f

File tree

2 files changed

+234
-0
lines changed

2 files changed

+234
-0
lines changed

test/IMQService.afterCall.spec.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*!
2+
* IMQService afterCall and promise branch Unit Tests (incremental)
3+
*
4+
* I'm Queue Software Project
5+
* Copyright (C) 2025 imqueue.com <[email protected]>
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*
20+
* If you want to use this code in a closed source (commercial) project, you can
21+
* purchase a proprietary commercial license. Please contact us at
22+
* <[email protected]> to get commercial licensing options.
23+
*/
24+
import { expect } from 'chai';
25+
import * as sinon from 'sinon';
26+
import { uuid } from '@imqueue/core';
27+
import { logger } from './mocks';
28+
import {
29+
IMQService,
30+
IMQRPCRequest,
31+
expose,
32+
AFTER_HOOK_ERROR,
33+
} from '..';
34+
35+
class AfterHookService extends IMQService {
36+
@expose()
37+
public ping() {
38+
return 'pong';
39+
}
40+
41+
@expose()
42+
public async asyncHello(name: string) {
43+
return await new Promise<string>(resolve => setTimeout(() => resolve(`Hello, ${name}!`), 5));
44+
}
45+
}
46+
47+
describe('IMQService hooks (afterCall) and promise branch', () => {
48+
it('should execute afterCall hook without error', async () => {
49+
const afterCall = sinon.spy(async () => {});
50+
const service: any = new AfterHookService({ logger, afterCall });
51+
const request: IMQRPCRequest = {
52+
from: 'HookClient',
53+
method: 'ping',
54+
args: [],
55+
};
56+
const id = uuid();
57+
const sendSpy = sinon.spy(service.imq, 'send');
58+
59+
await service.start();
60+
service.imq.emit('message', request, id);
61+
62+
await new Promise((resolve, reject) => setTimeout(() => {
63+
try {
64+
expect(sendSpy.called).to.be.true;
65+
expect(afterCall.calledOnce).to.be.true;
66+
resolve(undefined);
67+
} catch (err) {
68+
reject(err);
69+
}
70+
}));
71+
72+
await service.destroy();
73+
});
74+
75+
it('should catch afterCall error and log AFTER_HOOK_ERROR', async () => {
76+
const warnSpy = sinon.spy(logger, 'warn');
77+
const afterCall = async () => { throw new Error('after boom'); };
78+
const service: any = new AfterHookService({ logger, afterCall });
79+
const request: IMQRPCRequest = {
80+
from: 'HookClient',
81+
method: 'ping',
82+
args: [],
83+
};
84+
const id = uuid();
85+
86+
await service.start();
87+
service.imq.emit('message', request, id);
88+
89+
await new Promise((resolve, reject) => setTimeout(() => {
90+
try {
91+
expect(warnSpy.called).to.be.true;
92+
const hasAfter = warnSpy.getCalls().some((c: any) => c.args && c.args[0] === AFTER_HOOK_ERROR);
93+
expect(hasAfter).to.be.true;
94+
resolve(undefined);
95+
} catch (err) {
96+
reject(err);
97+
}
98+
}));
99+
100+
warnSpy.restore();
101+
await service.destroy();
102+
});
103+
104+
it('should await promise-returning method result before sending', async () => {
105+
const service: any = new AfterHookService({ logger });
106+
// make an exposed method return a Promise to hit thenable branch
107+
service.exposed = async () => await new Promise<string>(resolve => setTimeout(() => resolve('Hello, IMQ!'), 5));
108+
const request: IMQRPCRequest = {
109+
from: 'HookClient',
110+
method: 'exposed',
111+
args: [],
112+
};
113+
const id = uuid();
114+
115+
// stub to assert when send() is actually called, after promise resolved
116+
const done = new Promise<void>((resolve, reject) => {
117+
sinon.stub(service.imq, 'send').callsFake(async (_to: string, response: any) => {
118+
try {
119+
expect(response.data).to.equal('Hello, IMQ!');
120+
resolve();
121+
} catch (err) {
122+
reject(err);
123+
}
124+
return id;
125+
});
126+
});
127+
128+
await service.start();
129+
service.imq.emit('message', request, id);
130+
131+
await done;
132+
133+
await service.destroy();
134+
});
135+
});

test/IMQService.hooks.spec.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*!
2+
* IMQService Hooks Unit Tests (incremental)
3+
*
4+
* I'm Queue Software Project
5+
* Copyright (C) 2025 imqueue.com <[email protected]>
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
19+
*
20+
* If you want to use this code in a closed source (commercial) project, you can
21+
* purchase a proprietary commercial license. Please contact us at
22+
* <[email protected]> to get commercial licensing options.
23+
*/
24+
import { expect } from 'chai';
25+
import * as sinon from 'sinon';
26+
import { uuid } from '@imqueue/core';
27+
import { logger } from './mocks';
28+
import {
29+
IMQService,
30+
IMQRPCRequest,
31+
expose,
32+
BEFORE_HOOK_ERROR,
33+
} from '..';
34+
35+
class HookTestService extends IMQService {
36+
@expose()
37+
public ping() {
38+
return 'pong';
39+
}
40+
}
41+
42+
describe('IMQService hooks (beforeCall)', () => {
43+
it('should execute beforeCall hook without error', async () => {
44+
const beforeCall = sinon.spy(async () => {});
45+
const service: any = new HookTestService({ logger, beforeCall });
46+
const request: IMQRPCRequest = {
47+
from: 'HookClient',
48+
method: 'ping',
49+
args: [],
50+
};
51+
const id = uuid();
52+
const sendSpy = sinon.spy(service.imq, 'send');
53+
54+
await service.start();
55+
service.imq.emit('message', request, id);
56+
57+
// wait for async send
58+
await new Promise((resolve, reject) => setTimeout(() => {
59+
try {
60+
expect(beforeCall.calledOnce).to.be.true;
61+
expect(sendSpy.called).to.be.true;
62+
resolve(undefined);
63+
} catch (err) {
64+
reject(err);
65+
}
66+
}));
67+
68+
await service.destroy();
69+
});
70+
71+
it('should catch beforeCall error and log BEFORE_HOOK_ERROR', async () => {
72+
const warnSpy = sinon.spy(logger, 'warn');
73+
const beforeCall = async () => { throw new Error('boom'); };
74+
const service: any = new HookTestService({ logger, beforeCall });
75+
const request: IMQRPCRequest = {
76+
from: 'HookClient',
77+
method: 'ping',
78+
args: [],
79+
};
80+
const id = uuid();
81+
82+
await service.start();
83+
service.imq.emit('message', request, id);
84+
85+
await new Promise((resolve, reject) => setTimeout(() => {
86+
try {
87+
expect(warnSpy.called).to.be.true;
88+
const calledWithBefore = warnSpy.getCalls().some((c: any) => c.args && c.args[0] === BEFORE_HOOK_ERROR);
89+
expect(calledWithBefore).to.be.true;
90+
resolve(undefined);
91+
} catch (err) {
92+
reject(err);
93+
}
94+
}));
95+
96+
warnSpy.restore();
97+
await service.destroy();
98+
});
99+
});

0 commit comments

Comments
 (0)