EditorHttpController.test.mjs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  1. import { beforeEach, describe, it, vi, expect } from 'vitest'
  2. import sinon from 'sinon'
  3. import mongodb from 'mongodb-legacy'
  4. import Errors from '../../../../app/src/Features/Errors/Errors.js'
  5. import MockRequest from '../helpers/MockRequest.mjs'
  6. import MockResponse from '../helpers/MockResponse.mjs'
  7. const { ObjectId } = mongodb
  8. const MODULE_PATH =
  9. '../../../../app/src/Features/Editor/EditorHttpController.mjs'
  10. describe('EditorHttpController', function () {
  11. beforeEach(async function (ctx) {
  12. ctx.ownerId = new ObjectId()
  13. ctx.project = {
  14. _id: new ObjectId(),
  15. owner_ref: ctx.ownerId,
  16. }
  17. ctx.user = {
  18. _id: new ObjectId(),
  19. projects: {},
  20. }
  21. ctx.members = [
  22. { user: { _id: 'owner', features: {} }, privilegeLevel: 'owner' },
  23. { user: { _id: 'one' }, privilegeLevel: 'readOnly' },
  24. ]
  25. ctx.ownerMember = ctx.members[0]
  26. ctx.invites = [{ _id: 'three' }, { _id: 'four' }]
  27. ctx.projectView = {
  28. _id: ctx.project._id,
  29. owner: {
  30. _id: 'owner',
  31. email: 'owner@example.com',
  32. other_property: true,
  33. },
  34. members: [
  35. { _id: 'owner', privileges: 'owner' },
  36. { _id: 'one', privileges: 'readOnly' },
  37. ],
  38. invites: [{ three: 3 }, { four: 4 }],
  39. }
  40. ctx.reducedProjectView = {
  41. _id: ctx.projectView._id,
  42. owner: { _id: ctx.projectView.owner._id },
  43. members: [],
  44. invites: [],
  45. }
  46. ctx.doc = { _id: new ObjectId(), name: 'excellent-original-idea.tex' }
  47. ctx.file = { _id: new ObjectId() }
  48. ctx.folder = { _id: new ObjectId() }
  49. ctx.source = 'editor'
  50. ctx.parentFolderId = 'mock-folder-id'
  51. ctx.req = new MockRequest(vi)
  52. ctx.res = new MockResponse(vi)
  53. ctx.next = sinon.stub()
  54. ctx.token = null
  55. ctx.docLines = ['hello', 'overleaf']
  56. ctx.AuthorizationManager = {
  57. isRestrictedUser: sinon.stub().returns(false),
  58. promises: {
  59. getPrivilegeLevelForProjectWithProjectAccess: sinon
  60. .stub()
  61. .resolves('owner'),
  62. },
  63. }
  64. const members = ctx.members
  65. const ownerMember = ctx.ownerMember
  66. ctx.CollaboratorsGetter = {
  67. ProjectAccess: class {
  68. loadOwnerAndInvitedMembers() {
  69. return { members, ownerMember }
  70. }
  71. loadOwner() {
  72. return ownerMember
  73. }
  74. isUserTokenMember() {
  75. return false
  76. }
  77. isUserInvitedMember() {
  78. return false
  79. }
  80. },
  81. promises: {
  82. isUserInvitedMemberOfProject: sinon.stub().resolves(false),
  83. },
  84. }
  85. ctx.CollaboratorsHandler = {
  86. promises: {
  87. userIsTokenMember: sinon.stub().resolves(false),
  88. },
  89. }
  90. ctx.invites = [
  91. {
  92. _id: 'invite_one',
  93. email: 'user-one@example.com',
  94. privileges: 'readOnly',
  95. projectId: ctx.project._id,
  96. },
  97. {
  98. _id: 'invite_two',
  99. email: 'user-two@example.com',
  100. privileges: 'readOnly',
  101. projectId: ctx.project._id,
  102. },
  103. ]
  104. ctx.CollaboratorsInviteGetter = {
  105. promises: {
  106. getAllInvites: sinon.stub().resolves(ctx.invites),
  107. },
  108. }
  109. ctx.EditorController = {
  110. promises: {
  111. addDoc: sinon.stub().resolves(ctx.doc),
  112. addFile: sinon.stub().resolves(ctx.file),
  113. addFolder: sinon.stub().resolves(ctx.folder),
  114. renameEntity: sinon.stub().resolves(),
  115. moveEntity: sinon.stub().resolves(),
  116. deleteEntity: sinon.stub().resolves(),
  117. },
  118. }
  119. ctx.ProjectDeleter = {
  120. promises: {
  121. unmarkAsDeletedByExternalSource: sinon.stub().resolves(),
  122. },
  123. }
  124. ctx.ProjectGetter = {
  125. promises: {
  126. getProjectWithoutDocLines: sinon.stub().resolves(ctx.project),
  127. },
  128. }
  129. ctx.ProjectEditorHandler = {
  130. buildProjectModelView: sinon.stub().returns(ctx.projectView),
  131. }
  132. ctx.Metrics = { inc: sinon.stub() }
  133. ctx.TokenAccessHandler = {
  134. getRequestToken: sinon.stub().returns(ctx.token),
  135. }
  136. ctx.SessionManager = {
  137. getLoggedInUserId: sinon.stub().returns(ctx.user._id),
  138. }
  139. ctx.ProjectEntityUpdateHandler = {
  140. promises: {
  141. convertDocToFile: sinon.stub().resolves(ctx.file),
  142. },
  143. }
  144. ctx.DocstoreManager = {
  145. promises: {
  146. getAllDeletedDocs: sinon.stub().resolves([]),
  147. },
  148. }
  149. ctx.HttpErrorHandler = {
  150. notFound: sinon.stub(),
  151. unprocessableEntity: sinon.stub(),
  152. }
  153. ctx.SplitTestHandler = {
  154. promises: {
  155. getAssignmentForUser: sinon.stub().resolves({ variant: 'default' }),
  156. },
  157. }
  158. ctx.UserGetter = { promises: { getUser: sinon.stub().resolves(null, {}) } }
  159. vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
  160. vi.importActual('../../../../app/src/Features/Errors/Errors.js')
  161. )
  162. vi.doMock(
  163. '../../../../app/src/Features/Project/ProjectDeleter.mjs',
  164. () => ({
  165. default: ctx.ProjectDeleter,
  166. })
  167. )
  168. vi.doMock('../../../../app/src/Features/Project/ProjectGetter.mjs', () => ({
  169. default: ctx.ProjectGetter,
  170. }))
  171. vi.doMock(
  172. '../../../../app/src/Features/Authorization/AuthorizationManager.mjs',
  173. () => ({
  174. default: ctx.AuthorizationManager,
  175. })
  176. )
  177. vi.doMock(
  178. '../../../../app/src/Features/Project/ProjectEditorHandler.mjs',
  179. () => ({
  180. default: ctx.ProjectEditorHandler,
  181. })
  182. )
  183. vi.doMock(
  184. '../../../../app/src/Features/Editor/EditorController.mjs',
  185. () => ({
  186. default: ctx.EditorController,
  187. })
  188. )
  189. vi.doMock('@overleaf/metrics', () => ({
  190. default: ctx.Metrics,
  191. }))
  192. vi.doMock(
  193. '../../../../app/src/Features/Collaborators/CollaboratorsGetter.mjs',
  194. () => ({
  195. default: ctx.CollaboratorsGetter,
  196. })
  197. )
  198. vi.doMock(
  199. '../../../../app/src/Features/Collaborators/CollaboratorsHandler.mjs',
  200. () => ({
  201. default: ctx.CollaboratorsHandler,
  202. })
  203. )
  204. vi.doMock(
  205. '../../../../app/src/Features/Collaborators/CollaboratorsInviteGetter.mjs',
  206. () => ({
  207. default: ctx.CollaboratorsInviteGetter,
  208. })
  209. )
  210. vi.doMock(
  211. '../../../../app/src/Features/TokenAccess/TokenAccessHandler.mjs',
  212. () => ({
  213. default: ctx.TokenAccessHandler,
  214. })
  215. )
  216. vi.doMock(
  217. '../../../../app/src/Features/Authentication/SessionManager.mjs',
  218. () => ({
  219. default: ctx.SessionManager,
  220. })
  221. )
  222. vi.doMock('../../../../app/src/infrastructure/FileWriter.mjs', () => ({
  223. default: ctx.FileWriter,
  224. }))
  225. vi.doMock(
  226. '../../../../app/src/Features/Project/ProjectEntityUpdateHandler.mjs',
  227. () => ({
  228. default: ctx.ProjectEntityUpdateHandler,
  229. })
  230. )
  231. vi.doMock(
  232. '../../../../app/src/Features/Docstore/DocstoreManager.mjs',
  233. () => ({
  234. default: ctx.DocstoreManager,
  235. })
  236. )
  237. vi.doMock(
  238. '../../../../app/src/Features/Errors/HttpErrorHandler.mjs',
  239. () => ({
  240. default: ctx.HttpErrorHandler,
  241. })
  242. )
  243. vi.doMock(
  244. '../../../../app/src/Features/SplitTests/SplitTestHandler.mjs',
  245. () => ({
  246. default: ctx.SplitTestHandler,
  247. })
  248. )
  249. vi.doMock(
  250. '../../../../app/src/Features/Compile/CompileManager.mjs',
  251. () => ({
  252. default: {},
  253. })
  254. )
  255. vi.doMock('../../../../app/src/Features/User/UserGetter.mjs', () => ({
  256. default: ctx.UserGetter,
  257. }))
  258. ctx.EditorHttpController = (await import(MODULE_PATH)).default
  259. })
  260. describe('joinProject', function () {
  261. beforeEach(function (ctx) {
  262. ctx.req.params = { Project_id: ctx.project._id.toString() }
  263. ctx.req.query = { user_id: ctx.user._id }
  264. ctx.req.body = { userId: ctx.user._id.toString() }
  265. })
  266. describe('successfully', function () {
  267. beforeEach(async function (ctx) {
  268. await new Promise(resolve => {
  269. sinon
  270. .stub(
  271. ctx.CollaboratorsGetter.ProjectAccess.prototype,
  272. 'isUserInvitedMember'
  273. )
  274. .returns(true)
  275. ctx.res.callback = resolve
  276. ctx.EditorHttpController.joinProject(ctx.req, ctx.res)
  277. })
  278. })
  279. it('should request a full view', function (ctx) {
  280. expect(
  281. ctx.ProjectEditorHandler.buildProjectModelView
  282. ).to.have.been.calledWith(
  283. ctx.project,
  284. ctx.ownerMember,
  285. ctx.members,
  286. ctx.invites,
  287. false
  288. )
  289. })
  290. it('should return the project and privilege level', function (ctx) {
  291. expect(ctx.res.json).toHaveBeenCalledWith({
  292. project: ctx.projectView,
  293. privilegeLevel: 'owner',
  294. isRestrictedUser: false,
  295. isTokenMember: false,
  296. isInvitedMember: true,
  297. })
  298. })
  299. it('should not try to unmark the project as deleted', function (ctx) {
  300. expect(ctx.ProjectDeleter.promises.unmarkAsDeletedByExternalSource).not
  301. .to.have.been.called
  302. })
  303. it('should send an inc metric', function (ctx) {
  304. expect(ctx.Metrics.inc).to.have.been.calledWith('editor.join-project')
  305. })
  306. })
  307. describe('when the project is marked as deleted', function () {
  308. beforeEach(async function (ctx) {
  309. await new Promise(resolve => {
  310. ctx.projectView.deletedByExternalDataSource = true
  311. ctx.res.callback = resolve
  312. ctx.EditorHttpController.joinProject(ctx.req, ctx.res)
  313. })
  314. })
  315. it('should unmark the project as deleted', function (ctx) {
  316. expect(
  317. ctx.ProjectDeleter.promises.unmarkAsDeletedByExternalSource
  318. ).to.have.been.calledWith(ctx.project._id.toString())
  319. })
  320. })
  321. describe('with a restricted user', function () {
  322. beforeEach(async function (ctx) {
  323. ctx.ProjectEditorHandler.buildProjectModelView.returns(
  324. ctx.reducedProjectView
  325. )
  326. ctx.AuthorizationManager.isRestrictedUser.returns(true)
  327. ctx.AuthorizationManager.promises.getPrivilegeLevelForProjectWithProjectAccess.resolves(
  328. 'readOnly'
  329. )
  330. await new Promise(resolve => {
  331. ctx.res.callback = resolve
  332. ctx.EditorHttpController.joinProject(ctx.req, ctx.res)
  333. })
  334. })
  335. it('should request a restricted view', function (ctx) {
  336. expect(
  337. ctx.ProjectEditorHandler.buildProjectModelView
  338. ).to.have.been.calledWith(ctx.project, ctx.ownerMember, [], [], true)
  339. })
  340. it('should mark the user as restricted, and hide details of owner', function (ctx) {
  341. expect(ctx.res.json).toHaveBeenCalledWith({
  342. project: ctx.reducedProjectView,
  343. privilegeLevel: 'readOnly',
  344. isRestrictedUser: true,
  345. isTokenMember: false,
  346. isInvitedMember: false,
  347. })
  348. })
  349. })
  350. describe('when not authorized', function () {
  351. beforeEach(async function (ctx) {
  352. await new Promise(resolve => {
  353. ctx.AuthorizationManager.promises.getPrivilegeLevelForProjectWithProjectAccess.resolves(
  354. null
  355. )
  356. ctx.res.callback = resolve
  357. ctx.EditorHttpController.joinProject(ctx.req, ctx.res)
  358. })
  359. })
  360. it('should send a 403 response', function (ctx) {
  361. expect(ctx.res.statusCode).to.equal(403)
  362. })
  363. })
  364. describe('with an anonymous user', function () {
  365. beforeEach(async function (ctx) {
  366. ctx.token = 'token'
  367. ctx.TokenAccessHandler.getRequestToken.returns(ctx.token)
  368. ctx.ProjectEditorHandler.buildProjectModelView.returns(
  369. ctx.reducedProjectView
  370. )
  371. ctx.req.body = {
  372. userId: 'anonymous-user',
  373. anonymousAccessToken: ctx.token,
  374. }
  375. ctx.AuthorizationManager.isRestrictedUser
  376. .withArgs(null, 'readOnly', false, false)
  377. .returns(true)
  378. ctx.AuthorizationManager.promises.getPrivilegeLevelForProjectWithProjectAccess
  379. .withArgs(null, ctx.project._id.toString(), ctx.token)
  380. .resolves('readOnly')
  381. await new Promise(resolve => {
  382. ctx.res.callback = resolve
  383. ctx.EditorHttpController.joinProject(ctx.req, ctx.res)
  384. })
  385. })
  386. it('should request a restricted view', function (ctx) {
  387. expect(
  388. ctx.ProjectEditorHandler.buildProjectModelView
  389. ).to.have.been.calledWith(ctx.project, ctx.ownerMember, [], [], true)
  390. })
  391. it('should mark the user as restricted', function (ctx) {
  392. expect(ctx.res.json).toHaveBeenCalledWith({
  393. project: ctx.reducedProjectView,
  394. privilegeLevel: 'readOnly',
  395. isRestrictedUser: true,
  396. isTokenMember: false,
  397. isInvitedMember: false,
  398. })
  399. })
  400. })
  401. describe('with a token access user', function () {
  402. beforeEach(async function (ctx) {
  403. await new Promise(resolve => {
  404. sinon
  405. .stub(
  406. ctx.CollaboratorsGetter.ProjectAccess.prototype,
  407. 'isUserInvitedMember'
  408. )
  409. .returns(false)
  410. sinon
  411. .stub(
  412. ctx.CollaboratorsGetter.ProjectAccess.prototype,
  413. 'isUserTokenMember'
  414. )
  415. .returns(true)
  416. ctx.AuthorizationManager.promises.getPrivilegeLevelForProjectWithProjectAccess.resolves(
  417. 'readAndWrite'
  418. )
  419. ctx.res.callback = resolve
  420. ctx.EditorHttpController.joinProject(ctx.req, ctx.res)
  421. })
  422. })
  423. it('should mark the user as being a token-access member', function (ctx) {
  424. expect(ctx.res.json).toHaveBeenCalledWith({
  425. project: ctx.projectView,
  426. privilegeLevel: 'readAndWrite',
  427. isRestrictedUser: false,
  428. isTokenMember: true,
  429. isInvitedMember: false,
  430. })
  431. })
  432. })
  433. describe('when project is not found', function () {
  434. beforeEach(async function (ctx) {
  435. ctx.ProjectGetter.promises.getProjectWithoutDocLines.resolves(null)
  436. await new Promise(resolve => {
  437. ctx.next.callsFake(() => resolve())
  438. ctx.EditorHttpController.joinProject(ctx.req, ctx.res, ctx.next)
  439. })
  440. })
  441. it('should handle return not found error', function (ctx) {
  442. expect(ctx.next).to.have.been.calledWith(
  443. sinon.match.instanceOf(Errors.NotFoundError)
  444. )
  445. })
  446. })
  447. })
  448. describe('addDoc', function () {
  449. beforeEach(function (ctx) {
  450. ctx.req.params = { Project_id: ctx.project._id }
  451. ctx.req.body = {
  452. name: (ctx.docName = 'doc-name'),
  453. parent_folder_id: ctx.parentFolderId,
  454. }
  455. })
  456. describe('successfully', function () {
  457. beforeEach(async function (ctx) {
  458. await new Promise(resolve => {
  459. ctx.res.callback = resolve
  460. ctx.EditorHttpController.addDoc(ctx.req, ctx.res)
  461. })
  462. })
  463. it('should call EditorController.addDoc', function (ctx) {
  464. expect(ctx.EditorController.promises.addDoc).to.have.been.calledWith(
  465. ctx.project._id,
  466. ctx.parentFolderId,
  467. ctx.docName,
  468. [],
  469. 'editor',
  470. ctx.user._id
  471. )
  472. })
  473. it('should send the doc back as JSON', function (ctx) {
  474. expect(ctx.res.json).toHaveBeenCalledWith(ctx.doc)
  475. })
  476. })
  477. describe('unsuccesfully', function () {
  478. it('handle name too short', async function (ctx) {
  479. await new Promise(resolve => {
  480. ctx.req.body.name = ''
  481. ctx.res.callback = () => {
  482. expect(ctx.res.statusCode).to.equal(400)
  483. resolve()
  484. }
  485. ctx.EditorHttpController.addDoc(ctx.req, ctx.res)
  486. })
  487. })
  488. it('handle too many files', async function (ctx) {
  489. ctx.EditorController.promises.addDoc.rejects(
  490. new Error('project_has_too_many_files')
  491. )
  492. await new Promise(resolve => {
  493. ctx.res.callback = () => {
  494. expect(ctx.res.body).to.equal('"project_has_too_many_files_limit"')
  495. expect(ctx.res.status).toHaveBeenCalledWith(400)
  496. resolve()
  497. }
  498. ctx.EditorHttpController.addDoc(ctx.req, ctx.res)
  499. })
  500. })
  501. })
  502. })
  503. describe('addFolder', function () {
  504. beforeEach(function (ctx) {
  505. ctx.folderName = 'folder-name'
  506. ctx.req.params = { Project_id: ctx.project._id }
  507. ctx.req.body = {
  508. name: ctx.folderName,
  509. parent_folder_id: ctx.parentFolderId,
  510. }
  511. })
  512. describe('successfully', function () {
  513. beforeEach(async function (ctx) {
  514. await new Promise(resolve => {
  515. ctx.res.callback = resolve
  516. ctx.EditorHttpController.addFolder(ctx.req, ctx.res)
  517. })
  518. })
  519. it('should call EditorController.addFolder', function (ctx) {
  520. expect(ctx.EditorController.promises.addFolder).to.have.been.calledWith(
  521. ctx.project._id,
  522. ctx.parentFolderId,
  523. ctx.folderName,
  524. 'editor'
  525. )
  526. })
  527. it('should send the folder back as JSON', function (ctx) {
  528. expect(ctx.res.json).toHaveBeenCalledWith(ctx.folder)
  529. })
  530. })
  531. describe('unsuccesfully', function () {
  532. it('handle name too short', async function (ctx) {
  533. await new Promise(resolve => {
  534. ctx.req.body.name = ''
  535. ctx.res.callback = () => {
  536. expect(ctx.res.statusCode).to.equal(400)
  537. resolve()
  538. }
  539. ctx.EditorHttpController.addFolder(ctx.req, ctx.res)
  540. })
  541. })
  542. it('handle too many files', async function (ctx) {
  543. await new Promise(resolve => {
  544. ctx.EditorController.promises.addFolder.rejects(
  545. new Error('project_has_too_many_files')
  546. )
  547. ctx.res.callback = () => {
  548. expect(ctx.res.body).to.equal('"project_has_too_many_files_limit"')
  549. expect(ctx.res.statusCode).to.equal(400)
  550. resolve()
  551. }
  552. ctx.EditorHttpController.addFolder(ctx.req, ctx.res)
  553. })
  554. })
  555. it('handle invalid element name', async function (ctx) {
  556. await new Promise(resolve => {
  557. ctx.EditorController.promises.addFolder.rejects(
  558. new Error('invalid element name')
  559. )
  560. ctx.res.callback = () => {
  561. expect(ctx.res.body).to.equal('"invalid_file_name"')
  562. expect(ctx.res.statusCode).to.equal(400)
  563. resolve()
  564. }
  565. ctx.EditorHttpController.addFolder(ctx.req, ctx.res)
  566. })
  567. })
  568. })
  569. })
  570. describe('renameEntity', function () {
  571. beforeEach(function (ctx) {
  572. ctx.entityId = 'entity-id-123'
  573. ctx.entityType = 'entity-type'
  574. ctx.req.params = {
  575. Project_id: ctx.project._id,
  576. entity_id: ctx.entityId,
  577. entity_type: ctx.entityType,
  578. }
  579. })
  580. describe('successfully', function () {
  581. beforeEach(async function (ctx) {
  582. await new Promise(resolve => {
  583. ctx.newName = 'new-name'
  584. ctx.req.body = { name: ctx.newName, source: ctx.source }
  585. ctx.res.callback = resolve
  586. ctx.EditorHttpController.renameEntity(ctx.req, ctx.res)
  587. })
  588. })
  589. it('should call EditorController.renameEntity', function (ctx) {
  590. expect(
  591. ctx.EditorController.promises.renameEntity
  592. ).to.have.been.calledWith(
  593. ctx.project._id,
  594. ctx.entityId,
  595. ctx.entityType,
  596. ctx.newName,
  597. ctx.user._id,
  598. ctx.source
  599. )
  600. })
  601. it('should send back a success response', function (ctx) {
  602. expect(ctx.res.sendStatus).toHaveBeenCalledWith(204)
  603. })
  604. })
  605. describe('with long name', function () {
  606. beforeEach(function (ctx) {
  607. ctx.newName = 'long'.repeat(100)
  608. ctx.req.body = { name: ctx.newName, source: ctx.source }
  609. ctx.EditorHttpController.renameEntity(ctx.req, ctx.res)
  610. })
  611. it('should send back a bad request status code', function (ctx) {
  612. expect(ctx.res.statusCode).to.equal(400)
  613. })
  614. })
  615. describe('with 0 length name', function () {
  616. beforeEach(function (ctx) {
  617. ctx.newName = ''
  618. ctx.req.body = { name: ctx.newName, source: ctx.source }
  619. ctx.EditorHttpController.renameEntity(ctx.req, ctx.res)
  620. })
  621. it('should send back a bad request status code', function (ctx) {
  622. expect(ctx.res.statusCode).to.equal(400)
  623. })
  624. })
  625. })
  626. describe('moveEntity', function () {
  627. beforeEach(async function (ctx) {
  628. await new Promise(resolve => {
  629. ctx.entityId = 'entity-id-123'
  630. ctx.entityType = 'entity-type'
  631. ctx.folderId = 'folder-id-123'
  632. ctx.req.params = {
  633. Project_id: ctx.project._id,
  634. entity_id: ctx.entityId,
  635. entity_type: ctx.entityType,
  636. }
  637. ctx.req.body = { folder_id: ctx.folderId, source: ctx.source }
  638. ctx.res.callback = resolve
  639. ctx.EditorHttpController.moveEntity(ctx.req, ctx.res)
  640. })
  641. })
  642. it('should call EditorController.moveEntity', function (ctx) {
  643. expect(ctx.EditorController.promises.moveEntity).to.have.been.calledWith(
  644. ctx.project._id,
  645. ctx.entityId,
  646. ctx.folderId,
  647. ctx.entityType,
  648. ctx.user._id,
  649. ctx.source
  650. )
  651. })
  652. it('should send back a success response', function (ctx) {
  653. expect(ctx.res.statusCode).to.equal(204)
  654. })
  655. })
  656. describe('deleteEntity', function () {
  657. beforeEach(async function (ctx) {
  658. await new Promise(resolve => {
  659. ctx.entityId = 'entity-id-123'
  660. ctx.entityType = 'entity-type'
  661. ctx.req.params = {
  662. Project_id: ctx.project._id,
  663. entity_id: ctx.entityId,
  664. entity_type: ctx.entityType,
  665. }
  666. ctx.res.callback = resolve
  667. ctx.EditorHttpController.deleteEntity(ctx.req, ctx.res)
  668. })
  669. })
  670. it('should call EditorController.deleteEntity', function (ctx) {
  671. expect(
  672. ctx.EditorController.promises.deleteEntity
  673. ).to.have.been.calledWith(
  674. ctx.project._id,
  675. ctx.entityId,
  676. ctx.entityType,
  677. 'editor',
  678. ctx.user._id
  679. )
  680. })
  681. it('should send back a success response', function (ctx) {
  682. expect(ctx.res.statusCode).to.equal(204)
  683. })
  684. })
  685. })