ProjectDetailsHandlerTests.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. /* eslint-disable
  2. handle-callback-err,
  3. max-len,
  4. no-return-assign,
  5. no-unused-vars,
  6. */
  7. // TODO: This file was created by bulk-decaffeinate.
  8. // Fix any style issues and re-enable lint.
  9. /*
  10. * decaffeinate suggestions:
  11. * DS101: Remove unnecessary use of Array.from
  12. * DS102: Remove unnecessary code created because of implicit returns
  13. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  14. */
  15. const should = require('chai').should()
  16. const modulePath = '../../../../app/src/Features/Project/ProjectDetailsHandler'
  17. const Errors = require('../../../../app/src/Features/Errors/Errors')
  18. const SandboxedModule = require('sandboxed-module')
  19. const sinon = require('sinon')
  20. const { assert } = require('chai')
  21. const { expect } = require('chai')
  22. require('chai').should()
  23. describe('ProjectDetailsHandler', function() {
  24. beforeEach(function() {
  25. this.project_id = '321l3j1kjkjl'
  26. this.user_id = 'user-id-123'
  27. this.project = {
  28. name: 'project',
  29. description: 'this is a great project',
  30. something: 'should not exist',
  31. compiler: 'latexxxxxx',
  32. owner_ref: this.user_id
  33. }
  34. this.user = { features: 'mock-features' }
  35. this.ProjectGetter = {
  36. getProjectWithoutDocLines: sinon
  37. .stub()
  38. .callsArgWith(1, null, this.project),
  39. getProject: sinon.stub().callsArgWith(2, null, this.project)
  40. }
  41. this.ProjectModel = {
  42. update: sinon.stub(),
  43. findOne: sinon.stub()
  44. }
  45. this.UserGetter = { getUser: sinon.stub().callsArgWith(1, null, this.user) }
  46. this.tpdsUpdateSender = { moveEntity: sinon.stub().callsArgWith(1) }
  47. this.ProjectEntityHandler = {
  48. flushProjectToThirdPartyDataStore: sinon.stub().callsArg(1)
  49. }
  50. this.CollaboratorsHandler = {
  51. removeUserFromProject: sinon.stub().callsArg(2)
  52. }
  53. return (this.handler = SandboxedModule.require(modulePath, {
  54. globals: {
  55. console: console
  56. },
  57. requires: {
  58. './ProjectGetter': this.ProjectGetter,
  59. '../../models/Project': {
  60. Project: this.ProjectModel
  61. },
  62. '../User/UserGetter': this.UserGetter,
  63. '../ThirdPartyDataStore/TpdsUpdateSender': this.tpdsUpdateSender,
  64. './ProjectEntityHandler': this.ProjectEntityHandler,
  65. '../Collaborators/CollaboratorsHandler': this.CollaboratorsHandler,
  66. 'logger-sharelatex': {
  67. log() {},
  68. warn() {},
  69. err() {}
  70. },
  71. './ProjectTokenGenerator': (this.ProjectTokenGenerator = {}),
  72. 'settings-sharelatex': (this.settings = {
  73. defaultFeatures: 'default-features'
  74. })
  75. }
  76. }))
  77. })
  78. describe('getDetails', function() {
  79. it('should find the project and owner', function(done) {
  80. return this.handler.getDetails(this.project_id, (err, details) => {
  81. details.name.should.equal(this.project.name)
  82. details.description.should.equal(this.project.description)
  83. details.compiler.should.equal(this.project.compiler)
  84. details.features.should.equal(this.user.features)
  85. assert.equal(details.something, undefined)
  86. return done()
  87. })
  88. })
  89. it('should find overleaf metadata if it exists', function(done) {
  90. this.project.overleaf = { id: 'id' }
  91. return this.handler.getDetails(this.project_id, (err, details) => {
  92. details.overleaf.should.equal(this.project.overleaf)
  93. assert.equal(details.something, undefined)
  94. return done()
  95. })
  96. })
  97. it('should return an error for a non-existent project', function(done) {
  98. this.ProjectGetter.getProject.callsArg(2, null, null)
  99. const err = new Errors.NotFoundError('project not found')
  100. return this.handler.getDetails(
  101. '0123456789012345678901234',
  102. (error, details) => {
  103. err.should.eql(error)
  104. return done()
  105. }
  106. )
  107. })
  108. it('should return the default features if no owner found', function(done) {
  109. this.UserGetter.getUser.callsArgWith(1, null, null)
  110. return this.handler.getDetails(this.project_id, (err, details) => {
  111. details.features.should.equal(this.settings.defaultFeatures)
  112. return done()
  113. })
  114. })
  115. it('should return the error', function(done) {
  116. const error = 'some error'
  117. this.ProjectGetter.getProject.callsArgWith(2, error)
  118. return this.handler.getDetails(this.project_id, err => {
  119. err.should.equal(error)
  120. return done()
  121. })
  122. })
  123. })
  124. describe('transferOwnership', function() {
  125. beforeEach(function() {
  126. this.handler.generateUniqueName = sinon
  127. .stub()
  128. .callsArgWith(2, null, 'teapot')
  129. return this.ProjectModel.update.callsArgWith(2)
  130. })
  131. it("should return a not found error if the project can't be found", function(done) {
  132. this.ProjectGetter.getProject.callsArgWith(2)
  133. return this.handler.transferOwnership('abc', '123', function(err) {
  134. err.should.exist
  135. err.name.should.equal('NotFoundError')
  136. return done()
  137. })
  138. })
  139. it("should return a not found error if the user can't be found", function(done) {
  140. this.ProjectGetter.getProject.callsArgWith(2)
  141. return this.handler.transferOwnership('abc', '123', function(err) {
  142. err.should.exist
  143. err.name.should.equal('NotFoundError')
  144. return done()
  145. })
  146. })
  147. it('should return an error if user cannot be removed as collaborator ', function(done) {
  148. const errorMessage = 'user-cannot-be-removed'
  149. this.CollaboratorsHandler.removeUserFromProject.callsArgWith(
  150. 2,
  151. errorMessage
  152. )
  153. return this.handler.transferOwnership('abc', '123', function(err) {
  154. err.should.exist
  155. err.should.equal(errorMessage)
  156. return done()
  157. })
  158. })
  159. it('should transfer ownership of the project', function(done) {
  160. return this.handler.transferOwnership('abc', '123', () => {
  161. sinon.assert.calledWith(
  162. this.ProjectModel.update,
  163. { _id: 'abc' },
  164. sinon.match({ $set: { name: 'teapot' } })
  165. )
  166. return done()
  167. })
  168. })
  169. it("should remove the user from the project's collaborators", function(done) {
  170. return this.handler.transferOwnership('abc', '123', () => {
  171. sinon.assert.calledWith(
  172. this.CollaboratorsHandler.removeUserFromProject,
  173. 'abc',
  174. '123'
  175. )
  176. return done()
  177. })
  178. })
  179. it('should flush the project to tpds', function(done) {
  180. return this.handler.transferOwnership('abc', '123', () => {
  181. sinon.assert.calledWith(
  182. this.ProjectEntityHandler.flushProjectToThirdPartyDataStore,
  183. 'abc'
  184. )
  185. return done()
  186. })
  187. })
  188. it('should generate a unique name for the project', function(done) {
  189. return this.handler.transferOwnership('abc', '123', () => {
  190. sinon.assert.calledWith(
  191. this.handler.generateUniqueName,
  192. '123',
  193. this.project.name
  194. )
  195. return done()
  196. })
  197. })
  198. it('should append the supplied suffix to the project name, if passed', function(done) {
  199. return this.handler.transferOwnership('abc', '123', ' wombat', () => {
  200. sinon.assert.calledWith(
  201. this.handler.generateUniqueName,
  202. '123',
  203. `${this.project.name} wombat`
  204. )
  205. return done()
  206. })
  207. })
  208. })
  209. describe('getProjectDescription', function() {
  210. it('should make a call to mongo just for the description', function(done) {
  211. this.ProjectGetter.getProject.callsArgWith(2)
  212. return this.handler.getProjectDescription(
  213. this.project_id,
  214. (err, description) => {
  215. this.ProjectGetter.getProject
  216. .calledWith(this.project_id, { description: true })
  217. .should.equal(true)
  218. return done()
  219. }
  220. )
  221. })
  222. it('should return what the mongo call returns', function(done) {
  223. const err = 'error'
  224. const description = 'cool project'
  225. this.ProjectGetter.getProject.callsArgWith(2, err, { description })
  226. return this.handler.getProjectDescription(
  227. this.project_id,
  228. (returnedErr, returnedDescription) => {
  229. err.should.equal(returnedErr)
  230. description.should.equal(returnedDescription)
  231. return done()
  232. }
  233. )
  234. })
  235. })
  236. describe('setProjectDescription', function() {
  237. beforeEach(function() {
  238. return (this.description = 'updated teh description')
  239. })
  240. it('should update the project detials', function(done) {
  241. this.ProjectModel.update.callsArgWith(2)
  242. return this.handler.setProjectDescription(
  243. this.project_id,
  244. this.description,
  245. () => {
  246. this.ProjectModel.update
  247. .calledWith(
  248. { _id: this.project_id },
  249. { description: this.description }
  250. )
  251. .should.equal(true)
  252. return done()
  253. }
  254. )
  255. })
  256. })
  257. describe('renameProject', function() {
  258. beforeEach(function() {
  259. this.handler.validateProjectName = sinon.stub().yields()
  260. this.ProjectModel.update.callsArgWith(2)
  261. return (this.newName = 'new name here')
  262. })
  263. it('should update the project with the new name', function(done) {
  264. const newName = 'new name here'
  265. return this.handler.renameProject(this.project_id, this.newName, () => {
  266. this.ProjectModel.update
  267. .calledWith({ _id: this.project_id }, { name: this.newName })
  268. .should.equal(true)
  269. return done()
  270. })
  271. })
  272. it('should tell the tpdsUpdateSender', function(done) {
  273. return this.handler.renameProject(this.project_id, this.newName, () => {
  274. this.tpdsUpdateSender.moveEntity
  275. .calledWith({
  276. project_id: this.project_id,
  277. project_name: this.project.name,
  278. newProjectName: this.newName
  279. })
  280. .should.equal(true)
  281. return done()
  282. })
  283. })
  284. it('should not do anything with an invalid name', function(done) {
  285. this.handler.validateProjectName = sinon
  286. .stub()
  287. .yields(new Error('invalid name'))
  288. return this.handler.renameProject(this.project_id, this.newName, () => {
  289. this.tpdsUpdateSender.moveEntity.called.should.equal(false)
  290. this.ProjectModel.update.called.should.equal(false)
  291. return done()
  292. })
  293. })
  294. })
  295. describe('validateProjectName', function() {
  296. it('should reject undefined names', function(done) {
  297. return this.handler.validateProjectName(undefined, function(error) {
  298. expect(error).to.exist
  299. return done()
  300. })
  301. })
  302. it('should reject empty names', function(done) {
  303. return this.handler.validateProjectName('', function(error) {
  304. expect(error).to.exist
  305. return done()
  306. })
  307. })
  308. it('should reject names with /s', function(done) {
  309. return this.handler.validateProjectName('foo/bar', function(error) {
  310. expect(error).to.exist
  311. return done()
  312. })
  313. })
  314. it('should reject names with \\s', function(done) {
  315. return this.handler.validateProjectName('foo\\bar', function(error) {
  316. expect(error).to.exist
  317. return done()
  318. })
  319. })
  320. it('should reject long names', function(done) {
  321. return this.handler.validateProjectName(
  322. new Array(1000).join('a'),
  323. function(error) {
  324. expect(error).to.exist
  325. return done()
  326. }
  327. )
  328. })
  329. it('should accept normal names', function(done) {
  330. return this.handler.validateProjectName('foobar', function(error) {
  331. expect(error).to.not.exist
  332. return done()
  333. })
  334. })
  335. })
  336. describe('ensureProjectNameIsUnique', function() {
  337. beforeEach(function() {
  338. this.result = {
  339. owned: [
  340. { _id: 1, name: 'name' },
  341. { _id: 2, name: 'name1' },
  342. { _id: 3, name: 'name11' },
  343. { _id: 100, name: 'numeric' }
  344. ],
  345. readAndWrite: [{ _id: 4, name: 'name2' }, { _id: 5, name: 'name22' }],
  346. readOnly: [{ _id: 6, name: 'name3' }, { _id: 7, name: 'name33' }],
  347. tokenReadAndWrite: [
  348. { _id: 8, name: 'name4' },
  349. { _id: 9, name: 'name44' }
  350. ],
  351. tokenReadOnly: [
  352. { _id: 10, name: 'name5' },
  353. { _id: 11, name: 'name55' },
  354. { _id: 12, name: 'x'.repeat(15) }
  355. ]
  356. }
  357. for (let i of Array.from(
  358. [
  359. 1,
  360. 2,
  361. 3,
  362. 4,
  363. 5,
  364. 6,
  365. 7,
  366. 8,
  367. 9,
  368. 10,
  369. 11,
  370. 12,
  371. 13,
  372. 14,
  373. 15,
  374. 16,
  375. 17,
  376. 18,
  377. 19,
  378. 20
  379. ].concat([30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40])
  380. )) {
  381. this.result.owned.push({ _id: 100 + i, name: `numeric (${i})` })
  382. }
  383. return (this.ProjectGetter.findAllUsersProjects = sinon
  384. .stub()
  385. .callsArgWith(2, null, this.result))
  386. })
  387. it('should leave a unique name unchanged', function(done) {
  388. return this.handler.ensureProjectNameIsUnique(
  389. this.user_id,
  390. 'unique-name',
  391. ['-test-suffix'],
  392. function(error, name, changed) {
  393. expect(name).to.equal('unique-name')
  394. expect(changed).to.equal(false)
  395. return done()
  396. }
  397. )
  398. })
  399. it('should append a suffix to an existing name', function(done) {
  400. return this.handler.ensureProjectNameIsUnique(
  401. this.user_id,
  402. 'name1',
  403. ['-test-suffix'],
  404. function(error, name, changed) {
  405. expect(name).to.equal('name1-test-suffix')
  406. expect(changed).to.equal(true)
  407. return done()
  408. }
  409. )
  410. })
  411. it('should fallback to a second suffix when needed', function(done) {
  412. return this.handler.ensureProjectNameIsUnique(
  413. this.user_id,
  414. 'name1',
  415. ['1', '-test-suffix'],
  416. function(error, name, changed) {
  417. expect(name).to.equal('name1-test-suffix')
  418. expect(changed).to.equal(true)
  419. return done()
  420. }
  421. )
  422. })
  423. it('should truncate the name when append a suffix if the result is too long', function(done) {
  424. this.handler.MAX_PROJECT_NAME_LENGTH = 20
  425. return this.handler.ensureProjectNameIsUnique(
  426. this.user_id,
  427. 'x'.repeat(15),
  428. ['-test-suffix'],
  429. function(error, name, changed) {
  430. expect(name).to.equal('x'.repeat(8) + '-test-suffix')
  431. expect(changed).to.equal(true)
  432. return done()
  433. }
  434. )
  435. })
  436. it('should use a numeric index if no suffix is supplied', function(done) {
  437. return this.handler.ensureProjectNameIsUnique(
  438. this.user_id,
  439. 'name1',
  440. [],
  441. function(error, name, changed) {
  442. expect(name).to.equal('name1 (1)')
  443. expect(changed).to.equal(true)
  444. return done()
  445. }
  446. )
  447. })
  448. it('should use a numeric index if all suffixes are exhausted', function(done) {
  449. return this.handler.ensureProjectNameIsUnique(
  450. this.user_id,
  451. 'name',
  452. ['1', '11'],
  453. function(error, name, changed) {
  454. expect(name).to.equal('name (1)')
  455. expect(changed).to.equal(true)
  456. return done()
  457. }
  458. )
  459. })
  460. it('should find the next lowest available numeric index for the base name', function(done) {
  461. return this.handler.ensureProjectNameIsUnique(
  462. this.user_id,
  463. 'numeric',
  464. [],
  465. function(error, name, changed) {
  466. expect(name).to.equal('numeric (21)')
  467. expect(changed).to.equal(true)
  468. return done()
  469. }
  470. )
  471. })
  472. it('should find the next available numeric index when a numeric index is already present', function(done) {
  473. return this.handler.ensureProjectNameIsUnique(
  474. this.user_id,
  475. 'numeric (5)',
  476. [],
  477. function(error, name, changed) {
  478. expect(name).to.equal('numeric (21)')
  479. expect(changed).to.equal(true)
  480. return done()
  481. }
  482. )
  483. })
  484. it('should not find a numeric index lower than the one already present', function(done) {
  485. return this.handler.ensureProjectNameIsUnique(
  486. this.user_id,
  487. 'numeric (31)',
  488. [],
  489. function(error, name, changed) {
  490. expect(name).to.equal('numeric (41)')
  491. expect(changed).to.equal(true)
  492. return done()
  493. }
  494. )
  495. })
  496. })
  497. describe('fixProjectName', function() {
  498. it('should change empty names to Untitled', function() {
  499. return expect(this.handler.fixProjectName('')).to.equal('Untitled')
  500. })
  501. it('should replace / with -', function() {
  502. return expect(this.handler.fixProjectName('foo/bar')).to.equal('foo-bar')
  503. })
  504. it("should replace \\ with ''", function() {
  505. return expect(this.handler.fixProjectName('foo \\ bar')).to.equal(
  506. 'foo bar'
  507. )
  508. })
  509. it('should truncate long names', function() {
  510. return expect(
  511. this.handler.fixProjectName(new Array(1000).join('a'))
  512. ).to.equal('a'.repeat(150))
  513. })
  514. it('should accept normal names', function() {
  515. return expect(this.handler.fixProjectName('foobar')).to.equal('foobar')
  516. })
  517. })
  518. describe('setPublicAccessLevel', function() {
  519. beforeEach(function() {
  520. this.ProjectModel.update.callsArgWith(2)
  521. return (this.accessLevel = 'readOnly')
  522. })
  523. it('should update the project with the new level', function(done) {
  524. return this.handler.setPublicAccessLevel(
  525. this.project_id,
  526. this.accessLevel,
  527. () => {
  528. this.ProjectModel.update
  529. .calledWith(
  530. { _id: this.project_id },
  531. { publicAccesLevel: this.accessLevel }
  532. )
  533. .should.equal(true)
  534. return done()
  535. }
  536. )
  537. })
  538. it('should not produce an error', function(done) {
  539. return this.handler.setPublicAccessLevel(
  540. this.project_id,
  541. this.accessLevel,
  542. err => {
  543. expect(err).to.not.exist
  544. return done()
  545. }
  546. )
  547. })
  548. describe('when update produces an error', function() {
  549. beforeEach(function() {
  550. return this.ProjectModel.update.callsArgWith(2, new Error('woops'))
  551. })
  552. it('should produce an error', function(done) {
  553. return this.handler.setPublicAccessLevel(
  554. this.project_id,
  555. this.accessLevel,
  556. err => {
  557. expect(err).to.exist
  558. expect(err).to.be.instanceof(Error)
  559. return done()
  560. }
  561. )
  562. })
  563. })
  564. })
  565. describe('ensureTokensArePresent', function() {
  566. beforeEach(function() {})
  567. describe('when the project has tokens', function() {
  568. beforeEach(function() {
  569. this.project = {
  570. _id: this.project_id,
  571. tokens: {
  572. readOnly: 'aaa',
  573. readAndWrite: '42bbb',
  574. readAndWritePrefix: '42'
  575. }
  576. }
  577. this.ProjectGetter.getProject = sinon
  578. .stub()
  579. .callsArgWith(2, null, this.project)
  580. return (this.ProjectModel.update = sinon.stub())
  581. })
  582. it('should get the project', function(done) {
  583. return this.handler.ensureTokensArePresent(
  584. this.project_id,
  585. (err, tokens) => {
  586. expect(this.ProjectGetter.getProject.callCount).to.equal(1)
  587. expect(
  588. this.ProjectGetter.getProject.calledWith(this.project_id, {
  589. tokens: 1
  590. })
  591. ).to.equal(true)
  592. return done()
  593. }
  594. )
  595. })
  596. it('should not update the project with new tokens', function(done) {
  597. return this.handler.ensureTokensArePresent(
  598. this.project_id,
  599. (err, tokens) => {
  600. expect(this.ProjectModel.update.callCount).to.equal(0)
  601. return done()
  602. }
  603. )
  604. })
  605. it('should produce the tokens without error', function(done) {
  606. return this.handler.ensureTokensArePresent(
  607. this.project_id,
  608. (err, tokens) => {
  609. expect(err).to.not.exist
  610. expect(tokens).to.deep.equal(this.project.tokens)
  611. return done()
  612. }
  613. )
  614. })
  615. })
  616. describe('when tokens are missing', function() {
  617. beforeEach(function() {
  618. this.project = { _id: this.project_id }
  619. this.ProjectGetter.getProject = sinon
  620. .stub()
  621. .callsArgWith(2, null, this.project)
  622. this.readOnlyToken = 'abc'
  623. this.readAndWriteToken = '42def'
  624. this.readAndWriteTokenPrefix = '42'
  625. this.ProjectTokenGenerator.generateUniqueReadOnlyToken = sinon
  626. .stub()
  627. .callsArgWith(0, null, this.readOnlyToken)
  628. this.ProjectTokenGenerator.readAndWriteToken = sinon.stub().returns({
  629. token: this.readAndWriteToken,
  630. numericPrefix: this.readAndWriteTokenPrefix
  631. })
  632. return (this.ProjectModel.update = sinon.stub().callsArgWith(2, null))
  633. })
  634. it('should get the project', function(done) {
  635. return this.handler.ensureTokensArePresent(
  636. this.project_id,
  637. (err, tokens) => {
  638. expect(this.ProjectGetter.getProject.callCount).to.equal(1)
  639. expect(
  640. this.ProjectGetter.getProject.calledWith(this.project_id, {
  641. tokens: 1
  642. })
  643. ).to.equal(true)
  644. return done()
  645. }
  646. )
  647. })
  648. it('should update the project with new tokens', function(done) {
  649. return this.handler.ensureTokensArePresent(
  650. this.project_id,
  651. (err, tokens) => {
  652. expect(
  653. this.ProjectTokenGenerator.generateUniqueReadOnlyToken.callCount
  654. ).to.equal(1)
  655. expect(
  656. this.ProjectTokenGenerator.readAndWriteToken.callCount
  657. ).to.equal(1)
  658. expect(this.ProjectModel.update.callCount).to.equal(1)
  659. expect(
  660. this.ProjectModel.update.calledWith(
  661. { _id: this.project_id },
  662. {
  663. $set: {
  664. tokens: {
  665. readOnly: this.readOnlyToken,
  666. readAndWrite: this.readAndWriteToken,
  667. readAndWritePrefix: this.readAndWriteTokenPrefix
  668. }
  669. }
  670. }
  671. )
  672. ).to.equal(true)
  673. return done()
  674. }
  675. )
  676. })
  677. it('should produce the tokens without error', function(done) {
  678. return this.handler.ensureTokensArePresent(
  679. this.project_id,
  680. (err, tokens) => {
  681. expect(err).to.not.exist
  682. expect(tokens).to.deep.equal({
  683. readOnly: this.readOnlyToken,
  684. readAndWrite: this.readAndWriteToken,
  685. readAndWritePrefix: this.readAndWriteTokenPrefix
  686. })
  687. return done()
  688. }
  689. )
  690. })
  691. })
  692. })
  693. })