mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2025-07-23 14:00:08 +02:00

* [web] prepare filestore migration for Server Pro/CE * [history-v1] remove unused USER_FILES_BUCKET_NAME env var from script * [server-ce] tests: write default docker-compose.override.yml on startup * [server-ce] tests: extend access logging of host-admin for response * [server-ce] tests: test text and binary file upload * [server-ce] tests: add tests for filestore migration * [web] simplify feature gate for filestore/project-history-blobs logic Co-authored-by: Brian Gough <brian.gough@overleaf.com> * [server-ce] test: fix flaky test helper --------- Co-authored-by: Brian Gough <brian.gough@overleaf.com> GitOrigin-RevId: f89bdab2749e2b7a49d609e2eac6bf621c727966
258 lines
7.2 KiB
TypeScript
258 lines
7.2 KiB
TypeScript
import { login } from './login'
|
|
import { openEmail } from './email'
|
|
import { v4 as uuid } from 'uuid'
|
|
|
|
export function createProject(
|
|
name: string,
|
|
{
|
|
type = 'Blank project',
|
|
newProjectButtonMatcher = /new project/i,
|
|
open = true,
|
|
}: {
|
|
type?: 'Blank project' | 'Example project'
|
|
newProjectButtonMatcher?: RegExp
|
|
open?: boolean
|
|
} = {}
|
|
): Cypress.Chainable<string> {
|
|
cy.url().then(url => {
|
|
if (!url.endsWith('/project')) {
|
|
cy.visit('/project')
|
|
}
|
|
})
|
|
const interceptId = uuid()
|
|
let projectId = ''
|
|
if (!open) {
|
|
cy.then(() => {
|
|
// Register intercept just before creating the project, otherwise we might
|
|
// intercept a request from a prior createProject invocation.
|
|
cy.intercept(
|
|
{ method: 'GET', url: /\/project\/[a-fA-F0-9]{24}$/, times: 1 },
|
|
req => {
|
|
projectId = req.url.split('/').pop()!
|
|
// Redirect back to the project dashboard, effectively reload the page.
|
|
req.redirect('/project')
|
|
}
|
|
).as(interceptId)
|
|
})
|
|
}
|
|
cy.findAllByRole('button').contains(newProjectButtonMatcher).click()
|
|
// FIXME: This should only look in the left menu
|
|
// The upgrading tests create projects in older versions of Server Pro which used different casing of the project type. Use case-insensitive match.
|
|
cy.findAllByText(type, { exact: false }).first().click()
|
|
cy.findByRole('dialog').within(() => {
|
|
cy.get('input').type(name)
|
|
cy.findByText('Create').click()
|
|
})
|
|
if (open) {
|
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
|
waitForMainDocToLoad()
|
|
return cy
|
|
.url()
|
|
.should('match', /\/project\/[a-fA-F0-9]{24}/)
|
|
.then(url => url.split('/').pop())
|
|
} else {
|
|
const alias = `@${interceptId}` // IDEs do not like computed values in cy.wait().
|
|
cy.wait(alias)
|
|
return cy.then(() => projectId)
|
|
}
|
|
}
|
|
|
|
export function openProjectByName(projectName: string) {
|
|
cy.visit('/project')
|
|
cy.findByText(projectName).click()
|
|
waitForMainDocToLoad()
|
|
}
|
|
|
|
export function openProjectById(projectId: string) {
|
|
cy.visit(`/project/${projectId}`)
|
|
waitForMainDocToLoad()
|
|
}
|
|
|
|
export function openProjectViaLinkSharingAsAnon(url: string) {
|
|
cy.visit(url)
|
|
waitForMainDocToLoad()
|
|
}
|
|
|
|
export function openProjectViaLinkSharingAsUser(
|
|
url: string,
|
|
projectName: string,
|
|
email: string
|
|
) {
|
|
cy.visit(url)
|
|
cy.findByText(projectName) // wait for lazy loading
|
|
cy.contains(`as ${email}`)
|
|
cy.findByText('OK, join project').click()
|
|
waitForMainDocToLoad()
|
|
}
|
|
|
|
export function openProjectViaInviteNotification(projectName: string) {
|
|
cy.visit('/project')
|
|
cy.findByText(projectName)
|
|
.parent()
|
|
.parent()
|
|
.within(() => {
|
|
cy.findByText('Join Project').click()
|
|
})
|
|
cy.findByText('Open Project').click()
|
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
|
waitForMainDocToLoad()
|
|
}
|
|
|
|
function shareProjectByEmail(
|
|
projectName: string,
|
|
email: string,
|
|
level: 'Viewer' | 'Editor'
|
|
) {
|
|
openProjectByName(projectName)
|
|
cy.findByText('Share').click()
|
|
cy.findByRole('dialog').within(() => {
|
|
cy.findByLabelText('Add people', { selector: 'input' }).type(`${email},`)
|
|
cy.findByLabelText('Add people', { selector: 'input' })
|
|
.parents('form')
|
|
.within(() => {
|
|
cy.findByTestId('add-collaborator-select')
|
|
.click()
|
|
.then(() => {
|
|
cy.findByText(level).click()
|
|
})
|
|
})
|
|
cy.findByText('Invite').click({ force: true })
|
|
cy.findByText('Invite not yet accepted.')
|
|
})
|
|
}
|
|
|
|
export function shareProjectByEmailAndAcceptInviteViaDash(
|
|
projectName: string,
|
|
email: string,
|
|
level: 'Viewer' | 'Editor'
|
|
) {
|
|
shareProjectByEmail(projectName, email, level)
|
|
|
|
login(email)
|
|
openProjectViaInviteNotification(projectName)
|
|
}
|
|
|
|
export function shareProjectByEmailAndAcceptInviteViaEmail(
|
|
projectName: string,
|
|
email: string,
|
|
level: 'Viewer' | 'Editor'
|
|
) {
|
|
shareProjectByEmail(projectName, email, level)
|
|
|
|
login(email)
|
|
|
|
openEmail(projectName, frame => {
|
|
frame.contains('View project').then(a => {
|
|
cy.log(
|
|
'bypass target=_blank and navigate current browser tab/cypress-iframe to project invite'
|
|
)
|
|
cy.visit(a.attr('href')!)
|
|
})
|
|
})
|
|
cy.url().should('match', /\/project\/[a-f0-9]+\/invite\/token\/[a-f0-9]+/)
|
|
cy.findByText(/user would like you to join/)
|
|
cy.contains(new RegExp(`You are accepting this invite as ${email}`))
|
|
cy.findByText('Join Project').click()
|
|
waitForMainDocToLoad()
|
|
}
|
|
|
|
export function enableLinkSharing() {
|
|
let linkSharingReadOnly: string
|
|
let linkSharingReadAndWrite: string
|
|
const origin = new URL(Cypress.config().baseUrl!).origin
|
|
|
|
waitForMainDocToLoad()
|
|
|
|
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', origin + '/read')
|
|
.then(el => {
|
|
linkSharingReadOnly = el.text()
|
|
})
|
|
cy.findByText('Anyone with this link can edit this project')
|
|
.next()
|
|
.should('contain.text', origin + '/')
|
|
.then(el => {
|
|
linkSharingReadAndWrite = el.text()
|
|
})
|
|
|
|
return cy.then(() => {
|
|
return { linkSharingReadOnly, linkSharingReadAndWrite }
|
|
})
|
|
}
|
|
|
|
export function waitForMainDocToLoad() {
|
|
cy.log('Wait for main doc to load; it will steal the focus after loading')
|
|
cy.get('.cm-content').should('contain.text', 'Introduction')
|
|
}
|
|
|
|
export function openFile(fileName: string, waitFor: string) {
|
|
// force: The file-tree pane is too narrow to display the full name.
|
|
cy.findByTestId('file-tree').findByText(fileName).click({ force: true })
|
|
|
|
// wait until we've switched to the selected file
|
|
cy.findByText('Loading…').should('not.exist')
|
|
cy.findByText(waitFor)
|
|
}
|
|
|
|
export function createNewFile() {
|
|
const fileName = `${uuid()}.tex`
|
|
|
|
cy.log('create new project file')
|
|
cy.get('button').contains('New file').click({ force: true })
|
|
cy.findByRole('dialog').within(() => {
|
|
cy.get('input').clear()
|
|
cy.get('input').type(fileName)
|
|
cy.findByText('Create').click()
|
|
})
|
|
// force: The file-tree pane is too narrow to display the full name.
|
|
cy.findByTestId('file-tree').findByText(fileName).click({ force: true })
|
|
|
|
// wait until we've switched to the newly created empty file
|
|
cy.findByText('Loading…').should('not.exist')
|
|
cy.get('.cm-line').should('have.length', 1)
|
|
|
|
return fileName
|
|
}
|
|
|
|
export function prepareFileUploadTest(binary = false) {
|
|
const name = `${uuid()}.txt`
|
|
const content = `Test File Content ${name}${binary ? ' \x00' : ''}`
|
|
cy.get('button').contains('Upload').click({ force: true })
|
|
cy.get('input[type=file]')
|
|
.first()
|
|
.selectFile(
|
|
{
|
|
contents: Cypress.Buffer.from(content),
|
|
fileName: name,
|
|
lastModified: Date.now(),
|
|
},
|
|
{ force: true }
|
|
)
|
|
|
|
// wait for the upload to finish
|
|
cy.findByRole('treeitem', { name })
|
|
|
|
return function check() {
|
|
cy.findByRole('treeitem', { name }).click()
|
|
if (binary) {
|
|
cy.findByText(content).should('not.have.class', 'cm-line')
|
|
} else {
|
|
cy.findByText(content).should('have.class', 'cm-line')
|
|
}
|
|
}
|
|
}
|
|
|
|
export function testNewFileUpload() {
|
|
it('can upload text file', () => {
|
|
const check = prepareFileUploadTest(false)
|
|
check()
|
|
})
|
|
|
|
it('can upload binary file', () => {
|
|
const check = prepareFileUploadTest(true)
|
|
check()
|
|
})
|
|
}
|