/** * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ import { expect, type Page } from '@playwright/test' import type { User } from './User' /** * Upload a file to the cloud. * @param options options for the file upload * @param options.name Name of the file * @param options.content File content * @param options.owner User who uploads the file * @param options.mtime Last modified time of the file * @return the file */ export async function uploadFile({ name, content = '', owner, mtime }: { content?: string name: string owner: User mtime?: number }) { // Upload file via WebDAV using page.request with requesttoken header const headers = { 'Content-Type': 'text/markdown', ...(mtime ? { 'x-oc-mtime': mtime.toString() } : {}), } const response = await owner.request.put( `/remote.php/webdav/${name}`, { data: content, headers }, ) if (!response.ok()) { throw new Error(`Failed to upload file: ${response.status()} ${response.statusText()}`) } // Extract file ID from response headers const ocFileId = response.headers()['oc-fileid'] const id = ocFileId ? Number(ocFileId.split('oc')?.[0]) : 0 return new Node({ id, name, page: owner.page }) } /** * Create a folder in the cloud. * @param options options for the file upload * @param options.name Name of the file * @param options.owner User who uploads the file * @return the folder */ export async function createFolder({ name, owner }: { name: string owner: User }) { const rootPath = `/remote.php/dav/files/${encodeURIComponent(owner.account.userId)}` const dirPath = name.split('/').map(encodeURIComponent).join('/') const response = await owner.request.fetch( `${rootPath}/${dirPath}`, { method: 'MKCOL' }, ) const id = parseInt(response.headers()['oc-fileid']) return new Node({ id, name, page: owner.page }) } const ocsHeaders = { Accept: 'application/json, text/plain, */*' } export class Node { public readonly id: number public readonly name: string private readonly page: Page constructor({ id, name, page }: { id: number, name: string, page: Page }) { this.id = id this.name = name this.page = page } async open() { // loading the file list may take a while const timeout = 15_000 await this.page.goto(`f/${this.id}`) await expect(this.page.getByLabel(this.name, { exact: true })) .toBeVisible({ timeout }) } async move(newName: string) { await this.page.request.fetch( `/remote.php/webdav/${this.name}`, { headers: { Destination: `/remote.php/webdav/${newName}`, }, method: 'MOVE', }) return new Node({ ...this, page: this.page, name: newName }) } async shareLink() { // const shareType = window.OC?.Share?.SHARE_TYPE_LINK ?? 3 const shareType = 3 const path = `/${this.name}` const response = await this.page.request.post( '/ocs/v2.php/apps/files_sharing/api/v1/shares', { data: { shareType, path }, headers: ocsHeaders, }) const { ocs } = await response.json() as { ocs: { data: { token: string, id: number } } } return ocs.data } async shareEditableLink() { const { token, id } = await this.shareLink() // Same permissions makeing the share editable in the UI would set // 1 = read; 2 = write; 16 = share; const permissions = 19 await this.page.request.put( `/ocs/v2.php/apps/files_sharing/api/v1/shares/${id}`, { data: { permissions }, headers: ocsHeaders, }) return { token, id } } }