AuthorizationManagerTests.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. const sinon = require('sinon')
  2. const { expect } = require('chai')
  3. const modulePath =
  4. '../../../../app/src/Features/Authorization/AuthorizationManager.js'
  5. const SandboxedModule = require('sandboxed-module')
  6. const Errors = require('../../../../app/src/Features/Errors/Errors')
  7. const PrivilegeLevels = require('../../../../app/src/Features/Authorization/PrivilegeLevels')
  8. const PublicAccessLevels = require('../../../../app/src/Features/Authorization/PublicAccessLevels')
  9. const { ObjectId } = require('mongodb-legacy')
  10. describe('AuthorizationManager', function () {
  11. beforeEach(function () {
  12. this.user = { _id: new ObjectId() }
  13. this.project = { _id: new ObjectId() }
  14. this.doc = { _id: new ObjectId() }
  15. this.thread = { _id: new ObjectId() }
  16. this.token = 'some-token'
  17. this.ProjectGetter = {
  18. promises: {
  19. getProject: sinon.stub().resolves(null),
  20. },
  21. }
  22. this.ProjectGetter.promises.getProject
  23. .withArgs(this.project._id)
  24. .resolves(this.project)
  25. this.CollaboratorsGetter = {
  26. promises: {
  27. getMemberIdPrivilegeLevel: sinon.stub().resolves(PrivilegeLevels.NONE),
  28. },
  29. }
  30. this.CollaboratorsHandler = {}
  31. this.User = {
  32. findOne: sinon.stub().returns({ exec: sinon.stub().resolves(null) }),
  33. }
  34. this.User.findOne
  35. .withArgs({ _id: this.user._id })
  36. .returns({ exec: sinon.stub().resolves(this.user) })
  37. this.TokenAccessHandler = {
  38. promises: {
  39. validateTokenForAnonymousAccess: sinon
  40. .stub()
  41. .resolves({ isValidReadAndWrite: false, isValidReadOnly: false }),
  42. },
  43. }
  44. this.DocumentUpdaterHandler = {
  45. promises: {
  46. getComment: sinon
  47. .stub()
  48. .resolves({ metadata: { user_id: new ObjectId() } }),
  49. },
  50. }
  51. this.AuthorizationManager = SandboxedModule.require(modulePath, {
  52. requires: {
  53. 'mongodb-legacy': { ObjectId },
  54. '../Collaborators/CollaboratorsGetter': this.CollaboratorsGetter,
  55. '../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
  56. '../Project/ProjectGetter': this.ProjectGetter,
  57. '../../models/User': { User: this.User },
  58. '../TokenAccess/TokenAccessHandler': this.TokenAccessHandler,
  59. '../DocumentUpdater/DocumentUpdaterHandler':
  60. this.DocumentUpdaterHandler,
  61. '@overleaf/settings': {
  62. passwordStrengthOptions: {},
  63. adminPrivilegeAvailable: true,
  64. },
  65. },
  66. })
  67. })
  68. describe('isRestrictedUser', function () {
  69. it('should produce the correct values', function () {
  70. const notRestrictedScenarios = [
  71. [null, 'readAndWrite', false, false],
  72. ['id', 'readAndWrite', true, false],
  73. ['id', 'readAndWrite', true, true],
  74. ['id', 'readOnly', false, false],
  75. ['id', 'readOnly', false, true],
  76. ['id', 'review', false, true],
  77. ]
  78. const restrictedScenarios = [
  79. [null, 'readOnly', false, false],
  80. ['id', 'readOnly', true, false],
  81. [null, false, true, false],
  82. [null, false, false, false],
  83. ['id', false, true, false],
  84. ['id', false, false, false],
  85. ]
  86. for (const notRestrictedArgs of notRestrictedScenarios) {
  87. expect(
  88. this.AuthorizationManager.isRestrictedUser(...notRestrictedArgs)
  89. ).to.equal(false)
  90. }
  91. for (const restrictedArgs of restrictedScenarios) {
  92. expect(
  93. this.AuthorizationManager.isRestrictedUser(...restrictedArgs)
  94. ).to.equal(true)
  95. }
  96. })
  97. })
  98. describe('getPrivilegeLevelForProject', function () {
  99. describe('with a token-based project', function () {
  100. beforeEach(function () {
  101. this.project.publicAccesLevel = 'tokenBased'
  102. })
  103. describe('with a user id with a privilege level', function () {
  104. beforeEach(async function () {
  105. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  106. .withArgs(this.user._id, this.project._id)
  107. .resolves(PrivilegeLevels.READ_ONLY)
  108. this.result =
  109. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  110. this.user._id,
  111. this.project._id,
  112. this.token
  113. )
  114. })
  115. it("should return the user's privilege level", function () {
  116. expect(this.result).to.equal('readOnly')
  117. })
  118. })
  119. describe('with a user id with no privilege level', function () {
  120. beforeEach(async function () {
  121. this.result =
  122. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  123. this.user._id,
  124. this.project._id,
  125. this.token
  126. )
  127. })
  128. it('should return false', function () {
  129. expect(this.result).to.equal(false)
  130. })
  131. })
  132. describe('with a user id who is an admin', function () {
  133. beforeEach(async function () {
  134. this.user.isAdmin = true
  135. this.result =
  136. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  137. this.user._id,
  138. this.project._id,
  139. this.token
  140. )
  141. })
  142. it('should return the user as an owner', function () {
  143. expect(this.result).to.equal('owner')
  144. })
  145. })
  146. describe('with no user (anonymous)', function () {
  147. describe('when the token is not valid', function () {
  148. beforeEach(async function () {
  149. this.result =
  150. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  151. null,
  152. this.project._id,
  153. this.token
  154. )
  155. })
  156. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  157. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  158. false
  159. )
  160. })
  161. it('should check if the token is valid', function () {
  162. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess.should.have.been.calledWith(
  163. this.project._id,
  164. this.token
  165. )
  166. })
  167. it('should return false', function () {
  168. expect(this.result).to.equal(false)
  169. })
  170. })
  171. describe('when the token is valid for read-and-write', function () {
  172. beforeEach(async function () {
  173. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess =
  174. sinon
  175. .stub()
  176. .withArgs(this.project._id, this.token)
  177. .resolves({ isValidReadAndWrite: true, isValidReadOnly: false })
  178. this.result =
  179. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  180. null,
  181. this.project._id,
  182. this.token
  183. )
  184. })
  185. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  186. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  187. false
  188. )
  189. })
  190. it('should check if the token is valid', function () {
  191. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess.should.have.been.calledWith(
  192. this.project._id,
  193. this.token
  194. )
  195. })
  196. it('should give read-write access', function () {
  197. expect(this.result).to.equal('readAndWrite')
  198. })
  199. })
  200. describe('when the token is valid for read-only', function () {
  201. beforeEach(async function () {
  202. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess =
  203. sinon
  204. .stub()
  205. .withArgs(this.project._id, this.token)
  206. .resolves({ isValidReadAndWrite: false, isValidReadOnly: true })
  207. this.result =
  208. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  209. null,
  210. this.project._id,
  211. this.token
  212. )
  213. })
  214. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  215. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  216. false
  217. )
  218. })
  219. it('should check if the token is valid', function () {
  220. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess.should.have.been.calledWith(
  221. this.project._id,
  222. this.token
  223. )
  224. })
  225. it('should give read-only access', function () {
  226. expect(this.result).to.equal('readOnly')
  227. })
  228. })
  229. })
  230. })
  231. describe('with a private project', function () {
  232. beforeEach(function () {
  233. this.project.publicAccesLevel = 'private'
  234. })
  235. describe('with a user id with a privilege level', function () {
  236. beforeEach(async function () {
  237. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  238. .withArgs(this.user._id, this.project._id)
  239. .resolves(PrivilegeLevels.READ_ONLY)
  240. this.result =
  241. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  242. this.user._id,
  243. this.project._id,
  244. this.token
  245. )
  246. })
  247. it("should return the user's privilege level", function () {
  248. expect(this.result).to.equal('readOnly')
  249. })
  250. })
  251. describe('with a user id with no privilege level', function () {
  252. beforeEach(async function () {
  253. this.result =
  254. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  255. this.user._id,
  256. this.project._id,
  257. this.token
  258. )
  259. })
  260. it('should return false', function () {
  261. expect(this.result).to.equal(false)
  262. })
  263. })
  264. describe('with a user id who is an admin', function () {
  265. beforeEach(async function () {
  266. this.user.isAdmin = true
  267. this.result =
  268. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  269. this.user._id,
  270. this.project._id,
  271. this.token
  272. )
  273. })
  274. it('should return the user as an owner', function () {
  275. expect(this.result).to.equal('owner')
  276. })
  277. })
  278. describe('with no user (anonymous)', function () {
  279. beforeEach(async function () {
  280. this.result =
  281. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  282. null,
  283. this.project._id,
  284. this.token
  285. )
  286. })
  287. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  288. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  289. false
  290. )
  291. })
  292. it('should return false', function () {
  293. expect(this.result).to.equal(false)
  294. })
  295. })
  296. })
  297. describe('with a public project', function () {
  298. beforeEach(function () {
  299. this.project.publicAccesLevel = 'readAndWrite'
  300. })
  301. describe('with a user id with a privilege level', function () {
  302. beforeEach(async function () {
  303. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  304. .withArgs(this.user._id, this.project._id)
  305. .resolves(PrivilegeLevels.READ_ONLY)
  306. this.result =
  307. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  308. this.user._id,
  309. this.project._id,
  310. this.token
  311. )
  312. })
  313. it("should return the user's privilege level", function () {
  314. expect(this.result).to.equal('readOnly')
  315. })
  316. })
  317. describe('with a user id with no privilege level', function () {
  318. beforeEach(async function () {
  319. this.result =
  320. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  321. this.user._id,
  322. this.project._id,
  323. this.token
  324. )
  325. })
  326. it('should return the public privilege level', function () {
  327. expect(this.result).to.equal('readAndWrite')
  328. })
  329. })
  330. describe('with a user id who is an admin', function () {
  331. beforeEach(async function () {
  332. this.user.isAdmin = true
  333. this.result =
  334. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  335. this.user._id,
  336. this.project._id,
  337. this.token
  338. )
  339. })
  340. it('should return the user as an owner', function () {
  341. expect(this.result).to.equal('owner')
  342. })
  343. })
  344. describe('with no user (anonymous)', function () {
  345. beforeEach(async function () {
  346. this.result =
  347. await this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  348. null,
  349. this.project._id,
  350. this.token
  351. )
  352. })
  353. it('should not call CollaboratorsGetter.getMemberIdPrivilegeLevel', function () {
  354. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel.called.should.equal(
  355. false
  356. )
  357. })
  358. it('should return the public privilege level', function () {
  359. expect(this.result).to.equal('readAndWrite')
  360. })
  361. })
  362. })
  363. describe("when the project doesn't exist", function () {
  364. it('should return a NotFoundError', async function () {
  365. const someOtherId = new ObjectId()
  366. await expect(
  367. this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  368. this.user._id,
  369. someOtherId,
  370. this.token
  371. )
  372. ).to.be.rejectedWith(Errors.NotFoundError)
  373. })
  374. })
  375. describe('when the project id is not valid', function () {
  376. beforeEach(function () {
  377. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  378. .withArgs(this.user._id, this.project._id)
  379. .resolves(PrivilegeLevels.READ_ONLY)
  380. })
  381. it('should return a error', async function () {
  382. await expect(
  383. this.AuthorizationManager.promises.getPrivilegeLevelForProject(
  384. undefined,
  385. 'not project id',
  386. this.token
  387. )
  388. ).to.be.rejected
  389. })
  390. })
  391. })
  392. testPermission('canUserReadProject', {
  393. siteAdmin: true,
  394. owner: true,
  395. readAndWrite: true,
  396. review: true,
  397. readOnly: true,
  398. publicReadAndWrite: true,
  399. publicReadOnly: true,
  400. tokenReadAndWrite: true,
  401. tokenReadOnly: true,
  402. })
  403. testPermission('canUserWriteOrReviewProjectContent', {
  404. siteAdmin: true,
  405. owner: true,
  406. readAndWrite: true,
  407. review: true,
  408. publicReadAndWrite: true,
  409. tokenReadAndWrite: true,
  410. })
  411. testPermission('canUserWriteProjectContent', {
  412. siteAdmin: true,
  413. owner: true,
  414. readAndWrite: true,
  415. publicReadAndWrite: true,
  416. tokenReadAndWrite: true,
  417. })
  418. testPermission('canUserWriteProjectSettings', {
  419. siteAdmin: true,
  420. owner: true,
  421. readAndWrite: true,
  422. tokenReadAndWrite: true,
  423. })
  424. testPermission('canUserRenameProject', {
  425. siteAdmin: true,
  426. owner: true,
  427. })
  428. testPermission('canUserAdminProject', { siteAdmin: true, owner: true })
  429. describe('isUserSiteAdmin', function () {
  430. describe('when user is admin', function () {
  431. beforeEach(function () {
  432. this.user.isAdmin = true
  433. })
  434. it('should return true', async function () {
  435. const isAdmin =
  436. await this.AuthorizationManager.promises.isUserSiteAdmin(
  437. this.user._id
  438. )
  439. expect(isAdmin).to.equal(true)
  440. })
  441. })
  442. describe('when user is not admin', function () {
  443. it('should return false', async function () {
  444. const isAdmin =
  445. await this.AuthorizationManager.promises.isUserSiteAdmin(
  446. this.user._id
  447. )
  448. expect(isAdmin).to.equal(false)
  449. })
  450. })
  451. describe('when user is not found', function () {
  452. it('should return false', async function () {
  453. const someOtherId = new ObjectId()
  454. const isAdmin =
  455. await this.AuthorizationManager.promises.isUserSiteAdmin(someOtherId)
  456. expect(isAdmin).to.equal(false)
  457. })
  458. })
  459. describe('when no user is passed', function () {
  460. it('should return false', async function () {
  461. const isAdmin =
  462. await this.AuthorizationManager.promises.isUserSiteAdmin(null)
  463. expect(isAdmin).to.equal(false)
  464. })
  465. })
  466. })
  467. describe('canUserDeleteOrResolveThread', function () {
  468. it('should return true when user has write permissions', async function () {
  469. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  470. .withArgs(this.user._id, this.project._id)
  471. .resolves(PrivilegeLevels.READ_AND_WRITE)
  472. const canResolve =
  473. await this.AuthorizationManager.promises.canUserDeleteOrResolveThread(
  474. this.user._id,
  475. this.project._id,
  476. this.doc._id,
  477. this.thread._id,
  478. this.token
  479. )
  480. expect(canResolve).to.equal(true)
  481. })
  482. it('should return false when user has read permission', async function () {
  483. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  484. .withArgs(this.user._id, this.project._id)
  485. .resolves(PrivilegeLevels.READ_ONLY)
  486. const canResolve =
  487. await this.AuthorizationManager.promises.canUserDeleteOrResolveThread(
  488. this.user._id,
  489. this.project._id,
  490. this.doc._id,
  491. this.thread._id,
  492. this.token
  493. )
  494. expect(canResolve).to.equal(false)
  495. })
  496. describe('when user has review permission', function () {
  497. beforeEach(function () {
  498. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  499. .withArgs(this.user._id, this.project._id)
  500. .resolves(PrivilegeLevels.REVIEW)
  501. })
  502. it('should return false when user is not the comment author', async function () {
  503. const canResolve =
  504. await this.AuthorizationManager.promises.canUserDeleteOrResolveThread(
  505. this.user._id,
  506. this.project._id,
  507. this.doc._id,
  508. this.thread._id,
  509. this.token
  510. )
  511. expect(canResolve).to.equal(false)
  512. })
  513. it('should return true when user is the comment author', async function () {
  514. this.DocumentUpdaterHandler.promises.getComment
  515. .withArgs(this.project._id, this.doc._id, this.thread._id)
  516. .resolves({ metadata: { user_id: this.user._id } })
  517. const canResolve =
  518. await this.AuthorizationManager.promises.canUserDeleteOrResolveThread(
  519. this.user._id,
  520. this.project._id,
  521. this.doc._id,
  522. this.thread._id,
  523. this.token
  524. )
  525. expect(canResolve).to.equal(true)
  526. })
  527. })
  528. })
  529. })
  530. function testPermission(permission, privilegeLevels) {
  531. describe(permission, function () {
  532. describe('when authenticated', function () {
  533. describe('when user is site admin', function () {
  534. beforeEach('set user as site admin', function () {
  535. this.user.isAdmin = true
  536. })
  537. expectPermission(permission, privilegeLevels.siteAdmin || false)
  538. })
  539. describe('when user is owner', function () {
  540. setupUserPrivilegeLevel(PrivilegeLevels.OWNER)
  541. expectPermission(permission, privilegeLevels.owner || false)
  542. })
  543. describe('when user has read-write access', function () {
  544. setupUserPrivilegeLevel(PrivilegeLevels.READ_AND_WRITE)
  545. expectPermission(permission, privilegeLevels.readAndWrite || false)
  546. })
  547. describe('when user has review access', function () {
  548. setupUserPrivilegeLevel(PrivilegeLevels.REVIEW)
  549. expectPermission(permission, privilegeLevels.review || false)
  550. })
  551. describe('when user has read-only access', function () {
  552. setupUserPrivilegeLevel(PrivilegeLevels.READ_ONLY)
  553. expectPermission(permission, privilegeLevels.readOnly || false)
  554. })
  555. describe('when user has read-write access as the public', function () {
  556. setupPublicAccessLevel(PublicAccessLevels.READ_AND_WRITE)
  557. expectPermission(
  558. permission,
  559. privilegeLevels.publicReadAndWrite || false
  560. )
  561. })
  562. describe('when user has read-only access as the public', function () {
  563. setupPublicAccessLevel(PublicAccessLevels.READ_ONLY)
  564. expectPermission(permission, privilegeLevels.publicReadOnly || false)
  565. })
  566. describe('when user is not found', function () {
  567. it('should return false', async function () {
  568. const otherUserId = new ObjectId()
  569. const value = await this.AuthorizationManager.promises[permission](
  570. otherUserId,
  571. this.project._id,
  572. this.token
  573. )
  574. expect(value).to.equal(false)
  575. })
  576. })
  577. })
  578. describe('when anonymous', function () {
  579. beforeEach(function () {
  580. this.user = null
  581. })
  582. describe('with read-write access through a token', function () {
  583. setupTokenAccessLevel('readAndWrite')
  584. expectPermission(permission, privilegeLevels.tokenReadAndWrite || false)
  585. })
  586. describe('with read-only access through a token', function () {
  587. setupTokenAccessLevel('readOnly')
  588. expectPermission(permission, privilegeLevels.tokenReadOnly || false)
  589. })
  590. describe('with public read-write access', function () {
  591. setupPublicAccessLevel(PublicAccessLevels.READ_AND_WRITE)
  592. expectPermission(
  593. permission,
  594. privilegeLevels.publicReadAndWrite || false
  595. )
  596. })
  597. describe('with public read-only access', function () {
  598. setupPublicAccessLevel(PublicAccessLevels.READ_ONLY)
  599. expectPermission(permission, privilegeLevels.publicReadOnly || false)
  600. })
  601. })
  602. })
  603. }
  604. function setupUserPrivilegeLevel(privilegeLevel) {
  605. beforeEach(`set user privilege level to ${privilegeLevel}`, function () {
  606. this.CollaboratorsGetter.promises.getMemberIdPrivilegeLevel
  607. .withArgs(this.user._id, this.project._id)
  608. .resolves(privilegeLevel)
  609. })
  610. }
  611. function setupPublicAccessLevel(level) {
  612. beforeEach(`set public access level to ${level}`, function () {
  613. this.project.publicAccesLevel = level
  614. })
  615. }
  616. function setupTokenAccessLevel(level) {
  617. beforeEach(`set token access level to ${level}`, function () {
  618. this.project.publicAccesLevel = PublicAccessLevels.TOKEN_BASED
  619. this.TokenAccessHandler.promises.validateTokenForAnonymousAccess
  620. .withArgs(this.project._id, this.token)
  621. .resolves({
  622. isValidReadAndWrite: level === 'readAndWrite',
  623. isValidReadOnly: level === 'readOnly',
  624. })
  625. })
  626. }
  627. function expectPermission(permission, expectedValue) {
  628. it(`should return ${expectedValue}`, async function () {
  629. const value = await this.AuthorizationManager.promises[permission](
  630. this.user && this.user._id,
  631. this.project._id,
  632. this.token
  633. )
  634. expect(value).to.equal(expectedValue)
  635. })
  636. }