commands.ts 8.2 KB

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