Skip to content

Commit 26ed8d2

Browse files
committed
All testing files for question service
1 parent bbb92c0 commit 26ed8d2

File tree

7 files changed

+393
-1
lines changed

7 files changed

+393
-1
lines changed

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

services/collaboration/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,25 @@
3030
"@types/cors": "^2.8.17",
3131
"@types/express": "^4.17.13",
3232
"@types/jsonwebtoken": "^9.0.7",
33+
"@types/mocha": "^10.0.8",
3334
"@types/mongoose": "^5.11.96",
3435
"@types/morgan": "^1.9.9",
3536
"@types/node": "^18.14.2",
37+
"@types/sinon": "^17.0.1",
38+
"@types/sinon-chai": "^4.0.0",
3639
"@types/ws": "^8.5.12",
3740
"@typescript-eslint/eslint-plugin": "^7.18.0",
3841
"@typescript-eslint/parser": "^7.18.0",
42+
"chai": "^4.5.0",
43+
"chai-http": "^4.4.0",
3944
"eslint": "^8.57.1",
4045
"eslint-config-prettier": "^9.1.0",
4146
"eslint-plugin-prettier": "^5.2.1",
47+
"mocha": "^10.7.3",
4248
"nodemon": "^3.1.7",
4349
"prettier": "^3.3.3",
50+
"sinon": "^17.0.1",
51+
"sinon-chai": "^3.7.0",
4452
"ts-node": "^10.9.1",
4553
"typescript": "^5.0.0",
4654
"typescript-eslint": "^8.11.0"

services/collaboration/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
"allowJs": true,
1313
"checkJs": false
1414
},
15-
"include": ["src/**/*"],
15+
"include": ["src/**/*", "tests/**/*"],
1616
"exclude": ["node_modules", "dist"]
1717
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
require('dotenv').config();
2+
import { expect } from 'chai';
3+
import sinon, { SinonStub } from 'sinon';
4+
import client, { Channel, Connection } from 'amqplib';
5+
import messageBroker from '../../src/events/broker';
6+
import config from '../../src/config';
7+
8+
describe('MessageBroker', () => {
9+
let connectStub: SinonStub;
10+
let assertQueueStub: SinonStub;
11+
let sendToQueueStub: SinonStub;
12+
let consumeStub: SinonStub;
13+
let ackStub: SinonStub;
14+
let connection: Connection;
15+
let channel: Channel;
16+
17+
beforeEach(() => {
18+
connection = {
19+
createChannel: sinon.stub(),
20+
close: sinon.stub(),
21+
} as unknown as Connection;
22+
23+
channel = {
24+
assertQueue: sinon.stub(),
25+
sendToQueue: sinon.stub(),
26+
consume: sinon.stub(),
27+
ack: sinon.stub(),
28+
} as unknown as Channel;
29+
30+
connectStub = sinon.stub(client, 'connect').resolves(connection);
31+
(connection.createChannel as SinonStub).resolves(channel);
32+
assertQueueStub = channel.assertQueue as SinonStub;
33+
sendToQueueStub = channel.sendToQueue as SinonStub;
34+
consumeStub = channel.consume as SinonStub;
35+
ackStub = channel.ack as SinonStub;
36+
37+
messageBroker['connected'] = false;
38+
messageBroker['connection'] = undefined;
39+
messageBroker['channel'] = undefined;
40+
});
41+
42+
afterEach(() => {
43+
sinon.restore();
44+
});
45+
46+
describe('connect', () => {
47+
it('should connect to RabbitMQ', async () => {
48+
await messageBroker.connect();
49+
50+
console.log(connectStub.args);
51+
expect(connectStub).to.have.been.calledWith(config.BROKER_URL);
52+
expect(connection.createChannel).to.have.been.called;
53+
expect(messageBroker['connected']).to.be.true;
54+
});
55+
56+
it('should not reconnect if already connected', async () => {
57+
messageBroker['connection'] = connection;
58+
messageBroker['channel'] = channel;
59+
messageBroker['connected'] = true;
60+
61+
await messageBroker.connect();
62+
63+
expect(connectStub).not.to.have.been.called;
64+
expect(connection.createChannel).not.to.have.been.called;
65+
});
66+
67+
it('should throw an error if connection fails', async () => {
68+
connectStub.rejects(new Error('Connection error'));
69+
70+
try {
71+
await messageBroker.connect();
72+
} catch (error) {
73+
if (error instanceof Error) {
74+
expect(error.message).to.equal('Connection error');
75+
}
76+
}
77+
});
78+
});
79+
80+
describe('produce', () => {
81+
it('should produce a message to the queue', async () => {
82+
await messageBroker.produce('testQueue', { test: 'message' });
83+
84+
expect(connectStub).to.have.been.called;
85+
expect(sendToQueueStub).to.have.been.calledWith('testQueue', Buffer.from(JSON.stringify({ test: 'message' })));
86+
});
87+
88+
it('should throw an error if producing message fails', async () => {
89+
sendToQueueStub.rejects(new Error('Produce error'));
90+
91+
try {
92+
await messageBroker.produce('testQueue', { test: 'message' });
93+
} catch (error) {
94+
if (error instanceof Error) {
95+
expect(error.message).to.equal('Produce error');
96+
}
97+
}
98+
});
99+
});
100+
101+
describe('consume', () => {
102+
it('should consume messages from the queue', async () => {
103+
const onMessage = sinon.stub();
104+
const msg = { content: Buffer.from(JSON.stringify({ test: 'message' })) };
105+
106+
consumeStub.yields(msg);
107+
108+
await messageBroker.consume('testQueue', onMessage);
109+
110+
expect(connectStub).to.have.been.called;
111+
expect(assertQueueStub).to.have.been.calledWith('testQueue', { durable: true });
112+
expect(consumeStub).to.have.been.calledWith('testQueue', sinon.match.func, { noAck: false });
113+
expect(onMessage).to.have.been.calledWith({ test: 'message' });
114+
expect(ackStub).to.have.been.calledWith(msg);
115+
});
116+
117+
it('should throw an error if consuming message fails', async () => {
118+
consumeStub.rejects(new Error('Consume error'));
119+
120+
try {
121+
await messageBroker.consume('testQueue', sinon.stub());
122+
} catch (error) {
123+
if (error instanceof Error) {
124+
expect(error.message).to.equal('Consume error');
125+
}
126+
}
127+
});
128+
});
129+
});
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
require('dotenv').config();
2+
import { expect } from 'chai';
3+
import sinon, { SinonStub } from 'sinon';
4+
import { Question } from '../../src/models/questionModel';
5+
import * as prod from '../../src/events/producer';
6+
import { consumeMatchFound, initializeConsumers } from'../../src/events/consumer';
7+
import { Difficulty, MatchFoundEvent } from '../../src/types/event';
8+
import messageBroker from '../../src/events/broker';
9+
import { Queues } from '../../src/events/queues';
10+
11+
describe('consumeMatchFound', () => {
12+
let findStub: SinonStub;
13+
let produceMatchFailedEventStub: SinonStub;
14+
let produceQuestionFoundEventStub: SinonStub;
15+
let execStub: SinonStub;
16+
17+
beforeEach(() => {
18+
findStub = sinon.stub(Question, 'find');
19+
produceMatchFailedEventStub = sinon.stub(prod, 'produceMatchFailedEvent');
20+
produceQuestionFoundEventStub = sinon.stub(prod, 'produceQuestionFoundEvent');
21+
execStub = sinon.stub().returnsThis();
22+
findStub.returns({ exec: execStub });
23+
});
24+
25+
afterEach(() => {
26+
sinon.restore();
27+
});
28+
29+
it('should produce a match failed event if no questions are found', async () => {
30+
const msg: MatchFoundEvent = {
31+
user1: { requestId: 'user1', id: 'id1', username: 'user1', email: '[email protected]' },
32+
user2: { requestId: 'user2', id: 'id2', username: 'user2', email: '[email protected]' },
33+
topics: ['topic1'],
34+
difficulty: Difficulty.Easy,
35+
};
36+
37+
execStub.resolves([]);
38+
39+
await consumeMatchFound(msg);
40+
41+
expect(findStub).to.have.been.calledWith({ topics: { $in: ['topic1'] }, difficulty: 'Easy' });
42+
expect(produceMatchFailedEventStub).to.have.been.calledWith('user1', 'user2');
43+
expect(produceQuestionFoundEventStub).not.to.have.been.called;
44+
});
45+
46+
it('should produce a question found event if questions are found', async () => {
47+
const msg: MatchFoundEvent = {
48+
user1: { requestId: 'user1', id: 'id1', username: 'user1', email: '[email protected]' },
49+
user2: { requestId: 'user2', id: 'id2', username: 'user2', email: '[email protected]' },
50+
topics: ['topic1'],
51+
difficulty: Difficulty.Easy,
52+
};
53+
54+
const mockQuestions = [
55+
{ title: 'Question 1', description: 'Description 1', topics: ['topic1'], difficulty: 'Easy' },
56+
];
57+
58+
execStub.resolves(mockQuestions);
59+
60+
await consumeMatchFound(msg);
61+
62+
expect(findStub).to.have.been.calledWith({ topics: { $in: ['topic1'] }, difficulty: 'Easy' });
63+
expect(produceMatchFailedEventStub).not.to.have.been.called;
64+
expect(produceQuestionFoundEventStub).to.have.been.calledWith(msg.user1, msg.user2, mockQuestions[0]);
65+
});
66+
67+
it('should handle errors during question retrieval', async () => {
68+
const msg: MatchFoundEvent = {
69+
user1: { requestId: 'user1', id: 'id1', username: 'user1', email: '[email protected]' },
70+
user2: { requestId: 'user2', id: 'id2', username: 'user2', email: '[email protected]' },
71+
topics: ['topic1'],
72+
difficulty: Difficulty.Easy,
73+
};
74+
75+
const error = new Error('Database error');
76+
execStub.rejects(error);
77+
78+
try {
79+
await consumeMatchFound(msg);
80+
} catch (err) {
81+
expect(err).to.equal(error);
82+
}
83+
84+
expect(findStub).to.have.been.calledWith({ topics: { $in: ['topic1'] }, difficulty: 'Easy' });
85+
expect(produceMatchFailedEventStub).not.to.have.been.called;
86+
expect(produceQuestionFoundEventStub).not.to.have.been.called;
87+
});
88+
});
89+
90+
describe('initializeConsumers', () => {
91+
let consumeStub: SinonStub;
92+
93+
beforeEach(() => {
94+
consumeStub = sinon.stub(messageBroker, 'consume');
95+
});
96+
97+
afterEach(() => {
98+
sinon.restore();
99+
});
100+
101+
it('should initialize consumers correctly', () => {
102+
initializeConsumers();
103+
104+
expect(consumeStub).to.have.been.calledWith(Queues.MATCH_FOUND, consumeMatchFound);
105+
});
106+
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require('dotenv').config();
2+
import { expect } from 'chai';
3+
import sinon, { SinonStub } from 'sinon';
4+
import * as producer from '../../src/events/producer';
5+
import messageBroker from '../../src/events/broker';
6+
import { Queues } from '../../src/events/queues';
7+
import { UserWithRequest, Question, IdType, QuestionFoundEvent, MatchFailedEvent, Difficulty } from '../../src/types/event';
8+
9+
describe('Producer Tests', () => {
10+
let produceStub: SinonStub;
11+
12+
beforeEach(() => {
13+
produceStub = sinon.stub(messageBroker, 'produce');
14+
});
15+
16+
afterEach(() => {
17+
sinon.restore();
18+
});
19+
20+
describe('produceQuestionFoundEvent', () => {
21+
it('should produce a question found event', async () => {
22+
const user1: UserWithRequest = { requestId: 'user1', id: 'id1', username: 'user1', email: '[email protected]' };
23+
const user2: UserWithRequest = { requestId: 'user2', id: 'id2', username: 'user2', email: '[email protected]' };
24+
const question: Question = { id: 1, title: 'Question 1', description: 'Description 1', topics: ['topic1'], difficulty: Difficulty.Easy };
25+
26+
const expectedMessage: QuestionFoundEvent = {
27+
user1,
28+
user2,
29+
question,
30+
};
31+
32+
await producer.produceQuestionFoundEvent(user1, user2, question);
33+
34+
expect(produceStub).to.have.been.calledWith(Queues.QUESTION_FOUND, expectedMessage);
35+
});
36+
});
37+
38+
describe('produceMatchFailedEvent', () => {
39+
it('should produce a match failed event', async () => {
40+
const requestId1: IdType = 'request1';
41+
const requestId2: IdType = 'request2';
42+
const expectedMessage: MatchFailedEvent = { requestId1, requestId2, reason: 'No questions were found' };
43+
44+
await producer.produceMatchFailedEvent(requestId1, requestId2);
45+
46+
expect(produceStub).to.have.been.calledWith(Queues.MATCH_FAILED, expectedMessage);
47+
});
48+
});
49+
});

0 commit comments

Comments
 (0)