Przeglądaj źródła

Merge pull request #18543 from overleaf/jpa-server-pro-test-templates

[server-pro] tests: add tests for templates feature

GitOrigin-RevId: b36482af924e306207cbdedc1a8d1d548272ee77
Jakob Ackermann 2 lat temu
rodzic
commit
5a1cff55bc

+ 1 - 0
server-ce/test/git-bridge.spec.ts

@@ -258,6 +258,7 @@ Hello world
       cy.visit('/project')
       createProject('maybe git')
       cy.get('header').findByText('Menu').click()
+      cy.findByText('Word Count') // wait for lazy loading
       cy.findByText('Sync').should('not.exist')
       cy.findByText('Git').should('not.exist')
     })

+ 7 - 1
server-ce/test/helpers/config.ts

@@ -2,8 +2,14 @@ import { reconfigure } from './hostAdminClient'
 
 let lastConfig: string
 
-export function startWith({ pro = false, version = 'latest', vars = {} }) {
+export function startWith({
+  pro = false,
+  version = 'latest',
+  vars = {},
+  varsFn = () => ({}),
+}) {
   before(async function () {
+    Object.assign(vars, varsFn())
     const cfg = JSON.stringify({ pro, version, vars })
     if (lastConfig === cfg) return
 

+ 12 - 7
server-ce/test/helpers/login.ts

@@ -52,13 +52,18 @@ export function ensureUserExists({
 }
 
 export function login(username: string, password = DEFAULT_PASSWORD) {
-  cy.session([username, password, new Date()], () => {
-    cy.visit('/login')
-    cy.get('input[name="email"]').type(username)
-    cy.get('input[name="password"]').type(password)
-    cy.findByRole('button', { name: 'Login' }).click()
-    cy.url().should('contain', '/project')
-  })
+  const id = [username, password, new Date()]
+  function startOrResumeSession() {
+    cy.session(id, () => {
+      cy.visit('/login')
+      cy.get('input[name="email"]').type(username)
+      cy.get('input[name="password"]').type(password)
+      cy.findByRole('button', { name: 'Login' }).click()
+      cy.url().should('contain', '/project')
+    })
+  }
+  startOrResumeSession()
+  return startOrResumeSession
 }
 
 export function activateUser(url: string, password = DEFAULT_PASSWORD) {

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

@@ -147,6 +147,8 @@ const allowedVars = Joi.object(
       'SANDBOXED_COMPILES',
       'SANDBOXED_COMPILES_SIBLING_CONTAINERS',
       'ALL_TEX_LIVE_DOCKER_IMAGE_NAMES',
+      'OVERLEAF_TEMPLATES_USER_ID',
+      'OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS',
     ].map(name => [name, Joi.string()])
   )
 )

+ 1 - 0
server-ce/test/sandboxed-compiles.spec.ts

@@ -61,6 +61,7 @@ describe('SandboxedCompiles', function () {
       cy.get('[aria-label="View logs"]').click()
       cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2024\) /)
       cy.get('header').findByText('Menu').click()
+      cy.findByText('Word Count') // wait for lazy loading
       cy.findByText('TeX Live version').should('not.exist')
     })
   }

+ 249 - 0
server-ce/test/templates.spec.ts

@@ -0,0 +1,249 @@
+import { startWith } from './helpers/config'
+import { ensureUserExists, login } from './helpers/login'
+import { createProject } from './helpers/project'
+
+const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com'
+const ADMIN_USER = 'admin@example.com'
+const REGULAR_USER = 'user@example.com'
+const TEMPLATES_USER = 'templates@example.com'
+
+describe('Templates', () => {
+  ensureUserExists({ email: TEMPLATES_USER })
+  ensureUserExists({ email: WITHOUT_PROJECTS_USER })
+
+  let OVERLEAF_TEMPLATES_USER_ID: string
+  before(function () {
+    login(TEMPLATES_USER)
+    cy.visit('/')
+    cy.get('meta[name="ol-user_id"]').then(el => {
+      OVERLEAF_TEMPLATES_USER_ID = el.attr('content')!
+    })
+  })
+
+  function varsFn() {
+    return {
+      OVERLEAF_TEMPLATES_USER_ID,
+      OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS:
+        '[{"name":"All Templates","url":"/templates/all"}]',
+    }
+  }
+
+  describe('enabled in Server Pro', () => {
+    startWith({
+      pro: true,
+      varsFn,
+    })
+    ensureUserExists({ email: REGULAR_USER })
+    ensureUserExists({ email: ADMIN_USER, isAdmin: true })
+
+    it('should show templates link on welcome page', () => {
+      login(WITHOUT_PROJECTS_USER)
+      cy.visit('/')
+      cy.findByText('Browse templates').click()
+      cy.url().should('match', /\/templates$/)
+    })
+
+    it('should have templates feature', () => {
+      const resumeTemplatesUserSession = login(TEMPLATES_USER)
+      const name = `Template ${Date.now()}`
+      const description = `Template Description ${Date.now()}`
+
+      cy.visit('/')
+      createProject(name).as('templateProjectId')
+
+      cy.get('header').findByText('Menu').click()
+      cy.findByText('Manage Template').click()
+
+      cy.findByText('Template Description')
+        .click()
+        .parent()
+        .get('textarea')
+        .type(description)
+      cy.findByText('Publish').click()
+      cy.findByText('Publishing…').should('be.disabled')
+      cy.findByText('Publish').should('not.exist')
+      cy.findByText('Unpublish', { timeout: 10_000 })
+      cy.findByText('Republish')
+
+      cy.findByText('View it in the template gallery').click()
+      cy.url()
+        .should('match', /\/templates\/[a-f0-9]{24}$/)
+        .as('templateURL')
+
+      cy.findAllByText(name).first().should('exist')
+      cy.findByText(description)
+      cy.findByText('Open as Template')
+      cy.findByText('Unpublish')
+      cy.findByText('Republish')
+      cy.get('img')
+        .should('have.attr', 'src')
+        .and('match', /\/v\/0\//)
+      cy.findByText('Republish').click()
+      cy.findByText('Publishing…').parent().should('be.disabled')
+      cy.findByText('Republish', { timeout: 10_000 })
+      cy.get('img', { timeout: 10_000 })
+        .should('have.attr', 'src')
+        .and('match', /\/v\/1\//)
+
+      // custom tag
+      const tagName = `${Date.now()}`
+      cy.visit('/')
+      cy.findByText(name)
+        .parent()
+        .parent()
+        .within(() => cy.get('input[type="checkbox"]').first().check())
+      cy.get('.project-list-sidebar-react').within(() => {
+        cy.findAllByText('New Tag').first().click()
+      })
+      cy.focused().type(tagName)
+      cy.findByText('Create').click()
+      cy.get('.project-list-sidebar-react').within(() => {
+        cy.findByText(tagName)
+          .parent()
+          .within(() => cy.get('.name').should('have.text', `${tagName} (1)`))
+      })
+
+      // Check listing
+      cy.visit('/templates')
+      cy.findByText(tagName)
+      cy.visit('/templates/all')
+      cy.findByText(name)
+      cy.visit(`/templates/${tagName}`)
+      cy.findByText(name)
+
+      // Unpublish via template page
+      cy.get('@templateURL').then(url => cy.visit(`${url}`))
+      cy.findByText('Unpublish').click()
+      cy.url().should('match', /\/templates$/)
+      cy.get('@templateURL').then(url =>
+        cy.visit(`${url}`, {
+          failOnStatusCode: false,
+        })
+      )
+      cy.findByText('Not found')
+      cy.visit('/templates/all')
+      cy.findByText(name).should('not.exist')
+      cy.visit(`/templates/${tagName}`)
+      cy.findByText(name).should('not.exist')
+
+      // Publish again
+      cy.get('@templateProjectId').then(projectId =>
+        cy.visit(`/project/${projectId}`)
+      )
+      cy.get('header').findByText('Menu').click()
+      cy.findByText('Manage Template').click()
+      cy.findByText('Publish').click()
+      cy.findByText('Unpublish', { timeout: 10_000 })
+
+      // Should assign a new template id
+      cy.findByText('View it in the template gallery').click()
+      cy.url()
+        .should('match', /\/templates\/[a-f0-9]{24}$/)
+        .as('newTemplateURL')
+      cy.get('@newTemplateURL').then(newURL => {
+        cy.get('@templateURL').then(prevURL => {
+          expect(newURL).to.match(/\/templates\/[a-f0-9]{24}$/)
+          expect(prevURL).to.not.equal(newURL)
+        })
+      })
+
+      // Open project from template
+      login(REGULAR_USER)
+      cy.visit('/templates')
+      cy.findByText(tagName).click()
+      cy.findByText(name).click()
+      cy.findByText('Open as Template').click()
+      cy.url().should('match', /\/project\/[a-f0-9]{24}$/)
+      cy.get('.project-name').findByText(name)
+      cy.get('header').findByText('Menu').click()
+      cy.findByText('Word Count') // wait for lazy loading
+      cy.findByText('Manage Template').should('not.exist')
+
+      // Check management as regular user
+      cy.get('@newTemplateURL').then(url => cy.visit(`${url}`))
+      cy.findByText('Open as Template')
+      cy.findByText('Unpublish').should('not.exist')
+      cy.findByText('Republish').should('not.exist')
+
+      // Check management as admin user
+      login(ADMIN_USER)
+      cy.get('@newTemplateURL').then(url => cy.visit(`${url}`))
+      cy.findByText('Open as Template')
+      cy.findByText('Unpublish')
+      cy.findByText('Republish')
+      cy.get('@templateProjectId').then(projectId =>
+        cy.visit(`/project/${projectId}`)
+      )
+      cy.get('header').findByText('Menu').click()
+      cy.findByText('Manage Template').click()
+      cy.findByText('Unpublish')
+
+      resumeTemplatesUserSession()
+
+      // Unpublish via editor
+      cy.get('@templateProjectId').then(projectId =>
+        cy.visit(`/project/${projectId}`)
+      )
+      cy.get('header').findByText('Menu').click()
+      cy.findByText('Manage Template').click()
+      cy.findByText('Unpublish').click()
+      cy.findByText('Publish')
+      cy.visit('/templates/all')
+      cy.findByText(name).should('not.exist')
+
+      // check for template links, after creating the first project
+      cy.visit('/')
+      cy.findAllByRole('button')
+        .contains(/new project/i)
+        .click()
+      cy.findAllByText('All Templates')
+        .first()
+        .should('have.attr', 'href', '/templates/all')
+    })
+  })
+
+  function checkDisabled() {
+    it('should not have templates feature', () => {
+      login(TEMPLATES_USER)
+
+      cy.visit('/')
+      createProject('maybe templates')
+
+      cy.get('header').findByText('Menu').click()
+      cy.findByText('Word Count') // wait for lazy loading
+      cy.findByText('Manage Template').should('not.exist')
+
+      cy.visit('/templates', { failOnStatusCode: false })
+      cy.findByText('Not found')
+      cy.visit('/templates/all', { failOnStatusCode: false })
+      cy.findByText('Not found')
+
+      // check for template links, after creating the first project
+      cy.visit('/')
+      cy.findAllByRole('button')
+        .contains(/new project/i)
+        .click()
+      cy.findAllByText('All Templates').should('not.exist')
+    })
+
+    it('should not show templates link on welcome page', () => {
+      login(WITHOUT_PROJECTS_USER)
+      cy.visit('/')
+      cy.findByText(/new project/i) // wait for lazy loading
+      cy.findByText('Browse templates').should('not.exist')
+    })
+  }
+
+  describe('disabled Server Pro', () => {
+    startWith({ pro: true })
+    checkDisabled()
+  })
+
+  describe('unavailable in CE', () => {
+    startWith({
+      pro: false,
+      varsFn,
+    })
+    checkDisabled()
+  })
+})

+ 1 - 1
services/web/app/src/infrastructure/Features.js

@@ -65,7 +65,7 @@ const Features = {
       case 'oauth':
         return Boolean(Settings.oauth)
       case 'templates-server-pro':
-        return Boolean(Settings.templates)
+        return Boolean(Settings.templates?.user_id)
       case 'affiliations':
       case 'analytics':
         return Boolean(_.get(Settings, ['apis', 'v1', 'url']))