project-sharing.spec.ts 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import { v4 as uuid } from 'uuid'
  2. import { isExcludedBySharding, startWith } from './helpers/config'
  3. import { ensureUserExists, login } from './helpers/login'
  4. import {
  5. createProject,
  6. enableLinkSharing,
  7. openProjectByName,
  8. openProjectViaLinkSharingAsAnon,
  9. openProjectViaLinkSharingAsUser,
  10. shareProjectByEmailAndAcceptInviteViaDash,
  11. shareProjectByEmailAndAcceptInviteViaEmail,
  12. } from './helpers/project'
  13. import { throttledRecompile } from './helpers/compile'
  14. import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
  15. describe('Project Sharing', function () {
  16. if (isExcludedBySharding('CE_CUSTOM_2')) return
  17. ensureUserExists({ email: 'user@example.com' })
  18. startWith({ withDataDir: true })
  19. let projectName: string
  20. beforeWithReRunOnTestRetry(function () {
  21. projectName = `Project ${uuid()}`
  22. setupTestProject()
  23. })
  24. beforeEach(() => {
  25. // Always start with a fresh session
  26. cy.session([uuid()], () => {})
  27. })
  28. let linkSharingReadOnly: string
  29. let linkSharingReadAndWrite: string
  30. function setupTestProject() {
  31. login('user@example.com')
  32. createProject(projectName)
  33. // TODO(25342): re-enable
  34. // // Add chat message
  35. // cy.findByText('Chat').click()
  36. // // wait for lazy loading of the chat pane
  37. // cy.findByText('Send your first message to your collaborators')
  38. // cy.get(
  39. // 'textarea[placeholder="Send a message to your collaborators…"]'
  40. // ).type('New Chat Message{enter}')
  41. // Get link sharing links
  42. enableLinkSharing().then(
  43. ({ linkSharingReadOnly: ro, linkSharingReadAndWrite: rw }) => {
  44. linkSharingReadAndWrite = rw
  45. linkSharingReadOnly = ro
  46. }
  47. )
  48. }
  49. function expectContentReadOnlyAccess() {
  50. cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
  51. cy.get('.cm-content').should('contain.text', '\\maketitle')
  52. cy.get('.cm-content').should('have.attr', 'contenteditable', 'false')
  53. }
  54. function expectContentWriteAccess() {
  55. const section = `Test Section ${uuid()}`
  56. cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
  57. const recompile = throttledRecompile()
  58. // wait for the editor to finish loading
  59. cy.get('.cm-content').should('contain.text', '\\maketitle')
  60. // the editor should be writable
  61. cy.get('.cm-content').should('have.attr', 'contenteditable', 'true')
  62. cy.findByText('\\maketitle').parent().click()
  63. cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
  64. // should have written
  65. cy.get('.cm-content').should('contain.text', `\\section{${section}}`)
  66. // check PDF
  67. recompile()
  68. cy.get('.pdf-viewer').should('contain.text', projectName)
  69. cy.get('.pdf-viewer').should('contain.text', section)
  70. }
  71. function expectNoAccess() {
  72. // try read only access link
  73. cy.visit(linkSharingReadOnly)
  74. cy.url().should('match', /\/login/)
  75. // Cypress bugs: cypress resolves the link-sharing link outside the browser, and it carries over the hash of the link-sharing link to the login page redirect (bug 1).
  76. // Effectively, cypress then instructs the browser to change the page from /login#read-only-hash to /login#read-and-write-hash.
  77. // This is turn does not trigger a "page load", but rather just "scrolling", which in turn trips up the "page loaded" detection in cypress (bug 2).
  78. // Work around this by navigating away from the /login page in between checks.
  79. cy.visit('/user/password/reset')
  80. // try read and write access link
  81. cy.visit(linkSharingReadAndWrite)
  82. cy.url().should('match', /\/login/)
  83. }
  84. function expectChatAccess() {
  85. // TODO(25342): re-enable
  86. // cy.findByText('Chat').click()
  87. // cy.findByText('New Chat Message')
  88. }
  89. function expectHistoryAccess() {
  90. cy.findByText('History').click()
  91. cy.findByText('Labels')
  92. cy.findByText(/\\begin\{document}/)
  93. cy.findAllByTestId('history-version-metadata-users')
  94. .last()
  95. .should('have.text', 'user')
  96. cy.findByText('Back to editor').click()
  97. }
  98. function expectNoChatAccess() {
  99. // TODO(25342): re-enable
  100. // cy.findByText('Layout') // wait for lazy loading
  101. // cy.findByText('Chat').should('not.exist')
  102. }
  103. function expectNoHistoryAccess() {
  104. cy.findByText('Layout') // wait for lazy loading
  105. cy.findByText('History').should('not.exist')
  106. }
  107. function expectFullReadOnlyAccess() {
  108. expectContentReadOnlyAccess()
  109. expectChatAccess()
  110. expectHistoryAccess()
  111. }
  112. function expectRestrictedReadOnlyAccess() {
  113. expectContentReadOnlyAccess()
  114. expectNoChatAccess()
  115. expectNoHistoryAccess()
  116. }
  117. function expectReadAndWriteAccess() {
  118. expectContentWriteAccess()
  119. expectChatAccess()
  120. expectHistoryAccess()
  121. }
  122. function expectProjectDashboardEntry() {
  123. cy.visit('/project')
  124. cy.findByText(projectName)
  125. }
  126. function expectEditAuthoredAs(author: string) {
  127. cy.findByText('History').click()
  128. cy.findAllByTestId('history-version-metadata-users')
  129. .first()
  130. .should('contain.text', author) // might have other edits in the same group
  131. }
  132. describe('via email', function () {
  133. const email = 'collaborator-email@example.com'
  134. ensureUserExists({ email })
  135. beforeEach(function () {
  136. login('user@example.com')
  137. shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer')
  138. })
  139. it('should grant the collaborator read access', () => {
  140. expectFullReadOnlyAccess()
  141. expectProjectDashboardEntry()
  142. })
  143. })
  144. describe('read only', () => {
  145. const email = 'collaborator-ro@example.com'
  146. ensureUserExists({ email })
  147. beforeWithReRunOnTestRetry(function () {
  148. login('user@example.com')
  149. shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer')
  150. })
  151. it('should grant the collaborator read access', () => {
  152. login(email)
  153. openProjectByName(projectName)
  154. expectFullReadOnlyAccess()
  155. expectProjectDashboardEntry()
  156. })
  157. })
  158. describe('read and write', () => {
  159. const email = 'collaborator-rw@example.com'
  160. ensureUserExists({ email })
  161. beforeWithReRunOnTestRetry(function () {
  162. login('user@example.com')
  163. shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor')
  164. })
  165. it('should grant the collaborator write access', () => {
  166. login(email)
  167. openProjectByName(projectName)
  168. expectReadAndWriteAccess()
  169. expectEditAuthoredAs('You')
  170. expectProjectDashboardEntry()
  171. })
  172. })
  173. describe('token access', () => {
  174. describe('logged in', () => {
  175. describe('read only', () => {
  176. const email = 'collaborator-link-ro@example.com'
  177. ensureUserExists({ email })
  178. it('should grant restricted read access', () => {
  179. login(email)
  180. openProjectViaLinkSharingAsUser(
  181. linkSharingReadOnly,
  182. projectName,
  183. email
  184. )
  185. expectRestrictedReadOnlyAccess()
  186. expectProjectDashboardEntry()
  187. })
  188. })
  189. describe('read and write', () => {
  190. const email = 'collaborator-link-rw@example.com'
  191. ensureUserExists({ email })
  192. it('should grant full write access', () => {
  193. login(email)
  194. openProjectViaLinkSharingAsUser(
  195. linkSharingReadAndWrite,
  196. projectName,
  197. email
  198. )
  199. expectReadAndWriteAccess()
  200. expectEditAuthoredAs('You')
  201. expectProjectDashboardEntry()
  202. })
  203. })
  204. })
  205. describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', () => {
  206. describe('wrap startup', () => {
  207. startWith({
  208. vars: {
  209. OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
  210. },
  211. withDataDir: true,
  212. })
  213. it('should block access', () => {
  214. expectNoAccess()
  215. })
  216. })
  217. describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => {
  218. startWith({
  219. vars: {
  220. OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
  221. OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
  222. },
  223. withDataDir: true,
  224. })
  225. it('should block access', () => {
  226. expectNoAccess()
  227. })
  228. })
  229. })
  230. describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', () => {
  231. describe('wrap startup', () => {
  232. startWith({
  233. vars: {
  234. OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
  235. },
  236. withDataDir: true,
  237. })
  238. it('should grant read access with read link', () => {
  239. openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
  240. expectRestrictedReadOnlyAccess()
  241. })
  242. it('should prompt for login with write link', () => {
  243. cy.visit(linkSharingReadAndWrite)
  244. cy.url().should('match', /\/login/)
  245. })
  246. })
  247. describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => {
  248. startWith({
  249. vars: {
  250. OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
  251. OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
  252. },
  253. withDataDir: true,
  254. })
  255. it('should grant read access with read link', () => {
  256. openProjectViaLinkSharingAsAnon(linkSharingReadOnly)
  257. expectRestrictedReadOnlyAccess()
  258. })
  259. it('should grant write access with write link', () => {
  260. openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite)
  261. expectReadAndWriteAccess()
  262. expectEditAuthoredAs('Anonymous')
  263. })
  264. })
  265. })
  266. })
  267. })