UserMembershipController.test.mjs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. import { expect, vi, describe, it, beforeEach } from 'vitest'
  2. import MockRequest from '../helpers/MockRequest.mjs'
  3. import MockResponse from '../helpers/MockResponse.mjs'
  4. import EntityConfigs from '../../../../app/src/Features/UserMembership/UserMembershipEntityConfigs.mjs'
  5. import Errors from '../../../../app/src/Features/Errors/Errors.js'
  6. import UserMembershipErrors from '../../../../app/src/Features/UserMembership/UserMembershipErrors.mjs'
  7. const modulePath =
  8. '../../../../app/src/Features/UserMembership/UserMembershipController.mjs'
  9. vi.mock(
  10. '../../../../app/src/Features/UserMembership/UserMembershipErrors.mjs',
  11. () =>
  12. vi.importActual(
  13. '../../../../app/src/Features/UserMembership/UserMembershipErrors.mjs'
  14. )
  15. )
  16. vi.mock('../../../../app/src/Features/Errors/Errors.js', () =>
  17. vi.importActual('../../../../app/src/Features/Errors/Errors.js')
  18. )
  19. describe('UserMembershipController', () => {
  20. let recurlySubscriptionId
  21. beforeEach(async ctx => {
  22. recurlySubscriptionId = 'mock-recurly-subscription-id'
  23. ctx.req = new MockRequest(vi)
  24. ctx.req.params.id = 'mock-entity-id'
  25. ctx.req.ip = '1.2.3.4'
  26. ctx.user = { _id: 'mock-user-id' }
  27. ctx.newUser = { _id: 'mock-new-user-id', email: 'new-user-email@foo.bar' }
  28. ctx.subscription = {
  29. _id: 'mock-subscription-id',
  30. admin_id: 'mock-admin-id',
  31. manager_ids: ['mock-admin-id'],
  32. planCode: 'group_professional',
  33. recurlySubscription_id: recurlySubscriptionId,
  34. fetchV1Data: vi.fn(callback => callback(null, ctx.subscription)),
  35. }
  36. ctx.institution = {
  37. _id: 'mock-institution-id',
  38. v1Id: 123,
  39. fetchV1Data: vi.fn(callback => {
  40. const institution = { ...ctx.institution }
  41. institution.name = 'Test Institution Name'
  42. callback(null, institution)
  43. }),
  44. managerIds: ['mock-member-id-1'],
  45. }
  46. ctx.users = [
  47. {
  48. _id: 'mock-member-id-1',
  49. email: 'mock-email-1@foo.com',
  50. last_logged_in_at: '2020-08-09T12:43:11.467Z',
  51. last_active_at: '2021-08-09T12:43:11.467Z',
  52. },
  53. {
  54. _id: 'mock-member-id-2',
  55. email: 'mock-email-2@foo.com',
  56. last_logged_in_at: '2020-05-20T10:41:11.407Z',
  57. last_active_at: '2021-05-20T10:41:11.407Z',
  58. },
  59. {
  60. _id: 'mock-member-id-3',
  61. email: 'mock-email-3@foo.com',
  62. last_logged_in_at: '2021-08-10T10:41:11.407Z',
  63. last_active_at: '2021-08-20T10:41:11.407Z',
  64. enrollment: {
  65. managedBy: 'some-other-subscription-id',
  66. enrolledAt: '2021-05-20T10:41:11.407Z',
  67. sso: undefined,
  68. },
  69. },
  70. {
  71. _id: 'mock-member-id-4',
  72. email: 'mock-email-4@foo.com',
  73. last_logged_in_at: '2021-01-01T10:41:11.407Z',
  74. last_active_at: '2021-01-02T10:41:11.407Z',
  75. enrollment: {
  76. managedBy: 'mock-subscription-id',
  77. enrolledAt: '2021-01-02T10:41:11.407Z',
  78. sso: undefined,
  79. },
  80. },
  81. {
  82. _id: 'mock-member-id-5',
  83. email: 'mock-email-5@foo.com',
  84. last_logged_in_at: '2023-01-01T10:41:11.407Z',
  85. last_active_at: '2023-01-02T10:41:11.407Z',
  86. enrollment: {
  87. sso: [{ groupId: ctx.subscription._id }],
  88. },
  89. },
  90. {
  91. _id: 'mock-member-id-6',
  92. email: 'mock-email-6@foo.com',
  93. last_logged_in_at: '2024-01-01T10:41:11.407Z',
  94. last_active_at: '2024-01-02T10:41:11.407Z',
  95. enrollment: {
  96. managedBy: 'mock-subscription-id',
  97. enrolledAt: '2024-01-02T10:41:11.407Z',
  98. sso: [{ groupId: ctx.subscription._id }],
  99. },
  100. },
  101. ]
  102. ctx.Settings = {
  103. managedUsers: {
  104. enabled: false,
  105. },
  106. plans: [
  107. {
  108. planCode: 'personal',
  109. name: 'Personal',
  110. price_in_cents: 0,
  111. features: {
  112. collaborators: -1,
  113. dropbox: true,
  114. github: true,
  115. gitBridge: true,
  116. versioning: true,
  117. compileTimeout: 180,
  118. compileGroup: 'standard',
  119. references: true,
  120. trackChanges: true,
  121. },
  122. },
  123. ],
  124. }
  125. ctx.SessionManager = {
  126. getSessionUser: vi.fn().mockReturnValue(ctx.user),
  127. getLoggedInUserId: vi.fn().mockReturnValue(ctx.user._id),
  128. }
  129. ctx.SSOConfig = {
  130. findById: vi.fn().mockReturnValue({
  131. exec: vi.fn().mockResolvedValue({ enabled: true }),
  132. }),
  133. }
  134. ctx.UserMembershipHandler = {
  135. getEntity: vi.fn((_entity, _options, callback) =>
  136. callback(null, ctx.subscription)
  137. ),
  138. createEntity: vi.fn((_entity, _options, callback) =>
  139. callback(null, ctx.institution)
  140. ),
  141. getUsers: vi.fn((_entity, _options, callback) =>
  142. callback(null, ctx.users)
  143. ),
  144. addUser: vi.fn((_entity, _options, _email, callback) =>
  145. callback(null, ctx.newUser)
  146. ),
  147. removeUser: vi.fn((_entity, _options, _userId, callback) =>
  148. callback(null)
  149. ),
  150. promises: {
  151. getUsers: vi.fn().mockResolvedValue(ctx.users),
  152. addUser: vi.fn().mockResolvedValue(ctx.newUser),
  153. removeUser: vi.fn().mockResolvedValue(),
  154. createEntity: vi.fn().mockResolvedValue(ctx.institution),
  155. },
  156. }
  157. ctx.SplitTestHandler = {
  158. promises: {
  159. getAssignment: vi.fn().mockResolvedValue({ variant: 'default' }),
  160. },
  161. getAssignment: vi.fn((_testName, _userId, callback) =>
  162. callback(null, { variant: 'default' })
  163. ),
  164. }
  165. ctx.RecurlyClient = {
  166. promises: {
  167. getSubscription: vi.fn().mockResolvedValue({
  168. id: recurlySubscriptionId,
  169. }),
  170. },
  171. }
  172. ctx.PlansLocator = {
  173. findLocalPlanInSettings: vi.fn().mockReturnValue({
  174. planCode: 'group_professional',
  175. canUseFlexibleLicensing: true,
  176. }),
  177. }
  178. vi.doMock(
  179. '../../../../app/src/Features/Authentication/SessionManager',
  180. () => ({
  181. default: ctx.SessionManager,
  182. })
  183. )
  184. vi.doMock(
  185. '../../../../app/src/Features/SplitTests/SplitTestHandler',
  186. () => ({
  187. default: ctx.SplitTestHandler,
  188. })
  189. )
  190. vi.doMock(
  191. '../../../../app/src/Features/UserMembership/UserMembershipHandler',
  192. () => ({
  193. default: ctx.UserMembershipHandler,
  194. })
  195. )
  196. vi.doMock(
  197. '../../../../app/src/Features/Subscription/RecurlyClient',
  198. () => ({
  199. default: ctx.RecurlyClient,
  200. })
  201. )
  202. vi.doMock(
  203. '../../../../app/src/Features/Subscription/PlansLocator.mjs',
  204. () => ({
  205. default: ctx.PlansLocator,
  206. })
  207. )
  208. vi.doMock('@overleaf/settings', () => ({
  209. default: ctx.Settings,
  210. }))
  211. vi.doMock('../../../../app/src/models/SSOConfig', () => ({
  212. SSOConfig: ctx.SSOConfig,
  213. }))
  214. ctx.Modules = {
  215. promises: {
  216. hooks: {
  217. fire: vi.fn(),
  218. },
  219. },
  220. }
  221. vi.doMock('../../../../app/src/infrastructure/Modules.mjs', () => ({
  222. default: ctx.Modules,
  223. }))
  224. ctx.UserMembershipController = (await import(modulePath)).default
  225. })
  226. describe('index', () => {
  227. beforeEach(ctx => {
  228. ctx.req.user = ctx.user
  229. ctx.req.entity = ctx.subscription
  230. ctx.req.entityConfig = EntityConfigs.group
  231. ctx.Modules.promises.hooks.fire.mockResolvedValue([])
  232. })
  233. it('get users', async ({
  234. UserMembershipController,
  235. req,
  236. UserMembershipHandler,
  237. subscription,
  238. }) => {
  239. expect.assertions(1)
  240. await UserMembershipController.manageGroupMembers(req, {
  241. render: () => {
  242. expect(UserMembershipHandler.promises.getUsers).toHaveBeenCalledWith(
  243. subscription,
  244. {
  245. modelName: 'Subscription',
  246. baseQuery: { groupPlan: true },
  247. fields: {
  248. access: 'manager_ids',
  249. membership: 'member_ids',
  250. name: 'teamName',
  251. primaryKey: '_id',
  252. read: ['invited_emails', 'teamInvites', 'member_ids'],
  253. write: null,
  254. },
  255. hasMembersLimit: true,
  256. readOnly: true,
  257. }
  258. )
  259. },
  260. })
  261. })
  262. it('render group view', async ({
  263. UserMembershipController,
  264. req,
  265. subscription,
  266. users,
  267. }) => {
  268. expect.assertions(4)
  269. subscription.managedUsersEnabled = false
  270. await UserMembershipController.manageGroupMembers(req, {
  271. render: (viewPath, viewParams) => {
  272. expect(viewPath).to.equal('user_membership/group-members-react')
  273. expect(viewParams.users).to.deep.equal(users)
  274. expect(viewParams.groupSize).to.equal(subscription.membersLimit)
  275. expect(viewParams.managedUsersActive).to.equal(false)
  276. },
  277. })
  278. })
  279. it('render group view with managed users', async ({
  280. UserMembershipController,
  281. req,
  282. subscription,
  283. users,
  284. }) => {
  285. expect.assertions(5)
  286. subscription.managedUsersEnabled = true
  287. await UserMembershipController.manageGroupMembers(req, {
  288. render: (viewPath, viewParams) => {
  289. expect(viewPath).to.equal('user_membership/group-members-react')
  290. expect(viewParams.users).to.deep.equal(users)
  291. expect(viewParams.groupSize).to.equal(subscription.membersLimit)
  292. expect(viewParams.managedUsersActive).to.equal(true)
  293. expect(viewParams.isUserGroupManager).to.equal(false)
  294. },
  295. })
  296. })
  297. describe('canUseAddSeatsFeature', () => {
  298. beforeEach(ctx => {
  299. ctx.subscription.admin_id = 'mock-admin-id'
  300. ctx.SessionManager.getLoggedInUserId.mockReturnValue('mock-admin-id')
  301. })
  302. it('should be true when all conditions are met', async ({
  303. UserMembershipController,
  304. req,
  305. }) => {
  306. expect.assertions(1)
  307. await UserMembershipController.manageGroupMembers(req, {
  308. render: (viewPath, viewParams) => {
  309. expect(viewParams.canUseAddSeatsFeature).to.equal(true)
  310. },
  311. })
  312. })
  313. it('should be false when plan does not support flexible licensing', async ({
  314. UserMembershipController,
  315. req,
  316. PlansLocator,
  317. }) => {
  318. expect.assertions(1)
  319. PlansLocator.findLocalPlanInSettings.mockReturnValue({
  320. planCode: 'group_professional',
  321. canUseFlexibleLicensing: false,
  322. })
  323. await UserMembershipController.manageGroupMembers(req, {
  324. render: (viewPath, viewParams) => {
  325. expect(viewParams.canUseAddSeatsFeature).to.equal(false)
  326. },
  327. })
  328. })
  329. it('should be false when user is not admin', async ({
  330. UserMembershipController,
  331. req,
  332. SessionManager,
  333. }) => {
  334. expect.assertions(1)
  335. SessionManager.getLoggedInUserId.mockReturnValue('mock-user-id')
  336. await UserMembershipController.manageGroupMembers(req, {
  337. render: (viewPath, viewParams) => {
  338. expect(viewParams.canUseAddSeatsFeature).to.equal(false)
  339. },
  340. })
  341. })
  342. it('should be false when recurly subscription does not exist', async ({
  343. UserMembershipController,
  344. req,
  345. subscription,
  346. }) => {
  347. expect.assertions(1)
  348. subscription.recurlySubscription_id = null
  349. await UserMembershipController.manageGroupMembers(req, {
  350. render: (viewPath, viewParams) => {
  351. expect(viewParams.canUseAddSeatsFeature).to.equal(false)
  352. },
  353. })
  354. })
  355. it('should be true when recurly subscription has pending changes', async ({
  356. UserMembershipController,
  357. req,
  358. RecurlyClient,
  359. }) => {
  360. expect.assertions(1)
  361. RecurlyClient.promises.getSubscription.mockResolvedValue({
  362. id: recurlySubscriptionId,
  363. pendingChange: {},
  364. })
  365. await UserMembershipController.manageGroupMembers(req, {
  366. render: (viewPath, viewParams) => {
  367. expect(viewParams.canUseAddSeatsFeature).to.equal(true)
  368. },
  369. })
  370. })
  371. it('should be false when fetching recurly subscription fails', async ({
  372. UserMembershipController,
  373. req,
  374. RecurlyClient,
  375. }) => {
  376. expect.assertions(1)
  377. RecurlyClient.promises.getSubscription.mockRejectedValue(
  378. new Error('Recurly error')
  379. )
  380. await UserMembershipController.manageGroupMembers(req, {
  381. render: (viewPath, viewParams) => {
  382. expect(viewParams.canUseAddSeatsFeature).to.equal(false)
  383. },
  384. })
  385. })
  386. it('should be false when plan is not found', async ({
  387. UserMembershipController,
  388. req,
  389. PlansLocator,
  390. }) => {
  391. expect.assertions(1)
  392. PlansLocator.findLocalPlanInSettings.mockReturnValue(null)
  393. await UserMembershipController.manageGroupMembers(req, {
  394. render: (viewPath, viewParams) => {
  395. expect(viewParams.canUseAddSeatsFeature).to.equal(false)
  396. },
  397. })
  398. })
  399. })
  400. it('render group managers view', async ({
  401. UserMembershipController,
  402. req,
  403. user,
  404. }) => {
  405. expect.assertions(2)
  406. req.user = user
  407. req.entityConfig = EntityConfigs.groupManagers
  408. await UserMembershipController.manageGroupManagers(req, {
  409. render: (viewPath, viewParams) => {
  410. expect(viewPath).to.equal('user_membership/group-managers-react')
  411. expect(viewParams.groupSize).to.equal(undefined)
  412. },
  413. })
  414. })
  415. it('render institution view', async ({
  416. UserMembershipController,
  417. req,
  418. user,
  419. institution,
  420. }) => {
  421. expect.assertions(3)
  422. req.user = user
  423. req.entity = institution
  424. req.entityConfig = EntityConfigs.institution
  425. await UserMembershipController.manageInstitutionManagers(req, {
  426. render: (viewPath, viewParams) => {
  427. expect(viewPath).to.equal(
  428. 'user_membership/institution-managers-react'
  429. )
  430. expect(viewParams.name).to.equal('Test Institution Name')
  431. expect(viewParams.groupSize).to.equal(undefined)
  432. },
  433. })
  434. })
  435. })
  436. describe('add', () => {
  437. beforeEach(ctx => {
  438. ctx.req.body.email = ctx.newUser.email
  439. ctx.req.entity = ctx.subscription
  440. ctx.req.entityConfig = EntityConfigs.groupManagers
  441. })
  442. it('add user', async ({
  443. UserMembershipController,
  444. req,
  445. UserMembershipHandler,
  446. subscription,
  447. newUser,
  448. }) => {
  449. expect.assertions(1)
  450. await UserMembershipController.add(req, {
  451. json: () => {
  452. expect(UserMembershipHandler.promises.addUser).toHaveBeenCalledWith(
  453. subscription,
  454. {
  455. modelName: 'Subscription',
  456. baseQuery: { groupPlan: true },
  457. fields: {
  458. access: 'manager_ids',
  459. membership: 'member_ids',
  460. name: 'teamName',
  461. primaryKey: '_id',
  462. read: ['manager_ids'],
  463. write: 'manager_ids',
  464. },
  465. },
  466. newUser.email,
  467. { initiatorId: 'mock-user-id', ipAddress: '1.2.3.4' }
  468. )
  469. },
  470. })
  471. })
  472. it('return user object', async ({
  473. UserMembershipController,
  474. req,
  475. newUser,
  476. }) => {
  477. expect.assertions(1)
  478. await UserMembershipController.add(req, {
  479. json: payload => {
  480. expect(payload.user).to.equal(newUser)
  481. },
  482. })
  483. })
  484. it('handle readOnly entity', async ({ UserMembershipController, req }) => {
  485. expect.assertions(2)
  486. req.entityConfig = EntityConfigs.group
  487. await UserMembershipController.add(req, null, error => {
  488. expect(error).to.exist
  489. expect(error).to.be.an.instanceof(Errors.NotFoundError)
  490. })
  491. })
  492. it('handle user already added', async ({
  493. UserMembershipController,
  494. req,
  495. UserMembershipHandler,
  496. }) => {
  497. expect.assertions(1)
  498. UserMembershipHandler.promises.addUser.mockRejectedValue(
  499. new UserMembershipErrors.UserAlreadyAddedError()
  500. )
  501. await UserMembershipController.add(
  502. req,
  503. {
  504. status: () => ({
  505. json: payload => {
  506. expect(payload.error.code).to.equal('user_already_added')
  507. },
  508. }),
  509. },
  510. () => {}
  511. )
  512. })
  513. it('handle user not found', async ({
  514. UserMembershipController,
  515. req,
  516. UserMembershipHandler,
  517. }) => {
  518. expect.assertions(1)
  519. UserMembershipHandler.promises.addUser.mockRejectedValue(
  520. new UserMembershipErrors.UserNotFoundError()
  521. )
  522. await UserMembershipController.add(req, {
  523. status: () => ({
  524. json: payload => {
  525. expect(payload.error.code).to.equal('user_not_found')
  526. },
  527. }),
  528. })
  529. })
  530. it('handle invalid email', async ({ UserMembershipController, req }) => {
  531. expect.assertions(1)
  532. req.body.email = 'not_valid_email'
  533. await UserMembershipController.add(req, {
  534. status: () => ({
  535. json: payload => {
  536. expect(payload.error.code).to.equal('invalid_email')
  537. },
  538. }),
  539. })
  540. })
  541. })
  542. describe('remove', () => {
  543. beforeEach(ctx => {
  544. ctx.req.params.userId = ctx.newUser._id
  545. ctx.req.entity = ctx.subscription
  546. ctx.req.entityConfig = EntityConfigs.groupManagers
  547. })
  548. it('remove user', async ({
  549. UserMembershipController,
  550. req,
  551. UserMembershipHandler,
  552. subscription,
  553. newUser,
  554. }) => {
  555. expect.assertions(1)
  556. await UserMembershipController.remove(req, {
  557. sendStatus: () => {
  558. expect(
  559. UserMembershipHandler.promises.removeUser
  560. ).toHaveBeenCalledWith(
  561. subscription,
  562. {
  563. modelName: 'Subscription',
  564. baseQuery: {
  565. groupPlan: true,
  566. },
  567. fields: {
  568. access: 'manager_ids',
  569. membership: 'member_ids',
  570. name: 'teamName',
  571. primaryKey: '_id',
  572. read: ['manager_ids'],
  573. write: 'manager_ids',
  574. },
  575. },
  576. newUser._id,
  577. { initiatorId: 'mock-user-id', ipAddress: '1.2.3.4' }
  578. )
  579. },
  580. })
  581. })
  582. it('handle readOnly entity', async ({ UserMembershipController, req }) => {
  583. expect.assertions(2)
  584. req.entityConfig = EntityConfigs.group
  585. await UserMembershipController.remove(req, null, error => {
  586. expect(error).to.exist
  587. expect(error).to.be.an.instanceof(Errors.NotFoundError)
  588. })
  589. })
  590. it('prevent self removal', async ({
  591. UserMembershipController,
  592. req,
  593. user,
  594. }) => {
  595. expect.assertions(1)
  596. req.params.userId = user._id
  597. await UserMembershipController.remove(req, {
  598. status: () => ({
  599. json: payload => {
  600. expect(payload.error.code).to.equal('managers_cannot_remove_self')
  601. },
  602. }),
  603. })
  604. })
  605. it('prevent admin removal', async ({
  606. UserMembershipController,
  607. req,
  608. UserMembershipHandler,
  609. }) => {
  610. expect.assertions(1)
  611. UserMembershipHandler.promises.removeUser.mockRejectedValue(
  612. new UserMembershipErrors.UserIsManagerError()
  613. )
  614. await UserMembershipController.remove(req, {
  615. status: () => ({
  616. json: payload => {
  617. expect(payload.error.code).to.equal('managers_cannot_remove_admin')
  618. },
  619. }),
  620. })
  621. })
  622. })
  623. describe('exportCsv', () => {
  624. beforeEach(ctx => {
  625. ctx.req.entity = ctx.subscription
  626. ctx.req.entityConfig = EntityConfigs.groupManagers
  627. ctx.res = new MockResponse(vi)
  628. ctx.UserMembershipController.exportCsv(ctx.req, ctx.res)
  629. })
  630. it('get users', ({ UserMembershipHandler, subscription }) => {
  631. expect(UserMembershipHandler.promises.getUsers).toHaveBeenCalledWith(
  632. subscription,
  633. {
  634. modelName: 'Subscription',
  635. baseQuery: { groupPlan: true },
  636. fields: {
  637. access: 'manager_ids',
  638. membership: 'member_ids',
  639. name: 'teamName',
  640. primaryKey: '_id',
  641. read: ['manager_ids'],
  642. write: 'manager_ids',
  643. },
  644. }
  645. )
  646. })
  647. it('should set the correct content type on the request', ({ res }) => {
  648. expect(res.contentType).toHaveBeenCalledWith('text/csv; charset=utf-8')
  649. })
  650. it('should name the exported csv file', ({ res }) => {
  651. expect(res.header).toHaveBeenCalledWith(
  652. 'Content-Disposition',
  653. 'attachment; filename="Group.csv"'
  654. )
  655. })
  656. it('should export the correct csv', ({ res }) => {
  657. expect(res.send).toHaveBeenCalledWith(
  658. '"email","last_logged_in_at","last_active_at"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z"\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z"\n"mock-email-3@foo.com","2021-08-10T10:41:11.407Z","2021-08-20T10:41:11.407Z"\n"mock-email-4@foo.com","2021-01-01T10:41:11.407Z","2021-01-02T10:41:11.407Z"\n"mock-email-5@foo.com","2023-01-01T10:41:11.407Z","2023-01-02T10:41:11.407Z"\n"mock-email-6@foo.com","2024-01-01T10:41:11.407Z","2024-01-02T10:41:11.407Z"'
  659. )
  660. })
  661. })
  662. describe('exportCsv when group is managed', () => {
  663. beforeEach(ctx => {
  664. ctx.req.entity = Object.assign(
  665. { managedUsersEnabled: true },
  666. ctx.subscription
  667. )
  668. ctx.req.entityConfig = EntityConfigs.groupManagers
  669. ctx.res = new MockResponse(vi)
  670. ctx.UserMembershipController.exportCsv(ctx.req, ctx.res)
  671. })
  672. it('should export the correct csv', ({ res }) => {
  673. expect(res.send).toHaveBeenCalledWith(
  674. '"email","last_logged_in_at","last_active_at","managed"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z",false\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z",false\n"mock-email-3@foo.com","2021-08-10T10:41:11.407Z","2021-08-20T10:41:11.407Z",false\n"mock-email-4@foo.com","2021-01-01T10:41:11.407Z","2021-01-02T10:41:11.407Z",true\n"mock-email-5@foo.com","2023-01-01T10:41:11.407Z","2023-01-02T10:41:11.407Z",false\n"mock-email-6@foo.com","2024-01-01T10:41:11.407Z","2024-01-02T10:41:11.407Z",true'
  675. )
  676. })
  677. })
  678. describe('exportCsv when group has SSO', () => {
  679. beforeEach(ctx => {
  680. ctx.req.entity = Object.assign(
  681. { ssoConfig: 'sso-config-id' },
  682. ctx.subscription
  683. )
  684. ctx.req.entityConfig = EntityConfigs.groupManagers
  685. ctx.Modules.promises.hooks.fire.mockResolvedValue([true])
  686. ctx.res = new MockResponse(vi)
  687. ctx.UserMembershipController.exportCsv(ctx.req, ctx.res)
  688. })
  689. it('should export the correct csv', ({ res }) => {
  690. expect(res.send).toHaveBeenCalledWith(
  691. '"email","last_logged_in_at","last_active_at","sso"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z",false\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z",false\n"mock-email-3@foo.com","2021-08-10T10:41:11.407Z","2021-08-20T10:41:11.407Z",false\n"mock-email-4@foo.com","2021-01-01T10:41:11.407Z","2021-01-02T10:41:11.407Z",false\n"mock-email-5@foo.com","2023-01-01T10:41:11.407Z","2023-01-02T10:41:11.407Z",true\n"mock-email-6@foo.com","2024-01-01T10:41:11.407Z","2024-01-02T10:41:11.407Z",true'
  692. )
  693. })
  694. })
  695. describe('exportCsv when group has SSO and managed users enabled', () => {
  696. beforeEach(ctx => {
  697. ctx.req.entity = Object.assign(
  698. { managedUsersEnabled: true },
  699. { ssoConfig: 'sso-config-id' },
  700. ctx.subscription
  701. )
  702. ctx.req.entityConfig = EntityConfigs.groupManagers
  703. ctx.Modules.promises.hooks.fire.mockResolvedValue([true])
  704. ctx.res = new MockResponse(vi)
  705. ctx.UserMembershipController.exportCsv(ctx.req, ctx.res)
  706. })
  707. it('should export the correct csv', ({ res }) => {
  708. expect(res.send).toHaveBeenCalledWith(
  709. '"email","last_logged_in_at","last_active_at","managed","sso"\n"mock-email-1@foo.com","2020-08-09T12:43:11.467Z","2021-08-09T12:43:11.467Z",false,false\n"mock-email-2@foo.com","2020-05-20T10:41:11.407Z","2021-05-20T10:41:11.407Z",false,false\n"mock-email-3@foo.com","2021-08-10T10:41:11.407Z","2021-08-20T10:41:11.407Z",false,false\n"mock-email-4@foo.com","2021-01-01T10:41:11.407Z","2021-01-02T10:41:11.407Z",true,false\n"mock-email-5@foo.com","2023-01-01T10:41:11.407Z","2023-01-02T10:41:11.407Z",false,true\n"mock-email-6@foo.com","2024-01-01T10:41:11.407Z","2024-01-02T10:41:11.407Z",true,true'
  710. )
  711. })
  712. })
  713. describe('new', () => {
  714. beforeEach(ctx => {
  715. ctx.req.params.name = 'publisher'
  716. ctx.req.params.id = 'abc'
  717. })
  718. it('renders view', async ({ UserMembershipController, req }) => {
  719. expect.assertions(2)
  720. await UserMembershipController.new(req, {
  721. render: (viewPath, data) => {
  722. expect(data.entityName).to.eq('publisher')
  723. expect(data.entityId).to.eq('abc')
  724. },
  725. })
  726. })
  727. })
  728. describe('create', () => {
  729. beforeEach(ctx => {
  730. ctx.req.params.name = 'institution'
  731. ctx.req.entityConfig = EntityConfigs.institution
  732. ctx.req.params.id = 123
  733. })
  734. it('creates institution', async ({
  735. UserMembershipController,
  736. req,
  737. UserMembershipHandler,
  738. }) => {
  739. expect.assertions(2)
  740. await UserMembershipController.create(req, {
  741. redirect: path => {
  742. expect(path).to.eq(EntityConfigs.institution.pathsFor(123).index)
  743. expect(
  744. UserMembershipHandler.promises.createEntity
  745. ).toHaveBeenCalledWith(123, {
  746. fields: {
  747. access: 'managerIds',
  748. membership: 'member_ids',
  749. name: 'name',
  750. primaryKey: 'v1Id',
  751. read: ['managerIds'],
  752. write: 'managerIds',
  753. },
  754. modelName: 'Institution',
  755. pathsFor: EntityConfigs.institution.pathsFor,
  756. })
  757. },
  758. })
  759. })
  760. })
  761. })