From a1cddcaf175bf32df6191cef1568f7112e0e7ef3 Mon Sep 17 00:00:00 2001 From: negue Date: Wed, 15 Dec 2021 02:16:50 +0100 Subject: [PATCH] Feature: new "report a bug" modal (#13530) * WIP: report a bug api/ui * fix lint * add USER_USERNAME * extend sendTxn tests / checks + fix bug report email * fix lint * add more checks to sendTxn - fix bug-report variables * fix lint / ci * fix test: reset email config url * fix test stub * fix tests * refactor the variables checks * lint. * move bug-report page as a modal * send user_email to the email * show true/false instead 1/0 * fix issues * fix footer report bug email if not logged in * fix styles/margins * prefill user's email * show facebook email if local email not existing * bugReportSuccessModal.vue * add BROWSER_UA to mail properties * extract bugReportLogic to its own lib file for unit test * test api validators * fix lint --- test/api/unit/libs/bug-report.test.js | 57 ++++ test/api/unit/libs/email.test.js | 91 +++++-- test/api/v4/POST-bug-report.test.js | 49 ++++ website/client/config/storybook/config.js | 12 + website/client/src/app.vue | 8 + .../client/src/assets/svg/check_circle.svg | 6 + website/client/src/components/appFooter.vue | 12 +- .../components/bug-report-modal.stories.js | 43 ++++ .../client/src/components/bugReportModal.vue | 243 ++++++++++++++++++ .../src/components/bugReportSuccessModal.vue | 154 +++++++++++ .../client/src/components/groups/group.vue | 3 +- .../src/components/groups/membersModal.vue | 3 +- .../client/src/components/groups/tavern.vue | 2 +- website/client/src/components/header/menu.vue | 2 +- .../notifications/newPrivateMessage.vue | 3 +- .../src/components/header/userDropdown.vue | 3 +- .../src/components/shared/closeIcon.vue | 15 +- .../client/src/components/static/contact.vue | 6 +- website/client/src/libs/consts.js | 8 + website/client/src/mixins/reportBug.js | 33 +-- website/client/src/router/index.js | 3 +- website/common/locales/en/generic.json | 13 +- .../server/controllers/api-v4/bug-report.js | 53 ++++ website/server/libs/bug-report.js | 39 +++ website/server/libs/email.js | 36 ++- 25 files changed, 833 insertions(+), 64 deletions(-) create mode 100644 test/api/unit/libs/bug-report.test.js create mode 100644 test/api/v4/POST-bug-report.test.js create mode 100644 website/client/src/assets/svg/check_circle.svg create mode 100644 website/client/src/components/bug-report-modal.stories.js create mode 100644 website/client/src/components/bugReportModal.vue create mode 100644 website/client/src/components/bugReportSuccessModal.vue create mode 100644 website/client/src/libs/consts.js create mode 100644 website/server/controllers/api-v4/bug-report.js create mode 100644 website/server/libs/bug-report.js diff --git a/test/api/unit/libs/bug-report.test.js b/test/api/unit/libs/bug-report.test.js new file mode 100644 index 0000000000..feac88ef32 --- /dev/null +++ b/test/api/unit/libs/bug-report.test.js @@ -0,0 +1,57 @@ +/* eslint-disable global-require */ +import nconf from 'nconf'; +import { generateUser } from '../../../helpers/api-unit.helper'; +import * as emailLib from '../../../../website/server/libs/email'; +import { bugReportLogic } from '../../../../website/server/libs/bug-report'; + +describe('bug-report', () => { + beforeEach(() => { + sandbox.stub(emailLib, 'sendTxn').returns(Promise.resolve()); + + const nconfGetStub = sandbox.stub(nconf, 'get'); + nconfGetStub.withArgs('ADMIN_EMAIL').returns('true'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('sends a mail using sendTxn', async () => { + const userId = '2b58daeb-bc50-4a83-b5d3-4ac52c7c0608'; + const userMail = 'me@me.com'; + const userMessage = 'The power is over 9000, please fix it'; + const userAgent = 'The UserAgent with a bunch of weird browser engine levels'; + + const user = generateUser({ + _id: userId, + }); + + const result = await bugReportLogic( + user, userMail, userMessage, userAgent, + ); + + expect(emailLib.sendTxn).to.be.called; + expect(result).to.deep.equal({ + sendMailResult: undefined, + emailData: { + BROWSER_UA: userAgent, + REPORT_MSG: userMessage, + USER_CLASS: 'warrior', + USER_CONSECUTIVE_MONTHS: 0, + USER_COSTUME: 'false', + USER_CUSTOMER_ID: undefined, + USER_CUSTOM_DAY: 0, + USER_DAILIES_PAUSED: 'false', + USER_EMAIL: userMail, + USER_HOURGLASSES: 0, + USER_ID: userId, + USER_LEVEL: 1, + USER_OFFSET_MONTHS: 0, + USER_PAYMENT_PLATFORM: undefined, + USER_SUBSCRIPTION: undefined, + USER_TIMEZONE_OFFSET: 0, + USER_USERNAME: undefined, + }, + }); + }); +}); diff --git a/test/api/unit/libs/email.test.js b/test/api/unit/libs/email.test.js index 7d0df88350..b3fe180086 100644 --- a/test/api/unit/libs/email.test.js +++ b/test/api/unit/libs/email.test.js @@ -148,9 +148,18 @@ describe('emails', () => { }); }); - describe('sendTxnEmail', () => { + describe('sendTxn', () => { + let sendTxn = null; + beforeEach(() => { sandbox.stub(got, 'post').returns(defer().promise); + + const nconfGetStub = sandbox.stub(nconf, 'get'); + nconfGetStub.withArgs('IS_PROD').returns(true); + nconfGetStub.withArgs('BASE_URL').returns('BASE_URL'); + + const attachEmail = requireAgain(pathToEmailLib); + sendTxn = attachEmail.sendTxn; }); afterEach(() => { @@ -158,16 +167,14 @@ describe('emails', () => { }); it('can send a txn email to one recipient', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - const attachEmail = requireAgain(pathToEmailLib); - const sendTxnEmail = attachEmail.sendTxn; const emailType = 'an email type'; const mailingInfo = { name: 'my name', email: 'my@email', }; - sendTxnEmail(mailingInfo, emailType); + sendTxn(mailingInfo, emailType); + expect(got.post).to.be.called; expect(got.post).to.be.calledWith('undefined/job', sinon.match({ json: { data: { @@ -179,27 +186,77 @@ describe('emails', () => { }); it('does not send email if address is missing', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - const attachEmail = requireAgain(pathToEmailLib); - const sendTxnEmail = attachEmail.sendTxn; const emailType = 'an email type'; const mailingInfo = { name: 'my name', // email: 'my@email', }; - sendTxnEmail(mailingInfo, emailType); + sendTxn(mailingInfo, emailType); expect(got.post).not.to.be.called; }); + it('throws error when mail target is only a string', () => { + const emailType = 'an email type'; + const mailingInfo = 'my email'; + + expect(sendTxn(mailingInfo, emailType)).to.throw; + }); + + it('throws error when mail target has no _id or email', () => { + const emailType = 'an email type'; + const mailingInfo = { + + }; + + expect(sendTxn(mailingInfo, emailType)).to.throw; + }); + + it('throws error when variables not an array', () => { + const emailType = 'an email type'; + const mailingInfo = { + name: 'my name', + email: 'my@email', + }; + const variables = {}; + + expect(sendTxn(mailingInfo, emailType, variables)).to.throw; + }); + it('throws error when variables array not contain name/content', () => { + const emailType = 'an email type'; + const mailingInfo = { + name: 'my name', + email: 'my@email', + }; + const variables = [ + { + + }, + ]; + + expect(sendTxn(mailingInfo, emailType, variables)).to.throw; + }); + it('throws no error when variables array contain name but no content', () => { + const emailType = 'an email type'; + const mailingInfo = { + name: 'my name', + email: 'my@email', + }; + const variables = [ + { + name: 'MY_VAR', + }, + ]; + + expect(sendTxn(mailingInfo, emailType, variables)).to.not.throw; + }); + it('uses getUserInfo in case of user data', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - const attachEmail = requireAgain(pathToEmailLib); - const sendTxnEmail = attachEmail.sendTxn; const emailType = 'an email type'; const mailingInfo = getUser(); - sendTxnEmail(mailingInfo, emailType); + sendTxn(mailingInfo, emailType); + expect(got.post).to.be.called; expect(got.post).to.be.calledWith('undefined/job', sinon.match({ json: { data: { @@ -211,17 +268,15 @@ describe('emails', () => { }); it('sends email with some default variables', () => { - sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true); - const attachEmail = requireAgain(pathToEmailLib); - const sendTxnEmail = attachEmail.sendTxn; const emailType = 'an email type'; const mailingInfo = { name: 'my name', email: 'my@email', }; - const variables = [1, 2, 3]; + const variables = []; - sendTxnEmail(mailingInfo, emailType, variables); + sendTxn(mailingInfo, emailType, variables); + expect(got.post).to.be.called; expect(got.post).to.be.calledWith('undefined/job', sinon.match({ json: { data: { diff --git a/test/api/v4/POST-bug-report.test.js b/test/api/v4/POST-bug-report.test.js new file mode 100644 index 0000000000..a963519415 --- /dev/null +++ b/test/api/v4/POST-bug-report.test.js @@ -0,0 +1,49 @@ +import { + generateUser, +} from '../../helpers/api-integration/v4'; + +describe('POST /bug-report', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('returns an error when message is not added', async () => { + await expect(user.post('/bug-report', { + message: '', + })) + .to.eventually.be.rejected.and.to.eql({ + code: 400, + error: 'BadRequest', + // seems it is not possible to get the real error message + message: 'Invalid request parameters.', + }); + }); + + it('returns an error when email is not added', async () => { + await expect(user.post('/bug-report', { + message: 'message', + email: '', + })) + .to.eventually.be.rejected.and.to.eql({ + code: 400, + error: 'BadRequest', + // seems it is not possible to get the real error message + message: 'Invalid request parameters.', + }); + }); + + it('returns an error when email is not valid', async () => { + await expect(user.post('/bug-report', { + message: 'message', + email: 'notamail', + })) + .to.eventually.be.rejected.and.to.eql({ + code: 400, + error: 'BadRequest', + // seems it is not possible to get the real error message + message: 'Invalid request parameters.', + }); + }); +}); diff --git a/website/client/config/storybook/config.js b/website/client/config/storybook/config.js index f1bc33bc7a..bc282bd5d7 100644 --- a/website/client/config/storybook/config.js +++ b/website/client/config/storybook/config.js @@ -40,6 +40,18 @@ store.state.user.data = { preferences: { }, + auth: { + local: { + // email: 'example@example.com', + }, + facebook: { + emails: [ + { + value: 'test@test.de', + }, + ], + }, + }, }; Vue.prototype.$store = store; diff --git a/website/client/src/app.vue b/website/client/src/app.vue index d6174f9ac2..4eb742c008 100644 --- a/website/client/src/app.vue +++ b/website/client/src/app.vue @@ -33,6 +33,8 @@ + +