- chore: use vite preprocessor for Cypress Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>pull/55885/head
parent
f050c32e88
commit
1082dee5a4
@ -1,32 +0,0 @@ |
||||
/** |
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
/* eslint-disable */ |
||||
import { mount } from '@cypress/vue2' |
||||
|
||||
type MountParams = Parameters<typeof mount>; |
||||
type OptionsParam = MountParams[1]; |
||||
|
||||
declare global { |
||||
namespace Cypress { |
||||
interface Chainable { |
||||
mount: typeof mount; |
||||
/** |
||||
* Mock an initial state for component testing |
||||
* |
||||
* @param app App name of the initial state |
||||
* @param key Key of the initial state |
||||
* @param value The mocked value of the initial state |
||||
*/ |
||||
mockInitialState: (app: string, key: string, value: any) => void |
||||
/** |
||||
* Unmock all initial states or one defined by app and key |
||||
* |
||||
* @param app app name of the initial state |
||||
* @param key the key of the initial state |
||||
*/ |
||||
unmockInitialState: (app?: string, key?: string) => void |
||||
} |
||||
} |
||||
} |
||||
@ -1,375 +0,0 @@ |
||||
/** |
||||
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
import { execSync } from 'child_process' |
||||
import Docker from 'dockerode' |
||||
import { existsSync } from 'fs' |
||||
import path, { basename } from 'path' |
||||
import { c as createTar } from 'tar' |
||||
import waitOn from 'wait-on' |
||||
|
||||
export const docker = new Docker() |
||||
|
||||
const CONTAINER_NAME = `nextcloud-cypress-tests_${basename(process.cwd()).replace(' ', '')}` |
||||
const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server' |
||||
|
||||
/** |
||||
* Start the testing container |
||||
* |
||||
* @param branch the branch of your current work |
||||
*/ |
||||
export async function startNextcloud(branch: string = getCurrentGitBranch()): Promise<any> { |
||||
try { |
||||
try { |
||||
// Pulling images
|
||||
console.log('\nPulling images... ⏳') |
||||
await new Promise((resolve, reject): any => docker.pull(SERVER_IMAGE, (err, stream) => { |
||||
if (err) { |
||||
reject(err) |
||||
} |
||||
if (stream === null) { |
||||
reject(new Error('Could not connect to docker, ensure docker is running.')) |
||||
return |
||||
} |
||||
|
||||
// https://github.com/apocas/dockerode/issues/357
|
||||
docker.modem.followProgress(stream, onFinished) |
||||
|
||||
/** |
||||
* |
||||
* @param err |
||||
*/ |
||||
function onFinished(err) { |
||||
if (!err) { |
||||
resolve(true) |
||||
return |
||||
} |
||||
reject(err) |
||||
} |
||||
})) |
||||
|
||||
const digest = await (await docker.getImage(SERVER_IMAGE).inspect()).RepoDigests.at(0) |
||||
const sha = digest?.split('@').at(1) |
||||
console.log('├─ Using image ' + sha) |
||||
console.log('└─ Done') |
||||
} catch (e) { |
||||
console.log('└─ Failed to pull images') |
||||
throw e |
||||
} |
||||
|
||||
// Remove old container if exists
|
||||
console.log('\nChecking running containers... 🔍') |
||||
try { |
||||
const oldContainer = docker.getContainer(CONTAINER_NAME) |
||||
const oldContainerData = await oldContainer.inspect() |
||||
if (oldContainerData) { |
||||
console.log('├─ Existing running container found') |
||||
console.log('├─ Removing... ⏳') |
||||
// Forcing any remnants to be removed just in case
|
||||
await oldContainer.remove({ force: true }) |
||||
console.log('└─ Done') |
||||
} |
||||
} catch { |
||||
console.log('└─ None found!') |
||||
} |
||||
|
||||
// Starting container
|
||||
console.log('\nStarting Nextcloud container... 🚀') |
||||
console.log(`├─ Using branch '${branch}'`) |
||||
const container = await docker.createContainer({ |
||||
Image: SERVER_IMAGE, |
||||
name: CONTAINER_NAME, |
||||
HostConfig: { |
||||
Mounts: [{ |
||||
Target: '/var/www/html/data', |
||||
Source: '', |
||||
Type: 'tmpfs', |
||||
ReadOnly: false, |
||||
}], |
||||
PortBindings: { |
||||
'80/tcp': [{ |
||||
HostIP: '0.0.0.0', |
||||
HostPort: '8083', |
||||
}], |
||||
}, |
||||
// If running the setup tests, let's bind to host
|
||||
// to communicate with the github actions DB services
|
||||
NetworkMode: process.env.SETUP_TESTING === 'true' ? await getGithubNetwork() : undefined, |
||||
}, |
||||
Env: [ |
||||
`BRANCH=${branch}`, |
||||
'APCU=1', |
||||
], |
||||
}) |
||||
await container.start() |
||||
|
||||
// Set proper permissions for the data folder
|
||||
await runExec(container, ['chown', '-R', 'www-data:www-data', '/var/www/html/data'], false, 'root') |
||||
await runExec(container, ['chmod', '0770', '/var/www/html/data'], false, 'root') |
||||
|
||||
// Get container's IP
|
||||
const ip = await getContainerIP(container) |
||||
|
||||
console.log(`├─ Nextcloud container's IP is ${ip} 🌏`) |
||||
return ip |
||||
} catch (err) { |
||||
console.log('└─ Unable to start the container 🛑') |
||||
console.log('\n', err, '\n') |
||||
stopNextcloud() |
||||
throw new Error('Unable to start the container') |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Configure Nextcloud |
||||
*/ |
||||
export async function configureNextcloud() { |
||||
console.log('\nConfiguring nextcloud...') |
||||
const container = docker.getContainer(CONTAINER_NAME) |
||||
await runExec(container, ['php', 'occ', '--version'], true) |
||||
|
||||
// Be consistent for screenshots
|
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'default_language', '--value', 'en'], true) |
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'force_language', '--value', 'en'], true) |
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'default_locale', '--value', 'en_US'], true) |
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'force_locale', '--value', 'en_US'], true) |
||||
await runExec(container, ['php', 'occ', 'config:system:set', 'enforce_theme', '--value', 'light'], true) |
||||
|
||||
// Speed up test and make them less flaky. If a cron execution is needed, it can be triggered manually.
|
||||
await runExec(container, ['php', 'occ', 'background:cron'], true) |
||||
|
||||
// Checking apcu
|
||||
const distributed = await runExec(container, ['php', 'occ', 'config:system:get', 'memcache.distributed']) |
||||
const local = await runExec(container, ['php', 'occ', 'config:system:get', 'memcache.local']) |
||||
const hashing = await runExec(container, ['php', 'occ', 'config:system:get', 'hashing_default_password']) |
||||
|
||||
console.log('├─ Checking APCu configuration... 👀') |
||||
if (!distributed.trim().includes('Memcache\\APCu') |
||||
|| !local.trim().includes('Memcache\\APCu') |
||||
|| !hashing.trim().includes('true')) { |
||||
console.log('└─ APCu is not properly configured 🛑') |
||||
throw new Error('APCu is not properly configured') |
||||
} |
||||
console.log('│ └─ OK !') |
||||
|
||||
// Saving DB state
|
||||
console.log('├─ Creating init DB snapshot...') |
||||
await runExec(container, ['cp', '/var/www/html/data/owncloud.db', '/var/www/html/data/owncloud.db-init'], true) |
||||
console.log('├─ Creating init data backup...') |
||||
await runExec(container, ['tar', 'cf', 'data-init.tar', 'admin'], true, undefined, '/var/www/html/data') |
||||
|
||||
console.log('└─ Nextcloud is now ready to use 🎉') |
||||
} |
||||
|
||||
/** |
||||
* Applying local changes to the container |
||||
* Only triggered if we're not in CI. Otherwise the |
||||
* continuous-integration-shallow-server image will |
||||
* already fetch the proper branch. |
||||
*/ |
||||
export async function applyChangesToNextcloud() { |
||||
console.log('\nApply local changes to nextcloud...') |
||||
|
||||
const htmlPath = '/var/www/html' |
||||
const folderPaths = [ |
||||
'./3rdparty', |
||||
'./apps', |
||||
'./core', |
||||
'./dist', |
||||
'./lib', |
||||
'./ocs', |
||||
'./ocs-provider', |
||||
'./resources', |
||||
'./tests', |
||||
'./console.php', |
||||
'./cron.php', |
||||
'./index.php', |
||||
'./occ', |
||||
'./public.php', |
||||
'./remote.php', |
||||
'./status.php', |
||||
'./version.php', |
||||
].filter((folderPath) => { |
||||
const fullPath = path.resolve(__dirname, '..', folderPath) |
||||
|
||||
if (existsSync(fullPath)) { |
||||
console.log(`├─ Copying ${folderPath}`) |
||||
return true |
||||
} |
||||
return false |
||||
}) |
||||
|
||||
// Don't try to apply changes, when there are none. Otherwise we
|
||||
// still execute the 'chown' command, which is not needed.
|
||||
if (folderPaths.length === 0) { |
||||
console.log('└─ No local changes found to apply') |
||||
return |
||||
} |
||||
|
||||
const container = docker.getContainer(CONTAINER_NAME) |
||||
|
||||
// Tar-streaming the above folders into the container
|
||||
const serverTar = createTar({ gzip: false }, folderPaths) |
||||
await container.putArchive(serverTar, { |
||||
path: htmlPath, |
||||
}) |
||||
|
||||
// Making sure we have the proper permissions
|
||||
await runExec(container, ['chown', '-R', 'www-data:www-data', htmlPath], false, 'root') |
||||
|
||||
console.log('└─ Changes applied successfully 🎉') |
||||
} |
||||
|
||||
/** |
||||
* Force stop the testing container |
||||
*/ |
||||
export async function stopNextcloud() { |
||||
try { |
||||
const container = docker.getContainer(CONTAINER_NAME) |
||||
console.log('Stopping Nextcloud container...') |
||||
container.remove({ force: true }) |
||||
console.log('└─ Nextcloud container removed 🥀') |
||||
} catch (err) { |
||||
console.log(err) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get the testing container's IP |
||||
* |
||||
* @param container the container to get the IP from |
||||
*/ |
||||
export async function getContainerIP(container = docker.getContainer(CONTAINER_NAME)): Promise<string> { |
||||
let ip = '' |
||||
let tries = 0 |
||||
while (ip === '' && tries < 10) { |
||||
tries++ |
||||
|
||||
container.inspect(function(err, data) { |
||||
if (err) { |
||||
throw err |
||||
} |
||||
|
||||
if (data?.HostConfig.PortBindings?.['80/tcp']?.[0]?.HostPort) { |
||||
ip = `localhost:${data.HostConfig.PortBindings['80/tcp'][0].HostPort}` |
||||
} else { |
||||
ip = data?.NetworkSettings?.IPAddress || '' |
||||
} |
||||
}) |
||||
|
||||
if (ip !== '') { |
||||
break |
||||
} |
||||
|
||||
await sleep(1000 * tries) |
||||
} |
||||
|
||||
return ip |
||||
} |
||||
|
||||
// Would be simpler to start the container from cypress.config.ts,
|
||||
// but when checking out different branches, it can take a few seconds
|
||||
// Until we can properly configure the baseUrl retry intervals,
|
||||
// We need to make sure the server is already running before cypress
|
||||
// https://github.com/cypress-io/cypress/issues/22676
|
||||
/** |
||||
* |
||||
* @param ip |
||||
*/ |
||||
export async function waitOnNextcloud(ip: string) { |
||||
console.log('├─ Waiting for Nextcloud to be ready... ⏳') |
||||
await waitOn({ |
||||
resources: [`http://${ip}/index.php`], |
||||
// wait for nextcloud to be up and return any non error status
|
||||
validateStatus: (status) => status >= 200 && status < 400, |
||||
// timout in ms
|
||||
timeout: 5 * 60 * 1000, |
||||
// timeout for a single HTTP request
|
||||
httpTimeout: 60 * 1000, |
||||
}) |
||||
console.log('└─ Done') |
||||
} |
||||
|
||||
/** |
||||
* |
||||
* @param container |
||||
* @param command |
||||
* @param verbose |
||||
* @param user |
||||
* @param workdir |
||||
*/ |
||||
async function runExec( |
||||
container: Docker.Container, |
||||
command: string[], |
||||
verbose = false, |
||||
user = 'www-data', |
||||
workdir?: string, |
||||
): Promise<string> { |
||||
const exec = await container.exec({ |
||||
Cmd: command, |
||||
WorkingDir: workdir, |
||||
AttachStdout: true, |
||||
AttachStderr: true, |
||||
User: user, |
||||
}) |
||||
|
||||
return new Promise((resolve, reject) => { |
||||
let output = '' |
||||
exec.start({}, (err, stream) => { |
||||
if (err) { |
||||
reject(err) |
||||
} |
||||
if (stream) { |
||||
stream.setEncoding('utf-8') |
||||
stream.on('data', (str) => { |
||||
str = str.trim() |
||||
// Remove non printable characters
|
||||
// eslint-disable-next-line no-control-regex
|
||||
.replace(/[^\x0A\x0D\x20-\x7E]+/g, '') |
||||
// Remove non alphanumeric leading characters
|
||||
.replace(/^[^a-z]/gi, '') |
||||
output += str |
||||
if (verbose && str !== '') { |
||||
console.log(`├─ ${str.replace(/\n/gi, '\n├─ ')}`) |
||||
} |
||||
}) |
||||
stream.on('end', () => resolve(output)) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* |
||||
* @param milliseconds |
||||
*/ |
||||
function sleep(milliseconds: number) { |
||||
return new Promise((resolve) => setTimeout(resolve, milliseconds)) |
||||
} |
||||
|
||||
/** |
||||
* |
||||
*/ |
||||
function getCurrentGitBranch() { |
||||
return execSync('git rev-parse --abbrev-ref HEAD').toString().trim() || 'master' |
||||
} |
||||
|
||||
/** |
||||
* Get the network name of the github actions network |
||||
* This is used to connect to the database services |
||||
* started by github actions |
||||
*/ |
||||
async function getGithubNetwork(): Promise<string | undefined> { |
||||
console.log('├─ Looking for github actions network... 🔍') |
||||
const networks = await docker.listNetworks() |
||||
const network = networks.find((network) => network.Name.startsWith('github_network')) |
||||
if (network) { |
||||
console.log('│ └─ Found github actions network: ' + network.Name) |
||||
return network.Name |
||||
} |
||||
|
||||
console.log('│ └─ No github actions network found') |
||||
return undefined |
||||
} |
||||
@ -0,0 +1,20 @@ |
||||
<?php |
||||
|
||||
/** |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
$CONFIG = [ |
||||
'apps_paths' => [ |
||||
[ |
||||
'path' => '/var/www/html/apps', |
||||
'url' => '/apps', |
||||
'writable' => false, |
||||
], |
||||
[ |
||||
'path' => '/var/www/html/apps-cypress', |
||||
'url' => '/apps-cypress', |
||||
'writable' => true, |
||||
], |
||||
], |
||||
]; |
||||
@ -0,0 +1,19 @@ |
||||
/*! |
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors |
||||
* SPDX-License-Identifier: AGPL-3.0-or-later |
||||
*/ |
||||
|
||||
export function randomString(length: number) { |
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' |
||||
const alphaNumeric = characters + '0123456789' |
||||
let result = '' |
||||
for (let i = 0; i < length; i++) { |
||||
// Ensure the first character is alphabetic
|
||||
if (i === 0) { |
||||
result += characters.charAt(Math.floor(Math.random() * characters.length)) |
||||
continue |
||||
} |
||||
result += alphaNumeric.charAt(Math.floor(Math.random() * alphaNumeric.length)) |
||||
} |
||||
return result |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue