From 897cef38d7941c22fa2ce6f10b7949663b81bf3e Mon Sep 17 00:00:00 2001 From: gabriellsh Date: Thu, 31 Oct 2024 15:54:40 -0300 Subject: [PATCH] Add unit tests --- .../routes/avatar/middlewares/auth.spec.ts | 55 +++++-- apps/meteor/server/routes/avatar/user.spec.ts | 138 +++++++++++++++++- 2 files changed, 183 insertions(+), 10 deletions(-) diff --git a/apps/meteor/server/routes/avatar/middlewares/auth.spec.ts b/apps/meteor/server/routes/avatar/middlewares/auth.spec.ts index cffa82eabd0b..05292fb16c48 100644 --- a/apps/meteor/server/routes/avatar/middlewares/auth.spec.ts +++ b/apps/meteor/server/routes/avatar/middlewares/auth.spec.ts @@ -10,7 +10,7 @@ const mocks = { }, }; -const { protectAvatarsWithFallback } = proxyquire.noCallThru().load('./auth.ts', { +const { protectAvatarsWithFallback, protectAvatars } = proxyquire.noCallThru().load('./auth.ts', { '../utils': mocks.utils, }); @@ -54,10 +54,10 @@ describe('#protectAvatarsWithFallback()', () => { expect(response.end.calledOnce).to.be.true; }); - it(`should write 200 to head and write fallback to body (user avatar)`, async () => { + it(`should write 200 to head and write fallback to body (room avatar)`, async () => { mocks.utils.renderSVGLetters.returns('fallback'); - await protectAvatarsWithFallback({ url: '/jon' }, response, next); + await protectAvatarsWithFallback({ url: '/room/jon' }, response, next); expect(next.called).to.be.false; expect(response.setHeader.called).to.be.false; expect(response.writeHead.calledWith(200, { 'Content-Type': 'image/svg+xml' })).to.be.true; @@ -65,14 +65,51 @@ describe('#protectAvatarsWithFallback()', () => { expect(response.end.calledOnce).to.be.true; }); - it(`should write 200 to head and write fallback to body (room avatar)`, async () => { - mocks.utils.renderSVGLetters.returns('fallback'); + it(`should call next if user can access avatar`, async () => { + mocks.utils.userCanAccessAvatar.returns(true); + const request = { url: '/jon' }; + + await protectAvatarsWithFallback(request, response, next); + expect(mocks.utils.userCanAccessAvatar.calledWith(request)).to.be.true; + expect(next.called).to.be.true; + }); +}); + +describe('#protectAvatars()', () => { + const response = { + setHeader: sinon.spy(), + writeHead: sinon.spy(), + write: sinon.spy(), + end: sinon.spy(), + }; + const next = sinon.spy(); + + afterEach(() => { + response.setHeader.resetHistory(); + response.writeHead.resetHistory(); + response.end.resetHistory(); + next.resetHistory(); + + Object.values(mocks.utils).forEach((mock) => mock.reset()); + }); + + it(`should write 404 to head if no url provided`, async () => { + await protectAvatars({}, response, next); - await protectAvatarsWithFallback({ url: '/room/jon' }, response, next); expect(next.called).to.be.false; expect(response.setHeader.called).to.be.false; - expect(response.writeHead.calledWith(200, { 'Content-Type': 'image/svg+xml' })).to.be.true; - expect(response.write.calledWith('fallback')).to.be.true; + expect(response.writeHead.calledWith(404)).to.be.true; + expect(response.end.calledOnce).to.be.true; + }); + + it(`should write 404 to head if access is denied`, async () => { + mocks.utils.userCanAccessAvatar.returns(false); + + await protectAvatars({ url: '/room/jon' }, response, next); + + expect(next.called).to.be.false; + expect(response.setHeader.called).to.be.false; + expect(response.writeHead.calledWith(404)).to.be.true; expect(response.end.calledOnce).to.be.true; }); @@ -80,7 +117,7 @@ describe('#protectAvatarsWithFallback()', () => { mocks.utils.userCanAccessAvatar.returns(true); const request = { url: '/jon' }; - await protectAvatarsWithFallback(request, response, next); + await protectAvatars(request, response, next); expect(mocks.utils.userCanAccessAvatar.calledWith(request)).to.be.true; expect(next.called).to.be.true; }); diff --git a/apps/meteor/server/routes/avatar/user.spec.ts b/apps/meteor/server/routes/avatar/user.spec.ts index d34948cd3964..e26a4f08b0a8 100644 --- a/apps/meteor/server/routes/avatar/user.spec.ts +++ b/apps/meteor/server/routes/avatar/user.spec.ts @@ -7,6 +7,7 @@ import sinon from 'sinon'; const mocks = { settingsGet: sinon.stub(), findOneByUsernameIgnoringCase: sinon.stub(), + findOneById: sinon.stub(), utils: { serveSvgAvatarInRequestedFormat: sinon.spy(), wasFallbackModified: sinon.stub(), @@ -15,15 +16,18 @@ const mocks = { }, serverFetch: sinon.stub(), avatarFindOneByName: sinon.stub(), + avatarFindOneByUserId: sinon.stub(), }; -const { userAvatarByUsername } = proxyquire.noCallThru().load('./user', { +const { userAvatarById, userAvatarByUsername } = proxyquire.noCallThru().load('./user', { '@rocket.chat/models': { Users: { findOneByUsernameIgnoringCase: mocks.findOneByUsernameIgnoringCase, + findOneById: mocks.findOneById, }, Avatars: { findOneByName: mocks.avatarFindOneByName, + findOneByUserId: mocks.avatarFindOneByUserId, }, }, '../../../app/settings/server': { @@ -37,6 +41,138 @@ const { userAvatarByUsername } = proxyquire.noCallThru().load('./user', { }, }); +describe('#userAvatarById()', () => { + const response = { + setHeader: sinon.spy(), + writeHead: sinon.spy(), + end: sinon.spy(), + }; + const next = sinon.spy(); + + afterEach(() => { + mocks.settingsGet.reset(); + mocks.avatarFindOneByUserId.reset(); + + response.setHeader.resetHistory(); + response.writeHead.resetHistory(); + response.end.resetHistory(); + next.resetHistory(); + + Object.values(mocks.utils).forEach((mock) => ('reset' in mock ? mock.reset() : mock.resetHistory())); + }); + + it(`should do nothing if url is not in request object`, async () => { + await userAvatarById({}, response, next); + expect(next.called).to.be.false; + expect(response.setHeader.called).to.be.false; + expect(response.writeHead.called).to.be.false; + expect(response.end.called).to.be.false; + }); + + it(`should write 404 if Id is not provided`, async () => { + await userAvatarById({ url: '/' }, response, next); + expect(next.called).to.be.false; + expect(response.setHeader.called).to.be.false; + expect(response.writeHead.calledWith(404)).to.be.true; + expect(response.end.calledOnce).to.be.true; + }); + + it(`should call external provider`, async () => { + const userId = 'xvf5Tr34'; + const request = { url: '/' + userId }; + + const pipe = sinon.spy(); + const mockResponseHeaders = new Headers(); + mockResponseHeaders.set('header1', 'true'); + mockResponseHeaders.set('header2', 'false'); + + mocks.serverFetch.returns({ + headers: mockResponseHeaders, + body: { pipe }, + }); + + mocks.settingsGet.returns('test123/{username}'); + + mocks.findOneById.returns({ username: 'jon' }); + + await userAvatarById(request, response, next); + + expect(mocks.utils.setCacheAndDispositionHeaders.calledWith(request, response)).to.be.true; + expect(mocks.findOneById.calledWith(userId)).to.be.true; + expect(mocks.serverFetch.calledWith('test123/jon')).to.be.true; + expect(response.setHeader.calledTwice).to.be.true; + expect(response.setHeader.getCall(0).calledWith('header1', 'true')).to.be.true; + expect(response.setHeader.getCall(1).calledWith('header2', 'false')).to.be.true; + expect(pipe.calledWith(response)).to.be.true; + }); + + it(`should serve avatar file if found`, async () => { + const request = { url: '/jon' }; + + const file = { uploadedAt: new Date(0), type: 'image/png', size: 100 }; + mocks.avatarFindOneByUserId.returns(file); + + await userAvatarById(request, response, next); + + expect(mocks.utils.setCacheAndDispositionHeaders.calledWith(request, response)).to.be.true; + expect(mocks.utils.serveAvatarFile.calledWith(file, request, response, next)).to.be.true; + }); + + it(`should write 304 to head if content is not modified`, async () => { + const request = { url: '/xyzabc', headers: {} }; + + mocks.utils.wasFallbackModified.returns(false); + + await userAvatarById(request, response, next); + + expect(mocks.utils.setCacheAndDispositionHeaders.calledWith(request, response)).to.be.true; + expect(response.writeHead.calledWith(304)).to.be.true; + expect(response.end.calledOnce).to.be.true; + }); + + it(`should write 404 if username is not found`, async () => { + mocks.utils.wasFallbackModified.returns(true); + mocks.findOneById.returns(null); + + const userId = 'awdasdaw'; + const request = { url: '/' + userId, headers: {} }; + + await userAvatarById(request, response, next); + expect(mocks.utils.setCacheAndDispositionHeaders.calledWith(request, response)).to.be.true; + + expect(response.writeHead.calledWith(404)).to.be.true; + expect(response.end.calledOnce).to.be.true; + }); + + it(`should fallback to SVG if no avatar found`, async () => { + const userId = '2apso9283'; + const request = { url: '/' + userId, headers: {} }; + + mocks.findOneById.returns({ username: 'jon' }); + mocks.utils.wasFallbackModified.returns(true); + + await userAvatarById(request, response, next); + + expect(mocks.findOneById.calledWith(userId)).to.be.true; + expect(mocks.utils.setCacheAndDispositionHeaders.calledWith(request, response)).to.be.true; + expect(mocks.utils.serveSvgAvatarInRequestedFormat.calledWith({ nameOrUsername: 'jon', req: request, res: response })).to.be.true; + }); + + it(`should fallback to SVG with user name if UI_Use_Name_Avatar is true`, async () => { + const userId = '2apso9283'; + const request = { url: '/' + userId, headers: {} }; + + mocks.findOneById.returns({ username: 'jon', name: 'Doe' }); + mocks.utils.wasFallbackModified.returns(true); + mocks.settingsGet.withArgs('UI_Use_Name_Avatar').returns(true); + + await userAvatarById(request, response, next); + + expect(mocks.utils.setCacheAndDispositionHeaders.calledWith(request, response)).to.be.true; + expect(mocks.utils.serveSvgAvatarInRequestedFormat.calledWith({ nameOrUsername: 'Doe', req: request, res: response })).to.be.true; + }); +}); + describe('#userAvatarByUsername()', () => { const response = { setHeader: sinon.spy(),