mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #19608 from overleaf/jpa-git-bridge-e2e
[server-pro] extend e2e test coverage for git-access GitOrigin-RevId: 3e6f3901037636140470b8169df224c329155598
This commit is contained in:
parent
606f9eaec7
commit
7eacbe898e
3 changed files with 186 additions and 61 deletions
|
@ -1,6 +1,11 @@
|
|||
import { v4 as uuid } from 'uuid'
|
||||
import { isExcludedBySharding, startWith } from './helpers/config'
|
||||
import { ensureUserExists, login } from './helpers/login'
|
||||
import { createProject } from './helpers/project'
|
||||
import {
|
||||
createProject,
|
||||
enableLinkSharing,
|
||||
shareProjectByEmailAndAcceptInvite,
|
||||
} from './helpers/project'
|
||||
|
||||
import git from 'isomorphic-git'
|
||||
import http from 'isomorphic-git/http/web'
|
||||
|
@ -32,6 +37,7 @@ describe('git-bridge', function () {
|
|||
cy.findByText('Delete token').click()
|
||||
})
|
||||
}
|
||||
|
||||
function maybeClearAllTokens() {
|
||||
cy.visit('/user/settings')
|
||||
cy.findByText('Git Integration')
|
||||
|
@ -46,10 +52,10 @@ describe('git-bridge', function () {
|
|||
|
||||
beforeEach(function () {
|
||||
login('user@example.com')
|
||||
maybeClearAllTokens()
|
||||
})
|
||||
|
||||
it('should render the git-bridge UI in the settings', () => {
|
||||
maybeClearAllTokens()
|
||||
cy.visit('/user/settings')
|
||||
cy.findByText('Git Integration')
|
||||
cy.get('button').contains('Generate token').click()
|
||||
|
@ -71,6 +77,7 @@ describe('git-bridge', function () {
|
|||
})
|
||||
|
||||
it('should render the git-bridge UI in the editor', function () {
|
||||
maybeClearAllTokens()
|
||||
cy.visit('/project')
|
||||
createProject('git').as('projectId')
|
||||
cy.get('header').findByText('Menu').click()
|
||||
|
@ -106,9 +113,74 @@ describe('git-bridge', function () {
|
|||
})
|
||||
})
|
||||
|
||||
it('should expose interface for git', () => {
|
||||
cy.visit('/project')
|
||||
createProject('git').as('projectId')
|
||||
describe('git access', () => {
|
||||
ensureUserExists({ email: 'collaborator-rw@example.com' })
|
||||
ensureUserExists({ email: 'collaborator-ro@example.com' })
|
||||
ensureUserExists({ email: 'collaborator-link-rw@example.com' })
|
||||
ensureUserExists({ email: 'collaborator-link-ro@example.com' })
|
||||
|
||||
let projectName: string
|
||||
beforeEach(() => {
|
||||
cy.visit('/project')
|
||||
projectName = uuid()
|
||||
createProject(projectName).as('projectId')
|
||||
})
|
||||
|
||||
it('should expose r/w interface to owner', () => {
|
||||
maybeClearAllTokens()
|
||||
cy.visit('/project')
|
||||
cy.findByText(projectName).click()
|
||||
checkGitAccess('readAndWrite')
|
||||
})
|
||||
|
||||
it('should expose r/w interface to invited r/w collaborator', () => {
|
||||
shareProjectByEmailAndAcceptInvite(
|
||||
projectName,
|
||||
'collaborator-rw@example.com',
|
||||
'Can edit'
|
||||
)
|
||||
maybeClearAllTokens()
|
||||
cy.visit('/project')
|
||||
cy.findByText(projectName).click()
|
||||
checkGitAccess('readAndWrite')
|
||||
})
|
||||
|
||||
it('should expose r/o interface to invited r/o collaborator', () => {
|
||||
shareProjectByEmailAndAcceptInvite(
|
||||
projectName,
|
||||
'collaborator-ro@example.com',
|
||||
'Read only'
|
||||
)
|
||||
maybeClearAllTokens()
|
||||
cy.visit('/project')
|
||||
cy.findByText(projectName).click()
|
||||
checkGitAccess('readOnly')
|
||||
})
|
||||
|
||||
it('should expose r/w interface to link-sharing r/w collaborator', () => {
|
||||
enableLinkSharing().then(({ linkSharingReadAndWrite }) => {
|
||||
login('collaborator-link-rw@example.com')
|
||||
maybeClearAllTokens()
|
||||
cy.visit(linkSharingReadAndWrite)
|
||||
cy.findByText(projectName) // wait for lazy loading
|
||||
cy.findByText('Join Project').click()
|
||||
checkGitAccess('readAndWrite')
|
||||
})
|
||||
})
|
||||
|
||||
it('should expose r/o interface to link-sharing r/o collaborator', () => {
|
||||
enableLinkSharing().then(({ linkSharingReadOnly }) => {
|
||||
login('collaborator-link-ro@example.com')
|
||||
maybeClearAllTokens()
|
||||
cy.visit(linkSharingReadOnly)
|
||||
cy.findByText(projectName) // wait for lazy loading
|
||||
cy.findByText('Join Project').click()
|
||||
checkGitAccess('readOnly')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function checkGitAccess(access: 'readOnly' | 'readAndWrite') {
|
||||
const recompile = throttledRecompile()
|
||||
|
||||
cy.get('header').findByText('Menu').click()
|
||||
|
@ -133,22 +205,18 @@ describe('git-bridge', function () {
|
|||
// close editor menu
|
||||
cy.get('#left-menu-modal').click()
|
||||
|
||||
// check history
|
||||
cy.findAllByText('History').last().click()
|
||||
cy.findByText('(via Git)').should('not.exist')
|
||||
cy.findAllByText('Back to editor').last().click()
|
||||
|
||||
const fs = new LightningFS('fs')
|
||||
const dir = `/${projectId}`
|
||||
|
||||
async function readFile(path: string) {
|
||||
async function readFile(path: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, { encoding: 'utf8' }, (err, blob) => {
|
||||
if (err) return reject(err)
|
||||
resolve(blob)
|
||||
resolve(blob as string)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function writeFile(path: string, data: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
fs.writeFile(path, data, undefined, err => {
|
||||
|
@ -173,6 +241,7 @@ describe('git-bridge', function () {
|
|||
author: { name: 'user', email: 'user@example.com' },
|
||||
committer: { name: 'user', email: 'user@example.com' },
|
||||
}
|
||||
const mainTex = `${dir}/main.tex`
|
||||
|
||||
// Clone
|
||||
cy.then({ timeout: 10_000 }, async () => {
|
||||
|
@ -182,7 +251,14 @@ describe('git-bridge', function () {
|
|||
})
|
||||
})
|
||||
|
||||
const mainTex = `${dir}/main.tex`
|
||||
cy.findByText(/\\documentclass/)
|
||||
.parent()
|
||||
.parent()
|
||||
.then(async editor => {
|
||||
const onDisk = await readFile(mainTex)
|
||||
expect(onDisk.replaceAll('\n', '')).to.equal(editor.text())
|
||||
})
|
||||
|
||||
const text = `
|
||||
\\documentclass{article}
|
||||
\\begin{document}
|
||||
|
@ -202,12 +278,37 @@ Hello world
|
|||
...authorOptions,
|
||||
message: 'Swap main.tex',
|
||||
})
|
||||
await git.push({
|
||||
...commonOptions,
|
||||
...httpOptions,
|
||||
})
|
||||
})
|
||||
|
||||
if (access === 'readAndWrite') {
|
||||
// check history before push
|
||||
cy.findAllByText('History').last().click()
|
||||
cy.findByText('(via Git)').should('not.exist')
|
||||
cy.findAllByText('Back to editor').last().click()
|
||||
|
||||
cy.then(async () => {
|
||||
await git.push({
|
||||
...commonOptions,
|
||||
...httpOptions,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
cy.then(async () => {
|
||||
try {
|
||||
await git.push({
|
||||
...commonOptions,
|
||||
...httpOptions,
|
||||
})
|
||||
expect.fail('push should have failed')
|
||||
} catch (err) {
|
||||
expect(err).to.match(/branches were not updated/)
|
||||
expect(err).to.match(/forbidden/)
|
||||
}
|
||||
})
|
||||
|
||||
return // return early, below are write access bits
|
||||
}
|
||||
|
||||
// check push in editor
|
||||
cy.findByText(/\\documentclass/)
|
||||
.parent()
|
||||
|
@ -250,7 +351,7 @@ Hello world
|
|||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
function checkDisabled() {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { login } from './login'
|
||||
|
||||
export function createProject(
|
||||
name: string,
|
||||
{
|
||||
|
@ -20,3 +22,53 @@ export function createProject(
|
|||
.should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||
.then(url => url.split('/').pop())
|
||||
}
|
||||
|
||||
export function shareProjectByEmailAndAcceptInvite(
|
||||
projectName: string,
|
||||
email: string,
|
||||
level: 'Read only' | 'Can edit'
|
||||
) {
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
export function enableLinkSharing() {
|
||||
let linkSharingReadOnly: string
|
||||
let linkSharingReadAndWrite: string
|
||||
|
||||
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()
|
||||
})
|
||||
|
||||
return cy.then(() => {
|
||||
return { linkSharingReadOnly, linkSharingReadAndWrite }
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { v4 as uuid } from 'uuid'
|
||||
import { isExcludedBySharding, startWith } from './helpers/config'
|
||||
import { ensureUserExists, login } from './helpers/login'
|
||||
import { createProject } from './helpers/project'
|
||||
import {
|
||||
createProject,
|
||||
enableLinkSharing,
|
||||
shareProjectByEmailAndAcceptInvite,
|
||||
} from './helpers/project'
|
||||
import { throttledRecompile } from './helpers/compile'
|
||||
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
||||
|
||||
|
@ -36,46 +40,12 @@ describe('Project Sharing', function () {
|
|||
).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()
|
||||
})
|
||||
enableLinkSharing().then(
|
||||
({ linkSharingReadOnly: ro, linkSharingReadAndWrite: rw }) => {
|
||||
linkSharingReadAndWrite = rw
|
||||
linkSharingReadOnly = ro
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function expectContentReadOnlyAccess() {
|
||||
|
@ -178,7 +148,8 @@ describe('Project Sharing', function () {
|
|||
ensureUserExists({ email })
|
||||
|
||||
beforeWithReRunOnTestRetry(function () {
|
||||
shareProjectByEmailAndAcceptInvite(email, 'Read only')
|
||||
login('user@example.com')
|
||||
shareProjectByEmailAndAcceptInvite(projectName, email, 'Read only')
|
||||
})
|
||||
|
||||
it('should grant the collaborator read access', () => {
|
||||
|
@ -195,7 +166,8 @@ describe('Project Sharing', function () {
|
|||
ensureUserExists({ email })
|
||||
|
||||
beforeWithReRunOnTestRetry(function () {
|
||||
shareProjectByEmailAndAcceptInvite(email, 'Can edit')
|
||||
login('user@example.com')
|
||||
shareProjectByEmailAndAcceptInvite(projectName, email, 'Can edit')
|
||||
})
|
||||
|
||||
it('should grant the collaborator write access', () => {
|
||||
|
|
Loading…
Reference in a new issue