/** * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ import type { SetupConfig, SetupLinks } from '../install' import SetupView from './Setup.vue' import '../../css/guest.css' const defaultConfig = Object.freeze({ adminlogin: '', adminpass: '', dbuser: '', dbpass: '', dbname: '', dbtablespace: '', dbhost: '', dbtype: '', databases: { sqlite: 'SQLite', mysql: 'MySQL/MariaDB', pgsql: 'PostgreSQL', }, directory: '', hasAutoconfig: false, htaccessWorking: true, serverRoot: '/var/www/html', errors: [], }) as SetupConfig const links = { adminInstall: 'https://docs.nextcloud.com/server/32/go.php?to=admin-install', adminSourceInstall: 'https://docs.nextcloud.com/server/32/go.php?to=admin-source_install', adminDBConfiguration: 'https://docs.nextcloud.com/server/32/go.php?to=admin-db-configuration', } as SetupLinks describe('Default setup page', () => { beforeEach(() => { cy.mockInitialState('core', 'links', links) }) afterEach(() => cy.unmockInitialState()) it('Renders default config', () => { cy.mockInitialState('core', 'config', defaultConfig) cy.mount(SetupView) cy.get('[data-cy-setup-form]').scrollIntoView() cy.get('[data-cy-setup-form]').should('be.visible') // Single note is the footer help cy.get('[data-cy-setup-form-note]') .should('have.length', 1) .should('be.visible') cy.get('[data-cy-setup-form-note]').should('contain', 'See the documentation') // DB radio selectors cy.get('[data-cy-setup-form-field^="dbtype"]') .should('exist') .find('input') .should('be.checked') cy.get('[data-cy-setup-form-field="dbtype-mysql"]').should('exist') cy.get('[data-cy-setup-form-field="dbtype-pgsql"]').should('exist') cy.get('[data-cy-setup-form-field="dbtype-oci"]').should('not.exist') // Sqlite warning cy.get('[data-cy-setup-form-db-note="sqlite"]') .should('be.visible') // admin login, password, data directory and 3 DB radio selectors cy.get('[data-cy-setup-form-field]') .should('be.visible') .should('have.length', 6) }) it('Renders single DB sqlite', () => { const config = { ...defaultConfig, databases: { sqlite: 'SQLite', }, } cy.mockInitialState('core', 'config', config) cy.mount(SetupView) // No DB radio selectors if only sqlite cy.get('[data-cy-setup-form-field^="dbtype"]') .should('not.exist') // Two warnings: sqlite and single db support cy.get('[data-cy-setup-form-db-note="sqlite"]') .should('be.visible') cy.get('[data-cy-setup-form-db-note="single-db"]') .should('be.visible') // Admin login, password and data directory cy.get('[data-cy-setup-form-field]') .should('be.visible') .should('have.length', 3) }) it('Renders single DB mysql', () => { const config = { ...defaultConfig, databases: { mysql: 'MySQL/MariaDB', }, } cy.mockInitialState('core', 'config', config) cy.mount(SetupView) // No DB radio selectors if only mysql cy.get('[data-cy-setup-form-field^="dbtype"]') .should('not.exist') // Single db support warning cy.get('[data-cy-setup-form-db-note="single-db"]') .should('be.visible') .invoke('html') .should('contains', links.adminSourceInstall) // No SQLite warning cy.get('[data-cy-setup-form-db-note="sqlite"]') .should('not.exist') // Admin login, password, data directory, db user, // db password, db name and db host cy.get('[data-cy-setup-form-field]') .should('be.visible') .should('have.length', 7) }) it('Changes fields from sqlite to mysql then oci', () => { const config = { ...defaultConfig, databases: { sqlite: 'SQLite', mysql: 'MySQL/MariaDB', pgsql: 'PostgreSQL', oci: 'Oracle', }, } cy.mockInitialState('core', 'config', config) cy.mount(SetupView) // SQLite selected cy.get('[data-cy-setup-form-field="dbtype-sqlite"]') .should('be.visible') .find('input') .should('be.checked') // Admin login, password, data directory and 4 DB radio selectors cy.get('[data-cy-setup-form-field]') .should('be.visible') .should('have.length', 7) // Change to MySQL cy.get('[data-cy-setup-form-field="dbtype-mysql"]').click() cy.get('[data-cy-setup-form-field="dbtype-mysql"] input').should('be.checked') // Admin login, password, data directory, db user, db password, // db name, db host and 4 DB radio selectors cy.get('[data-cy-setup-form-field]') .should('be.visible') .should('have.length', 11) // Change to Oracle cy.get('[data-cy-setup-form-field="dbtype-oci"]').click() cy.get('[data-cy-setup-form-field="dbtype-oci"] input').should('be.checked') // Admin login, password, data directory, db user, db password, // db name, db table space, db host and 4 DB radio selectors cy.get('[data-cy-setup-form-field]') .should('be.visible') .should('have.length', 12) cy.get('[data-cy-setup-form-field="dbtablespace"]') .should('be.visible') }) }) describe('Setup page with errors and warning', () => { beforeEach(() => { cy.mockInitialState('core', 'links', links) }) afterEach(() => cy.unmockInitialState()) it('Renders error from backend', () => { const config = { ...defaultConfig, errors: [ { error: 'Error message', hint: 'Error hint', }, ], } cy.mockInitialState('core', 'config', config) cy.mount(SetupView) // Error message and hint cy.get('[data-cy-setup-form-note="error"]') .should('be.visible') .should('have.length', 1) .should('contain', 'Error message') .should('contain', 'Error hint') }) it('Renders errors from backend', () => { const config = { ...defaultConfig, errors: [ 'Error message 1', { error: 'Error message', hint: 'Error hint', }, ], } cy.mockInitialState('core', 'config', config) cy.mount(SetupView) // Error message and hint cy.get('[data-cy-setup-form-note="error"]') .should('be.visible') .should('have.length', 2) cy.get('[data-cy-setup-form-note="error"]').eq(0) .should('contain', 'Error message 1') cy.get('[data-cy-setup-form-note="error"]').eq(1) .should('contain', 'Error message') .should('contain', 'Error hint') }) it('Renders all the submitted fields on error', () => { const config = { ...defaultConfig, adminlogin: 'admin', adminpass: 'password', dbname: 'nextcloud', dbtype: 'mysql', dbuser: 'nextcloud', dbpass: 'password', dbhost: 'localhost', directory: '/var/www/html/nextcloud', } as SetupConfig cy.mockInitialState('core', 'config', config) cy.mount(SetupView) cy.get('input[data-cy-setup-form-field="adminlogin"]') .should('have.value', 'admin') cy.get('input[data-cy-setup-form-field="adminpass"]') .should('have.value', 'password') cy.get('[data-cy-setup-form-field="dbtype-mysql"] input') .should('be.checked') cy.get('input[data-cy-setup-form-field="dbname"]') .should('have.value', 'nextcloud') cy.get('input[data-cy-setup-form-field="dbuser"]') .should('have.value', 'nextcloud') cy.get('input[data-cy-setup-form-field="dbpass"]') .should('have.value', 'password') cy.get('input[data-cy-setup-form-field="dbhost"]') .should('have.value', 'localhost') cy.get('input[data-cy-setup-form-field="directory"]') .should('have.value', '/var/www/html/nextcloud') }) it('Renders the htaccess warning', () => { const config = { ...defaultConfig, htaccessWorking: false, } cy.mockInitialState('core', 'config', config) cy.mount(SetupView) cy.get('[data-cy-setup-form-note="htaccess"]') .should('be.visible') .should('contain', 'Security warning') .invoke('html') .should('contains', links.adminInstall) }) }) describe('Setup page with autoconfig', () => { beforeEach(() => { cy.mockInitialState('core', 'links', links) }) afterEach(() => cy.unmockInitialState()) it('Renders autoconfig', () => { const config = { ...defaultConfig, hasAutoconfig: true, dbname: 'nextcloud', dbtype: 'mysql', dbuser: 'nextcloud', dbpass: 'password', dbhost: 'localhost', directory: '/var/www/html/nextcloud', } as SetupConfig cy.mockInitialState('core', 'config', config) cy.mount(SetupView) // Autoconfig info note cy.get('[data-cy-setup-form-note="autoconfig"]') .should('be.visible') .should('contain', 'Autoconfig file detected') // Database and storage section is hidden as already set in autoconfig cy.get('[data-cy-setup-form-advanced-config]').should('be.visible') .invoke('attr', 'open') .should('equal', undefined) // Oracle tablespace is hidden cy.get('[data-cy-setup-form-field="dbtablespace"]') .should('not.exist') }) }) describe('Submit a full form sends the data', () => { beforeEach(() => { cy.mockInitialState('core', 'links', links) }) afterEach(() => cy.unmockInitialState()) it('Submits a full form', () => { const config = { ...defaultConfig, adminlogin: 'admin', adminpass: 'password', dbname: 'nextcloud', dbtype: 'mysql', dbuser: 'nextcloud', dbpass: 'password', dbhost: 'localhost', dbtablespace: 'tablespace', directory: '/var/www/html/nextcloud', } as SetupConfig cy.intercept('POST', '**', { delay: 2000, }).as('setup') cy.mockInitialState('core', 'config', config) cy.mount(SetupView) // Not chaining breaks the test as the POST prevents the element from being retrieved twice // eslint-disable-next-line cypress/unsafe-to-chain-command cy.get('[data-cy-setup-form-submit]') .click() .invoke('attr', 'disabled') .should('equal', 'disabled', { timeout: 500 }) cy.wait('@setup') .its('request.body') .should('deep.equal', new URLSearchParams({ adminlogin: 'admin', adminpass: 'password', directory: '/var/www/html/nextcloud', dbtype: 'mysql', dbuser: 'nextcloud', dbpass: 'password', dbname: 'nextcloud', dbhost: 'localhost', }).toString()) }) })