commands.ts 8.7 KB

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