Explorar o código

Merge pull request #18678 from overleaf/jpa-test-sharing

[server-pro] add tests for project sharing

GitOrigin-RevId: c1862e01ff6feba048e340ba5549ccad9fd07d2c
Jakob Ackermann %!s(int64=2) %!d(string=hai) anos
pai
achega
5adb9a63c3
Modificáronse 2 ficheiros con 311 adicións e 0 borrados
  1. 2 0
      server-ce/test/host-admin.js
  2. 309 0
      server-ce/test/project-sharing.spec.ts

+ 2 - 0
server-ce/test/host-admin.js

@@ -117,6 +117,8 @@ const allowedVars = Joi.object(
       'ALL_TEX_LIVE_DOCKER_IMAGE_NAMES',
       'OVERLEAF_TEMPLATES_USER_ID',
       'OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS',
+      'OVERLEAF_ALLOW_PUBLIC_ACCESS',
+      'OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING',
       // Old branding, used for upgrade tests
       'SHARELATEX_MONGO_URL',
       'SHARELATEX_REDIS_HOST',

+ 309 - 0
server-ce/test/project-sharing.spec.ts

@@ -0,0 +1,309 @@
+import { v4 as uuid } from 'uuid'
+import { startWith } from './helpers/config'
+import { ensureUserExists, login } from './helpers/login'
+import { createProject } from './helpers/project'
+import { throttledRecompile } from './helpers/compile'
+
+describe('Project Sharing', function () {
+  ensureUserExists({ email: 'user@example.com' })
+  startWith({ withDataDir: true })
+
+  let projectName: string
+  before(function () {
+    projectName = `Project ${uuid()}`
+    setupTestProject()
+  })
+
+  beforeEach(() => {
+    // Always start with a fresh session
+    cy.session([uuid()], () => {})
+  })
+
+  let linkSharingReadOnly: string
+  let linkSharingReadAndWrite: string
+
+  function setupTestProject() {
+    login('user@example.com')
+    cy.visit('/project')
+    createProject(projectName)
+
+    // Add chat message
+    cy.findByText('Chat').click()
+    cy.get(
+      'textarea[placeholder="Send a message to your collaborators…"]'
+    ).type('New Chat Message{enter}')
+
+    // Get link sharing links
+    cy.findByText('Share').click()
+    cy.findByText('Turn on link sharing').click()
+    cy.findByText('Anyone with this link can view this project')
+      .next()
+      .should('contain.text', 'http://sharelatex/')
+      .then(el => {
+        linkSharingReadOnly = el.text()
+      })
+    cy.findByText('Anyone with this link can edit this project')
+      .next()
+      .should('contain.text', 'http://sharelatex/')
+      .then(el => {
+        linkSharingReadAndWrite = el.text()
+      })
+  }
+
+  function shareProjectByEmailAndAcceptInvite(
+    email: string,
+    level: 'Read Only' | 'Can Edit'
+  ) {
+    login('user@example.com')
+    cy.visit('/project')
+    cy.findByText(projectName).click()
+    cy.findByText('Share').click()
+    cy.findByRole('dialog').within(() => {
+      cy.get('input').type(`${email},`)
+      cy.get('input')
+        .parents('form')
+        .within(() => cy.findByText('Can Edit').parent().select(level))
+      cy.findByText('Share').click({ force: true })
+    })
+
+    login(email)
+    cy.visit('/project')
+    cy.findByText(new RegExp(projectName))
+      .parent()
+      .parent()
+      .within(() => {
+        cy.findByText('Join Project').click()
+      })
+  }
+
+  function expectContentReadOnlyAccess() {
+    cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
+    cy.get('.cm-content').should('contain.text', '\\maketitle')
+    cy.get('.cm-content').should('have.attr', 'contenteditable', 'false')
+  }
+
+  function expectContentWriteAccess() {
+    const section = `Test Section ${uuid()}`
+    cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
+    const recompile = throttledRecompile()
+    // wait for the editor to finish loading
+    cy.get('.cm-content').should('contain.text', '\\maketitle')
+    // the editor should be writable
+    cy.get('.cm-content').should('have.attr', 'contenteditable', 'true')
+    cy.findByText('\\maketitle').parent().click()
+    cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
+    // should have written
+    cy.get('.cm-content').should('contain.text', `\\section{${section}}`)
+    // check PDF
+    recompile()
+    cy.get('.pdf-viewer').should('contain.text', projectName)
+    cy.get('.pdf-viewer').should('contain.text', section)
+  }
+
+  function expectNoAccess() {
+    // try read only access link
+    cy.visit(linkSharingReadOnly)
+    cy.url().should('match', /\/login/)
+
+    // 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).
+    // Effectively, cypress then instructs the browser to change the page from /login#read-only-hash to /login#read-and-write-hash.
+    // 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).
+    // Work around this by navigating away from the /login page in between checks.
+    cy.visit('/user/password/reset')
+
+    // try read and write access link
+    cy.visit(linkSharingReadAndWrite)
+    cy.url().should('match', /\/login/)
+  }
+
+  function expectChatAccess() {
+    cy.findByText('Chat').click()
+    cy.findByText('New Chat Message')
+  }
+
+  function expectHistoryAccess() {
+    cy.findByText('History').click()
+    cy.findByText('Labels')
+    cy.findByText(/\\begin\{document}/)
+    cy.findAllByTestId('history-version-metadata-users')
+      .last()
+      .should('have.text', 'user')
+    cy.findByText('Back to editor').click()
+  }
+
+  function expectNoChatAccess() {
+    cy.findByText('Layout') // wait for lazy loading
+    cy.findByText('Chat').should('not.exist')
+  }
+
+  function expectNoHistoryAccess() {
+    cy.findByText('Layout') // wait for lazy loading
+    cy.findByText('History').should('not.exist')
+  }
+
+  function expectFullReadOnlyAccess() {
+    expectContentReadOnlyAccess()
+    expectChatAccess()
+    expectHistoryAccess()
+  }
+
+  function expectRestrictedReadOnlyAccess() {
+    expectContentReadOnlyAccess()
+    expectNoChatAccess()
+    expectNoHistoryAccess()
+  }
+
+  function expectReadAndWriteAccess() {
+    expectContentWriteAccess()
+    expectChatAccess()
+    expectHistoryAccess()
+  }
+
+  function expectProjectDashboardEntry() {
+    cy.visit('/project')
+    cy.findByText(projectName)
+  }
+
+  function expectEditAuthoredAs(author: string) {
+    cy.findByText('History').click()
+    cy.findAllByTestId('history-version-metadata-users')
+      .first()
+      .should('contain.text', author) // might have other edits in the same group
+  }
+
+  describe('read only', () => {
+    const email = 'collaborator-ro@example.com'
+    ensureUserExists({ email })
+
+    before(function () {
+      shareProjectByEmailAndAcceptInvite(email, 'Read Only')
+    })
+
+    it('should grant the collaborator read access', () => {
+      login(email)
+      cy.visit('/project')
+      cy.findByText(projectName).click()
+      expectFullReadOnlyAccess()
+      expectProjectDashboardEntry()
+    })
+  })
+
+  describe('read and write', () => {
+    const email = 'collaborator-rw@example.com'
+    ensureUserExists({ email })
+
+    before(function () {
+      shareProjectByEmailAndAcceptInvite(email, 'Can Edit')
+    })
+
+    it('should grant the collaborator write access', () => {
+      login(email)
+      cy.visit('/project')
+      cy.findByText(projectName).click()
+      expectReadAndWriteAccess()
+      expectEditAuthoredAs('You')
+      expectProjectDashboardEntry()
+    })
+  })
+
+  describe('token access', () => {
+    describe('logged in', () => {
+      describe('read only', () => {
+        const email = 'collaborator-link-ro@example.com'
+        ensureUserExists({ email })
+
+        it('should grant restricted read access', () => {
+          login(email)
+          cy.visit(linkSharingReadOnly)
+          cy.findByText(projectName) // wait for lazy loading
+          cy.findByText('Join Project').click()
+          expectRestrictedReadOnlyAccess()
+          expectProjectDashboardEntry()
+        })
+      })
+
+      describe('read and write', () => {
+        const email = 'collaborator-link-rw@example.com'
+        ensureUserExists({ email })
+
+        it('should grant full write access', () => {
+          login(email)
+          cy.visit(linkSharingReadAndWrite)
+          cy.findByText(projectName) // wait for lazy loading
+          cy.findByText('Join Project').click()
+          expectReadAndWriteAccess()
+          expectEditAuthoredAs('You')
+          expectProjectDashboardEntry()
+        })
+      })
+    })
+
+    describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', () => {
+      describe('wrap startup', () => {
+        startWith({
+          vars: {
+            OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
+          },
+          withDataDir: true,
+        })
+        it('should block access', () => {
+          expectNoAccess()
+        })
+      })
+
+      describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => {
+        startWith({
+          vars: {
+            OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false',
+            OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
+          },
+          withDataDir: true,
+        })
+        it('should block access', () => {
+          expectNoAccess()
+        })
+      })
+    })
+
+    describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', () => {
+      describe('wrap startup', () => {
+        startWith({
+          vars: {
+            OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
+          },
+          withDataDir: true,
+        })
+        it('should grant read access with read link', () => {
+          cy.visit(linkSharingReadOnly)
+          expectRestrictedReadOnlyAccess()
+        })
+
+        it('should prompt for login with write link', () => {
+          cy.visit(linkSharingReadAndWrite)
+          cy.url().should('match', /\/login/)
+        })
+      })
+
+      describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => {
+        startWith({
+          vars: {
+            OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true',
+            OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true',
+          },
+          withDataDir: true,
+        })
+
+        it('should grant read access with read link', () => {
+          cy.visit(linkSharingReadOnly)
+          expectRestrictedReadOnlyAccess()
+        })
+
+        it('should grant write access with write link', () => {
+          cy.visit(linkSharingReadAndWrite)
+          expectReadAndWriteAccess()
+          expectEditAuthoredAs('Anonymous')
+        })
+      })
+    })
+  })
+})