UserGetterTests.js 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434
  1. const { ObjectId } = require('mongodb-legacy')
  2. const SandboxedModule = require('sandboxed-module')
  3. const assert = require('assert')
  4. const moment = require('moment')
  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. const {
  14. normalizeQuery,
  15. normalizeMultiQuery,
  16. } = require('../../../../app/src/Features/Helpers/Mongo')
  17. describe('UserGetter', function () {
  18. beforeEach(function () {
  19. const confirmedAt = new Date()
  20. this.fakeUser = {
  21. _id: new ObjectId(),
  22. email: 'email2@foo.bar',
  23. emails: [
  24. {
  25. email: 'email1@foo.bar',
  26. reversedHostname: 'rab.oof',
  27. confirmedAt,
  28. lastConfirmedAt: confirmedAt,
  29. },
  30. { email: 'email2@foo.bar', reversedHostname: 'rab.oof' },
  31. ],
  32. }
  33. this.findOne = sinon.stub().callsArgWith(2, null, this.fakeUser)
  34. this.findToArrayStub = sinon.stub().yields(null, [this.fakeUser])
  35. this.find = sinon.stub().returns({ toArray: this.findToArrayStub })
  36. this.Mongo = {
  37. db: {
  38. users: {
  39. findOne: this.findOne,
  40. find: this.find,
  41. },
  42. },
  43. ObjectId,
  44. }
  45. this.getUserAffiliations = sinon.stub().resolves([])
  46. this.Modules = {
  47. promises: { hooks: { fire: sinon.stub().resolves() } },
  48. }
  49. this.AsyncLocalStorage = {
  50. storage: {
  51. getStore: sinon.stub().returns(undefined),
  52. },
  53. }
  54. this.UserGetter = SandboxedModule.require(modulePath, {
  55. requires: {
  56. '../Helpers/Mongo': { normalizeQuery, normalizeMultiQuery },
  57. '../../infrastructure/mongodb': this.Mongo,
  58. '@overleaf/settings': (this.settings = {
  59. reconfirmNotificationDays: 14,
  60. }),
  61. '../Institutions/InstitutionsAPI': {
  62. promises: {
  63. getUserAffiliations: this.getUserAffiliations,
  64. },
  65. },
  66. '../../infrastructure/Features': {
  67. hasFeature: sinon.stub().returns(true),
  68. },
  69. '../../models/User': {
  70. User: (this.User = {}),
  71. },
  72. '../../infrastructure/Modules': this.Modules,
  73. '../../infrastructure/AsyncLocalStorage': this.AsyncLocalStorage,
  74. },
  75. })
  76. })
  77. describe('getSsoUsersAtInstitution', function () {
  78. it('should throw an error when no projection is passed', function (done) {
  79. this.UserGetter.getSsoUsersAtInstitution(1, undefined, error => {
  80. expect(error).to.exist
  81. expect(error.message).to.equal('missing projection')
  82. done()
  83. })
  84. })
  85. })
  86. describe('getUser', function () {
  87. it('should get user', function (done) {
  88. const query = { _id: '000000000000000000000000' }
  89. const projection = { email: 1 }
  90. this.UserGetter.getUser(query, projection, (error, user) => {
  91. expect(error).to.not.exist
  92. this.findOne.called.should.equal(true)
  93. this.findOne.calledWith(query, { projection }).should.equal(true)
  94. user.should.deep.equal(this.fakeUser)
  95. done()
  96. })
  97. })
  98. it('should not allow null query', function (done) {
  99. this.UserGetter.getUser(null, {}, error => {
  100. error.should.exist
  101. error.message.should.equal('no query provided')
  102. done()
  103. })
  104. })
  105. })
  106. describe('getUsers', function () {
  107. it('should get users with array of userIds', function (done) {
  108. const query = [new ObjectId()]
  109. const projection = { email: 1 }
  110. this.UserGetter.getUsers(query, projection, (error, users) => {
  111. expect(error).to.not.exist
  112. this.find.should.have.been.calledWithMatch(
  113. { _id: { $in: query } },
  114. { projection }
  115. )
  116. users.should.deep.equal([this.fakeUser])
  117. done()
  118. })
  119. })
  120. it('should not call mongo with empty list', function (done) {
  121. const query = []
  122. const projection = { email: 1 }
  123. this.UserGetter.getUsers(query, projection, (error, users) => {
  124. expect(error).to.not.exist
  125. expect(users).to.deep.equal([])
  126. expect(this.find).to.not.have.been.called
  127. done()
  128. })
  129. })
  130. it('should not allow null query', function (done) {
  131. this.UserGetter.getUser(null, {}, error => {
  132. error.should.exist
  133. error.message.should.equal('no query provided')
  134. done()
  135. })
  136. })
  137. })
  138. describe('getUserFullEmails', function () {
  139. it('should get user', function (done) {
  140. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  141. const projection = { email: 1, emails: 1, samlIdentifiers: 1 }
  142. this.UserGetter.getUserFullEmails(
  143. this.fakeUser._id,
  144. (error, fullEmails) => {
  145. expect(error).to.not.exist
  146. this.UserGetter.promises.getUser.called.should.equal(true)
  147. this.UserGetter.promises.getUser
  148. .calledWith(this.fakeUser._id, projection)
  149. .should.equal(true)
  150. done()
  151. }
  152. )
  153. })
  154. it('should fetch emails data', function (done) {
  155. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  156. this.UserGetter.getUserFullEmails(
  157. this.fakeUser._id,
  158. (error, fullEmails) => {
  159. expect(error).to.not.exist
  160. assert.deepEqual(fullEmails, [
  161. {
  162. email: 'email1@foo.bar',
  163. reversedHostname: 'rab.oof',
  164. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  165. lastConfirmedAt: this.fakeUser.emails[0].lastConfirmedAt,
  166. emailHasInstitutionLicence: false,
  167. default: false,
  168. },
  169. {
  170. email: 'email2@foo.bar',
  171. reversedHostname: 'rab.oof',
  172. emailHasInstitutionLicence: false,
  173. default: true,
  174. lastConfirmedAt: null,
  175. },
  176. ])
  177. done()
  178. }
  179. )
  180. })
  181. it('should merge affiliation data', function (done) {
  182. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  183. const affiliationsData = [
  184. {
  185. email: 'email1@foo.bar',
  186. role: 'Prof',
  187. cached_confirmed_at: '2019-07-11T18:25:01.639Z',
  188. cached_reconfirmed_at: '2021-07-11T18:25:01.639Z',
  189. department: 'Maths',
  190. entitlement: false,
  191. inferred: false,
  192. licence: 'pro_plus',
  193. institution: {
  194. name: 'University Name',
  195. isUniversity: true,
  196. confirmed: true,
  197. },
  198. last_day_to_reconfirm: undefined,
  199. past_reconfirm_date: false,
  200. portal: undefined,
  201. },
  202. ]
  203. this.getUserAffiliations.resolves(affiliationsData)
  204. this.UserGetter.getUserFullEmails(
  205. this.fakeUser._id,
  206. (error, fullEmails) => {
  207. expect(error).to.not.exist
  208. assert.deepEqual(fullEmails, [
  209. {
  210. email: 'email1@foo.bar',
  211. reversedHostname: 'rab.oof',
  212. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  213. lastConfirmedAt: this.fakeUser.emails[0].lastConfirmedAt,
  214. default: false,
  215. emailHasInstitutionLicence: true,
  216. affiliation: {
  217. institution: affiliationsData[0].institution,
  218. inferred: affiliationsData[0].inferred,
  219. department: affiliationsData[0].department,
  220. role: affiliationsData[0].role,
  221. lastDayToReconfirm: undefined,
  222. licence: affiliationsData[0].licence,
  223. inReconfirmNotificationPeriod: false,
  224. cachedConfirmedAt: '2019-07-11T18:25:01.639Z',
  225. cachedReconfirmedAt: '2021-07-11T18:25:01.639Z',
  226. cachedEntitlement: false,
  227. cachedLastDayToReconfirm: undefined,
  228. cachedPastReconfirmDate: false,
  229. pastReconfirmDate: false,
  230. portal: undefined,
  231. },
  232. },
  233. {
  234. email: 'email2@foo.bar',
  235. reversedHostname: 'rab.oof',
  236. emailHasInstitutionLicence: false,
  237. default: true,
  238. lastConfirmedAt: null,
  239. },
  240. ])
  241. done()
  242. }
  243. )
  244. })
  245. it('should merge SAML identifier', function (done) {
  246. const fakeSamlIdentifiers = [
  247. { providerId: 'saml_id', exteranlUserId: 'whatever' },
  248. ]
  249. const fakeUserWithSaml = this.fakeUser
  250. fakeUserWithSaml.emails[0].samlProviderId = 'saml_id'
  251. fakeUserWithSaml.samlIdentifiers = fakeSamlIdentifiers
  252. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  253. this.getUserAffiliations.resolves([])
  254. this.UserGetter.getUserFullEmails(
  255. this.fakeUser._id,
  256. (error, fullEmails) => {
  257. expect(error).to.not.exist
  258. assert.deepEqual(fullEmails, [
  259. {
  260. email: 'email1@foo.bar',
  261. reversedHostname: 'rab.oof',
  262. confirmedAt: this.fakeUser.emails[0].confirmedAt,
  263. lastConfirmedAt: this.fakeUser.emails[0].lastConfirmedAt,
  264. default: false,
  265. emailHasInstitutionLicence: false,
  266. samlProviderId: 'saml_id',
  267. samlIdentifier: fakeSamlIdentifiers[0],
  268. },
  269. {
  270. email: 'email2@foo.bar',
  271. reversedHostname: 'rab.oof',
  272. emailHasInstitutionLicence: false,
  273. default: true,
  274. lastConfirmedAt: null,
  275. },
  276. ])
  277. done()
  278. }
  279. )
  280. })
  281. it('should get user when it has no emails field', function (done) {
  282. this.fakeUserNoEmails = {
  283. _id: '12390i',
  284. email: 'email2@foo.bar',
  285. }
  286. this.UserGetter.promises.getUser = sinon
  287. .stub()
  288. .resolves(this.fakeUserNoEmails)
  289. const projection = { email: 1, emails: 1, samlIdentifiers: 1 }
  290. this.UserGetter.getUserFullEmails(
  291. this.fakeUserNoEmails._id,
  292. (error, fullEmails) => {
  293. expect(error).to.not.exist
  294. this.UserGetter.promises.getUser.called.should.equal(true)
  295. this.UserGetter.promises.getUser
  296. .calledWith(this.fakeUserNoEmails._id, projection)
  297. .should.equal(true)
  298. assert.deepEqual(fullEmails, [])
  299. done()
  300. }
  301. )
  302. })
  303. describe('affiliation reconfirmation', function () {
  304. const institutionNonSSO = {
  305. id: 1,
  306. name: 'University Name',
  307. commonsAccount: true,
  308. isUniversity: true,
  309. confirmed: true,
  310. ssoBeta: false,
  311. ssoEnabled: false,
  312. maxConfirmationMonths: 12,
  313. }
  314. const institutionSSO = {
  315. id: 2,
  316. name: 'SSO University Name',
  317. isUniversity: true,
  318. confirmed: true,
  319. ssoBeta: false,
  320. ssoEnabled: true,
  321. maxConfirmationMonths: 12,
  322. }
  323. describe('non-SSO institutions', function () {
  324. const email1 = 'leonard@example-affiliation.com'
  325. const email2 = 'mccoy@example-affiliation.com'
  326. const affiliationsData = [
  327. {
  328. email: email1,
  329. role: 'Prof',
  330. department: 'Medicine',
  331. inferred: false,
  332. licence: 'pro_plus',
  333. institution: institutionNonSSO,
  334. },
  335. {
  336. email: email2,
  337. role: 'Prof',
  338. department: 'Medicine',
  339. inferred: false,
  340. licence: 'pro_plus',
  341. institution: institutionNonSSO,
  342. },
  343. ]
  344. it('should flag inReconfirmNotificationPeriod for all affiliations in period', function (done) {
  345. const { maxConfirmationMonths } = institutionNonSSO
  346. const confirmed1 = moment()
  347. .subtract(maxConfirmationMonths + 2, 'months')
  348. .toDate()
  349. const lastDayToReconfirm1 = moment(confirmed1)
  350. .add(maxConfirmationMonths, 'months')
  351. .toDate()
  352. const confirmed2 = moment()
  353. .subtract(maxConfirmationMonths + 1, 'months')
  354. .toDate()
  355. const lastDayToReconfirm2 = moment(confirmed2)
  356. .add(maxConfirmationMonths, 'months')
  357. .toDate()
  358. const user = {
  359. _id: '12390i',
  360. email: email1,
  361. emails: [
  362. {
  363. email: email1,
  364. reversedHostname: 'moc.noitailiffa-elpmaxe',
  365. confirmedAt: confirmed1,
  366. default: true,
  367. },
  368. {
  369. email: email2,
  370. reversedHostname: 'moc.noitailiffa-elpmaxe',
  371. confirmedAt: confirmed2,
  372. },
  373. ],
  374. }
  375. const affiliations = [...affiliationsData]
  376. affiliations[0].last_day_to_reconfirm = lastDayToReconfirm1
  377. affiliations[1].last_day_to_reconfirm = lastDayToReconfirm2
  378. this.getUserAffiliations.resolves(affiliations)
  379. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  380. this.UserGetter.getUserFullEmails(
  381. this.fakeUser._id,
  382. (error, fullEmails) => {
  383. expect(error).to.not.exist
  384. expect(
  385. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  386. ).to.equal(true)
  387. expect(
  388. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  389. ).to.equal(true)
  390. done()
  391. }
  392. )
  393. })
  394. it('should not flag affiliations outside of notification period', function (done) {
  395. const { maxConfirmationMonths } = institutionNonSSO
  396. const confirmed1 = new Date()
  397. const lastDayToReconfirm1 = moment(confirmed1)
  398. .add(maxConfirmationMonths, 'months')
  399. .toDate()
  400. const confirmed2 = moment()
  401. .subtract(maxConfirmationMonths, 'months')
  402. .add(30, 'days')
  403. .toDate() // expires in 30 days and reconfirmNotificationDays is set to 14
  404. const lastDayToReconfirm2 = moment(confirmed2)
  405. .add(maxConfirmationMonths, 'months')
  406. .toDate()
  407. const user = {
  408. _id: '12390i',
  409. email: email1,
  410. emails: [
  411. {
  412. email: email1,
  413. reversedHostname: 'moc.noitailiffa-elpmaxe',
  414. confirmedAt: confirmed1,
  415. default: true,
  416. },
  417. {
  418. email: email2,
  419. reversedHostname: 'moc.noitailiffa-elpmaxe',
  420. confirmedAt: confirmed2,
  421. },
  422. ],
  423. }
  424. const affiliations = [...affiliationsData]
  425. affiliations[0].last_day_to_reconfirm = lastDayToReconfirm1
  426. affiliations[1].last_day_to_reconfirm = lastDayToReconfirm2
  427. this.getUserAffiliations.resolves(affiliations)
  428. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  429. this.UserGetter.getUserFullEmails(
  430. this.fakeUser._id,
  431. (error, fullEmails) => {
  432. expect(error).to.not.exist
  433. expect(
  434. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  435. ).to.equal(false)
  436. expect(
  437. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  438. ).to.equal(false)
  439. done()
  440. }
  441. )
  442. })
  443. })
  444. describe('SSO institutions', function () {
  445. it('should flag only linked email, if in notification period', function (done) {
  446. const { maxConfirmationMonths } = institutionSSO
  447. const email1 = 'email1@sso.bar'
  448. const email2 = 'email2@sso.bar'
  449. const email3 = 'email3@sso.bar'
  450. const reconfirmedAt = new Date('2019-09-24')
  451. const lastDayToReconfirm = moment(reconfirmedAt)
  452. .add(maxConfirmationMonths, 'months')
  453. .toDate()
  454. const user = {
  455. _id: '12390i',
  456. email: email1,
  457. emails: [
  458. {
  459. email: email1,
  460. reversedHostname: 'rab.oss',
  461. confirmedAt: new Date('2019-09-24'),
  462. reconfirmedAt,
  463. default: true,
  464. },
  465. {
  466. email: email2,
  467. reversedHostname: 'rab.oss',
  468. confirmedAt: new Date('2019-09-24'),
  469. reconfirmedAt,
  470. samlProviderId: institutionSSO.id,
  471. },
  472. {
  473. email: email3,
  474. reversedHostname: 'rab.oss',
  475. confirmedAt: new Date('2019-09-24'),
  476. reconfirmedAt,
  477. },
  478. ],
  479. samlIdentifiers: [
  480. {
  481. providerId: institutionSSO.id,
  482. externalUserId: 'abc123',
  483. },
  484. ],
  485. }
  486. const affiliations = [
  487. {
  488. email: email1,
  489. role: 'Prof',
  490. department: 'Maths!',
  491. inferred: false,
  492. licence: 'pro_plus',
  493. institution: institutionSSO,
  494. last_day_to_reconfirm: lastDayToReconfirm,
  495. },
  496. {
  497. email: email2,
  498. role: 'Prof',
  499. department: 'Maths',
  500. inferred: false,
  501. licence: 'pro_plus',
  502. institution: institutionSSO,
  503. last_day_to_reconfirm: lastDayToReconfirm,
  504. },
  505. {
  506. email: email3,
  507. role: 'Prof',
  508. department: 'Maths',
  509. inferred: false,
  510. licence: 'pro_plus',
  511. institution: institutionSSO,
  512. last_day_to_reconfirm: lastDayToReconfirm,
  513. },
  514. ]
  515. this.getUserAffiliations.resolves(affiliations)
  516. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  517. this.UserGetter.getUserFullEmails(
  518. this.fakeUser._id,
  519. (error, fullEmails) => {
  520. expect(error).to.not.exist
  521. expect(
  522. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  523. ).to.equal(false)
  524. expect(
  525. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  526. ).to.equal(true)
  527. expect(
  528. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  529. ).to.equal(false)
  530. done()
  531. }
  532. )
  533. })
  534. })
  535. describe('multiple institution affiliations', function () {
  536. it('should flag each institution', function (done) {
  537. const { maxConfirmationMonths } = institutionSSO
  538. const email1 = 'email1@sso.bar'
  539. const email2 = 'email2@sso.bar'
  540. const email3 = 'email3@foo.bar'
  541. const email4 = 'email4@foo.bar'
  542. const confirmed1 = new Date('2019-09-24T20:25:08.503Z')
  543. const lastDayToReconfirm1 = moment(confirmed1)
  544. .add(maxConfirmationMonths, 'months')
  545. .toDate()
  546. const confirmed2 = new Date('2019-10-24T20:25:08.503Z')
  547. const lastDayToReconfirm2 = moment(confirmed2)
  548. .add(maxConfirmationMonths, 'months')
  549. .toDate()
  550. const affiliationsData = [
  551. {
  552. email: email1,
  553. role: 'Prof',
  554. department: 'Maths',
  555. inferred: false,
  556. licence: 'pro_plus',
  557. institution: institutionSSO,
  558. last_day_to_reconfirm: lastDayToReconfirm1,
  559. },
  560. {
  561. email: email2,
  562. role: 'Prof',
  563. department: 'Maths',
  564. inferred: false,
  565. licence: 'pro_plus',
  566. institution: institutionSSO,
  567. last_day_to_reconfirm: lastDayToReconfirm1,
  568. },
  569. {
  570. email: email3,
  571. role: 'Prof',
  572. department: 'Maths',
  573. inferred: false,
  574. licence: 'pro_plus',
  575. institution: institutionNonSSO,
  576. last_day_to_reconfirm: lastDayToReconfirm2,
  577. },
  578. {
  579. email: email4,
  580. role: 'Prof',
  581. department: 'Maths',
  582. inferred: false,
  583. licence: 'pro_plus',
  584. institution: institutionNonSSO,
  585. last_day_to_reconfirm: lastDayToReconfirm1,
  586. },
  587. ]
  588. const user = {
  589. _id: '12390i',
  590. email: email1,
  591. emails: [
  592. {
  593. email: email1,
  594. reversedHostname: 'rab.oss',
  595. confirmedAt: confirmed1,
  596. default: true,
  597. },
  598. {
  599. email: email2,
  600. reversedHostname: 'rab.oss',
  601. confirmedAt: confirmed1,
  602. samlProviderId: institutionSSO.id,
  603. },
  604. {
  605. email: email3,
  606. reversedHostname: 'rab.oof',
  607. confirmedAt: confirmed2,
  608. },
  609. {
  610. email: email4,
  611. reversedHostname: 'rab.oof',
  612. confirmedAt: confirmed1,
  613. },
  614. ],
  615. samlIdentifiers: [
  616. {
  617. providerId: institutionSSO.id,
  618. externalUserId: 'abc123',
  619. },
  620. ],
  621. }
  622. this.getUserAffiliations.resolves(affiliationsData)
  623. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  624. this.UserGetter.getUserFullEmails(
  625. this.fakeUser._id,
  626. (error, fullEmails) => {
  627. expect(error).to.not.exist
  628. expect(
  629. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  630. ).to.to.equal(false)
  631. expect(
  632. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  633. ).to.equal(true)
  634. expect(
  635. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  636. ).to.equal(true)
  637. expect(
  638. fullEmails[3].affiliation.inReconfirmNotificationPeriod
  639. ).to.equal(true)
  640. done()
  641. }
  642. )
  643. })
  644. })
  645. describe('reconfirmedAt', function () {
  646. it('only use confirmedAt when no reconfirmedAt', function (done) {
  647. const { maxConfirmationMonths } = institutionSSO
  648. const email1 = 'email1@foo.bar'
  649. const reconfirmed1 = moment().subtract(
  650. maxConfirmationMonths * 3,
  651. 'months'
  652. )
  653. const lastDayToReconfirm1 = moment(reconfirmed1)
  654. .add(maxConfirmationMonths, 'months')
  655. .toDate()
  656. const email2 = 'email2@foo.bar'
  657. const reconfirmed2 = moment().subtract(
  658. maxConfirmationMonths * 2,
  659. 'months'
  660. )
  661. const lastDayToReconfirm2 = moment(reconfirmed2)
  662. .add(maxConfirmationMonths, 'months')
  663. .toDate()
  664. const email3 = 'email3@foo.bar'
  665. const reconfirmed3 = moment().subtract(
  666. maxConfirmationMonths * 4,
  667. 'months'
  668. )
  669. const lastDayToReconfirm3 = moment(reconfirmed3)
  670. .add(maxConfirmationMonths, 'months')
  671. .toDate()
  672. const affiliationsData = [
  673. {
  674. email: email1,
  675. role: 'Prof',
  676. department: 'Maths',
  677. inferred: false,
  678. licence: 'pro_plus',
  679. institution: institutionNonSSO,
  680. last_day_to_reconfirm: lastDayToReconfirm1,
  681. },
  682. {
  683. email: email2,
  684. role: 'Prof',
  685. department: 'Maths',
  686. inferred: false,
  687. licence: 'pro_plus',
  688. institution: institutionNonSSO,
  689. last_day_to_reconfirm: lastDayToReconfirm2,
  690. },
  691. {
  692. email: email3,
  693. role: 'Prof',
  694. department: 'Maths',
  695. inferred: false,
  696. licence: 'pro_plus',
  697. institution: institutionNonSSO,
  698. last_day_to_reconfirm: lastDayToReconfirm3,
  699. },
  700. ]
  701. const user = {
  702. _id: '12390i',
  703. email: email1,
  704. emails: [
  705. {
  706. email: email1,
  707. reversedHostname: 'rab.oof',
  708. confirmedAt: moment().subtract(
  709. institutionSSO.maxConfirmationMonths * 2,
  710. 'months'
  711. ),
  712. reconfirmedAt: reconfirmed1,
  713. default: true,
  714. },
  715. {
  716. email: email2,
  717. reversedHostname: 'rab.oof',
  718. confirmedAt: moment().subtract(
  719. institutionSSO.maxConfirmationMonths * 3,
  720. 'months'
  721. ),
  722. reconfirmedAt: reconfirmed2,
  723. },
  724. {
  725. email: email3,
  726. reversedHostname: 'rab.oof',
  727. confirmedAt: moment().subtract(
  728. institutionSSO.maxConfirmationMonths * 4,
  729. 'months'
  730. ),
  731. reconfirmedAt: reconfirmed3,
  732. },
  733. ],
  734. }
  735. this.getUserAffiliations.resolves(affiliationsData)
  736. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  737. this.UserGetter.getUserFullEmails(
  738. this.fakeUser._id,
  739. (error, fullEmails) => {
  740. expect(error).to.not.exist
  741. expect(
  742. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  743. ).to.equal(true)
  744. expect(
  745. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  746. ).to.equal(true)
  747. expect(
  748. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  749. ).to.equal(true)
  750. done()
  751. }
  752. )
  753. })
  754. })
  755. describe('before reconfirmation period expires and within reconfirmation notification period', function () {
  756. const email = 'leonard@example-affiliation.com'
  757. it('should flag the email', function (done) {
  758. const { maxConfirmationMonths } = institutionNonSSO
  759. const confirmedAt = moment()
  760. .subtract(maxConfirmationMonths, 'months')
  761. .subtract(14, 'days')
  762. .toDate() // expires in 14 days
  763. const affiliationsData = [
  764. {
  765. email,
  766. role: 'Prof',
  767. department: 'Medicine',
  768. inferred: false,
  769. licence: 'pro_plus',
  770. institution: institutionNonSSO,
  771. last_day_to_reconfirm: moment(confirmedAt)
  772. .add(maxConfirmationMonths, 'months')
  773. .toDate(),
  774. },
  775. ]
  776. const user = {
  777. _id: '12390i',
  778. email,
  779. emails: [
  780. {
  781. email,
  782. confirmedAt,
  783. default: true,
  784. },
  785. ],
  786. }
  787. this.getUserAffiliations.resolves(affiliationsData)
  788. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  789. this.UserGetter.getUserFullEmails(
  790. this.fakeUser._id,
  791. (error, fullEmails) => {
  792. expect(error).to.not.exist
  793. expect(
  794. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  795. ).to.equal(true)
  796. done()
  797. }
  798. )
  799. })
  800. })
  801. describe('when no Settings.reconfirmNotificationDays', function () {
  802. it('should always return inReconfirmNotificationPeriod:false', function (done) {
  803. const email1 = 'email1@sso.bar'
  804. const email2 = 'email2@foo.bar'
  805. const email3 = 'email3@foo.bar'
  806. const confirmedAtAboutToExpire = moment()
  807. .subtract(institutionNonSSO.maxConfirmationMonths, 'months')
  808. .subtract(14, 'days')
  809. .toDate() // expires in 14 days
  810. const affiliationsData = [
  811. {
  812. email: email1,
  813. institution: institutionSSO,
  814. },
  815. {
  816. email: email2,
  817. institution: institutionNonSSO,
  818. },
  819. {
  820. email: email3,
  821. institution: institutionNonSSO,
  822. },
  823. ]
  824. const user = {
  825. _id: '12390i',
  826. email: email1,
  827. emails: [
  828. {
  829. email: email1,
  830. confirmedAt: confirmedAtAboutToExpire,
  831. default: true,
  832. samlProviderId: institutionSSO.id,
  833. },
  834. {
  835. email: email2,
  836. confirmedAt: new Date('2019-09-24T20:25:08.503Z'),
  837. },
  838. {
  839. email: email3,
  840. confirmedAt: new Date('2019-10-24T20:25:08.503Z'),
  841. },
  842. ],
  843. samlIdentifiers: [
  844. {
  845. providerId: institutionSSO.id,
  846. externalUserId: 'abc123',
  847. },
  848. ],
  849. }
  850. this.settings.reconfirmNotificationDays = undefined
  851. this.getUserAffiliations.resolves(affiliationsData)
  852. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  853. this.UserGetter.getUserFullEmails(
  854. this.fakeUser._id,
  855. (error, fullEmails) => {
  856. expect(error).to.not.exist
  857. expect(
  858. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  859. ).to.to.equal(false)
  860. expect(
  861. fullEmails[1].affiliation.inReconfirmNotificationPeriod
  862. ).to.equal(false)
  863. expect(
  864. fullEmails[2].affiliation.inReconfirmNotificationPeriod
  865. ).to.equal(false)
  866. done()
  867. }
  868. )
  869. })
  870. })
  871. it('should flag to show notification if v1 shows as past reconfirmation but v2 does not', function (done) {
  872. const email = 'abc123@test.com'
  873. const confirmedAt = new Date()
  874. const affiliationsData = [
  875. {
  876. email,
  877. licence: 'free',
  878. institution: institutionNonSSO,
  879. last_day_to_reconfirm: '2020-07-11T18:25:01.639Z',
  880. past_reconfirm_date: true,
  881. },
  882. ]
  883. const user = {
  884. _id: '12390i',
  885. email,
  886. emails: [
  887. {
  888. email,
  889. confirmedAt,
  890. default: true,
  891. },
  892. ],
  893. }
  894. this.getUserAffiliations.resolves(affiliationsData)
  895. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  896. this.UserGetter.getUserFullEmails(
  897. this.fakeUser._id,
  898. (error, fullEmails) => {
  899. expect(error).to.not.exist
  900. expect(
  901. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  902. ).to.equal(true)
  903. done()
  904. }
  905. )
  906. })
  907. it('should flag to show notification if v1 shows as reconfirmation upcoming but v2 does not', function (done) {
  908. const email = 'abc123@test.com'
  909. const { maxConfirmationMonths } = institutionNonSSO
  910. const affiliationsData = [
  911. {
  912. email,
  913. licence: 'free',
  914. institution: institutionNonSSO,
  915. last_day_to_reconfirm: moment()
  916. .subtract(maxConfirmationMonths, 'months')
  917. .add(3, 'day')
  918. .toDate(),
  919. },
  920. ]
  921. const user = {
  922. _id: '12390i',
  923. email,
  924. emails: [
  925. {
  926. email,
  927. confirmedAt: new Date(),
  928. default: true,
  929. },
  930. ],
  931. }
  932. this.getUserAffiliations.resolves(affiliationsData)
  933. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  934. this.UserGetter.getUserFullEmails(
  935. this.fakeUser._id,
  936. (error, fullEmails) => {
  937. expect(error).to.not.exist
  938. expect(
  939. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  940. ).to.equal(true)
  941. done()
  942. }
  943. )
  944. })
  945. it('should flag to show notification if v2 shows as reconfirmation upcoming but v1 does not', function (done) {
  946. const email = 'abc123@test.com'
  947. const { maxConfirmationMonths } = institutionNonSSO
  948. const datePastReconfirmation = moment()
  949. .subtract(maxConfirmationMonths, 'months')
  950. .add(3, 'day')
  951. .toDate()
  952. const dateNotPastReconfirmation = moment().add(1, 'month').toDate()
  953. const affiliationsData = [
  954. {
  955. email,
  956. licence: 'free',
  957. institution: institutionNonSSO,
  958. last_day_to_reconfirm: dateNotPastReconfirmation,
  959. },
  960. ]
  961. const user = {
  962. _id: '12390i',
  963. email,
  964. emails: [
  965. {
  966. email,
  967. confirmedAt: datePastReconfirmation,
  968. default: true,
  969. },
  970. ],
  971. }
  972. this.getUserAffiliations.resolves(affiliationsData)
  973. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  974. this.UserGetter.getUserFullEmails(
  975. this.fakeUser._id,
  976. (error, fullEmails) => {
  977. expect(error).to.not.exist
  978. expect(
  979. fullEmails[0].affiliation.inReconfirmNotificationPeriod
  980. ).to.equal(true)
  981. done()
  982. }
  983. )
  984. })
  985. describe('cachedLastDayToReconfirm', function () {
  986. const email = 'abc123@test.com'
  987. const confirmedAt = new Date('2019-07-11T18:25:01.639Z')
  988. const lastDay = '2020-07-11T18:25:01.639Z'
  989. const affiliationsData = [
  990. {
  991. email,
  992. licence: 'professional',
  993. institution: institutionSSO,
  994. last_day_to_reconfirm: lastDay,
  995. past_reconfirm_date: true,
  996. },
  997. ]
  998. const user = {
  999. _id: '12390i',
  1000. email,
  1001. emails: [
  1002. {
  1003. email,
  1004. confirmedAt,
  1005. default: true,
  1006. },
  1007. ],
  1008. }
  1009. it('should set cachedLastDayToReconfirm for SSO institutions if email is linked to SSO', async function () {
  1010. const userLinked = Object.assign({}, user)
  1011. userLinked.emails[0].samlProviderId = institutionSSO.id.toString()
  1012. this.getUserAffiliations.resolves(affiliationsData)
  1013. this.UserGetter.promises.getUser = sinon.stub().resolves(userLinked)
  1014. const fullEmails = await this.UserGetter.promises.getUserFullEmails(
  1015. this.fakeUser._id
  1016. )
  1017. expect(fullEmails[0].affiliation.cachedLastDayToReconfirm).to.equal(
  1018. lastDay
  1019. )
  1020. })
  1021. it('should NOT set cachedLastDayToReconfirm for SSO institutions if email is NOT linked to SSO', async function () {
  1022. this.getUserAffiliations.resolves(affiliationsData)
  1023. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  1024. const fullEmails = await this.UserGetter.promises.getUserFullEmails(
  1025. this.fakeUser._id
  1026. )
  1027. expect(fullEmails[0].affiliation.cachedLastDayToReconfirm).to.equal(
  1028. lastDay
  1029. )
  1030. })
  1031. it('should set cachedLastDayToReconfirm for non-SSO institutions', async function () {
  1032. this.getUserAffiliations.resolves(affiliationsData)
  1033. this.UserGetter.promises.getUser = sinon.stub().resolves(user)
  1034. const fullEmails = await this.UserGetter.promises.getUserFullEmails(
  1035. this.fakeUser._id
  1036. )
  1037. expect(fullEmails[0].affiliation.cachedLastDayToReconfirm).to.equal(
  1038. lastDay
  1039. )
  1040. })
  1041. })
  1042. })
  1043. describe('caching full emails data if run inside AsyncLocalStorage context', function () {
  1044. it('should store the data in the AsyncLocalStorage store', async function () {
  1045. this.store = {}
  1046. this.AsyncLocalStorage.storage.getStore.returns(this.store)
  1047. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  1048. this.getUserAffiliations.resolves([
  1049. {
  1050. email: 'email1@foo.bar',
  1051. licence: 'professional',
  1052. institution: {},
  1053. },
  1054. ])
  1055. const fullEmails = await this.UserGetter.promises.getUserFullEmails(
  1056. this.fakeUser._id
  1057. )
  1058. expect(this.UserGetter.promises.getUser).to.have.been.calledOnce
  1059. expect(this.getUserAffiliations).to.have.been.calledOnce
  1060. expect(fullEmails).to.be.an('array')
  1061. expect(fullEmails.length).to.equal(2)
  1062. expect(this.store.userFullEmails[this.fakeUser._id]).to.deep.equal(
  1063. fullEmails
  1064. )
  1065. })
  1066. it('should fetch data from the store if available', async function () {
  1067. this.store = {
  1068. userFullEmails: {
  1069. [this.fakeUser._id]: [{ email: '1' }, { email: '2' }],
  1070. },
  1071. }
  1072. this.AsyncLocalStorage.storage.getStore.returns(this.store)
  1073. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  1074. const fullEmails = await this.UserGetter.promises.getUserFullEmails(
  1075. this.fakeUser._id,
  1076. this.req
  1077. )
  1078. expect(this.UserGetter.promises.getUser).to.not.have.been.called
  1079. expect(this.getUserAffiliations).to.not.have.been.called
  1080. expect(fullEmails).to.be.an('array')
  1081. expect(fullEmails.length).to.equal(2)
  1082. expect(this.store.userFullEmails[this.fakeUser._id]).to.deep.equal(
  1083. fullEmails
  1084. )
  1085. })
  1086. it('should not return cached data for different user ids', async function () {
  1087. this.store = {}
  1088. this.AsyncLocalStorage.storage.getStore.returns(this.store)
  1089. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  1090. const fullEmails = await this.UserGetter.promises.getUserFullEmails(
  1091. this.fakeUser._id,
  1092. this.req
  1093. )
  1094. expect(this.UserGetter.promises.getUser).to.have.been.calledOnce
  1095. expect(this.getUserAffiliations).to.have.been.calledOnce
  1096. expect(fullEmails).to.be.an('array')
  1097. expect(fullEmails.length).to.equal(2)
  1098. this.otherUser = {
  1099. _id: new ObjectId(),
  1100. email: 'other@foo.bar',
  1101. emails: [
  1102. {
  1103. email: 'other@foo.bar',
  1104. reversedHostname: 'rab.oof',
  1105. confirmedAt: new Date(),
  1106. lastConfirmedAt: new Date(),
  1107. },
  1108. ],
  1109. }
  1110. this.UserGetter.promises.getUser.resolves(this.otherUser)
  1111. this.getUserAffiliations.resolves([
  1112. {
  1113. email: 'other@foo.bar',
  1114. licence: 'professional',
  1115. institution: {},
  1116. },
  1117. ])
  1118. const fullEmailsOther =
  1119. await this.UserGetter.promises.getUserFullEmails(
  1120. this.otherUser._id,
  1121. this.req
  1122. )
  1123. expect(this.UserGetter.promises.getUser).to.have.been.calledTwice
  1124. expect(this.getUserAffiliations).to.have.been.calledTwice
  1125. expect(fullEmailsOther).to.not.deep.equal(fullEmails)
  1126. expect(fullEmailsOther).to.be.an('array')
  1127. expect(fullEmailsOther.length).to.equal(1)
  1128. expect(this.store.userFullEmails[this.fakeUser._id]).to.deep.equal(
  1129. fullEmails
  1130. )
  1131. expect(this.store.userFullEmails[this.otherUser._id]).to.deep.equal(
  1132. fullEmailsOther
  1133. )
  1134. })
  1135. })
  1136. })
  1137. describe('getUserConfirmedEmails', function () {
  1138. beforeEach(function () {
  1139. this.fakeUser = {
  1140. emails: [
  1141. {
  1142. email: 'email1@foo.bar',
  1143. reversedHostname: 'rab.oof',
  1144. confirmedAt: new Date(),
  1145. },
  1146. { email: 'email2@foo.bar', reversedHostname: 'rab.oof' },
  1147. {
  1148. email: 'email3@foo.bar',
  1149. reversedHostname: 'rab.oof',
  1150. confirmedAt: new Date(),
  1151. },
  1152. ],
  1153. }
  1154. this.UserGetter.promises.getUser = sinon.stub().resolves(this.fakeUser)
  1155. })
  1156. it('should get user', async function () {
  1157. const projection = { emails: 1 }
  1158. await this.UserGetter.promises.getUserConfirmedEmails(this.fakeUser._id)
  1159. this.UserGetter.promises.getUser
  1160. .calledWith(this.fakeUser._id, projection)
  1161. .should.equal(true)
  1162. })
  1163. it('should return only confirmed emails', async function () {
  1164. const confirmedEmails =
  1165. await this.UserGetter.promises.getUserConfirmedEmails(this.fakeUser._id)
  1166. expect(confirmedEmails.length).to.equal(2)
  1167. expect(confirmedEmails[0].email).to.equal('email1@foo.bar')
  1168. expect(confirmedEmails[1].email).to.equal('email3@foo.bar')
  1169. })
  1170. })
  1171. describe('getUserbyMainEmail', function () {
  1172. it('query user by main email', function (done) {
  1173. const email = 'hello@world.com'
  1174. const projection = { emails: 1 }
  1175. this.UserGetter.getUserByMainEmail(email, projection, (error, user) => {
  1176. expect(error).to.not.exist
  1177. this.findOne.called.should.equal(true)
  1178. this.findOne.calledWith({ email }, { projection }).should.equal(true)
  1179. done()
  1180. })
  1181. })
  1182. it('return user if found', function (done) {
  1183. const email = 'hello@world.com'
  1184. this.UserGetter.getUserByMainEmail(email, (error, user) => {
  1185. expect(error).to.not.exist
  1186. user.should.deep.equal(this.fakeUser)
  1187. done()
  1188. })
  1189. })
  1190. it('trim email', function (done) {
  1191. const email = 'hello@world.com'
  1192. this.UserGetter.getUserByMainEmail(` ${email} `, (error, user) => {
  1193. expect(error).to.not.exist
  1194. this.findOne.called.should.equal(true)
  1195. this.findOne.calledWith({ email }).should.equal(true)
  1196. done()
  1197. })
  1198. })
  1199. })
  1200. describe('getUserByAnyEmail', function () {
  1201. it('query user for any email', function (done) {
  1202. const email = 'hello@world.com'
  1203. const expectedQuery = {
  1204. emails: { $exists: true },
  1205. 'emails.email': email,
  1206. }
  1207. const projection = { emails: 1 }
  1208. this.UserGetter.getUserByAnyEmail(
  1209. ` ${email} `,
  1210. projection,
  1211. (error, user) => {
  1212. expect(error).to.not.exist
  1213. this.findOne
  1214. .calledWith(expectedQuery, { projection })
  1215. .should.equal(true)
  1216. user.should.deep.equal(this.fakeUser)
  1217. done()
  1218. }
  1219. )
  1220. })
  1221. it('query contains $exists:true so partial index is used', function (done) {
  1222. const expectedQuery = {
  1223. emails: { $exists: true },
  1224. 'emails.email': '',
  1225. }
  1226. this.UserGetter.getUserByAnyEmail('', {}, (error, user) => {
  1227. expect(error).to.not.exist
  1228. this.findOne
  1229. .calledWith(expectedQuery, { projection: {} })
  1230. .should.equal(true)
  1231. done()
  1232. })
  1233. })
  1234. it('checks main email as well', function (done) {
  1235. this.findOne.callsArgWith(2, null, null)
  1236. const email = 'hello@world.com'
  1237. const projection = { emails: 1 }
  1238. this.UserGetter.getUserByAnyEmail(
  1239. ` ${email} `,
  1240. projection,
  1241. (error, user) => {
  1242. expect(error).to.not.exist
  1243. this.findOne.calledTwice.should.equal(true)
  1244. this.findOne.calledWith({ email }, { projection }).should.equal(true)
  1245. done()
  1246. }
  1247. )
  1248. })
  1249. })
  1250. describe('getUsersByHostname', function () {
  1251. it('should find user by hostname', function (done) {
  1252. const hostname = 'bar.foo'
  1253. const expectedQuery = {
  1254. emails: { $exists: true },
  1255. 'emails.reversedHostname': hostname.split('').reverse().join(''),
  1256. }
  1257. const projection = { emails: 1 }
  1258. this.UserGetter.getUsersByHostname(
  1259. hostname,
  1260. projection,
  1261. (error, users) => {
  1262. expect(error).to.not.exist
  1263. this.find.calledOnce.should.equal(true)
  1264. this.find.calledWith(expectedQuery, { projection }).should.equal(true)
  1265. done()
  1266. }
  1267. )
  1268. })
  1269. })
  1270. describe('getUsersByAnyConfirmedEmail', function () {
  1271. it('should find users by confirmed email', function (done) {
  1272. const emails = ['confirmed@example.com']
  1273. this.UserGetter.getUsersByAnyConfirmedEmail(emails, (error, users) => {
  1274. expect(error).to.not.exist
  1275. expect(this.find).to.be.calledOnceWith(
  1276. {
  1277. 'emails.email': { $in: emails }, // use the index on emails.email
  1278. emails: {
  1279. $exists: true,
  1280. $elemMatch: {
  1281. email: { $in: emails },
  1282. confirmedAt: { $exists: true },
  1283. },
  1284. },
  1285. },
  1286. { projection: {} }
  1287. )
  1288. done()
  1289. })
  1290. })
  1291. })
  1292. describe('getUsersByV1Id', function () {
  1293. it('should find users by list of v1 ids', function (done) {
  1294. const v1Ids = [501]
  1295. const expectedQuery = {
  1296. 'overleaf.id': { $in: v1Ids },
  1297. }
  1298. const projection = { emails: 1 }
  1299. this.UserGetter.getUsersByV1Ids(v1Ids, projection, (error, users) => {
  1300. expect(error).to.not.exist
  1301. this.find.calledOnce.should.equal(true)
  1302. this.find.calledWith(expectedQuery, { projection }).should.equal(true)
  1303. done()
  1304. })
  1305. })
  1306. })
  1307. describe('ensureUniqueEmailAddress', function () {
  1308. beforeEach(function () {
  1309. this.UserGetter.getUserByAnyEmail = sinon.stub()
  1310. })
  1311. it('should return error if existing user is found', function (done) {
  1312. this.UserGetter.getUserByAnyEmail.callsArgWith(1, null, this.fakeUser)
  1313. this.UserGetter.ensureUniqueEmailAddress(this.newEmail, err => {
  1314. expect(err).to.exist
  1315. expect(err).to.be.an.instanceof(Errors.EmailExistsError)
  1316. done()
  1317. })
  1318. })
  1319. it('should return null if no user is found', function (done) {
  1320. this.UserGetter.getUserByAnyEmail.callsArgWith(1)
  1321. this.UserGetter.ensureUniqueEmailAddress(this.newEmail, err => {
  1322. expect(err).not.to.exist
  1323. done()
  1324. })
  1325. })
  1326. })
  1327. describe('getUserFeatures', function () {
  1328. beforeEach(function () {
  1329. this.Modules.promises.hooks.fire = sinon.stub().resolves()
  1330. this.fakeUser.features = {}
  1331. })
  1332. it('should return user features', function (done) {
  1333. this.fakeUser.features = { feature1: true, feature2: false }
  1334. this.UserGetter.getUserFeatures(new ObjectId(), (error, features) => {
  1335. expect(error).to.not.exist
  1336. expect(features).to.deep.equal(this.fakeUser.features)
  1337. done()
  1338. })
  1339. })
  1340. it('should return user features when using promises', async function () {
  1341. this.fakeUser.features = { feature1: true, feature2: false }
  1342. const features = await this.UserGetter.promises.getUserFeatures(
  1343. this.fakeUser._id
  1344. )
  1345. expect(features).to.deep.equal(this.fakeUser.features)
  1346. })
  1347. it('should take into account features overrides from modules', async function () {
  1348. // this case occurs when the user has bought the ai bundle on WF, which should include our error assistant
  1349. const bundleFeatures = { aiErrorAssistant: true }
  1350. this.fakeUser.features = { aiErrorAssistant: false }
  1351. this.Modules.promises.hooks.fire = sinon.stub().resolves([bundleFeatures])
  1352. const features = await this.UserGetter.promises.getUserFeatures(
  1353. this.fakeUser._id
  1354. )
  1355. expect(features).to.deep.equal(bundleFeatures)
  1356. this.Modules.promises.hooks.fire.should.have.been.calledWith(
  1357. 'getModuleProvidedFeatures',
  1358. this.fakeUser._id
  1359. )
  1360. })
  1361. it('should handle modules not returning any features', async function () {
  1362. this.Modules.promises.hooks.fire = sinon.stub().resolves([])
  1363. this.fakeUser.features = { test: true }
  1364. const features = await this.UserGetter.promises.getUserFeatures(
  1365. this.fakeUser._id
  1366. )
  1367. expect(features).to.deep.equal({ test: true })
  1368. this.Modules.promises.hooks.fire.should.have.been.calledWith(
  1369. 'getModuleProvidedFeatures',
  1370. this.fakeUser._id
  1371. )
  1372. })
  1373. })
  1374. })