mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #18465 from overleaf/jpa-more-server-pro-e2e-tests
[server-pro] add more e2e tests for Server Pro GitOrigin-RevId: 003a92ae6c12b58d1d31679f9d9e54d83cfc4a1e
This commit is contained in:
parent
8eb8b233c0
commit
2754c90ea6
14 changed files with 774 additions and 83 deletions
|
@ -403,76 +403,6 @@ if (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ######################
|
|
||||||
// Overleaf Server Pro
|
|
||||||
// ######################
|
|
||||||
|
|
||||||
if (parse(process.env.OVERLEAF_IS_SERVER_PRO) === true) {
|
|
||||||
settings.bypassPercentageRollouts = true
|
|
||||||
settings.apis.references = { url: 'http://127.0.0.1:3040' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compiler
|
|
||||||
// --------
|
|
||||||
if (process.env.SANDBOXED_COMPILES === 'true') {
|
|
||||||
settings.clsi = {
|
|
||||||
dockerRunner: true,
|
|
||||||
docker: {
|
|
||||||
image: process.env.TEX_LIVE_DOCKER_IMAGE,
|
|
||||||
env: {
|
|
||||||
HOME: '/tmp',
|
|
||||||
PATH:
|
|
||||||
process.env.COMPILER_PATH ||
|
|
||||||
'/usr/local/texlive/2015/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
|
|
||||||
},
|
|
||||||
user: 'www-data',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (settings.path == null) {
|
|
||||||
settings.path = {}
|
|
||||||
}
|
|
||||||
settings.path.synctexBaseDir = () => '/compile'
|
|
||||||
if (process.env.SANDBOXED_COMPILES_SIBLING_CONTAINERS === 'true') {
|
|
||||||
console.log('Using sibling containers for sandboxed compiles')
|
|
||||||
if (process.env.SANDBOXED_COMPILES_HOST_DIR) {
|
|
||||||
settings.path.sandboxedCompilesHostDir =
|
|
||||||
process.env.SANDBOXED_COMPILES_HOST_DIR
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
'Sibling containers, but SANDBOXED_COMPILES_HOST_DIR not set'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Templates
|
|
||||||
// ---------
|
|
||||||
if (process.env.OVERLEAF_TEMPLATES_USER_ID) {
|
|
||||||
settings.templates = {
|
|
||||||
mountPointUrl: '/templates',
|
|
||||||
user_id: process.env.OVERLEAF_TEMPLATES_USER_ID,
|
|
||||||
}
|
|
||||||
|
|
||||||
settings.templateLinks = parse(
|
|
||||||
process.env.OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// /Learn
|
|
||||||
// -------
|
|
||||||
if (process.env.OVERLEAF_PROXY_LEARN != null) {
|
|
||||||
settings.proxyLearn = parse(process.env.OVERLEAF_PROXY_LEARN)
|
|
||||||
if (settings.proxyLearn) {
|
|
||||||
settings.nav.header_extras = [
|
|
||||||
{
|
|
||||||
url: '/learn',
|
|
||||||
text: 'documentation',
|
|
||||||
},
|
|
||||||
].concat(settings.nav.header_extras || [])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /References
|
// /References
|
||||||
// -----------
|
// -----------
|
||||||
if (process.env.OVERLEAF_ELASTICSEARCH_URL != null) {
|
if (process.env.OVERLEAF_ELASTICSEARCH_URL != null) {
|
||||||
|
|
|
@ -6,6 +6,9 @@ all: test-e2e
|
||||||
# Notable the container labels com.docker.compose.project.working_dir and com.docker.compose.project.config_files need to match when creating containers from the docker host (how you started things) and from host-admin (how tests reconfigure the instance).
|
# Notable the container labels com.docker.compose.project.working_dir and com.docker.compose.project.config_files need to match when creating containers from the docker host (how you started things) and from host-admin (how tests reconfigure the instance).
|
||||||
export PWD = $(shell pwd)
|
export PWD = $(shell pwd)
|
||||||
|
|
||||||
|
export TEX_LIVE_DOCKER_IMAGE ?= quay.io/sharelatex/texlive-full:2022.1
|
||||||
|
export ALL_TEX_LIVE_DOCKER_IMAGES ?= quay.io/sharelatex/texlive-full:2022.1,quay.io/sharelatex/texlive-full:2021.1
|
||||||
|
|
||||||
test-e2e:
|
test-e2e:
|
||||||
docker compose up --build --no-log-prefix --exit-code-from=e2e e2e
|
docker compose up --build --no-log-prefix --exit-code-from=e2e e2e
|
||||||
|
|
||||||
|
@ -18,5 +21,7 @@ clean:
|
||||||
prefetch:
|
prefetch:
|
||||||
docker compose pull e2e mongo redis
|
docker compose pull e2e mongo redis
|
||||||
docker compose build
|
docker compose build
|
||||||
|
echo -n "$$ALL_TEX_LIVE_DOCKER_IMAGES" | xargs -d, -I% \
|
||||||
|
sh -exc 'tag=%; re_tag=quay.io/sharelatex/$${tag#*/}; docker pull $$tag; docker tag $$tag $$re_tag'
|
||||||
|
|
||||||
.PHONY: test-e2e test-e2e-open
|
.PHONY: test-e2e test-e2e-open
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { ensureUserExists, login } from './helpers/login'
|
import { ensureUserExists, login } from './helpers/login'
|
||||||
import { createProject } from './helpers/project'
|
import { createProject } from './helpers/project'
|
||||||
import { startWith } from './helpers/config'
|
import { startWith } from './helpers/config'
|
||||||
|
import { throttledRecompile } from './helpers/compile'
|
||||||
|
|
||||||
describe('Project creation and compilation', function () {
|
describe('Project creation and compilation', function () {
|
||||||
startWith({})
|
startWith({})
|
||||||
|
@ -13,12 +14,10 @@ describe('Project creation and compilation', function () {
|
||||||
// this is the first project created, the welcome screen is displayed instead of the project list
|
// this is the first project created, the welcome screen is displayed instead of the project list
|
||||||
createProject('test-project')
|
createProject('test-project')
|
||||||
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||||
|
const recompile = throttledRecompile()
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByText('\\maketitle').parent().click()
|
||||||
cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}')
|
cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}')
|
||||||
// Wait for the PDF compilation throttling
|
recompile()
|
||||||
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
|
||||||
cy.wait(3000)
|
|
||||||
cy.findByText('Recompile').click()
|
|
||||||
cy.get('.pdf-viewer').should('contain.text', 'Test Section')
|
cy.get('.pdf-viewer').should('contain.text', 'Test Section')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
1
server-ce/test/cypress/.gitignore
vendored
1
server-ce/test/cypress/.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
downloads/
|
downloads/
|
||||||
results/
|
results/
|
||||||
|
compiles/
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
import '@testing-library/cypress/add-commands'
|
import '@testing-library/cypress/add-commands'
|
||||||
|
|
||||||
|
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||||
|
if (err.message.includes('ResizeObserver')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
|
@ -14,6 +14,7 @@ services:
|
||||||
# The host-admin service initiates the mongo replica set
|
# The host-admin service initiates the mongo replica set
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
|
OVERLEAF_SITE_URL: 'http://sharelatex'
|
||||||
OVERLEAF_APP_NAME: Overleaf Community Edition
|
OVERLEAF_APP_NAME: Overleaf Community Edition
|
||||||
OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true
|
OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true
|
||||||
OVERLEAF_REDIS_HOST: redis
|
OVERLEAF_REDIS_HOST: redis
|
||||||
|
@ -39,6 +40,16 @@ services:
|
||||||
redis:
|
redis:
|
||||||
image: redis:7.2.1
|
image: redis:7.2.1
|
||||||
|
|
||||||
|
git-bridge:
|
||||||
|
image: quay.io/sharelatex/git-bridge:latest
|
||||||
|
environment:
|
||||||
|
GIT_BRIDGE_API_BASE_URL: "http://sharelatex:3000/api/v0/" # "http://sharelatex/api/v0/" for version 4.1.6 and earlier
|
||||||
|
GIT_BRIDGE_OAUTH2_SERVER: "http://sharelatex"
|
||||||
|
GIT_BRIDGE_POSTBACK_BASE_URL: "http://git-bridge:8000"
|
||||||
|
GIT_BRIDGE_ROOT_DIR: "/data/git-bridge"
|
||||||
|
user: root
|
||||||
|
command: ["/server-pro-start.sh"]
|
||||||
|
|
||||||
e2e:
|
e2e:
|
||||||
image: cypress/included:13.6.6
|
image: cypress/included:13.6.6
|
||||||
stop_grace_period: 0s
|
stop_grace_period: 0s
|
||||||
|
@ -87,6 +98,8 @@ services:
|
||||||
stop_grace_period: 0s
|
stop_grace_period: 0s
|
||||||
environment:
|
environment:
|
||||||
PWD:
|
PWD:
|
||||||
|
TEX_LIVE_DOCKER_IMAGE:
|
||||||
|
ALL_TEX_LIVE_DOCKER_IMAGES:
|
||||||
IMAGE_TAG_CE: ${IMAGE_TAG_CE:-sharelatex/sharelatex:latest}
|
IMAGE_TAG_CE: ${IMAGE_TAG_CE:-sharelatex/sharelatex:latest}
|
||||||
IMAGE_TAG_PRO: ${IMAGE_TAG_PRO:-quay.io/sharelatex/sharelatex-pro:latest}
|
IMAGE_TAG_PRO: ${IMAGE_TAG_PRO:-quay.io/sharelatex/sharelatex-pro:latest}
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|
280
server-ce/test/git-bridge.spec.ts
Normal file
280
server-ce/test/git-bridge.spec.ts
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
import { startWith } from './helpers/config'
|
||||||
|
import { ensureUserExists, login } from './helpers/login'
|
||||||
|
import { createProject } from './helpers/project'
|
||||||
|
|
||||||
|
import git from 'isomorphic-git'
|
||||||
|
import http from 'isomorphic-git/http/web'
|
||||||
|
import LightningFS from '@isomorphic-git/lightning-fs'
|
||||||
|
import { throttledRecompile } from './helpers/compile'
|
||||||
|
|
||||||
|
describe('git-bridge', function () {
|
||||||
|
const ENABLED_VARS = {
|
||||||
|
GIT_BRIDGE_ENABLED: 'true',
|
||||||
|
GIT_BRIDGE_HOST: 'git-bridge',
|
||||||
|
GIT_BRIDGE_PORT: '8000',
|
||||||
|
V1_HISTORY_URL: 'http://sharelatex:3100/api',
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('enabled in Server Pro', function () {
|
||||||
|
startWith({
|
||||||
|
pro: true,
|
||||||
|
vars: ENABLED_VARS,
|
||||||
|
})
|
||||||
|
ensureUserExists({ email: 'user@example.com' })
|
||||||
|
|
||||||
|
function clearAllTokens() {
|
||||||
|
cy.get('button.linking-git-bridge-revoke-button').each(el => {
|
||||||
|
cy.wrap(el).click()
|
||||||
|
cy.findByText('Delete token').click()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function maybeClearAllTokens() {
|
||||||
|
cy.visit('/user/settings')
|
||||||
|
cy.findByText('Git Integration')
|
||||||
|
cy.get('button')
|
||||||
|
.contains(/Generate token|Add another token/)
|
||||||
|
.then(btn => {
|
||||||
|
if (btn.text() === 'Add another token') {
|
||||||
|
clearAllTokens()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
login('user@example.com')
|
||||||
|
maybeClearAllTokens()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render the git-bridge UI in the settings', () => {
|
||||||
|
cy.visit('/user/settings')
|
||||||
|
cy.findByText('Git Integration')
|
||||||
|
cy.get('button').contains('Generate token').click()
|
||||||
|
cy.get('code')
|
||||||
|
.contains(/olp_[a-zA-Z0-9]{16}/)
|
||||||
|
.as('newToken')
|
||||||
|
cy.findAllByText('Close').last().click()
|
||||||
|
cy.get('@newToken').then(token => {
|
||||||
|
// There can be more than one token with the same prefix when retrying
|
||||||
|
cy.findAllByText(
|
||||||
|
`${token.text().slice(0, 'olp_1234'.length)}${'*'.repeat(12)}`
|
||||||
|
).should('have.length.at.least', 1)
|
||||||
|
})
|
||||||
|
cy.get('button').contains('Generate token').should('not.exist')
|
||||||
|
cy.get('button').contains('Add another token').should('exist')
|
||||||
|
clearAllTokens()
|
||||||
|
cy.get('button').contains('Generate token').should('exist')
|
||||||
|
cy.get('button').contains('Add another token').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render the git-bridge UI in the editor', function () {
|
||||||
|
cy.visit('/project')
|
||||||
|
createProject('git').as('projectId')
|
||||||
|
cy.get('header').findByText('Menu').click()
|
||||||
|
cy.findByText('Sync')
|
||||||
|
cy.findByText('Git').click()
|
||||||
|
cy.findByRole('dialog').within(() => {
|
||||||
|
cy.get('@projectId').then(id => {
|
||||||
|
cy.get('code').contains(`git clone http://git@sharelatex/git/${id}`)
|
||||||
|
})
|
||||||
|
cy.findByRole('button', {
|
||||||
|
name: 'Generate token',
|
||||||
|
}).click()
|
||||||
|
cy.get('code').contains(/olp_[a-zA-Z0-9]{16}/)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Re-open
|
||||||
|
cy.url().then(url => cy.visit(url))
|
||||||
|
cy.get('header').findByText('Menu').click()
|
||||||
|
cy.findByText('Git').click()
|
||||||
|
cy.findByRole('dialog').within(() => {
|
||||||
|
cy.get('@projectId').then(id => {
|
||||||
|
cy.get('code').contains(`git clone http://git@sharelatex/git/${id}`)
|
||||||
|
})
|
||||||
|
cy.findByText('Generate token').should('not.exist')
|
||||||
|
cy.findByText(/generate a new one in Account Settings/)
|
||||||
|
cy.findByText('Go to settings')
|
||||||
|
.should('have.attr', 'target', '_blank')
|
||||||
|
.and('have.attr', 'href', '/user/settings')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should expose interface for git', () => {
|
||||||
|
cy.visit('/project')
|
||||||
|
createProject('git').as('projectId')
|
||||||
|
const recompile = throttledRecompile()
|
||||||
|
|
||||||
|
cy.get('header').findByText('Menu').click()
|
||||||
|
cy.findByText('Sync')
|
||||||
|
cy.findByText('Git').click()
|
||||||
|
cy.get('@projectId').then(projectId => {
|
||||||
|
cy.findByRole('dialog').within(() => {
|
||||||
|
cy.get('code').contains(
|
||||||
|
`git clone http://git@sharelatex/git/${projectId}`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
cy.findByRole('button', {
|
||||||
|
name: 'Generate token',
|
||||||
|
}).click()
|
||||||
|
cy.get('code')
|
||||||
|
.contains(/olp_[a-zA-Z0-9]{16}/)
|
||||||
|
.then(async tokenEl => {
|
||||||
|
const token = tokenEl.text()
|
||||||
|
|
||||||
|
// close Git modal
|
||||||
|
cy.findAllByText('Close').last().click()
|
||||||
|
// 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) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(path, { encoding: 'utf8' }, (err, blob) => {
|
||||||
|
if (err) return reject(err)
|
||||||
|
resolve(blob)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
async function writeFile(path: string, data: string) {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
fs.writeFile(path, data, undefined, err => {
|
||||||
|
if (err) return reject(err)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonOptions = {
|
||||||
|
dir,
|
||||||
|
fs,
|
||||||
|
}
|
||||||
|
const httpOptions = {
|
||||||
|
http,
|
||||||
|
url: `http://sharelatex/git/${projectId}`,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Basic ${Buffer.from(`git:${token}`).toString('base64')}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const authorOptions = {
|
||||||
|
author: { name: 'user', email: 'user@example.com' },
|
||||||
|
committer: { name: 'user', email: 'user@example.com' },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone
|
||||||
|
cy.then({ timeout: 10_000 }, async () => {
|
||||||
|
await git.clone({
|
||||||
|
...commonOptions,
|
||||||
|
...httpOptions,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const mainTex = `${dir}/main.tex`
|
||||||
|
const text = `
|
||||||
|
\\documentclass{article}
|
||||||
|
\\begin{document}
|
||||||
|
Hello world
|
||||||
|
\\end{document}
|
||||||
|
`
|
||||||
|
|
||||||
|
// Make a change
|
||||||
|
cy.then(async () => {
|
||||||
|
await writeFile(mainTex, text)
|
||||||
|
await git.add({
|
||||||
|
...commonOptions,
|
||||||
|
filepath: 'main.tex',
|
||||||
|
})
|
||||||
|
await git.commit({
|
||||||
|
...commonOptions,
|
||||||
|
...authorOptions,
|
||||||
|
message: 'Swap main.tex',
|
||||||
|
})
|
||||||
|
await git.push({
|
||||||
|
...commonOptions,
|
||||||
|
...httpOptions,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// check push in editor
|
||||||
|
cy.findByText(/\\documentclass/)
|
||||||
|
.parent()
|
||||||
|
.parent()
|
||||||
|
.should('have.text', text.replaceAll('\n', ''))
|
||||||
|
|
||||||
|
// Wait for history sync - trigger flush by toggling the UI
|
||||||
|
cy.findAllByText('History').last().click()
|
||||||
|
cy.findAllByText('Back to editor').last().click()
|
||||||
|
|
||||||
|
// check push in history
|
||||||
|
cy.findAllByText('History').last().click()
|
||||||
|
cy.findByText(/Hello world/)
|
||||||
|
cy.findByText('(via Git)').should('exist')
|
||||||
|
|
||||||
|
// Back to the editor
|
||||||
|
cy.findAllByText('Back to editor').last().click()
|
||||||
|
cy.findByText(/\\documentclass/)
|
||||||
|
.parent()
|
||||||
|
.parent()
|
||||||
|
.click()
|
||||||
|
.type('% via editor{enter}')
|
||||||
|
|
||||||
|
// Trigger flush via compile
|
||||||
|
recompile()
|
||||||
|
|
||||||
|
// Back into the history, check what we just added
|
||||||
|
cy.findAllByText('History').last().click()
|
||||||
|
cy.findByText(/% via editor/)
|
||||||
|
|
||||||
|
// Pull the change
|
||||||
|
cy.then(async () => {
|
||||||
|
await git.pull({
|
||||||
|
...commonOptions,
|
||||||
|
...httpOptions,
|
||||||
|
...authorOptions,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await readFile(mainTex)).to.equal(text + '% via editor\n')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function checkDisabled() {
|
||||||
|
ensureUserExists({ email: 'user@example.com' })
|
||||||
|
|
||||||
|
it('should not render the git-bridge UI in the settings', () => {
|
||||||
|
login('user@example.com')
|
||||||
|
cy.visit('/user/settings')
|
||||||
|
cy.findByText('Git Integration').should('not.exist')
|
||||||
|
})
|
||||||
|
it('should not render the git-bridge UI in the editor', function () {
|
||||||
|
login('user@example.com')
|
||||||
|
cy.visit('/project')
|
||||||
|
createProject('maybe git')
|
||||||
|
cy.get('header').findByText('Menu').click()
|
||||||
|
cy.findByText('Sync').should('not.exist')
|
||||||
|
cy.findByText('Git').should('not.exist')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('disabled in Server Pro', () => {
|
||||||
|
startWith({
|
||||||
|
pro: true,
|
||||||
|
})
|
||||||
|
checkDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unavailable in CE', () => {
|
||||||
|
startWith({
|
||||||
|
pro: false,
|
||||||
|
vars: ENABLED_VARS,
|
||||||
|
})
|
||||||
|
checkDisabled()
|
||||||
|
})
|
||||||
|
})
|
22
server-ce/test/helpers/compile.ts
Normal file
22
server-ce/test/helpers/compile.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Helper function for throttling clicks on the recompile button to avoid hitting server side rate limits.
|
||||||
|
* The naive approach is waiting a fixed a mount of time (3s) just before clicking the button.
|
||||||
|
* This helper takes into account that other UI interactions take time. We can deduce that latency from the fixed delay (3s minus other latency). This can bring down the effective waiting time to 0s.
|
||||||
|
*/
|
||||||
|
export function throttledRecompile() {
|
||||||
|
let lastCompile = 0
|
||||||
|
function queueReset() {
|
||||||
|
cy.then(() => {
|
||||||
|
lastCompile = Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
queueReset()
|
||||||
|
return () =>
|
||||||
|
cy.then(() => {
|
||||||
|
const msSinceLastCompile = Date.now() - lastCompile
|
||||||
|
cy.wait(Math.max(0, 3_000 - msSinceLastCompile))
|
||||||
|
cy.findByText('Recompile').click()
|
||||||
|
queueReset()
|
||||||
|
})
|
||||||
|
}
|
|
@ -57,6 +57,8 @@ async function fetchJSON(
|
||||||
const { error, stdout, stderr } = await res.json()
|
const { error, stdout, stderr } = await res.json()
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(input, init, 'failed:', error)
|
console.error(input, init, 'failed:', error)
|
||||||
|
if (stdout) console.log(stdout)
|
||||||
|
if (stderr) console.warn(stderr)
|
||||||
const err = new Error(error.message)
|
const err = new Error(error.message)
|
||||||
Object.assign(err, error)
|
Object.assign(err, error)
|
||||||
throw err
|
throw err
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
|
const Path = require('path')
|
||||||
const { execFile } = require('child_process')
|
const { execFile } = require('child_process')
|
||||||
const express = require('express')
|
const express = require('express')
|
||||||
const bodyParser = require('body-parser')
|
const bodyParser = require('body-parser')
|
||||||
|
@ -9,8 +10,9 @@ const {
|
||||||
} = require('celebrate')
|
} = require('celebrate')
|
||||||
const YAML = require('js-yaml')
|
const YAML = require('js-yaml')
|
||||||
|
|
||||||
const FILES = {
|
const PATHS = {
|
||||||
DOCKER_COMPOSE: 'docker-compose.override.yml',
|
DOCKER_COMPOSE_OVERRIDE: 'docker-compose.override.yml',
|
||||||
|
SANDBOXED_COMPILES_HOST_DIR: Path.join(__dirname, 'cypress/compiles'),
|
||||||
}
|
}
|
||||||
const IMAGES = {
|
const IMAGES = {
|
||||||
CE: process.env.IMAGE_TAG_CE.replace(/:.+/, ''),
|
CE: process.env.IMAGE_TAG_CE.replace(/:.+/, ''),
|
||||||
|
@ -21,7 +23,7 @@ let mongoIsInitialized = false
|
||||||
|
|
||||||
function readDockerComposeOverride() {
|
function readDockerComposeOverride() {
|
||||||
try {
|
try {
|
||||||
return YAML.load(fs.readFileSync(FILES.DOCKER_COMPOSE, 'utf-8'))
|
return YAML.load(fs.readFileSync(PATHS.DOCKER_COMPOSE_OVERRIDE, 'utf-8'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.code !== 'ENOENT') {
|
if (error.code !== 'ENOENT') {
|
||||||
throw error
|
throw error
|
||||||
|
@ -31,13 +33,14 @@ function readDockerComposeOverride() {
|
||||||
sharelatex: {
|
sharelatex: {
|
||||||
environment: {},
|
environment: {},
|
||||||
},
|
},
|
||||||
|
'git-bridge': {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function writeDockerComposeOverride(cfg) {
|
function writeDockerComposeOverride(cfg) {
|
||||||
fs.writeFileSync(FILES.DOCKER_COMPOSE, YAML.dump(cfg))
|
fs.writeFileSync(PATHS.DOCKER_COMPOSE_OVERRIDE, YAML.dump(cfg))
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = express()
|
const app = express()
|
||||||
|
@ -95,6 +98,7 @@ function setVersionDockerCompose({ pro, version }) {
|
||||||
const cfg = readDockerComposeOverride()
|
const cfg = readDockerComposeOverride()
|
||||||
|
|
||||||
cfg.services.sharelatex.image = `${pro ? IMAGES.PRO : IMAGES.CE}:${version}`
|
cfg.services.sharelatex.image = `${pro ? IMAGES.PRO : IMAGES.CE}:${version}`
|
||||||
|
cfg.services['git-bridge'].image = `quay.io/sharelatex/git-bridge:${version}`
|
||||||
|
|
||||||
writeDockerComposeOverride(cfg)
|
writeDockerComposeOverride(cfg)
|
||||||
}
|
}
|
||||||
|
@ -128,17 +132,52 @@ app.post(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const allowedVars = Joi.object().keys({
|
const allowedVars = Joi.object(
|
||||||
OVERLEAF_APP_NAME: Joi.string(),
|
Object.fromEntries(
|
||||||
OVERLEAF_LEFT_FOOTER: Joi.string(),
|
[
|
||||||
OVERLEAF_RIGHT_FOOTER: Joi.string(),
|
'OVERLEAF_APP_NAME',
|
||||||
})
|
'OVERLEAF_LEFT_FOOTER',
|
||||||
|
'OVERLEAF_RIGHT_FOOTER',
|
||||||
|
'OVERLEAF_PROXY_LEARN',
|
||||||
|
'GIT_BRIDGE_ENABLED',
|
||||||
|
'GIT_BRIDGE_HOST',
|
||||||
|
'GIT_BRIDGE_PORT',
|
||||||
|
'V1_HISTORY_URL',
|
||||||
|
'DOCKER_RUNNER',
|
||||||
|
'SANDBOXED_COMPILES',
|
||||||
|
'SANDBOXED_COMPILES_SIBLING_CONTAINERS',
|
||||||
|
'ALL_TEX_LIVE_DOCKER_IMAGE_NAMES',
|
||||||
|
].map(name => [name, Joi.string()])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
function setVarsDockerCompose({ vars }) {
|
function setVarsDockerCompose({ vars }) {
|
||||||
const cfg = readDockerComposeOverride()
|
const cfg = readDockerComposeOverride()
|
||||||
|
|
||||||
cfg.services.sharelatex.environment = vars
|
cfg.services.sharelatex.environment = vars
|
||||||
|
|
||||||
|
if (cfg.services.sharelatex.environment.GIT_BRIDGE_ENABLED === 'true') {
|
||||||
|
cfg.services.sharelatex.depends_on = ['git-bridge']
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
cfg.services.sharelatex.environment
|
||||||
|
.SANDBOXED_COMPILES_SIBLING_CONTAINERS === 'true'
|
||||||
|
) {
|
||||||
|
cfg.services.sharelatex.environment.SANDBOXED_COMPILES_HOST_DIR =
|
||||||
|
PATHS.SANDBOXED_COMPILES_HOST_DIR
|
||||||
|
cfg.services.sharelatex.environment.TEX_LIVE_DOCKER_IMAGE =
|
||||||
|
process.env.TEX_LIVE_DOCKER_IMAGE
|
||||||
|
cfg.services.sharelatex.environment.ALL_TEX_LIVE_DOCKER_IMAGES =
|
||||||
|
process.env.ALL_TEX_LIVE_DOCKER_IMAGES
|
||||||
|
cfg.services.sharelatex.volumes = [
|
||||||
|
'/var/run/docker.sock:/var/run/docker.sock',
|
||||||
|
`${PATHS.SANDBOXED_COMPILES_HOST_DIR}:/var/lib/overleaf/data/compiles`,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
cfg.services.sharelatex.volumes = []
|
||||||
|
}
|
||||||
|
|
||||||
writeDockerComposeOverride(cfg)
|
writeDockerComposeOverride(cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +221,7 @@ app.post(
|
||||||
'--timeout',
|
'--timeout',
|
||||||
'0',
|
'0',
|
||||||
'sharelatex',
|
'sharelatex',
|
||||||
|
'git-bridge',
|
||||||
'mongo',
|
'mongo',
|
||||||
'redis'
|
'redis'
|
||||||
),
|
),
|
||||||
|
|
83
server-ce/test/learn-wiki.spec.ts
Normal file
83
server-ce/test/learn-wiki.spec.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import { startWith } from './helpers/config'
|
||||||
|
import { ensureUserExists, login } from './helpers/login'
|
||||||
|
|
||||||
|
describe('LearnWiki', function () {
|
||||||
|
const COPYING_A_PROJECT_URL = '/learn/how-to/Copying_a_project'
|
||||||
|
const UPLOADING_A_PROJECT_URL = '/learn/how-to/Uploading_a_project'
|
||||||
|
|
||||||
|
describe('enabled in Pro', () => {
|
||||||
|
startWith({
|
||||||
|
pro: true,
|
||||||
|
vars: {
|
||||||
|
OVERLEAF_PROXY_LEARN: 'true',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ensureUserExists({ email: 'user@example.com' })
|
||||||
|
|
||||||
|
it('should add a documentation entry to the nav bar', () => {
|
||||||
|
login('user@example.com')
|
||||||
|
cy.visit('/project')
|
||||||
|
cy.get('nav').findByText('Documentation')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should render wiki page', () => {
|
||||||
|
login('user@example.com')
|
||||||
|
cy.visit(UPLOADING_A_PROJECT_URL)
|
||||||
|
// Wiki content
|
||||||
|
cy.get('.page').findByText('Uploading a project')
|
||||||
|
cy.get('.page').contains(/how to create an Overleaf project/)
|
||||||
|
cy.get('img[alt="Creating a new project on Overleaf"]')
|
||||||
|
.should('be.visible')
|
||||||
|
.and((el: any) => {
|
||||||
|
expect(el[0].naturalWidth, 'renders image').to.be.greaterThan(0)
|
||||||
|
})
|
||||||
|
// Wiki navigation
|
||||||
|
cy.get('.contents').findByText('Copying a project')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should navigate back and forth', function () {
|
||||||
|
login('user@example.com')
|
||||||
|
cy.visit(COPYING_A_PROJECT_URL)
|
||||||
|
cy.get('.page').findByText('Copying a project')
|
||||||
|
cy.get('.contents').findByText('Uploading a project').click()
|
||||||
|
cy.url().should('contain', UPLOADING_A_PROJECT_URL)
|
||||||
|
cy.get('.page').findByText('Uploading a project')
|
||||||
|
cy.get('.contents').findByText('Copying a project').click()
|
||||||
|
cy.url().should('contain', COPYING_A_PROJECT_URL)
|
||||||
|
cy.get('.page').findByText('Copying a project')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('disabled in Pro', () => {
|
||||||
|
startWith({ pro: true })
|
||||||
|
checkDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('unavailable in CE', () => {
|
||||||
|
startWith({
|
||||||
|
pro: false,
|
||||||
|
vars: {
|
||||||
|
OVERLEAF_PROXY_LEARN: 'true',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
checkDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
|
function checkDisabled() {
|
||||||
|
ensureUserExists({ email: 'user@example.com' })
|
||||||
|
|
||||||
|
it('should not add a documentation entry to the nav bar', () => {
|
||||||
|
login('user@example.com')
|
||||||
|
cy.visit('/project')
|
||||||
|
cy.findByText('Documentation').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not render wiki page', () => {
|
||||||
|
login('user@example.com')
|
||||||
|
cy.visit(COPYING_A_PROJECT_URL, {
|
||||||
|
failOnStatusCode: false,
|
||||||
|
})
|
||||||
|
cy.findByText('Not found')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
229
server-ce/test/package-lock.json
generated
229
server-ce/test/package-lock.json
generated
|
@ -6,11 +6,13 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "@overleaf/server-ce/test",
|
"name": "@overleaf/server-ce/test",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@isomorphic-git/lightning-fs": "^4.6.0",
|
||||||
"@testing-library/cypress": "^10.0.1",
|
"@testing-library/cypress": "^10.0.1",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"celebrate": "^15.0.3",
|
"celebrate": "^15.0.3",
|
||||||
"cypress": "13.6.6",
|
"cypress": "13.6.6",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"isomorphic-git": "^1.25.10",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
}
|
}
|
||||||
|
@ -238,6 +240,25 @@
|
||||||
"@hapi/hoek": "^9.0.0"
|
"@hapi/hoek": "^9.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@isomorphic-git/idb-keyval": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isomorphic-git/idb-keyval/-/idb-keyval-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-r8/AdpiS0/WJCNR/t/gsgL+M8NMVj/ek7s60uz3LmpCaTF2mEVlZJlB01ZzalgYzRLXwSPC92o+pdzjM7PN/pA=="
|
||||||
|
},
|
||||||
|
"node_modules/@isomorphic-git/lightning-fs": {
|
||||||
|
"version": "4.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@isomorphic-git/lightning-fs/-/lightning-fs-4.6.0.tgz",
|
||||||
|
"integrity": "sha512-tfon8f1h6LawjFI/d8lZPWRPTxmdvyTMbkT/j5yo6dB0hALhKw5D9JsdCcUu/D1pAcMMiU7GZFDsDGqylerr7g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@isomorphic-git/idb-keyval": "3.3.2",
|
||||||
|
"isomorphic-textencoder": "1.0.1",
|
||||||
|
"just-debounce-it": "1.1.0",
|
||||||
|
"just-once": "1.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"superblocktxt": "src/superblocktxt.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sideway/address": {
|
"node_modules/@sideway/address": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
|
||||||
|
@ -469,6 +490,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
|
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/async-lock": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="
|
||||||
|
},
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
@ -719,6 +745,11 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/clean-git-ref": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="
|
||||||
|
},
|
||||||
"node_modules/clean-stack": {
|
"node_modules/clean-stack": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
|
||||||
|
@ -857,6 +888,17 @@
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
|
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/crc-32": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
|
||||||
|
"bin": {
|
||||||
|
"crc32": "bin/crc32.njs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
@ -972,6 +1014,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decompress-response": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"mimic-response": "^3.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deep-equal": {
|
"node_modules/deep-equal": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz",
|
||||||
|
@ -1054,6 +1110,11 @@
|
||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/diff3": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g=="
|
||||||
|
},
|
||||||
"node_modules/dom-accessibility-api": {
|
"node_modules/dom-accessibility-api": {
|
||||||
"version": "0.5.16",
|
"version": "0.5.16",
|
||||||
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
|
||||||
|
@ -1284,6 +1345,11 @@
|
||||||
"node >=0.6.0"
|
"node >=0.6.0"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-text-encoding": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w=="
|
||||||
|
},
|
||||||
"node_modules/fd-slicer": {
|
"node_modules/fd-slicer": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
|
||||||
|
@ -1649,6 +1715,14 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/ignore": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/indent-string": {
|
"node_modules/indent-string": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
|
||||||
|
@ -1976,6 +2050,46 @@
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/isomorphic-git": {
|
||||||
|
"version": "1.25.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.25.10.tgz",
|
||||||
|
"integrity": "sha512-IxGiaKBwAdcgBXwIcxJU6rHLk+NrzYaaPKXXQffcA0GW3IUrQXdUPDXDo+hkGVcYruuz/7JlGBiuaeTCgIgivQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"async-lock": "^1.4.1",
|
||||||
|
"clean-git-ref": "^2.0.1",
|
||||||
|
"crc-32": "^1.2.0",
|
||||||
|
"diff3": "0.0.3",
|
||||||
|
"ignore": "^5.1.4",
|
||||||
|
"minimisted": "^2.0.0",
|
||||||
|
"pako": "^1.0.10",
|
||||||
|
"pify": "^4.0.1",
|
||||||
|
"readable-stream": "^3.4.0",
|
||||||
|
"sha.js": "^2.4.9",
|
||||||
|
"simple-get": "^4.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"isogit": "cli.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/isomorphic-git/node_modules/pify": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/isomorphic-textencoder": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/isomorphic-textencoder/-/isomorphic-textencoder-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-676hESgHullDdHDsj469hr+7t3i/neBKU9J7q1T4RHaWwLAsaQnywC0D1dIUId0YZ+JtVrShzuBk1soo0+GVcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"fast-text-encoding": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isstream": {
|
"node_modules/isstream": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||||
|
@ -2049,6 +2163,16 @@
|
||||||
"verror": "1.10.0"
|
"verror": "1.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/just-debounce-it": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/just-debounce-it/-/just-debounce-it-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-87Nnc0qZKgBZuhFZjYVjSraic0x7zwjhaTMrCKlj0QYKH6lh0KbFzVnfu6LHan03NO7J8ygjeBeD0epejn5Zcg=="
|
||||||
|
},
|
||||||
|
"node_modules/just-once": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/just-once/-/just-once-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-+rZVpl+6VyTilK7vB/svlMPil4pxqIJZkbnN7DKZTOzyXfun6ZiFeq2Pk4EtCEHZ0VU4EkdFzG8ZK5F3PErcDw=="
|
||||||
|
},
|
||||||
"node_modules/lazy-ass": {
|
"node_modules/lazy-ass": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
|
||||||
|
@ -2237,6 +2361,17 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mimic-response": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
@ -2256,6 +2391,14 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/minimisted": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
@ -2380,6 +2523,11 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
|
@ -2555,6 +2703,19 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/readable-stream": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.3",
|
||||||
|
"string_decoder": "^1.1.1",
|
||||||
|
"util-deprecate": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regenerator-runtime": {
|
"node_modules/regenerator-runtime": {
|
||||||
"version": "0.14.0",
|
"version": "0.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
|
||||||
|
@ -2753,6 +2914,18 @@
|
||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/sha.js": {
|
||||||
|
"version": "2.4.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
||||||
|
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"inherits": "^2.0.1",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sha.js": "bin.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
|
@ -2790,6 +2963,49 @@
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-concat": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/simple-get": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"decompress-response": "^6.0.0",
|
||||||
|
"once": "^1.3.1",
|
||||||
|
"simple-concat": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/slice-ansi": {
|
"node_modules/slice-ansi": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
|
||||||
|
@ -2846,6 +3062,14 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string_decoder": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "~5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
@ -3035,6 +3259,11 @@
|
||||||
"requires-port": "^1.0.0"
|
"requires-port": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||||
|
},
|
||||||
"node_modules/utils-merge": {
|
"node_modules/utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
|
|
|
@ -9,11 +9,13 @@
|
||||||
"format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx,json}'"
|
"format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx,json}'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@isomorphic-git/lightning-fs": "^4.6.0",
|
||||||
"@testing-library/cypress": "^10.0.1",
|
"@testing-library/cypress": "^10.0.1",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"celebrate": "^15.0.3",
|
"celebrate": "^15.0.3",
|
||||||
"cypress": "13.6.6",
|
"cypress": "13.6.6",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
|
"isomorphic-git": "^1.25.10",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
}
|
}
|
||||||
|
|
79
server-ce/test/sandboxed-compiles.spec.ts
Normal file
79
server-ce/test/sandboxed-compiles.spec.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { ensureUserExists, login } from './helpers/login'
|
||||||
|
import { createProject } from './helpers/project'
|
||||||
|
import { startWith } from './helpers/config'
|
||||||
|
import { throttledRecompile } from './helpers/compile'
|
||||||
|
|
||||||
|
describe('SandboxedCompiles', function () {
|
||||||
|
ensureUserExists({ email: 'user@example.com' })
|
||||||
|
|
||||||
|
const enabledVars = {
|
||||||
|
DOCKER_RUNNER: 'true',
|
||||||
|
SANDBOXED_COMPILES: 'true',
|
||||||
|
SANDBOXED_COMPILES_SIBLING_CONTAINERS: 'true',
|
||||||
|
ALL_TEX_LIVE_DOCKER_IMAGE_NAMES: '2023,2022',
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('enabled in Server Pro', () => {
|
||||||
|
startWith({
|
||||||
|
pro: true,
|
||||||
|
vars: enabledVars,
|
||||||
|
})
|
||||||
|
beforeEach(function () {
|
||||||
|
login('user@example.com')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should offer TexLive images and switch the compiler', () => {
|
||||||
|
cy.visit('/project')
|
||||||
|
createProject('sandboxed')
|
||||||
|
const recompile = throttledRecompile()
|
||||||
|
// check produced PDF
|
||||||
|
cy.get('.pdf-viewer').should('contain.text', 'sandboxed')
|
||||||
|
cy.get('[aria-label="View logs"]').click()
|
||||||
|
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2023\) /)
|
||||||
|
cy.get('header').findByText('Menu').click()
|
||||||
|
cy.findByText('TeX Live version')
|
||||||
|
.parent()
|
||||||
|
.findByText('2023')
|
||||||
|
.parent()
|
||||||
|
.select('2022')
|
||||||
|
|
||||||
|
// close editor menu
|
||||||
|
cy.get('#left-menu-modal').click()
|
||||||
|
|
||||||
|
// Trigger compile with other TexLive version
|
||||||
|
recompile()
|
||||||
|
|
||||||
|
cy.get('[aria-label="View logs"]').click()
|
||||||
|
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2022\) /)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function checkUsesDefaultCompiler() {
|
||||||
|
beforeEach(function () {
|
||||||
|
login('user@example.com')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not offer TexLive images and use default compiler', () => {
|
||||||
|
cy.visit('/project')
|
||||||
|
createProject('sandboxed')
|
||||||
|
// check produced PDF
|
||||||
|
cy.get('.pdf-viewer').should('contain.text', 'sandboxed')
|
||||||
|
cy.get('[aria-label="View logs"]').click()
|
||||||
|
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2024\) /)
|
||||||
|
cy.get('header').findByText('Menu').click()
|
||||||
|
cy.findByText('TeX Live version').should('not.exist')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('disabled in Server Pro', () => {
|
||||||
|
startWith({ pro: true })
|
||||||
|
|
||||||
|
checkUsesDefaultCompiler()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe.skip('unavailable in CE', () => {
|
||||||
|
startWith({ pro: false, vars: enabledVars })
|
||||||
|
|
||||||
|
checkUsesDefaultCompiler()
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue