/** * @copyright Copyright (c) 2022 John Molakvoæ * * @author John Molakvoæ * * @license AGPL-3.0-or-later * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ // eslint-disable-next-line n/no-extraneous-import import type { AxiosResponse } from 'axios' import axios from '@nextcloud/axios' import { addCommands, User } from '@nextcloud/cypress' import { basename } from 'path' // Add custom commands import '@testing-library/cypress/add-commands' import 'cypress-if' import 'cypress-wait-until' addCommands() // Register this file's custom commands types declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars interface Chainable { /** * Enable or disable a given user */ // eslint-disable-next-line @typescript-eslint/no-explicit-any enableUser(user: User, enable?: boolean): Cypress.Chainable>, /** * Upload a file from the fixtures folder to a given user storage. * **Warning**: Using this function will reset the previous session */ uploadFile(user: User, fixture?: string, mimeType?: string, target?: string): Cypress.Chainable, /** * Upload a raw content to a given user storage. * **Warning**: Using this function will reset the previous session */ uploadContent(user: User, content: Blob, mimeType: string, target: string, mtime?: number): Cypress.Chainable, /** * Create a new directory * **Warning**: Using this function will reset the previous session */ mkdir(user: User, target: string): Cypress.Chainable, /** * Set a file as favorite (or remove from favorite) */ setFileAsFavorite(user: User, target: string, favorite?: boolean): Cypress.Chainable, /** * Reset the admin theming entirely. * **Warning**: Using this function will reset the previous session */ resetAdminTheming(): Cypress.Chainable, /** * Reset the user theming settings. * If provided, will clear session and login as the given user. * **Warning**: Providing a user will reset the previous session. */ resetUserTheming(user?: User): Cypress.Chainable, /** * Run an occ command in the docker container. */ runOccCommand(command: string, options?: Partial): Cypress.Chainable, } } } const url = (Cypress.config('baseUrl') || '').replace(/\/index.php\/?$/g, '') Cypress.env('baseUrl', url) /** * Enable or disable a user * TODO: standardise in @nextcloud/cypress * * @param {User} user the user to dis- / enable * @param {boolean} enable True if the user should be enable, false to disable */ Cypress.Commands.add('enableUser', (user: User, enable = true) => { const url = `${Cypress.config('baseUrl')}/ocs/v2.php/cloud/users/${user.userId}/${enable ? 'enable' : 'disable'}`.replace('index.php/', '') return cy.request({ method: 'PUT', url, form: true, auth: { user: 'admin', password: 'admin', }, headers: { 'OCS-ApiRequest': 'true', 'Content-Type': 'application/x-www-form-urlencoded', }, }).then((response) => { cy.log(`Enabled user ${user}`, response.status) return cy.wrap(response) }) }) /** * cy.uploadedFile - uploads a file from the fixtures folder * TODO: standardise in @nextcloud/cypress * * @param {User} user the owner of the file, e.g. admin * @param {string} fixture the fixture file name, e.g. image1.jpg * @param {string} mimeType e.g. image/png * @param {string} [target] the target of the file relative to the user root */ Cypress.Commands.add('uploadFile', (user, fixture = 'image.jpg', mimeType = 'image/jpeg', target = `/${fixture}`) => { // get fixture return cy.fixture(fixture, 'base64').then(async file => { // convert the base64 string to a blob const blob = Cypress.Blob.base64StringToBlob(file, mimeType) cy.uploadContent(user, blob, mimeType, target) }) }) Cypress.Commands.add('setFileAsFavorite', (user: User, target: string, favorite = true) => { // eslint-disable-next-line cypress/unsafe-to-chain-command cy.clearAllCookies() .then(async () => { try { const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}` const filePath = target.split('/').map(encodeURIComponent).join('/') const response = await axios({ url: `${rootPath}${filePath}`, method: 'PROPPATCH', auth: { username: user.userId, password: user.password, }, headers: { 'Content-Type': 'application/xml', }, data: ` ${favorite ? 1 : 0} `, }) cy.log(`Created directory ${target}`, response) } catch (error) { cy.log('error', error) throw new Error('Unable to process fixture') } }) }) Cypress.Commands.add('mkdir', (user: User, target: string) => { // eslint-disable-next-line cypress/unsafe-to-chain-command cy.clearCookies() .then(async () => { try { const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}` const filePath = target.split('/').map(encodeURIComponent).join('/') const response = await axios({ url: `${rootPath}${filePath}`, method: 'MKCOL', auth: { username: user.userId, password: user.password, }, }) cy.log(`Created directory ${target}`, response) } catch (error) { cy.log('error', error) throw new Error('Unable to create directory') } }) }) /** * cy.uploadedContent - uploads a raw content * TODO: standardise in @nextcloud/cypress * * @param {User} user the owner of the file, e.g. admin * @param {Blob} blob the content to upload * @param {string} mimeType e.g. image/png * @param {string} target the target of the file relative to the user root */ Cypress.Commands.add('uploadContent', (user: User, blob: Blob, mimeType: string, target: string, mtime?: number) => { cy.clearCookies() return cy.then(async () => { const fileName = basename(target) // Process paths const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}` const filePath = target.split('/').map(encodeURIComponent).join('/') try { const file = new File([blob], fileName, { type: mimeType }) const response = await axios({ url: `${rootPath}${filePath}`, method: 'PUT', data: file, headers: { 'Content-Type': mimeType, 'X-OC-MTime': mtime ? `${mtime}` : undefined, }, auth: { username: user.userId, password: user.password, }, }) cy.log(`Uploaded content as ${fileName}`, response) return response } catch (error) { cy.log('error', error) throw new Error('Unable to process fixture') } }) }) /** * Reset the admin theming entirely */ Cypress.Commands.add('resetAdminTheming', () => { const admin = new User('admin', 'admin') cy.clearCookies() cy.login(admin) // Clear all settings cy.request('/csrftoken').then(({ body }) => { const requestToken = body.token axios({ method: 'POST', url: '/index.php/apps/theming/ajax/undoAllChanges', headers: { requesttoken: requestToken, }, }) }) // Clear admin session cy.clearCookies() }) /** * Reset the current or provided user theming settings * It does not reset the theme config as it is enforced in the * server config for cypress testing. */ Cypress.Commands.add('resetUserTheming', (user?: User) => { if (user) { cy.clearCookies() cy.login(user) } // Reset background config cy.request('/csrftoken').then(({ body }) => { const requestToken = body.token cy.request({ method: 'POST', url: '/apps/theming/background/default', headers: { requesttoken: requestToken, }, }) }) if (user) { // Clear current session cy.clearCookies() } }) Cypress.Commands.add('runOccCommand', (command: string, options?: Partial) => { const env = Object.entries(options?.env ?? {}).map(([name, value]) => `-e '${name}=${value}'`).join(' ') return cy.exec(`docker exec --user www-data ${env} nextcloud-cypress-tests-server php ./occ ${command}`, options) })