From f62c3efafa95316f8636d5e0a4fdf6c3d36a396f Mon Sep 17 00:00:00 2001 From: muhme Date: Mon, 13 May 2024 14:38:42 +0200 Subject: [PATCH 01/14] Fix for issue #43465 writing configuration.php Fix for issue #43465 'Cypress System Tests fail when writing configuration.php' . remember the original file permission . set 644 . write file . restore original file permission additional: . writing file to ${Cypress.env('cmsPath')}/configuration.php` and no more to 'configuration.php' . error handle file is not existing --- tests/System/plugins/fs.js | 39 +++++++++++++++++++++- tests/System/plugins/index.js | 2 ++ tests/System/support/commands/config.js | 43 +++++++++++++++++-------- 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/tests/System/plugins/fs.js b/tests/System/plugins/fs.js index b33d05dacd5b1..a27ebe00385a6 100644 --- a/tests/System/plugins/fs.js +++ b/tests/System/plugins/fs.js @@ -33,4 +33,41 @@ function writeFile(path, content, config) { return null; } -module.exports = { writeFile, deleteFolder }; +/** + * Get file permissions. + * + * @param {string} path The file path + * + * @returns string e.g. '644' + */ +function getFilePermissions(path) { + try { + const stats = fs.statSync(path); + return (stats.mode & parseInt('777', 8)).toString(8); + } catch (err) { + console.error(`Failed to get file permissions: ${err}`); + // Rethrow to send the error to the Cypress test + throw err; + } +} + +/** + * Change file permissions. + * + * @param {string} path The file path + * @param {string} mode file mode, e.g. '644' + * + * @returns null to indicate success + */ +function changeFilePermissions(path, mode) { + try { + fs.chmodSync(path, mode); + return null; + } catch (err) { + console.error(`Failed to change file permissions: ${err}`); + // Rethrow to send the error to the Cypress tes + throw err;t + } +} + +module.exports = { writeFile, deleteFolder, getFilePermissions, changeFilePermissions }; diff --git a/tests/System/plugins/index.js b/tests/System/plugins/index.js index ce228d8765c00..a20d0c70574c7 100644 --- a/tests/System/plugins/index.js +++ b/tests/System/plugins/index.js @@ -16,6 +16,8 @@ function setupPlugins(on, config) { cleanupDB: () => db.deleteInsertedItems(config), writeFile: ({ path, content }) => fs.writeFile(path, content, config), deleteFolder: (path) => fs.deleteFolder(path, config), + getFilePermissions: (path) => fs.getFilePermissions(path), + changeFilePermissions: ({ path, mode }) => fs.changeFilePermissions(path, mode), getMails: () => mail.getMails(), clearEmails: () => mail.clearEmails(), startMailServer: () => mail.startMailServer(config), diff --git a/tests/System/support/commands/config.js b/tests/System/support/commands/config.js index 724c3598d6d61..7300593c779cb 100644 --- a/tests/System/support/commands/config.js +++ b/tests/System/support/commands/config.js @@ -1,18 +1,35 @@ Cypress.Commands.add('config_setParameter', (parameter, value) => { - cy.readFile(`${Cypress.env('cmsPath')}/configuration.php`).then((fileContent) => { - // Setup the new value - let newValue = value; - if (typeof value === 'string') { - newValue = `'${value}'`; - } + const configPath = `${Cypress.env('cmsPath')}/configuration.php`; - // The regex to find the line of the parameter - const regex = new RegExp(`^.*\\$${parameter}\\s.*$`, 'mg'); + cy.readFile(configPath).then( + // Success handler + (fileContent) => { + // Setup the new value + let newValue = typeof value === 'string' ? `'${value}'` : value; - // Replace the whole line with the new value - const content = fileContent.replace(regex, `public $${parameter} = ${newValue};`); + // The regex to find the line of the parameter + const regex = new RegExp(`^.*\\$${parameter}\\s.*$`, 'mg'); - // Write the modified content back to the configuration file - cy.task('writeFile', { path: 'configuration.php', content }); - }); + // Replace the whole line with the new value + const content = fileContent.replace(regex, `public $${parameter} = ${newValue};`); + + // Remember the original file permissions + cy.task('getFilePermissions', configPath).then((originalPermissions) => { + // To be save set read and write for owner + cy.task('changeFilePermissions', { path: configPath, mode: '644' }).then(() => { + // Write the changed file content back + cy.task('writeFile', { path: configPath, content }).then(() => { + // Restore the original file permissions + cy.task('changeFilePermissions', { path: configPath, mode: originalPermissions }); + }); + }); + }); + }, + // Failure handler + (err) => { + cy.log(`Failed to read the file ${configPath}: ${err.message}`); + // Rethrow to fail + throw err; + }, + ); }); From 6b4257477632b6e8b87f885cbf54343e4007f4ac Mon Sep 17 00:00:00 2001 From: muhme Date: Mon, 13 May 2024 14:48:00 +0200 Subject: [PATCH 02/14] typo --- tests/System/plugins/fs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/System/plugins/fs.js b/tests/System/plugins/fs.js index a27ebe00385a6..b18dca99b30c5 100644 --- a/tests/System/plugins/fs.js +++ b/tests/System/plugins/fs.js @@ -66,7 +66,7 @@ function changeFilePermissions(path, mode) { } catch (err) { console.error(`Failed to change file permissions: ${err}`); // Rethrow to send the error to the Cypress tes - throw err;t + throw err; } } From 127d37093cb46915c0be51741142d81981026819 Mon Sep 17 00:00:00 2001 From: muhme Date: Mon, 13 May 2024 15:57:12 +0200 Subject: [PATCH 03/14] updated system tests README --- tests/System/README.md | 44 ++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/tests/System/README.md b/tests/System/README.md index 8d070961ebf72..5a44d503347ab 100644 --- a/tests/System/README.md +++ b/tests/System/README.md @@ -6,20 +6,38 @@ The CMS system tests are executed in real browsers and are using the [cypress.io A couple of steps are needed before the CMS system tests can be executed on the system. 1. Clone Joomla into a folder where it can be served by a web server +``` +git clone --depth 1 https://github.com/muhme/joomla-cms +``` 2. Install the PHP and Javascript dependencies by running the following commands: - 1. `composer install` - 2. `npm ci` -3. Copy the cypress.config.dist.js to cypress.config.js in the root of the joomla folder -4. Adjust the baseUrl in the cypress.config.js file, it should point to the Joomla base url -5. Adapt the env variables in the file cypress.config.js, they should point to the site, user data and database environment -6. In order to run the api tests you will need to change the value in your configuration.php for $secret to `tEstValue` -7. Ensure the system has all the required dependencies according to the Cypress [documentation](https://docs.cypress.io/guides/getting-started/installing-cypress) -8. Run the command `npm run cypress:install` +``` +cd joomla-cms +composer install +npm ci +``` +3. Copy the `cypress.config.dist.js` to `cypress.config.js` in the root of the joomla folder +4. Adjust the `baseUrl` in the `cypress.config.js` file, it should point to the Joomla base URL +5. Adapt the env variables in the file `cypress.config.js`, they should point to the site, user data and database environment +6. Ensure the system has all the required dependencies according to the Cypress [documentation](https://docs.cypress.io/guides/getting-started/installing-cypress) +7. Install Cypress +``` +npm run cypress:install +``` +8. Run Joomla installation with headless Cypress +``` +npm run cypress:run -- --spec 'tests/System/integration/install/Installation.cy.js' +``` ## Run the existing tests -Cypress has a nice gui which lists all the existing tests and is able to launch a browser where the tests are executed. To open the cypress gui, run the following command: +You can use Cypress headless: +``` +npm run cypress:run +``` -`npm run cypress:open` +And Cypress has a nice GUI which lists all the existing tests and is able to launch a browser where the tests are executed. To open the Cypress GUI, run the following command: +``` +npm run cypress:open +``` ## Create new tests To Create new tests, create a cy.js file in a new folder which matches the following pattern (replace foo with the extension name to test): @@ -42,10 +60,12 @@ Tests should be: The CMS tests come with some convenient [cypress tasks](https://docs.cypress.io/api/commands/task) which execute actions on the server in a node environment. That's why the `cy.` namespace is not available. The following tasks are available, served by the file tests/System/plugins/index.js: -- **queryDB** Executes a query on the database -- **cleanupDB** does some cleanup, is executed automatically after every test +- **queryTestDB** executes a query on the database +- **deleteInsertedItems** deletes the inserted items from the database - **writeFile** writes a file relative to the CMS root folder - **deleteFolder** deletes a folder relative to the CMS root folder +- **getFilePermissions** get file permissions +- **changeFilePermissions** change file permissions With the following code in a test a task can be executed `cy.task('writeFile', { path: 'images/dummy.text', content: '1' })`. Each task is asynchronous and must be chained, so to get the result a `.then(() => {})` must follow when executing a task. From 224e28286c489d1dcea116d7520b61e4d0b1a0d7 Mon Sep 17 00:00:00 2001 From: muhme Date: Mon, 13 May 2024 19:35:54 +0200 Subject: [PATCH 04/14] corrected task names --- tests/System/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/System/README.md b/tests/System/README.md index 5a44d503347ab..b9ff74de1881f 100644 --- a/tests/System/README.md +++ b/tests/System/README.md @@ -60,8 +60,8 @@ Tests should be: The CMS tests come with some convenient [cypress tasks](https://docs.cypress.io/api/commands/task) which execute actions on the server in a node environment. That's why the `cy.` namespace is not available. The following tasks are available, served by the file tests/System/plugins/index.js: -- **queryTestDB** executes a query on the database -- **deleteInsertedItems** deletes the inserted items from the database +- **queryDB** executes a query on the database +- **cleanupDB** deletes the inserted items from the database - **writeFile** writes a file relative to the CMS root folder - **deleteFolder** deletes a folder relative to the CMS root folder - **getFilePermissions** get file permissions From bf014e096f4f6082e0cf43916ff5cec6a5609569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20L=C3=BCbbe?= Date: Tue, 14 May 2024 04:38:55 +0200 Subject: [PATCH 05/14] Update tests/System/README.md of course, thank you for checking Co-authored-by: Richard Fath --- tests/System/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/System/README.md b/tests/System/README.md index b9ff74de1881f..96e36521c0c4e 100644 --- a/tests/System/README.md +++ b/tests/System/README.md @@ -7,7 +7,7 @@ A couple of steps are needed before the CMS system tests can be executed on the 1. Clone Joomla into a folder where it can be served by a web server ``` -git clone --depth 1 https://github.com/muhme/joomla-cms +git clone --depth 1 https://github.com/joomla/joomla-cms ``` 2. Install the PHP and Javascript dependencies by running the following commands: ``` From 5cf1bc1d8f99924c88a9ce1e04ddaa48b7f0bd28 Mon Sep 17 00:00:00 2001 From: muhme Date: Tue, 14 May 2024 18:57:51 +0200 Subject: [PATCH 06/14] deleted failure handler config_setParameter() deleted failure handler for readFile as it is not needed, tested with chmod 0, Cypress fails with clear reason: CypressError: `cy.readFile("./configuration.php")` failed while trying to read the file at the following path: `.../43465/joomla-cms/configuration.php` The following error occurred: > "EACCES: permission denied, open '/Users/hlu/Desktop/no_backup/43465/joomla-cms/configuration.php'" --- tests/System/support/commands/config.js | 43 ++++++++++--------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/tests/System/support/commands/config.js b/tests/System/support/commands/config.js index 7300593c779cb..2495560052a71 100644 --- a/tests/System/support/commands/config.js +++ b/tests/System/support/commands/config.js @@ -1,35 +1,26 @@ Cypress.Commands.add('config_setParameter', (parameter, value) => { const configPath = `${Cypress.env('cmsPath')}/configuration.php`; - cy.readFile(configPath).then( - // Success handler - (fileContent) => { - // Setup the new value - let newValue = typeof value === 'string' ? `'${value}'` : value; + cy.readFile(configPath).then((fileContent) => { + // Setup the new value + let newValue = typeof value === 'string' ? `'${value}'` : value; - // The regex to find the line of the parameter - const regex = new RegExp(`^.*\\$${parameter}\\s.*$`, 'mg'); + // The regex to find the line of the parameter + const regex = new RegExp(`^.*\\$${parameter}\\s.*$`, 'mg'); - // Replace the whole line with the new value - const content = fileContent.replace(regex, `public $${parameter} = ${newValue};`); + // Replace the whole line with the new value + const content = fileContent.replace(regex, `public $${parameter} = ${newValue};`); - // Remember the original file permissions - cy.task('getFilePermissions', configPath).then((originalPermissions) => { - // To be save set read and write for owner - cy.task('changeFilePermissions', { path: configPath, mode: '644' }).then(() => { - // Write the changed file content back - cy.task('writeFile', { path: configPath, content }).then(() => { - // Restore the original file permissions - cy.task('changeFilePermissions', { path: configPath, mode: originalPermissions }); - }); + // Remember the original file permissions + cy.task('getFilePermissions', configPath).then((originalPermissions) => { + // To be save, set write for owner and read for all + cy.task('changeFilePermissions', { path: configPath, mode: '644' }).then(() => { + // Write the changed file content back + cy.task('writeFile', { path: configPath, content }).then(() => { + // Restore the original file permissions + cy.task('changeFilePermissions', { path: configPath, mode: originalPermissions }); }); }); - }, - // Failure handler - (err) => { - cy.log(`Failed to read the file ${configPath}: ${err.message}`); - // Rethrow to fail - throw err; - }, - ); + }); + }); }); From 4cd169a4e867b29cbbdca7a7637ffec0da3cacb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20L=C3=BCbbe?= Date: Wed, 15 May 2024 16:02:58 +0200 Subject: [PATCH 07/14] typo Co-authored-by: Brian Teeman --- tests/System/plugins/fs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/System/plugins/fs.js b/tests/System/plugins/fs.js index b18dca99b30c5..9bfb9b3588b31 100644 --- a/tests/System/plugins/fs.js +++ b/tests/System/plugins/fs.js @@ -65,7 +65,7 @@ function changeFilePermissions(path, mode) { return null; } catch (err) { console.error(`Failed to change file permissions: ${err}`); - // Rethrow to send the error to the Cypress tes + // Rethrow to send the error to the Cypress test throw err; } } From 9da94153ddc59ea8ffb26c8eb1e6f4bac9959dc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Heiko=20L=C3=BCbbe?= Date: Wed, 15 May 2024 16:03:30 +0200 Subject: [PATCH 08/14] typo Co-authored-by: Brian Teeman --- tests/System/support/commands/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/System/support/commands/config.js b/tests/System/support/commands/config.js index 2495560052a71..935522c863a32 100644 --- a/tests/System/support/commands/config.js +++ b/tests/System/support/commands/config.js @@ -13,7 +13,7 @@ Cypress.Commands.add('config_setParameter', (parameter, value) => { // Remember the original file permissions cy.task('getFilePermissions', configPath).then((originalPermissions) => { - // To be save, set write for owner and read for all + // To be safe, set write for owner and read for all cy.task('changeFilePermissions', { path: configPath, mode: '644' }).then(() => { // Write the changed file content back cy.task('writeFile', { path: configPath, content }).then(() => { From 8de3ebe7b4a66f8aacec5e8e6d6278e171443408 Mon Sep 17 00:00:00 2001 From: muhme Date: Wed, 15 May 2024 16:47:44 +0200 Subject: [PATCH 09/14] chain the then()-calls Chaining the then()-calls for a not so deeply nested code source looks catchy - thank Allon for the recommendation --- tests/System/support/commands/config.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/tests/System/support/commands/config.js b/tests/System/support/commands/config.js index 935522c863a32..d2a35b58d4eb7 100644 --- a/tests/System/support/commands/config.js +++ b/tests/System/support/commands/config.js @@ -12,15 +12,13 @@ Cypress.Commands.add('config_setParameter', (parameter, value) => { const content = fileContent.replace(regex, `public $${parameter} = ${newValue};`); // Remember the original file permissions - cy.task('getFilePermissions', configPath).then((originalPermissions) => { - // To be safe, set write for owner and read for all - cy.task('changeFilePermissions', { path: configPath, mode: '644' }).then(() => { - // Write the changed file content back - cy.task('writeFile', { path: configPath, content }).then(() => { - // Restore the original file permissions - cy.task('changeFilePermissions', { path: configPath, mode: originalPermissions }); - }); - }); + cy.task('getFilePermissions', configPath) + // To be save, set write for owner and read for all + .then((originalPermissions) => { cy.task('changeFilePermissions', { path: configPath, mode: '644' }) + // Write the changed file content back + .then(() => cy.task('writeFile', { path: configPath, content })) + // Restore the original file permissions + .then(() => cy.task('changeFilePermissions', { path: configPath, mode: originalPermissions })); }); }); }); From 76562d635ddcef505486bad17048d0106ed7f9cd Mon Sep 17 00:00:00 2001 From: muhme Date: Fri, 17 May 2024 06:37:01 +0200 Subject: [PATCH 10/14] adopted code formatting for better readability --- tests/System/support/commands/config.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/System/support/commands/config.js b/tests/System/support/commands/config.js index d2a35b58d4eb7..ad48e28868779 100644 --- a/tests/System/support/commands/config.js +++ b/tests/System/support/commands/config.js @@ -12,13 +12,13 @@ Cypress.Commands.add('config_setParameter', (parameter, value) => { const content = fileContent.replace(regex, `public $${parameter} = ${newValue};`); // Remember the original file permissions - cy.task('getFilePermissions', configPath) + cy.task('getFilePermissions', configPath).then((originalPermissions) => { // To be save, set write for owner and read for all - .then((originalPermissions) => { cy.task('changeFilePermissions', { path: configPath, mode: '644' }) - // Write the changed file content back - .then(() => cy.task('writeFile', { path: configPath, content })) - // Restore the original file permissions - .then(() => cy.task('changeFilePermissions', { path: configPath, mode: originalPermissions })); + cy.task('changeFilePermissions', { path: configPath, mode: '644' }) + // Write the changed file content back + .then(() => cy.task('writeFile', { path: configPath, content })) + // Restore the original file permissions + .then(() => cy.task('changeFilePermissions', { path: configPath, mode: originalPermissions })); }); }); }); From 6b210934b373cb24a6b36d679a6f56be505975ed Mon Sep 17 00:00:00 2001 From: muhme Date: Fri, 17 May 2024 14:14:14 +0200 Subject: [PATCH 11/14] fixing lint:js errors - deleted console.log statements - used const for never changing value - refactored file mask to not use bitwise operation '&' --- tests/System/plugins/fs.js | 21 +++++---------------- tests/System/support/commands/config.js | 2 +- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/tests/System/plugins/fs.js b/tests/System/plugins/fs.js index 9bfb9b3588b31..d3682ba7fa0c7 100644 --- a/tests/System/plugins/fs.js +++ b/tests/System/plugins/fs.js @@ -41,14 +41,9 @@ function writeFile(path, content, config) { * @returns string e.g. '644' */ function getFilePermissions(path) { - try { - const stats = fs.statSync(path); - return (stats.mode & parseInt('777', 8)).toString(8); - } catch (err) { - console.error(`Failed to get file permissions: ${err}`); - // Rethrow to send the error to the Cypress test - throw err; - } + const stats = fs.statSync(path); + const modeStr = stats.mode.toString(8); + return modeStr.slice(-3); } /** @@ -60,14 +55,8 @@ function getFilePermissions(path) { * @returns null to indicate success */ function changeFilePermissions(path, mode) { - try { - fs.chmodSync(path, mode); - return null; - } catch (err) { - console.error(`Failed to change file permissions: ${err}`); - // Rethrow to send the error to the Cypress test - throw err; - } + fs.chmodSync(path, mode); + return null; } module.exports = { writeFile, deleteFolder, getFilePermissions, changeFilePermissions }; diff --git a/tests/System/support/commands/config.js b/tests/System/support/commands/config.js index ad48e28868779..55b9ce1ecb617 100644 --- a/tests/System/support/commands/config.js +++ b/tests/System/support/commands/config.js @@ -3,7 +3,7 @@ Cypress.Commands.add('config_setParameter', (parameter, value) => { cy.readFile(configPath).then((fileContent) => { // Setup the new value - let newValue = typeof value === 'string' ? `'${value}'` : value; + const newValue = typeof value === 'string' ? `'${value}'` : value; // The regex to find the line of the parameter const regex = new RegExp(`^.*\\$${parameter}\\s.*$`, 'mg'); From ca2b3dd10497ee05ea7990d0452805a8d11ea08a Mon Sep 17 00:00:00 2001 From: muhme Date: Sat, 18 May 2024 11:09:29 +0200 Subject: [PATCH 12/14] fixed lint:testjs errors --- tests/System/plugins/fs.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/System/plugins/fs.js b/tests/System/plugins/fs.js index d3682ba7fa0c7..5b13909bbb5bc 100644 --- a/tests/System/plugins/fs.js +++ b/tests/System/plugins/fs.js @@ -59,4 +59,6 @@ function changeFilePermissions(path, mode) { return null; } -module.exports = { writeFile, deleteFolder, getFilePermissions, changeFilePermissions }; +module.exports = { + writeFile, deleteFolder, getFilePermissions, changeFilePermissions, +}; From 33cd330c346265dbeb157458f7b5ccbb65ea7168 Mon Sep 17 00:00:00 2001 From: muhme Date: Tue, 4 Jun 2024 15:28:12 +0200 Subject: [PATCH 13/14] Better fix for configuration.php permission issue Working with the code when fighting with the drone shows that a `chmod` was already implemented in `writeFile()`. Following changes with this commit: - Only using `chmod` method synchronously - Replaced setting directory mode to setting file mode before writing - Setting file mode only if the file exists - Having final file mode as parameter with default 0o444 - Using 0o444 as default file mode and not hard-wired 0o777 - The methods `getFilePermissions()` and `changeFilePermissions()` created for this PR earlier are deleted. Enhancement of the `tests/System/README.md` for troubleshooting three-user-problem in having Cypress running user, web server running user and `root` user. This commitment has been extensively tested in various combinations. Every test contains: - Checking error before - Doing the patch - Running installation twice and running overall test suite Tests are: - macOS 14.5 Sonoma, local with apache & Cypress same user, branch 4.4-dev - error before `> EACCES: permission denied, open './configuration.php'` - Docker, one container with joomla and one container with Cypress, using `root` users inside containers - no error before, but `configuration.php` is 777 - after the patch `configuration.php` is 444 inside container and shown 644 on host - tested four times, branches 4.4-dev, 5.1-dev, 5.2-dev and 6.0-dev - Ubuntu 24.04 LTS local installation, one non-root users running Cypress and another non-root user running Apache, branch 4.4-dev - error before `> EACCES: permission denied, open './configuration.php'` - need to use `sudo` and need to set `umask 0`, see troubleshooting - Windows 11 Pro, Laragon with Cmder, branch 4.4-dev - error before `> EPERM: operation not permitted, open 'C:\laragon\www\joomla-cms\configuration.php'` All tests are successful: - running `Installation.cy.js` twice, checking `configuration.php` 444 and params are set - running complete system test suite without errors --- tests/System/README.md | 57 +++++++++++++++++++++-- tests/System/plugins/fs.js | 61 ++++++++++--------------- tests/System/plugins/index.js | 4 +- tests/System/support/commands/config.js | 11 +---- 4 files changed, 80 insertions(+), 53 deletions(-) diff --git a/tests/System/README.md b/tests/System/README.md index 96e36521c0c4e..3f341aa8d34e6 100644 --- a/tests/System/README.md +++ b/tests/System/README.md @@ -25,18 +25,19 @@ npm run cypress:install ``` 8. Run Joomla installation with headless Cypress ``` -npm run cypress:run -- --spec 'tests/System/integration/install/Installation.cy.js' +npx cypress run --spec tests/System/integration/install/Installation.cy.js ``` +:point_right: In the case of `EACCES` or `EPERM` error, see troubleshooting at the end. ## Run the existing tests You can use Cypress headless: ``` -npm run cypress:run +npx cypress run ``` And Cypress has a nice GUI which lists all the existing tests and is able to launch a browser where the tests are executed. To open the Cypress GUI, run the following command: ``` -npm run cypress:open +npx cypress open ``` ## Create new tests @@ -92,3 +93,53 @@ The API commands make API requests to the CMS API endpoint `/api/index.php/v1`. - **api_patch** add the path and content for the body as arguments - **api_delete** add the path as argument - **api_getBearerToken** returns the bearer token and no request object + +# Troubleshooting +## `EACCES: permission denied` or `EPERM: operation not permitted` + +If the Cypress installation step or the entire test suite is executed by a non-root user, the following error may occur: +``` +1) Install Joomla + Install Joomla: + CypressError: `cy.task('writeFile')` failed with the following error: + > EACCES: permission denied, open './configuration.php' +``` +Or on Microsoft Windows you will see: +``` + > EPERM: operation not permitted, open 'C:\laragon\www\joomla-cms\configuration.php' +``` + +The reason for this is that the Cypress installation first creates the Joomla file `configuration.php` +from the web server and then some of the parameters in the file are configured with Cypress by the current user. +This can cause a file access problem if different users are used for the web server and the execution of Cypress. + +You have to give the user running Cypress the right to write `configuration.php` +e.g. with the command `sudo` on macOS, Linux or Windows WSL 2: +``` +sudo npx cypress run --spec tests/System/integration/install/Installation.cy.js +``` + +If the `root` user does not have a Cypress installation, you can use the Cypress installation cache of the current user: +``` +sudo CYPRESS_CACHE_FOLDER=$HOME/.cache/Cypress npx cypress run --spec tests/System/integration/install/Installation.cy.js +``` + +## Errors from test spec `api/com_media/Files.cy.js` +If you are using `sudo` and running the `com_media/Files` API test specification, you may see errors like: +``` + > 404: Not Found + > 500: Internal Server Error +``` +Reason for this is, that the Cypress test creates the directory `images/test-dir` as `root` user and prevents web server user `www-data` from creating files inside. You have to set `umask` additionally: +``` +sudo bash -c "umask 0 && CYPRESS_CACHE_FOLDER=$HOME/.cache/Cypress npx cypress run --spec tests/System/integration/api/com_media/Files.cy.js" +``` +Or if `root` user has Cypress installed: +``` +sudo bash -c "umask 0 && npx cypress run --spec tests/System/integration/api/com_media/Files.cy.js" +``` +Or to run the System test suite: +``` +sudo bash -c "umask 0 && npx cypress run" +``` + diff --git a/tests/System/plugins/fs.js b/tests/System/plugins/fs.js index 5b13909bbb5bc..3bcc7034c2ee5 100644 --- a/tests/System/plugins/fs.js +++ b/tests/System/plugins/fs.js @@ -16,49 +16,34 @@ function deleteFolder(path, config) { } /** - * Writes the given content to a file for the given path. + * Writes the given content to the file with the given relative path. * - * @param {string} path The path - * @param {mixed} content The content - * @param {object} config The config - * - * @returns null - */ -function writeFile(path, content, config) { - fs.mkdirSync(fspath.dirname(`${config.env.cmsPath}/${path}`), { recursive: true, mode: 0o777 }); - fs.chmod(fspath.dirname(`${config.env.cmsPath}/${path}`), 0o777); - fs.writeFileSync(`${config.env.cmsPath}/${path}`, content); - fs.chmod(`${config.env.cmsPath}/${path}`, 0o777); - - return null; -} - -/** - * Get file permissions. + * If directory entries from the path do not exist, they are created recursively with the file mask 0o777. + * If the file already exists, it will be overwritten. + * Finally, the given file mode or the default 0o444 is set for the given file. * - * @param {string} path The file path + * @param {string} path The relative file path (e.g. 'images/test-dir/override.jpg') + * @param {mixed} content The file content + * @param {object} config The Cypress configuration + * @param {number} [mode=0o444] The file mode to be used (in octal) * - * @returns string e.g. '644' + * @returns null */ -function getFilePermissions(path) { - const stats = fs.statSync(path); - const modeStr = stats.mode.toString(8); - return modeStr.slice(-3); -} +function writeFile(path, content, config, mode = 0o444) { + const fullPath = fspath.join(config.env.cmsPath, path); + // Create missing parent directories with 'rwxrwxrwx' + fs.mkdirSync(fspath.dirname(fullPath), { recursive: true, mode: 0o777 }); + // Check if the file exists + if (fs.existsSync(fullPath)) { + // Set 'rw-rw-rw-' to be able to overwrite the file + fs.chmodSync(fullPath, 0o666); + } + // Write or overwrite the file on relative path with given content + fs.writeFileSync(fullPath, content); + // Finally set given file mode or default 'r--r--r--' + fs.chmodSync(fullPath, mode); -/** - * Change file permissions. - * - * @param {string} path The file path - * @param {string} mode file mode, e.g. '644' - * - * @returns null to indicate success - */ -function changeFilePermissions(path, mode) { - fs.chmodSync(path, mode); return null; } -module.exports = { - writeFile, deleteFolder, getFilePermissions, changeFilePermissions, -}; +module.exports = { writeFile, deleteFolder }; diff --git a/tests/System/plugins/index.js b/tests/System/plugins/index.js index a20d0c70574c7..771335eb69a13 100644 --- a/tests/System/plugins/index.js +++ b/tests/System/plugins/index.js @@ -14,10 +14,8 @@ function setupPlugins(on, config) { on('task', { queryDB: (query) => db.queryTestDB(query, config), cleanupDB: () => db.deleteInsertedItems(config), - writeFile: ({ path, content }) => fs.writeFile(path, content, config), + writeFile: ({ path, content, mode }) => fs.writeFile(path, content, config, mode), deleteFolder: (path) => fs.deleteFolder(path, config), - getFilePermissions: (path) => fs.getFilePermissions(path), - changeFilePermissions: ({ path, mode }) => fs.changeFilePermissions(path, mode), getMails: () => mail.getMails(), clearEmails: () => mail.clearEmails(), startMailServer: () => mail.startMailServer(config), diff --git a/tests/System/support/commands/config.js b/tests/System/support/commands/config.js index 55b9ce1ecb617..e02bd1d862d8e 100644 --- a/tests/System/support/commands/config.js +++ b/tests/System/support/commands/config.js @@ -11,14 +11,7 @@ Cypress.Commands.add('config_setParameter', (parameter, value) => { // Replace the whole line with the new value const content = fileContent.replace(regex, `public $${parameter} = ${newValue};`); - // Remember the original file permissions - cy.task('getFilePermissions', configPath).then((originalPermissions) => { - // To be save, set write for owner and read for all - cy.task('changeFilePermissions', { path: configPath, mode: '644' }) - // Write the changed file content back - .then(() => cy.task('writeFile', { path: configPath, content })) - // Restore the original file permissions - .then(() => cy.task('changeFilePermissions', { path: configPath, mode: originalPermissions })); - }); + // Write the modified content back to the configuration file + cy.task('writeFile', { path: configPath, content }); }); }); From 1f4da1c866cbf534c4ee3f98dd8ae2bb0fa82ccf Mon Sep 17 00:00:00 2001 From: muhme Date: Wed, 5 Jun 2024 19:06:41 +0200 Subject: [PATCH 14/14] configuration.php CMS path relative && umask 0 - corrected mistake task writeFile was used with cmsPath + 'configuration.php' - extended writeFile to set process umask 0 - to prevent the 3-user-problem == no need to set umask 0 in sudo anymore This commitment has been tested in various combinations. Every test contains: - Checking error before - Doing the patch - Running Installation.cy.js only and running overall test suite Tests are: - Docker environment with drone images, root running Cypress and www-data running Apache, branch 4.4-dev - no error before, but /tests/www/cmysql/configuration.php has 777 - Ubuntu 24.04 LTS local installation, one non-root user running Cypress and another non-root user running Apache, branch 4.4-dev - error before `> EACCES: permission denied, open './configuration.php'` - need to use `sudo`, see troubleshooting (umask 0 is no more needed) - Windows 11 Pro, Laragon with Cmder, branch 4.4-dev - error before `> EPERM: operation not permitted, open 'C:\laragon\www\joomla-cms\configuration.php'` - found out that on the second run cy.exec('rm configuration.php') does not work under Windows - deleted file manually and i will create an issue afterwards to avoid enlarging this one - macOS 14.5 Sonoma, local with apache & Cypress same user, branch 4.4-dev - error before `> EACCES: permission denied, open './configuration.php'` All tests are successful: - running `Installation.cy.js`, checking `configuration.php` 444 and params are set - running complete system test suite without errors --- tests/System/README.md | 31 +++++-------------------- tests/System/plugins/fs.js | 7 +++++- tests/System/support/commands/config.js | 4 ++-- 3 files changed, 14 insertions(+), 28 deletions(-) diff --git a/tests/System/README.md b/tests/System/README.md index 3f341aa8d34e6..5450021231a15 100644 --- a/tests/System/README.md +++ b/tests/System/README.md @@ -95,7 +95,7 @@ The API commands make API requests to the CMS API endpoint `/api/index.php/v1`. - **api_getBearerToken** returns the bearer token and no request object # Troubleshooting -## `EACCES: permission denied` or `EPERM: operation not permitted` +## Errors 'EACCES: permission denied' or 'EPERM: operation not permitted' If the Cypress installation step or the entire test suite is executed by a non-root user, the following error may occur: ``` @@ -109,37 +109,18 @@ Or on Microsoft Windows you will see: > EPERM: operation not permitted, open 'C:\laragon\www\joomla-cms\configuration.php' ``` -The reason for this is that the Cypress installation first creates the Joomla file `configuration.php` -from the web server and then some of the parameters in the file are configured with Cypress by the current user. -This can cause a file access problem if different users are used for the web server and the execution of Cypress. +The reason for this error is that Cypress first creates the Joomla file `configuration.php` via the web server. +Subsequently, some of the parameters in this file are configured by Cypress under the current user. +If the web server and Cypress are run by different users, this can lead to file access issues. You have to give the user running Cypress the right to write `configuration.php` e.g. with the command `sudo` on macOS, Linux or Windows WSL 2: ``` -sudo npx cypress run --spec tests/System/integration/install/Installation.cy.js +sudo npx cypress run ``` If the `root` user does not have a Cypress installation, you can use the Cypress installation cache of the current user: ``` -sudo CYPRESS_CACHE_FOLDER=$HOME/.cache/Cypress npx cypress run --spec tests/System/integration/install/Installation.cy.js -``` - -## Errors from test spec `api/com_media/Files.cy.js` -If you are using `sudo` and running the `com_media/Files` API test specification, you may see errors like: -``` - > 404: Not Found - > 500: Internal Server Error -``` -Reason for this is, that the Cypress test creates the directory `images/test-dir` as `root` user and prevents web server user `www-data` from creating files inside. You have to set `umask` additionally: -``` -sudo bash -c "umask 0 && CYPRESS_CACHE_FOLDER=$HOME/.cache/Cypress npx cypress run --spec tests/System/integration/api/com_media/Files.cy.js" -``` -Or if `root` user has Cypress installed: -``` -sudo bash -c "umask 0 && npx cypress run --spec tests/System/integration/api/com_media/Files.cy.js" -``` -Or to run the System test suite: -``` -sudo bash -c "umask 0 && npx cypress run" +sudo CYPRESS_CACHE_FOLDER=$HOME/.cache/Cypress npx cypress run ``` diff --git a/tests/System/plugins/fs.js b/tests/System/plugins/fs.js index 3bcc7034c2ee5..0b3b37227c2e9 100644 --- a/tests/System/plugins/fs.js +++ b/tests/System/plugins/fs.js @@ -1,5 +1,6 @@ const fs = require('fs'); const fspath = require('path'); +const { umask } = require('node:process'); /** * Deletes a folder with the given path recursive. @@ -16,7 +17,7 @@ function deleteFolder(path, config) { } /** - * Writes the given content to the file with the given relative path. + * Writes the given content to the file with the given path relative to the CMS root folder. * * If directory entries from the path do not exist, they are created recursively with the file mask 0o777. * If the file already exists, it will be overwritten. @@ -31,6 +32,8 @@ function deleteFolder(path, config) { */ function writeFile(path, content, config, mode = 0o444) { const fullPath = fspath.join(config.env.cmsPath, path); + // Prologue: Reset process file mode creation mask to ensure the umask value is not subtracted + const oldmask = umask(0); // Create missing parent directories with 'rwxrwxrwx' fs.mkdirSync(fspath.dirname(fullPath), { recursive: true, mode: 0o777 }); // Check if the file exists @@ -42,6 +45,8 @@ function writeFile(path, content, config, mode = 0o444) { fs.writeFileSync(fullPath, content); // Finally set given file mode or default 'r--r--r--' fs.chmodSync(fullPath, mode); + // Epilogue: Restore process file mode creation mask + umask(oldmask); return null; } diff --git a/tests/System/support/commands/config.js b/tests/System/support/commands/config.js index e02bd1d862d8e..a7ae850b8f6ab 100644 --- a/tests/System/support/commands/config.js +++ b/tests/System/support/commands/config.js @@ -11,7 +11,7 @@ Cypress.Commands.add('config_setParameter', (parameter, value) => { // Replace the whole line with the new value const content = fileContent.replace(regex, `public $${parameter} = ${newValue};`); - // Write the modified content back to the configuration file - cy.task('writeFile', { path: configPath, content }); + // Write the modified content back to the configuration file relative to the CMS root folder + cy.task('writeFile', { path: 'configuration.php', content }); }); });