Skip to content

Commit 2b3108c

Browse files
feat(notification): add exclude users without email (#119)
* feat(notification): add exclude users without email * feat(notification): update tests * Update microservices/notification/src/services/task-handlers/email-all.ts Co-authored-by: Mikhail Yarmaliuk <[email protected]> * Update microservices/notification/src/services/task-handlers/abstract.ts Co-authored-by: Mikhail Yarmaliuk <[email protected]> * feat(notification): update tests * feat(notification): add email group tests * feat(notification): update email group tests * feat(notification): update task services tests --------- Co-authored-by: Mikhail Yarmaliuk <[email protected]>
1 parent bd11bba commit 2b3108c

File tree

7 files changed

+312
-11
lines changed

7 files changed

+312
-11
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { Api } from '@lomray/microservice-helpers';
2+
import { TypeormMock } from '@lomray/microservice-helpers/mocks';
3+
import { waitResult } from '@lomray/microservice-helpers/test-helpers';
4+
import { expect } from 'chai';
5+
import sinon from 'sinon';
6+
import { taskMock } from '@__mocks__/task';
7+
import TaskType from '@constants/task-type';
8+
import EmailAll from '@services/task-handlers/email-all';
9+
10+
describe('services/task-handlers/email-all', () => {
11+
const sandbox = sinon.createSandbox();
12+
let apiGet: sinon.SinonStub;
13+
let emailAll: EmailAll;
14+
15+
beforeEach(() => {
16+
TypeormMock.sandbox.reset();
17+
emailAll = new EmailAll(TypeormMock.entityManager);
18+
apiGet = sinon.stub(Api, 'get');
19+
});
20+
21+
afterEach(() => {
22+
sandbox.restore();
23+
apiGet.restore();
24+
});
25+
26+
describe('take', () => {
27+
it('should correctly return false: task type is not email all', () => {
28+
expect(emailAll.take([taskMock])).to.false;
29+
});
30+
31+
it('should correctly return true: task type is email all', () => {
32+
expect(emailAll.take([{ ...taskMock, type: TaskType.EMAIL_ALL }])).to.true;
33+
});
34+
});
35+
36+
describe('sendEmailToAllUsers', () => {
37+
it('should correctly send email to all users', async () => {
38+
const getTotalUsersCountStub = sandbox.stub();
39+
const executeEmailAllTaskStub = sandbox.stub();
40+
41+
await emailAll['sendEmailToAllUsers'].call(
42+
{
43+
getTotalUsersCount: getTotalUsersCountStub,
44+
executeEmailAllTask: executeEmailAllTaskStub,
45+
currentPage: 1,
46+
},
47+
taskMock,
48+
);
49+
50+
expect(getTotalUsersCountStub).to.calledOnce;
51+
expect(getTotalUsersCountStub.args[0][0]).to.equal(true);
52+
expect(executeEmailAllTaskStub).to.calledOnce;
53+
});
54+
55+
it('should throw error: failed to send email to all users', async () => {
56+
const getTotalUsersCountStub = sandbox.stub().throws(new Error('Failed to get users count'));
57+
58+
expect(
59+
await waitResult(
60+
emailAll['sendEmailToAllUsers'].call(
61+
{ getTotalUsersCount: getTotalUsersCountStub },
62+
taskMock,
63+
),
64+
),
65+
).to.throw('Failed to get users count');
66+
});
67+
});
68+
});
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { Api } from '@lomray/microservice-helpers';
2+
import { TypeormMock } from '@lomray/microservice-helpers/mocks';
3+
import { waitResult } from '@lomray/microservice-helpers/test-helpers';
4+
import { expect } from 'chai';
5+
import sinon from 'sinon';
6+
import { taskMock } from '@__mocks__/task';
7+
import TaskType from '@constants/task-type';
8+
import Message from '@entities/message';
9+
import Recipient from '@entities/recipient';
10+
import type AbstractEmailProvider from '@services/email-provider/abstract';
11+
import EmailGroup from '@services/task-handlers/email-group';
12+
13+
describe('services/task-handlers/email-group', () => {
14+
const sandbox = sinon.createSandbox();
15+
let apiGet: sinon.SinonStub;
16+
let emailGroup: EmailGroup;
17+
const messageRepository = TypeormMock.entityManager.getRepository(Message);
18+
const recipientRepository = TypeormMock.entityManager.getRepository(Recipient);
19+
20+
beforeEach(() => {
21+
TypeormMock.sandbox.reset();
22+
emailGroup = new EmailGroup(TypeormMock.entityManager);
23+
apiGet = sinon.stub(Api, 'get');
24+
});
25+
26+
afterEach(() => {
27+
sandbox.restore();
28+
apiGet.restore();
29+
});
30+
31+
describe('take', () => {
32+
it('should correctly return false: task type is not email group', () => {
33+
expect(emailGroup.take([taskMock])).to.false;
34+
});
35+
36+
it('should correctly return true: task type is email group', () => {
37+
expect(emailGroup.take([{ ...taskMock, type: TaskType.EMAIL_GROUP }])).to.true;
38+
});
39+
});
40+
41+
describe('sendEmailToRecipients', () => {
42+
it('should correctly send email to recipients', async () => {
43+
const executeTaskStub = sandbox.stub();
44+
45+
await emailGroup['sendEmailToRecipients'].call(
46+
{
47+
messageTemplate: { taskId: 'id' },
48+
messageRepository,
49+
recipientRepository,
50+
executeTask: executeTaskStub,
51+
},
52+
taskMock,
53+
);
54+
55+
expect(executeTaskStub).to.calledOnce;
56+
});
57+
});
58+
59+
describe('processTasks', () => {
60+
it('should correctly exit process: task template was not found', async () => {
61+
const sendEmailToRecipientsStub = sandbox.stub();
62+
63+
expect(
64+
await waitResult(
65+
emailGroup['processTasks'].call(
66+
{
67+
messageTemplate: { taskId: 'id' },
68+
manager: TypeormMock.entityManager,
69+
endEmailToRecipients: sendEmailToRecipientsStub,
70+
},
71+
taskMock,
72+
),
73+
),
74+
).to.throw('Task message template was not found.');
75+
});
76+
77+
it('should correctly process tasks', async () => {
78+
const sendEmailToRecipientsStub = sandbox.stub();
79+
const checkIsMessageTemplateValidStub = sandbox.stub().resolves(true);
80+
81+
await emailGroup['processTasks'].call(
82+
{
83+
messageTemplate: { taskId: 'id' },
84+
manager: TypeormMock.entityManager,
85+
sendEmailToRecipients: sendEmailToRecipientsStub,
86+
checkIsMessageTemplateValid: checkIsMessageTemplateValidStub,
87+
},
88+
{ ...taskMock, messages: [{ params: { isTemplate: true } }] },
89+
);
90+
91+
expect(sendEmailToRecipientsStub).to.calledOnce;
92+
expect(checkIsMessageTemplateValidStub).to.calledOnce;
93+
});
94+
});
95+
96+
describe('executeTask', () => {
97+
it('should throw error: users API error', async () => {
98+
apiGet.returns({
99+
users: {
100+
user: {
101+
list() {
102+
return { error: { message: 'Users API error.' } };
103+
},
104+
},
105+
},
106+
});
107+
108+
expect(
109+
await waitResult(
110+
emailGroup['executeTask']({} as AbstractEmailProvider, [{}] as Recipient[]),
111+
),
112+
).to.throw('Users API error.');
113+
});
114+
});
115+
});

microservices/notification/__tests__/services/task-handlers/notice-all-test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { taskMock } from '@__mocks__/task';
88
import TaskType from '@constants/task-type';
99
import NoticeAll from '@services/task-handlers/notice-all';
1010

11-
describe('services/task-handlers/abstract', () => {
11+
describe('services/task-handlers/notice-all', () => {
1212
const sandbox = sinon.createSandbox();
1313
let apiGet: sinon.SinonStub;
1414
let noticeAll: NoticeAll;
@@ -64,4 +64,37 @@ describe('services/task-handlers/abstract', () => {
6464
).to.throw('Task notice template was not found.');
6565
});
6666
});
67+
68+
describe('sendNoticeToAllUsers', () => {
69+
it('should correctly send notice to all users', async () => {
70+
const getTotalUsersCountStub = sandbox.stub();
71+
const executeNoticeAllTaskStub = sandbox.stub();
72+
73+
await noticeAll['sendNoticeToAllUsers'].call(
74+
{
75+
getTotalUsersCount: getTotalUsersCountStub,
76+
executeNoticeAllTask: executeNoticeAllTaskStub,
77+
currentPage: 1,
78+
},
79+
taskMock,
80+
);
81+
82+
expect(getTotalUsersCountStub).to.calledOnce;
83+
expect(getTotalUsersCountStub.args[0][0]).to.equal(undefined);
84+
expect(executeNoticeAllTaskStub).to.calledOnce;
85+
});
86+
87+
it('should throw error: failed to send notice to all users', async () => {
88+
const getTotalUsersCountStub = sandbox.stub().throws(new Error('Failed to get users count'));
89+
90+
expect(
91+
await waitResult(
92+
noticeAll['sendNoticeToAllUsers'].call(
93+
{ getTotalUsersCount: getTotalUsersCountStub },
94+
taskMock,
95+
),
96+
),
97+
).to.throw('Failed to get users count');
98+
});
99+
});
67100
});
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { TypeormMock } from '@lomray/microservice-helpers/mocks';
2+
import { waitResult } from '@lomray/microservice-helpers/test-helpers';
3+
import { expect } from 'chai';
4+
import sinon from 'sinon';
5+
import { noticeMock } from '@__mocks__/notice';
6+
import { taskMock } from '@__mocks__/task';
7+
import TaskType from '@constants/task-type';
8+
import Notice from '@entities/notice';
9+
import TaskEntity from '@entities/task';
10+
import Task from '@services/task';
11+
12+
describe('services/email-provider/factory', () => {
13+
const sandbox = sinon.createSandbox();
14+
15+
beforeEach(() => {
16+
TypeormMock.sandbox.reset();
17+
});
18+
19+
afterEach(() => {
20+
sandbox.restore();
21+
});
22+
23+
describe('handleAfterInsert', () => {
24+
it('should create and attach recipients', async () => {
25+
const handleAttachStub = sandbox.stub();
26+
27+
await Task.handleAfterInsert.call(
28+
{ handleAttach: handleAttachStub },
29+
{} as TaskEntity,
30+
TypeormMock.entityManager,
31+
);
32+
33+
expect(handleAttachStub).to.calledOnce;
34+
});
35+
});
36+
37+
describe('handleAttach', () => {
38+
it('should throw an error if task type is not expected', async () => {
39+
expect(
40+
await waitResult(
41+
// @ts-ignore
42+
Task['handleAttach']({ ...taskMock, type: 'unknown' }, TypeormMock.entityManager),
43+
),
44+
).to.throw('Unexpected task type.');
45+
});
46+
47+
it('should correctly handle notice all type', async () => {
48+
const createAndAttachNoticeTemplateStub = sandbox.stub();
49+
50+
await Task['handleAttach'].call(
51+
{ createAndAttachNoticeTemplate: createAndAttachNoticeTemplateStub },
52+
{
53+
...taskMock,
54+
type: TaskType.NOTICE_ALL,
55+
notices: [{ ...noticeMock, params: { isTemplate: true } } as Notice],
56+
},
57+
TypeormMock.entityManager,
58+
);
59+
60+
expect(createAndAttachNoticeTemplateStub).to.calledOnce;
61+
});
62+
});
63+
});

microservices/notification/src/services/task-handlers/abstract.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Api, Log } from '@lomray/microservice-helpers';
2+
import { JQOperator } from '@lomray/microservices-types';
23
import _ from 'lodash';
34
import { EntityManager, Repository } from 'typeorm';
45
import TaskStatus from '@constants/task-status';
@@ -118,9 +119,22 @@ abstract class Abstract {
118119

119120
/**
120121
* Get total users count
121-
*/
122-
protected async getTotalUsersCount(): Promise<number> {
123-
const { result: usersCountResult, error: usersCountError } = await Api.get().users.user.count();
122+
* @description Users without email - facebook users that does not give access for their email
123+
*/
124+
protected async getTotalUsersCount(excludeUsersWithoutEmail = false): Promise<number> {
125+
const { result: usersCountResult, error: usersCountError } = await Api.get().users.user.count(
126+
excludeUsersWithoutEmail
127+
? {
128+
query: {
129+
where: {
130+
email: {
131+
[JQOperator.isNotNULL]: null,
132+
},
133+
},
134+
},
135+
}
136+
: undefined,
137+
);
124138

125139
if (usersCountError) {
126140
// Internal error. Case where error target doesn't exist

microservices/notification/src/services/task-handlers/email-all.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Api, Log } from '@lomray/microservice-helpers';
22
import type IUser from '@lomray/microservices-client-api/interfaces/users/entities/user';
3+
import { JQOperator } from '@lomray/microservices-types';
34
import _ from 'lodash';
45
import { In, Repository } from 'typeorm';
56
import TaskMode from '@constants/task-mode';
@@ -65,7 +66,7 @@ class EmailAll extends Abstract {
6566
* @description Get all users count and execute task
6667
*/
6768
private async sendEmailToAllUsers(task: TaskEntity): Promise<void> {
68-
const usersCount = await this.getTotalUsersCount();
69+
const usersCount = await this.getTotalUsersCount(true);
6970

7071
try {
7172
await this.executeEmailAllTask(task, usersCount);
@@ -98,6 +99,11 @@ class EmailAll extends Abstract {
9899
page: this.currentPage,
99100
pageSize: this.chunkSize,
100101
orderBy: { createdAt: 'ASC' },
102+
where: {
103+
email: {
104+
[JQOperator.isNotNULL]: null,
105+
},
106+
},
101107
},
102108
});
103109

microservices/notification/src/services/task-handlers/email-group.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class EmailGroup extends Abstract {
3838
this.messageRepository = this.manager.getRepository(MessageEntity);
3939
this.recipientRepository = this.manager.getRepository(RecipientEntity);
4040

41-
const messageTemplate = task.messages.find(({ params }) => params.isTemplate);
41+
const messageTemplate = task.messages?.find(({ params }) => params.isTemplate);
4242

4343
if (!messageTemplate) {
4444
// Internal error
@@ -102,19 +102,21 @@ class EmailGroup extends Abstract {
102102
},
103103
});
104104

105-
if (usersListError) {
106-
Log.error(usersListError.message);
105+
if (usersListError || !usersListResult) {
106+
Log.error(usersListError?.message);
107107

108-
throw new Error(usersListError.message);
108+
throw new Error(usersListError?.message);
109109
}
110110

111-
if (usersListResult?.list.some(({ email }) => !email)) {
111+
const { list: users } = usersListResult;
112+
113+
if (users.some(({ email }) => !email)) {
112114
// Throw internal error
113115
throw new Error('Some users have no email.');
114116
}
115117

116118
await Promise.all(
117-
usersListResult!.list.map(({ email, id }) =>
119+
users.map(({ email, id }) =>
118120
sendService.send({
119121
html: this.messageTemplate.html as string,
120122
taskId: this.messageTemplate.taskId as string,

0 commit comments

Comments
 (0)