UserGetterTests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. const { ObjectId } = require('mongojs')
  2. const should = require('chai').should()
  3. const SandboxedModule = require('sandboxed-module')
  4. const assert = require('assert')
  5. const path = require('path')
  6. const sinon = require('sinon')
  7. const modulePath = path.join(
  8. __dirname,
  9. '../../../../app/src/Features/User/UserGetter'
  10. )
  11. const { expect } = require('chai')
  12. const Errors = require('../../../../app/src/Features/Errors/Errors')
  13. describe('UserGetter', function() {
  14. beforeEach(function() {
  15. this.fakeUser = {
  16. _id: '12390i',
  17. email: 'email2@foo.bar',
  18. emails: [
  19. {
  20. email: 'email1@foo.bar',
  21. reversedHostname: 'rab.oof',
  22. confirmedAt: new Date()
  23. },
  24. { email: 'email2@foo.bar', reversedHostname: 'rab.oof' }
  25. ]
  26. }
  27. this.findOne = sinon.stub().callsArgWith(2, null, this.fakeUser)
  28. this.find = sinon.stub().callsArgWith(2, null, [this.fakeUser])
  29. this.Mongo = {
  30. db: {
  31. users: {
  32. findOne: this.findOne,
  33. find: this.find
  34. }
  35. },
  36. ObjectId
  37. }
  38. const settings = { apis: { v1: { url: 'v1.url', user: '', pass: '' } } }
  39. this.getUserAffiliations = sinon.stub().callsArgWith(1, null, [])
  40. this.UserGetter = SandboxedModule.require(modulePath, {
  41. globals: {
  42. console: console
  43. },
  44. requires: {
  45. 'logger-sharelatex': {
  46. log() {}
  47. },
  48. '../../infrastructure/mongojs': this.Mongo,
  49. 'metrics-sharelatex': {
  50. timeAsyncMethod: sinon.stub()
  51. },
  52. 'settings-sharelatex': settings,
  53. '../Institutions/InstitutionsAPI': {
  54. getUserAffiliations: this.getUserAffiliations
  55. },
  56. '../../infrastructure/Features': {
  57. hasFeature: sinon.stub().returns(true)
  58. },
  59. '../Errors/Errors': Errors
  60. }
  61. })
  62. })
  63. describe('getUser', function() {
  64. it('should get user', function(done) {
  65. const query = { _id: 'foo' }
  66. const projection = { email: 1 }
  67. this.UserGetter.getUser(query, projection, (error, user) => {
  68. expect(error).to.not.exist
  69. this.findOne.called.should.equal(true)
  70. this.findOne.calledWith(query, projection).should.equal(true)
  71. user.should.deep.equal(this.fakeUser)
  72. done()
  73. })
  74. })
  75. it('should not allow null query', function(done) {
  76. this.UserGetter.getUser(null, {}, error => {
  77. error.should.exist
  78. error.message.should.equal('no query provided')
  79. done()
  80. })
  81. })
  82. })
  83. describe('getUsers', function() {
  84. it('should get users with array of userIds', function(done) {
  85. const query = [new ObjectId()]
  86. const projection = { email: 1 }
  87. this.UserGetter.getUsers(query, projection, (error, users) => {
  88. expect(error).to.not.exist
  89. this.find.should.have.been.calledWithMatch(
  90. { _id: { $in: query } },
  91. projection
  92. )
  93. users.should.deep.equal([this.fakeUser])
  94. done()
  95. })
  96. })
  97. it('should not allow null query', function(done) {
  98. this.UserGetter.getUser(null, {}, error => {
  99. error.should.exist
  100. error.message.should.equal('no query provided')
  101. done()
  102. })
  103. })
  104. })
  105. describe('getUserFullEmails', function() {
  106. it('should get user', function(done) {
  107. this.UserGetter.getUser = sinon
  108. .stub()
  109. .callsArgWith(2, null, this.fakeUser)
  110. const projection = { email: 1, emails: 1, samlIdentifiers: 1 }
  111. this.UserGetter.getUserFullEmails(
  112. this.fakeUser._id,
  113. (error, fullEmails) => {
  114. expect(error).to.not.exist
  115. this.UserGetter.getUser.called.should.equal(true)
  116. this.UserGetter.getUser
  117. .calledWith(this.fakeUser._id, projection)
  118. .should.equal(true)
  119. done()
  120. }
  121. )
  122. })
  123. it('should fetch emails data', function(done) {
  124. this.UserGetter.getUser = sinon
  125. .stub()
  126. .callsArgWith(2, null, this.fakeUser)
  127. this.UserGetter.getUserFullEmails(
  128. this.fakeUser._id,
  129. (error, fullEmails) => {
  130. expect(error).to.not.exist
  131. assert.deepEqual(fullEmails, [
  132. {
  133. email: 'email1@foo.bar',
  134. reversedHostname: 'rab.oof',
  135. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  136. emailHasInstitutionLicence: false,
  137. default: false
  138. },
  139. {
  140. email: 'email2@foo.bar',
  141. reversedHostname: 'rab.oof',
  142. emailHasInstitutionLicence: false,
  143. default: true
  144. }
  145. ])
  146. done()
  147. }
  148. )
  149. })
  150. it('should merge affiliation data', function(done) {
  151. this.UserGetter.getUser = sinon
  152. .stub()
  153. .callsArgWith(2, null, this.fakeUser)
  154. const affiliationsData = [
  155. {
  156. email: 'email1@foo.bar',
  157. role: 'Prof',
  158. department: 'Maths',
  159. inferred: false,
  160. licence: 'pro_plus',
  161. institution: {
  162. name: 'University Name',
  163. isUniversity: true,
  164. confirmed: true
  165. }
  166. }
  167. ]
  168. this.getUserAffiliations.callsArgWith(1, null, affiliationsData)
  169. this.UserGetter.getUserFullEmails(
  170. this.fakeUser._id,
  171. (error, fullEmails) => {
  172. expect(error).to.not.exist
  173. assert.deepEqual(fullEmails, [
  174. {
  175. email: 'email1@foo.bar',
  176. reversedHostname: 'rab.oof',
  177. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  178. default: false,
  179. emailHasInstitutionLicence: true,
  180. affiliation: {
  181. institution: affiliationsData[0].institution,
  182. inferred: affiliationsData[0].inferred,
  183. department: affiliationsData[0].department,
  184. role: affiliationsData[0].role,
  185. licence: affiliationsData[0].licence
  186. }
  187. },
  188. {
  189. email: 'email2@foo.bar',
  190. reversedHostname: 'rab.oof',
  191. emailHasInstitutionLicence: false,
  192. default: true
  193. }
  194. ])
  195. done()
  196. }
  197. )
  198. })
  199. it('should merge SAML identifier', function(done) {
  200. const fakeSamlIdentifiers = [
  201. { providerId: 'saml_id', exteranlUserId: 'whatever' }
  202. ]
  203. const fakeUserWithSaml = this.fakeUser
  204. fakeUserWithSaml.emails[0].samlProviderId = 'saml_id'
  205. fakeUserWithSaml.samlIdentifiers = fakeSamlIdentifiers
  206. this.UserGetter.getUser = sinon.stub().yields(null, this.fakeUser)
  207. this.getUserAffiliations.callsArgWith(1, null, [])
  208. this.UserGetter.getUserFullEmails(
  209. this.fakeUser._id,
  210. (error, fullEmails) => {
  211. expect(error).to.not.exist
  212. assert.deepEqual(fullEmails, [
  213. {
  214. email: 'email1@foo.bar',
  215. reversedHostname: 'rab.oof',
  216. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  217. default: false,
  218. emailHasInstitutionLicence: false,
  219. samlProviderId: 'saml_id',
  220. samlIdentifier: fakeSamlIdentifiers[0]
  221. },
  222. {
  223. email: 'email2@foo.bar',
  224. reversedHostname: 'rab.oof',
  225. emailHasInstitutionLicence: false,
  226. default: true
  227. }
  228. ])
  229. done()
  230. }
  231. )
  232. })
  233. it('should get user when it has no emails field', function(done) {
  234. this.fakeUser = {
  235. _id: '12390i',
  236. email: 'email2@foo.bar'
  237. }
  238. this.UserGetter.getUser = sinon
  239. .stub()
  240. .callsArgWith(2, null, this.fakeUser)
  241. const projection = { email: 1, emails: 1, samlIdentifiers: 1 }
  242. this.UserGetter.getUserFullEmails(
  243. this.fakeUser._id,
  244. (error, fullEmails) => {
  245. expect(error).to.not.exist
  246. this.UserGetter.getUser.called.should.equal(true)
  247. this.UserGetter.getUser
  248. .calledWith(this.fakeUser._id, projection)
  249. .should.equal(true)
  250. assert.deepEqual(fullEmails, [])
  251. done()
  252. }
  253. )
  254. })
  255. })
  256. describe('getUserbyMainEmail', function() {
  257. it('query user by main email', function(done) {
  258. const email = 'hello@world.com'
  259. const projection = { emails: 1 }
  260. this.UserGetter.getUserByMainEmail(email, projection, (error, user) => {
  261. expect(error).to.not.exist
  262. this.findOne.called.should.equal(true)
  263. this.findOne.calledWith({ email }, projection).should.equal(true)
  264. done()
  265. })
  266. })
  267. it('return user if found', function(done) {
  268. const email = 'hello@world.com'
  269. this.UserGetter.getUserByMainEmail(email, (error, user) => {
  270. expect(error).to.not.exist
  271. user.should.deep.equal(this.fakeUser)
  272. done()
  273. })
  274. })
  275. it('trim email', function(done) {
  276. const email = 'hello@world.com'
  277. this.UserGetter.getUserByMainEmail(` ${email} `, (error, user) => {
  278. expect(error).to.not.exist
  279. this.findOne.called.should.equal(true)
  280. this.findOne.calledWith({ email }).should.equal(true)
  281. done()
  282. })
  283. })
  284. })
  285. describe('getUserByAnyEmail', function() {
  286. it('query user for any email', function(done) {
  287. const email = 'hello@world.com'
  288. const expectedQuery = {
  289. emails: { $exists: true },
  290. 'emails.email': email
  291. }
  292. const projection = { emails: 1 }
  293. this.UserGetter.getUserByAnyEmail(
  294. ` ${email} `,
  295. projection,
  296. (error, user) => {
  297. expect(error).to.not.exist
  298. this.findOne.calledWith(expectedQuery, projection).should.equal(true)
  299. user.should.deep.equal(this.fakeUser)
  300. done()
  301. }
  302. )
  303. })
  304. it('query contains $exists:true so partial index is used', function(done) {
  305. const expectedQuery = {
  306. emails: { $exists: true },
  307. 'emails.email': ''
  308. }
  309. this.UserGetter.getUserByAnyEmail('', {}, (error, user) => {
  310. expect(error).to.not.exist
  311. this.findOne.calledWith(expectedQuery, {}).should.equal(true)
  312. done()
  313. })
  314. })
  315. it('checks main email as well', function(done) {
  316. this.findOne.callsArgWith(2, null, null)
  317. const email = 'hello@world.com'
  318. const projection = { emails: 1 }
  319. this.UserGetter.getUserByAnyEmail(
  320. ` ${email} `,
  321. projection,
  322. (error, user) => {
  323. expect(error).to.not.exist
  324. this.findOne.calledTwice.should.equal(true)
  325. this.findOne.calledWith({ email }, projection).should.equal(true)
  326. done()
  327. }
  328. )
  329. })
  330. })
  331. describe('getUsersByHostname', function() {
  332. it('should find user by hostname', function(done) {
  333. const hostname = 'bar.foo'
  334. const expectedQuery = {
  335. emails: { $exists: true },
  336. 'emails.reversedHostname': hostname
  337. .split('')
  338. .reverse()
  339. .join('')
  340. }
  341. const projection = { emails: 1 }
  342. this.UserGetter.getUsersByHostname(
  343. hostname,
  344. projection,
  345. (error, users) => {
  346. expect(error).to.not.exist
  347. this.find.calledOnce.should.equal(true)
  348. this.find.calledWith(expectedQuery, projection).should.equal(true)
  349. done()
  350. }
  351. )
  352. })
  353. })
  354. describe('getUsersByV1Id', function() {
  355. it('should find users by list of v1 ids', function(done) {
  356. const v1Ids = [501]
  357. const expectedQuery = {
  358. 'overleaf.id': { $in: v1Ids }
  359. }
  360. const projection = { emails: 1 }
  361. this.UserGetter.getUsersByV1Ids(v1Ids, projection, (error, users) => {
  362. expect(error).to.not.exist
  363. this.find.calledOnce.should.equal(true)
  364. this.find.calledWith(expectedQuery, projection).should.equal(true)
  365. done()
  366. })
  367. })
  368. })
  369. describe('ensureUniqueEmailAddress', function() {
  370. beforeEach(function() {
  371. this.UserGetter.getUserByAnyEmail = sinon.stub()
  372. })
  373. it('should return error if existing user is found', function(done) {
  374. this.UserGetter.getUserByAnyEmail.callsArgWith(1, null, this.fakeUser)
  375. this.UserGetter.ensureUniqueEmailAddress(this.newEmail, err => {
  376. should.exist(err)
  377. expect(err).to.be.an.instanceof(Errors.EmailExistsError)
  378. done()
  379. })
  380. })
  381. it('should return null if no user is found', function(done) {
  382. this.UserGetter.getUserByAnyEmail.callsArgWith(1)
  383. this.UserGetter.ensureUniqueEmailAddress(this.newEmail, err => {
  384. should.not.exist(err)
  385. done()
  386. })
  387. })
  388. })
  389. })