project-sharing.spec.ts 9.2 KB

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