// Production Vue component + RPC + actual Nextcloud, not login/install UAT. const { chromium, expect, request } = require('@playwright/test') const { exerciseNextcloudPagination } = require('./pagination.cjs') const assert = require('node:assert/strict') const fs = require('node:fs/promises') const base = new URL(process.argv[2]) assert.equal(base.hostname, '127.0.0.1') assert.equal(base.protocol, 'http:') ;(async () => { const browser = await chromium.launch({ executablePath: process.env.CHROMIUM_EXECUTABLE, headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] }) try { const context = await browser.newContext({ acceptDownloads: true, serviceWorkers: 'block' }) await context.route('**/*', route => new URL(route.request().url()).origin === base.origin ? route.continue() : route.abort()) await context.routeWebSocket('**/*', socket => socket.close()) const page = await context.newPage() const errors = [] page.on('pageerror', error => errors.push(error.message)) await page.goto(base.href) await expect(page.getByRole('button', { name: 'Connect a library', exact: true })).toBeVisible() const connect = async (login, password) => { await page.getByRole('button', { name: 'Connect a library', exact: true }).click() await page.getByLabel('Nextcloud login', { exact: true }).fill(login) await page.getByLabel('Nextcloud app password', { exact: true }).fill(password) await page.getByRole('button', { name: 'Connect', exact: true }).click() await expect(page.getByLabel('Library', { exact: true })).toContainText(`Nextcloud · ${login}`) await expect(page.getByLabel('Nextcloud app password', { exact: true })).toHaveCount(0) } await connect('alice', 'Synthetic-Cloud-A-Only-2026!') const downloadButton = page.getByRole('button', { name: 'Download contract.txt', exact: true }) await expect(downloadButton).toBeVisible() const pendingDownload = page.waitForEvent('download') await downloadButton.click() const download = await pendingDownload assert.equal(download.suggestedFilename(), 'contract.txt') assert.equal(await fs.readFile(await download.path(), 'utf8'), 'abcdefgh') await connect('bob', 'Synthetic-Cloud-B-Only-2026!') await expect(downloadButton).toHaveCount(0) await page.reload() await expect(page.getByLabel('Library', { exact: true }).locator('option')).toHaveCount(2) await page.getByLabel('Library', { exact: true }).selectOption({ label: 'Nextcloud · alice' }) await expect(downloadButton).toBeVisible() const alice = await page.getByLabel('Library', { exact: true }).inputValue() const rpc = (method, params) => page.evaluate(async ({ method, params }) => { const csrf = document.cookie.match(/(?:^|;\s*)csrf_token=([^;]+)/)[1] const r = await fetch('/rpc/v1', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }) if (!r.ok) throw new Error(`RPC HTTP ${r.status}`) const body = await r.json() if (body.error) throw new Error(body.error.message) return body.result }, { method, params }) await exerciseNextcloudPagination({ page, request, expect, connectionId: alice, rpc }) const listing = await rpc('cloud.sources.browse', { connection_id: alice, folder: [], page: 1 }) const item = listing.items.find(item => item.name === 'contract.txt') assert.ok(item) const { url } = await rpc('cloud.sources.prepare-stream', { connection_id: alice, upstream_id: item.upstream_id, revision: item.revision, locator: item.locator, download: true }) const range = await context.request.get(new URL(url, base).href, { headers: { Range: 'bytes=2-5' } }) assert.equal(range.status(), 206) assert.equal(range.headers()['content-range'], 'bytes 2-5/8') assert.equal(await range.text(), 'cdef') const foreign = await context.request.get(new URL(url, base).href, { headers: { Origin: 'http://other.invalid' } }) assert.equal(foreign.status(), 403) const unauth = await browser.newContext() const denied = await unauth.request.get(new URL(url, base).href) assert.equal(denied.status(), 401) const deniedRpc = await unauth.request.post(new URL('/rpc/v1', base).href, { data: { jsonrpc: '2.0', id: 1, method: 'cloud.sources.list' } }) assert.equal(deniedRpc.status(), 401) await unauth.close() const badCsrf = await context.request.post(new URL('/rpc/v1', base).href, { headers: { Origin: base.origin, 'X-CSRF-Token': 'invalid-synthetic' }, data: { jsonrpc: '2.0', id: 1, method: 'cloud.sources.disconnect', params: { connection_id: alice } }, }) assert.equal(badCsrf.status(), 403) await page.getByRole('button', { name: 'Disconnect', exact: true }).click() await page.getByRole('dialog').getByRole('button', { name: 'Disconnect', exact: true }).click() await expect(page.getByLabel('Library', { exact: true }).locator('option')).toHaveCount(1) await expect(page.getByLabel('Library', { exact: true })).toContainText('Nextcloud · bob') const revoked = await context.request.get(new URL(url, base).href) assert.equal(revoked.status(), 409) assert.ok((await rpc('cloud.sources.browse', { connection_id: await page.getByLabel('Library', { exact: true }).inputValue(), folder: [], page: 1 })).items) assert.deepEqual(errors, []) console.log('PASS production Cloud UI→RPC→Nextcloud: connect two accounts, exact download/range, reload, session/origin/CSRF denial, disconnect revocation and other-account survival; no File Browser') await context.close() } finally { await browser.close() } })().catch(error => { console.error(error); process.exitCode = 1 })