commands.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. /**
  2. * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
  3. *
  4. * @author John Molakvoæ <skjnldsv@protonmail.com>
  5. *
  6. * @license AGPL-3.0-or-later
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as
  10. * published by the Free Software Foundation, either version 3 of the
  11. * License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  20. *
  21. */
  22. // eslint-disable-next-line n/no-extraneous-import
  23. import type { AxiosResponse } from 'axios'
  24. import axios from '@nextcloud/axios'
  25. import { addCommands, User } from '@nextcloud/cypress'
  26. import { basename } from 'path'
  27. // Add custom commands
  28. import '@testing-library/cypress/add-commands'
  29. import 'cypress-if'
  30. import 'cypress-wait-until'
  31. addCommands()
  32. // Register this file's custom commands types
  33. declare global {
  34. // eslint-disable-next-line @typescript-eslint/no-namespace
  35. namespace Cypress {
  36. // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
  37. interface Chainable<Subject = any> {
  38. /**
  39. * Enable or disable a given user
  40. */
  41. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  42. enableUser(user: User, enable?: boolean): Cypress.Chainable<Cypress.Response<any>>,
  43. /**
  44. * Upload a file from the fixtures folder to a given user storage.
  45. * **Warning**: Using this function will reset the previous session
  46. */
  47. uploadFile(user: User, fixture?: string, mimeType?: string, target?: string): Cypress.Chainable<void>,
  48. /**
  49. * Upload a raw content to a given user storage.
  50. * **Warning**: Using this function will reset the previous session
  51. */
  52. uploadContent(user: User, content: Blob, mimeType: string, target: string, mtime?: number): Cypress.Chainable<AxiosResponse>,
  53. /**
  54. * Create a new directory
  55. * **Warning**: Using this function will reset the previous session
  56. */
  57. mkdir(user: User, target: string): Cypress.Chainable<void>,
  58. /**
  59. * Set a file as favorite (or remove from favorite)
  60. */
  61. setFileAsFavorite(user: User, target: string, favorite?: boolean): Cypress.Chainable<void>,
  62. /**
  63. * Reset the admin theming entirely.
  64. * **Warning**: Using this function will reset the previous session
  65. */
  66. resetAdminTheming(): Cypress.Chainable<void>,
  67. /**
  68. * Reset the user theming settings.
  69. * If provided, will clear session and login as the given user.
  70. * **Warning**: Providing a user will reset the previous session.
  71. */
  72. resetUserTheming(user?: User): Cypress.Chainable<void>,
  73. /**
  74. * Run an occ command in the docker container.
  75. */
  76. runOccCommand(command: string, options?: Partial<Cypress.ExecOptions>): Cypress.Chainable<Cypress.Exec>,
  77. }
  78. }
  79. }
  80. const url = (Cypress.config('baseUrl') || '').replace(/\/index.php\/?$/g, '')
  81. Cypress.env('baseUrl', url)
  82. /**
  83. * Enable or disable a user
  84. * TODO: standardise in @nextcloud/cypress
  85. *
  86. * @param {User} user the user to dis- / enable
  87. * @param {boolean} enable True if the user should be enable, false to disable
  88. */
  89. Cypress.Commands.add('enableUser', (user: User, enable = true) => {
  90. const url = `${Cypress.config('baseUrl')}/ocs/v2.php/cloud/users/${user.userId}/${enable ? 'enable' : 'disable'}`.replace('index.php/', '')
  91. return cy.request({
  92. method: 'PUT',
  93. url,
  94. form: true,
  95. auth: {
  96. user: 'admin',
  97. password: 'admin',
  98. },
  99. headers: {
  100. 'OCS-ApiRequest': 'true',
  101. 'Content-Type': 'application/x-www-form-urlencoded',
  102. },
  103. }).then((response) => {
  104. cy.log(`Enabled user ${user}`, response.status)
  105. return cy.wrap(response)
  106. })
  107. })
  108. /**
  109. * cy.uploadedFile - uploads a file from the fixtures folder
  110. * TODO: standardise in @nextcloud/cypress
  111. *
  112. * @param {User} user the owner of the file, e.g. admin
  113. * @param {string} fixture the fixture file name, e.g. image1.jpg
  114. * @param {string} mimeType e.g. image/png
  115. * @param {string} [target] the target of the file relative to the user root
  116. */
  117. Cypress.Commands.add('uploadFile', (user, fixture = 'image.jpg', mimeType = 'image/jpeg', target = `/${fixture}`) => {
  118. // get fixture
  119. return cy.fixture(fixture, 'base64').then(async file => {
  120. // convert the base64 string to a blob
  121. const blob = Cypress.Blob.base64StringToBlob(file, mimeType)
  122. cy.uploadContent(user, blob, mimeType, target)
  123. })
  124. })
  125. Cypress.Commands.add('setFileAsFavorite', (user: User, target: string, favorite = true) => {
  126. // eslint-disable-next-line cypress/unsafe-to-chain-command
  127. cy.clearAllCookies()
  128. .then(async () => {
  129. try {
  130. const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}`
  131. const filePath = target.split('/').map(encodeURIComponent).join('/')
  132. const response = await axios({
  133. url: `${rootPath}${filePath}`,
  134. method: 'PROPPATCH',
  135. auth: {
  136. username: user.userId,
  137. password: user.password,
  138. },
  139. headers: {
  140. 'Content-Type': 'application/xml',
  141. },
  142. data: `<?xml version="1.0"?>
  143. <d:propertyupdate xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
  144. <d:set>
  145. <d:prop>
  146. <oc:favorite>${favorite ? 1 : 0}</oc:favorite>
  147. </d:prop>
  148. </d:set>
  149. </d:propertyupdate>`,
  150. })
  151. cy.log(`Created directory ${target}`, response)
  152. } catch (error) {
  153. cy.log('error', error)
  154. throw new Error('Unable to process fixture')
  155. }
  156. })
  157. })
  158. Cypress.Commands.add('mkdir', (user: User, target: string) => {
  159. // eslint-disable-next-line cypress/unsafe-to-chain-command
  160. cy.clearCookies()
  161. .then(async () => {
  162. try {
  163. const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}`
  164. const filePath = target.split('/').map(encodeURIComponent).join('/')
  165. const response = await axios({
  166. url: `${rootPath}${filePath}`,
  167. method: 'MKCOL',
  168. auth: {
  169. username: user.userId,
  170. password: user.password,
  171. },
  172. })
  173. cy.log(`Created directory ${target}`, response)
  174. } catch (error) {
  175. cy.log('error', error)
  176. throw new Error('Unable to create directory')
  177. }
  178. })
  179. })
  180. /**
  181. * cy.uploadedContent - uploads a raw content
  182. * TODO: standardise in @nextcloud/cypress
  183. *
  184. * @param {User} user the owner of the file, e.g. admin
  185. * @param {Blob} blob the content to upload
  186. * @param {string} mimeType e.g. image/png
  187. * @param {string} target the target of the file relative to the user root
  188. */
  189. Cypress.Commands.add('uploadContent', (user: User, blob: Blob, mimeType: string, target: string, mtime?: number) => {
  190. cy.clearCookies()
  191. return cy.then(async () => {
  192. const fileName = basename(target)
  193. // Process paths
  194. const rootPath = `${Cypress.env('baseUrl')}/remote.php/dav/files/${encodeURIComponent(user.userId)}`
  195. const filePath = target.split('/').map(encodeURIComponent).join('/')
  196. try {
  197. const file = new File([blob], fileName, { type: mimeType })
  198. const response = await axios({
  199. url: `${rootPath}${filePath}`,
  200. method: 'PUT',
  201. data: file,
  202. headers: {
  203. 'Content-Type': mimeType,
  204. 'X-OC-MTime': mtime ? `${mtime}` : undefined,
  205. },
  206. auth: {
  207. username: user.userId,
  208. password: user.password,
  209. },
  210. })
  211. cy.log(`Uploaded content as ${fileName}`, response)
  212. return response
  213. } catch (error) {
  214. cy.log('error', error)
  215. throw new Error('Unable to process fixture')
  216. }
  217. })
  218. })
  219. /**
  220. * Reset the admin theming entirely
  221. */
  222. Cypress.Commands.add('resetAdminTheming', () => {
  223. const admin = new User('admin', 'admin')
  224. cy.clearCookies()
  225. cy.login(admin)
  226. // Clear all settings
  227. cy.request('/csrftoken').then(({ body }) => {
  228. const requestToken = body.token
  229. axios({
  230. method: 'POST',
  231. url: '/index.php/apps/theming/ajax/undoAllChanges',
  232. headers: {
  233. requesttoken: requestToken,
  234. },
  235. })
  236. })
  237. // Clear admin session
  238. cy.clearCookies()
  239. })
  240. /**
  241. * Reset the current or provided user theming settings
  242. * It does not reset the theme config as it is enforced in the
  243. * server config for cypress testing.
  244. */
  245. Cypress.Commands.add('resetUserTheming', (user?: User) => {
  246. if (user) {
  247. cy.clearCookies()
  248. cy.login(user)
  249. }
  250. // Reset background config
  251. cy.request('/csrftoken').then(({ body }) => {
  252. const requestToken = body.token
  253. cy.request({
  254. method: 'POST',
  255. url: '/apps/theming/background/default',
  256. headers: {
  257. requesttoken: requestToken,
  258. },
  259. })
  260. })
  261. if (user) {
  262. // Clear current session
  263. cy.clearCookies()
  264. }
  265. })
  266. Cypress.Commands.add('runOccCommand', (command: string, options?: Partial<Cypress.ExecOptions>) => {
  267. const env = Object.entries(options?.env ?? {}).map(([name, value]) => `-e '${name}=${value}'`).join(' ')
  268. return cy.exec(`docker exec --user www-data ${env} nextcloud-cypress-tests-server php ./occ ${command}`, options)
  269. })