ProjectCRUDTests.mjs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import { expect } from 'chai'
  2. import UserHelper from './helpers/User.mjs'
  3. import { Project } from '../../../app/src/models/Project.mjs'
  4. import mongodb from 'mongodb-legacy'
  5. import cheerio from 'cheerio'
  6. import { Subscription } from '../../../app/src/models/Subscription.mjs'
  7. import Features from '../../../app/src/infrastructure/Features.mjs'
  8. import Metrics from './helpers/metrics.mjs'
  9. const ObjectId = mongodb.ObjectId
  10. const User = UserHelper.promises
  11. async function getProjectAccessStats() {
  12. const hit = await Metrics.promises.sumMetrics(
  13. s => s.startsWith('project_access_cache') && s.includes('"hit"')
  14. )
  15. const miss = await Metrics.promises.sumMetrics(
  16. s => s.startsWith('project_access_cache') && s.includes('"miss"')
  17. )
  18. return { hit, miss }
  19. }
  20. describe('Project CRUD', function () {
  21. beforeEach(async function () {
  22. this.user = new User()
  23. await this.user.login()
  24. this.projectId = await this.user.createProject('example-project')
  25. })
  26. describe('project page', function () {
  27. const loadProject = async function (user, projectId) {
  28. const { response, body } = await user.doRequest(
  29. 'GET',
  30. `/project/${projectId}`
  31. )
  32. expect(response.statusCode).to.equal(200)
  33. return body
  34. }
  35. it('should cast refProviders to booleans', async function () {
  36. await this.user.mongoUpdate({
  37. $set: {
  38. refProviders: {
  39. mendeley: { encrypted: 'aaa' },
  40. zotero: { encrypted: 'bbb' },
  41. },
  42. },
  43. })
  44. const body = await loadProject(this.user, this.projectId)
  45. const dom = cheerio.load(body)
  46. const metaOlUser = dom('meta[name="ol-user"]')[0]
  47. const userData = JSON.parse(metaOlUser.attribs.content)
  48. expect(userData.refProviders.mendeley).to.equal(true)
  49. expect(userData.refProviders.zotero).to.equal(true)
  50. })
  51. it('should show UpgradePrompt for user without a subscription', async function () {
  52. const body = await loadProject(this.user, this.projectId)
  53. expect(body).to.include(
  54. Features.hasFeature('saas')
  55. ? // `content` means true in this context
  56. '<meta name="ol-showUpgradePrompt" data-type="boolean" content>'
  57. : '<meta name="ol-showUpgradePrompt" data-type="boolean">'
  58. )
  59. })
  60. it('should not show UpgradePrompt for user with a subscription', async function () {
  61. await Subscription.create({
  62. admin_id: this.user._id,
  63. manager_ids: [this.user._id],
  64. })
  65. const body = await loadProject(this.user, this.projectId)
  66. // having no `content` means false in this context
  67. expect(body).to.include(
  68. '<meta name="ol-showUpgradePrompt" data-type="boolean">'
  69. )
  70. })
  71. it('should cache the project access', async function () {
  72. const prev = await getProjectAccessStats()
  73. await loadProject(this.user, this.projectId)
  74. if (Features.hasFeature('saas')) {
  75. expect(await getProjectAccessStats()).to.deep.equal({
  76. hit: prev.hit + 7,
  77. miss: prev.miss + 1,
  78. })
  79. } else {
  80. expect(await getProjectAccessStats()).to.deep.equal({
  81. hit: prev.hit + 4,
  82. miss: prev.miss + 1,
  83. })
  84. }
  85. })
  86. })
  87. describe("when project doesn't exist", function () {
  88. it('should return 404', async function () {
  89. const { response } = await this.user.doRequest(
  90. 'GET',
  91. '/project/aaaaaaaaaaaaaaaaaaaaaaaa'
  92. )
  93. expect(response.statusCode).to.equal(404)
  94. })
  95. })
  96. describe('when project has malformed id', function () {
  97. it('should return 404', async function () {
  98. const { response } = await this.user.doRequest('GET', '/project/blah')
  99. expect(response.statusCode).to.equal(404)
  100. })
  101. })
  102. describe('when trashing a project', function () {
  103. it('should mark the project as trashed for the user', async function () {
  104. const { response } = await this.user.doRequest(
  105. 'POST',
  106. `/project/${this.projectId}/trash`
  107. )
  108. expect(response.statusCode).to.equal(200)
  109. const trashedProject = await Project.findById(this.projectId).exec()
  110. expectObjectIdArrayEqual(trashedProject.trashed, [this.user._id])
  111. })
  112. it('does nothing if the user has already trashed the project', async function () {
  113. // Mark as trashed the first time
  114. await this.user.doRequest('POST', `/project/${this.projectId}/trash`)
  115. // And then a second time
  116. await this.user.doRequest('POST', `/project/${this.projectId}/trash`)
  117. const trashedProject = await Project.findById(this.projectId).exec()
  118. expectObjectIdArrayEqual(trashedProject.trashed, [this.user._id])
  119. })
  120. describe('with an array archived state', function () {
  121. it('should mark the project as not archived for the user', async function () {
  122. await Project.updateOne(
  123. { _id: this.projectId },
  124. { $set: { archived: [new ObjectId(this.user._id)] } }
  125. ).exec()
  126. const { response } = await this.user.doRequest(
  127. 'POST',
  128. `/project/${this.projectId}/trash`
  129. )
  130. expect(response.statusCode).to.equal(200)
  131. const trashedProject = await Project.findById(this.projectId).exec()
  132. expectObjectIdArrayEqual(trashedProject.archived, [])
  133. })
  134. })
  135. })
  136. describe('when untrashing a project', function () {
  137. it('should mark the project as untrashed for the user', async function () {
  138. await Project.updateOne(
  139. { _id: this.projectId },
  140. { trashed: [new ObjectId(this.user._id)] }
  141. ).exec()
  142. const { response } = await this.user.doRequest(
  143. 'DELETE',
  144. `/project/${this.projectId}/trash`
  145. )
  146. expect(response.statusCode).to.equal(200)
  147. const trashedProject = await Project.findById(this.projectId).exec()
  148. expectObjectIdArrayEqual(trashedProject.trashed, [])
  149. })
  150. it('does nothing if the user has already untrashed the project', async function () {
  151. await Project.updateOne(
  152. { _id: this.projectId },
  153. { trashed: [new ObjectId(this.user._id)] }
  154. ).exec()
  155. // Mark as untrashed the first time
  156. await this.user.doRequest('DELETE', `/project/${this.projectId}/trash`)
  157. // And then a second time
  158. await this.user.doRequest('DELETE', `/project/${this.projectId}/trash`)
  159. const trashedProject = await Project.findById(this.projectId).exec()
  160. expectObjectIdArrayEqual(trashedProject.trashed, [])
  161. })
  162. it('sets trashed to an empty array if not set', async function () {
  163. await this.user.doRequest('DELETE', `/project/${this.projectId}/trash`)
  164. const trashedProject = await Project.findById(this.projectId).exec()
  165. expectObjectIdArrayEqual(trashedProject.trashed, [])
  166. })
  167. })
  168. describe('ProjectAdminSettings', async function () {
  169. it('publicAccessLevel can be set to private', async function () {
  170. const { response } = await this.user.doRequest('POST', {
  171. url: `/project/${this.projectId}/settings/admin`,
  172. json: {
  173. publicAccessLevel: 'private',
  174. },
  175. })
  176. expect(response.statusCode).to.equal(204)
  177. const project = await Project.findById(this.projectId).exec()
  178. expect(project.publicAccesLevel).to.equal('private')
  179. })
  180. it('publicAccessLevel can be set to tokenBased', async function () {
  181. await this.user.makePrivate(this.projectId)
  182. const { response } = await this.user.doRequest('POST', {
  183. url: `/project/${this.projectId}/settings/admin`,
  184. json: {
  185. publicAccessLevel: 'tokenBased',
  186. },
  187. })
  188. expect(response.statusCode).to.equal(204)
  189. const project = await Project.findById(this.projectId).exec()
  190. expect(project.publicAccesLevel).to.equal('tokenBased')
  191. })
  192. it('returns a 400 when publicAccessLevel is an unsupported access level', async function () {
  193. await this.user.makePrivate(this.projectId)
  194. const { response, body } = await this.user.doRequest('POST', {
  195. url: `/project/${this.projectId}/settings/admin`,
  196. json: {
  197. publicAccessLevel: 'readOnly',
  198. },
  199. })
  200. expect(response.statusCode).to.equal(400)
  201. expect(body.details[0].message).to.equal('unexpected access level')
  202. const project = await Project.findById(this.projectId).exec()
  203. expect(project.publicAccesLevel).to.equal('private')
  204. })
  205. it('returns a 500 when no publicAccessLevel is provided', async function () {
  206. const { response, body } = await this.user.doRequest('POST', {
  207. url: `/project/${this.projectId}/settings/admin`,
  208. json: {},
  209. })
  210. expect(response.statusCode).to.equal(500)
  211. expect(body).to.equal('Internal Server Error')
  212. })
  213. })
  214. })
  215. function expectObjectIdArrayEqual(objectIdArray, stringArray) {
  216. const stringifiedArray = objectIdArray.map(id => id.toString())
  217. expect(stringifiedArray).to.deep.equal(stringArray)
  218. }