kopia lustrzana https://github.com/nextcloud/social
Merge pull request #1748 from nextcloud/artonge/feat/modernize_e2e_tests
Modernize e2e testspull/1617/head^2
commit
2ecd2247b1
|
@ -10,8 +10,6 @@ on:
|
||||||
env:
|
env:
|
||||||
APP_NAME: social
|
APP_NAME: social
|
||||||
BRANCH: ${{ github.base_ref }}
|
BRANCH: ${{ github.base_ref }}
|
||||||
CYPRESS_baseUrl: http://127.0.0.1:8082/index.php
|
|
||||||
TESTING: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
init:
|
init:
|
||||||
|
@ -19,17 +17,20 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout app
|
- name: Checkout app
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
|
||||||
|
|
||||||
|
- name: Install server dependencies
|
||||||
|
run: composer install
|
||||||
|
|
||||||
- name: Read package.json node and npm engines version
|
- name: Read package.json node and npm engines version
|
||||||
uses: skjnldsv/read-package-engines-version-actions@v1.1
|
uses: skjnldsv/read-package-engines-version-actions@0ce2ed60f6df073a62a77c0a4958dd0fc68e32e7 # v2.1
|
||||||
id: versions
|
id: versions
|
||||||
with:
|
with:
|
||||||
fallbackNode: "^12"
|
fallbackNode: "^14"
|
||||||
fallbackNpm: "^6"
|
fallbackNpm: "^7"
|
||||||
|
|
||||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||||
with:
|
with:
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||||
|
@ -37,17 +38,15 @@ jobs:
|
||||||
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
|
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
|
||||||
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||||
|
|
||||||
- name: Install dependencies & build app
|
- name: Install node dependencies & build app
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
composer install
|
|
||||||
TESTING=true npm run build --if-present
|
TESTING=true npm run build --if-present
|
||||||
|
|
||||||
- name: Save context
|
- name: Save context
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||||
with:
|
with:
|
||||||
key: cypress-context-${{ github.run_id }}
|
key: cypress-context-${{ github.run_id }}
|
||||||
path: /home/runner/work/social
|
path: ./
|
||||||
|
|
||||||
cypress:
|
cypress:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -57,32 +56,30 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
# run multiple copies of the current job in parallel
|
# run multiple copies of the current job in parallel
|
||||||
containers: [1, 2, 3, 4, 5, 6, 7, 8]
|
containers: ["component", 1, 2]
|
||||||
|
|
||||||
name: runner ${{ matrix.containers }}
|
name: runner ${{ matrix.containers }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Restore context
|
- name: Restore context
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
|
||||||
with:
|
with:
|
||||||
|
fail-on-cache-miss: true
|
||||||
key: cypress-context-${{ github.run_id }}
|
key: cypress-context-${{ github.run_id }}
|
||||||
path: /home/runner/work/social
|
path: ./
|
||||||
|
|
||||||
- name: Setup server
|
- name: Set up node ${{ needs.init.outputs.nodeVersion }}
|
||||||
run: |
|
uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
|
||||||
cd cypress
|
with:
|
||||||
docker-compose up -d
|
cache: "npm"
|
||||||
|
node-version: ${{ needs.init.outputs.nodeVersion }}
|
||||||
|
|
||||||
- name: Wait for server
|
- name: Set up npm ${{ needs.init.outputs.npmVersion }}
|
||||||
run: npm run wait-on $CYPRESS_baseUrl
|
run: npm i -g npm@"${{ needs.init.outputs.npmVersion }}"
|
||||||
|
|
||||||
- name: Enable app & configure server
|
- name: Run ${{ matrix.containers == 'component' && 'component' || 'E2E' }} cypress tests
|
||||||
run: |
|
|
||||||
cd cypress
|
|
||||||
docker-compose exec --env APP_NAME=${{ env.APP_NAME }} --env BRANCH=${{ env.BRANCH }} -T nextcloud bash /initserver.sh
|
|
||||||
|
|
||||||
- name: Cypress run
|
uses: cypress-io/github-action@db1693016f23ccf9043f4b2428f9b04e5d502a73 # v5.8.1
|
||||||
uses: cypress-io/github-action@v4
|
|
||||||
with:
|
with:
|
||||||
record: true
|
record: true
|
||||||
parallel: true
|
parallel: true
|
||||||
|
@ -90,9 +87,21 @@ jobs:
|
||||||
ci-build-id: ${{ github.sha }}-${{ github.run_number }}
|
ci-build-id: ${{ github.sha }}-${{ github.run_number }}
|
||||||
tag: ${{ github.event_name }}
|
tag: ${{ github.event_name }}
|
||||||
env:
|
env:
|
||||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
# Needs to be prefixed with CYPRESS_
|
||||||
|
CYPRESS_BRANCH: ${{ env.BRANCH }}
|
||||||
# https://github.com/cypress-io/github-action/issues/124
|
# https://github.com/cypress-io/github-action/issues/124
|
||||||
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
COMMIT_INFO_MESSAGE: ${{ github.event.pull_request.title }}
|
||||||
|
# Needed for some specific code workarounds
|
||||||
|
TESTING: true
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||||
|
|
||||||
|
- name: Upload snapshots
|
||||||
|
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: snapshots_${{ matrix.containers }}
|
||||||
|
path: cypress/snapshots
|
||||||
|
|
||||||
- name: Extract NC logs
|
- name: Extract NC logs
|
||||||
if: always()
|
if: always()
|
||||||
|
@ -104,3 +113,15 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: nc_logs_${{ matrix.containers }}
|
name: nc_logs_${{ matrix.containers }}
|
||||||
path: nextcloud.log
|
path: nextcloud.log
|
||||||
|
|
||||||
|
summary:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [init, cypress]
|
||||||
|
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
name: cypress-summary
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Summary status
|
||||||
|
run: if ${{ needs.init.result != 'success' || ( needs.cypress.result != 'success' && needs.cypress.result != 'skipped' ) }}; then exit 1; fi
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<info xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||||
<id>social</id>
|
<id>social</id>
|
||||||
<name>Social</name>
|
<name>Social</name>
|
||||||
<summary>🎉 Nextcloud becomes part of the federated social networks!</summary>
|
<summary>🎉 Nextcloud becomes part of the federated social networks!</summary>
|
||||||
|
@ -22,7 +22,8 @@
|
||||||
<author mail="maxence@artificial-owl.com" homepage="https://artificial-owl.com/">Maxence Lange</author>
|
<author mail="maxence@artificial-owl.com" homepage="https://artificial-owl.com/">Maxence Lange</author>
|
||||||
<author mail="jus@bitgrid.net">Julius Härtl</author>
|
<author mail="jus@bitgrid.net">Julius Härtl</author>
|
||||||
<author mail="jonas@violoncello.ch" homepage="https://violoncello.ch">Jonas Sulzer</author>
|
<author mail="jonas@violoncello.ch" homepage="https://violoncello.ch">Jonas Sulzer</author>
|
||||||
<author mail="hey@jancborchardt.net" homepage="https://jancborchardt.net">Jan-Christoph Borchardt</author>
|
<author mail="hey@jancborchardt.net" homepage="https://jancborchardt.net">Jan-Christoph
|
||||||
|
Borchardt</author>
|
||||||
<author mail="cyrpub@bollu.be">Cyrille Bollu</author>
|
<author mail="cyrpub@bollu.be">Cyrille Bollu</author>
|
||||||
<namespace>Social</namespace>
|
<namespace>Social</namespace>
|
||||||
<category>social</category>
|
<category>social</category>
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
<database>pgsql</database>
|
<database>pgsql</database>
|
||||||
<database>sqlite</database>
|
<database>sqlite</database>
|
||||||
<database>mysql</database>
|
<database>mysql</database>
|
||||||
<nextcloud min-version="26" max-version="28"/>
|
<nextcloud min-version="26" max-version="28" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<background-jobs>
|
<background-jobs>
|
||||||
|
@ -76,4 +77,4 @@
|
||||||
<contactsmenu>
|
<contactsmenu>
|
||||||
<provider>OCA\Social\Providers\ContactsMenuProvider</provider>
|
<provider>OCA\Social\Providers\ContactsMenuProvider</provider>
|
||||||
</contactsmenu>
|
</contactsmenu>
|
||||||
</info>
|
</info>
|
|
@ -49,4 +49,4 @@
|
||||||
"url": "https://git.friendi.ca/friendica/php-json-ld"
|
"url": "https://git.friendi.ca/friendica/php-json-ld"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Plik diff jest za duży
Load Diff
|
@ -1,28 +0,0 @@
|
||||||
const { defineConfig } = require('cypress')
|
|
||||||
const browserify = require('@cypress/browserify-preprocessor')
|
|
||||||
|
|
||||||
module.exports = defineConfig({
|
|
||||||
projectId: '7mqhfh',
|
|
||||||
|
|
||||||
viewportWidth: 1280,
|
|
||||||
viewportHeight: 720,
|
|
||||||
defaultCommandTimeout: 6000,
|
|
||||||
retries: 1,
|
|
||||||
|
|
||||||
env: {
|
|
||||||
failSilently: false,
|
|
||||||
type: 'actual',
|
|
||||||
},
|
|
||||||
|
|
||||||
screenshotsFolder: 'cypress/snapshots/actual',
|
|
||||||
trashAssetsBeforeRuns: true,
|
|
||||||
|
|
||||||
e2e: {
|
|
||||||
baseUrl: 'http://localhost:8082/index.php',
|
|
||||||
|
|
||||||
setupNodeEvents(on, config) {
|
|
||||||
// Fix browserslist extend https://github.com/cypress-io/cypress/issues/2983#issuecomment-570616682
|
|
||||||
on('file:preprocessor', browserify())
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
|
||||||
|
import {
|
||||||
|
configureNextcloud,
|
||||||
|
startNextcloud,
|
||||||
|
stopNextcloud,
|
||||||
|
waitOnNextcloud,
|
||||||
|
} from './cypress/dockerNode'
|
||||||
|
import { defineConfig } from 'cypress'
|
||||||
|
|
||||||
|
import browserify from '@cypress/browserify-preprocessor'
|
||||||
|
import getCompareSnapshotsPlugin from 'cypress-visual-regression/dist/plugin'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
projectId: '7mqhfh',
|
||||||
|
|
||||||
|
// 16/9 screen ratio
|
||||||
|
viewportWidth: 1280,
|
||||||
|
viewportHeight: 720,
|
||||||
|
|
||||||
|
// Tries again 2 more times on failure
|
||||||
|
retries: {
|
||||||
|
runMode: 2,
|
||||||
|
// do not retry in `cypress open`
|
||||||
|
openMode: 0,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Needed to trigger `after:run` events with cypress open
|
||||||
|
experimentalInteractiveRunEvents: true,
|
||||||
|
|
||||||
|
// faster video processing
|
||||||
|
videoCompression: false,
|
||||||
|
|
||||||
|
// Visual regression testing
|
||||||
|
env: {
|
||||||
|
failSilently: false,
|
||||||
|
type: 'actual',
|
||||||
|
},
|
||||||
|
screenshotsFolder: 'cypress/snapshots/actual',
|
||||||
|
trashAssetsBeforeRuns: true,
|
||||||
|
|
||||||
|
e2e: {
|
||||||
|
testIsolation: false,
|
||||||
|
|
||||||
|
// We've imported your old cypress plugins here.
|
||||||
|
// You may want to clean this up later by importing these.
|
||||||
|
async setupNodeEvents(on, config) {
|
||||||
|
// Fix browserslist extend https://github.com/cypress-io/cypress/issues/2983#issuecomment-570616682
|
||||||
|
on('file:preprocessor', browserify({ typescript: require.resolve('typescript') }))
|
||||||
|
getCompareSnapshotsPlugin(on, config)
|
||||||
|
|
||||||
|
// Disable spell checking to prevent rendering differences
|
||||||
|
on('before:browser:launch', (browser, launchOptions) => {
|
||||||
|
if (browser.family === 'chromium' && browser.name !== 'electron') {
|
||||||
|
launchOptions.preferences.default['browser.enable_spellchecking'] = false
|
||||||
|
return launchOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser.family === 'firefox') {
|
||||||
|
launchOptions.preferences['layout.spellcheckDefault'] = 0
|
||||||
|
return launchOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser.name === 'electron') {
|
||||||
|
launchOptions.preferences.spellcheck = false
|
||||||
|
return launchOptions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Remove container after run
|
||||||
|
on('after:run', () => {
|
||||||
|
stopNextcloud()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Before the browser launches
|
||||||
|
// starting Nextcloud testing container
|
||||||
|
return startNextcloud(process.env.BRANCH)
|
||||||
|
.then((ip) => {
|
||||||
|
// Setting container's IP as base Url
|
||||||
|
config.baseUrl = `http://${ip}/index.php`
|
||||||
|
return ip
|
||||||
|
})
|
||||||
|
.then(waitOnNextcloud)
|
||||||
|
.then(() => configureNextcloud(process.env.BRANCH))
|
||||||
|
.then(() => {
|
||||||
|
return config
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
'cypress/globals': true,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
'cypress',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'plugin:cypress/recommended',
|
||||||
|
],
|
||||||
|
};
|
|
@ -15,4 +15,3 @@ services:
|
||||||
# Using fallback to make sure this script doesn't mess
|
# Using fallback to make sure this script doesn't mess
|
||||||
# with the mounting if APP_NAME is not provided.
|
# with the mounting if APP_NAME is not provided.
|
||||||
- ../:/var/www/html/apps/${APP_NAME:-social}
|
- ../:/var/www/html/apps/${APP_NAME:-social}
|
||||||
- ./initserver.sh:/initserver.sh
|
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
*
|
||||||
|
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
*
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
/* eslint-disable n/no-unpublished-import */
|
||||||
|
/* eslint-disable n/no-extraneous-import */
|
||||||
|
|
||||||
|
import Docker from 'dockerode'
|
||||||
|
import path from 'path'
|
||||||
|
import waitOn from 'wait-on'
|
||||||
|
|
||||||
|
import pkg from '../package.json'
|
||||||
|
|
||||||
|
export const docker = new Docker()
|
||||||
|
|
||||||
|
const APP_PATH = path.resolve(__dirname, '../')
|
||||||
|
const APP_NAME = pkg.name
|
||||||
|
|
||||||
|
const CONTAINER_NAME = 'nextcloud-cypress-tests-' + APP_NAME
|
||||||
|
const SERVER_IMAGE = 'ghcr.io/nextcloud/continuous-integration-shallow-server'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start the testing container
|
||||||
|
*/
|
||||||
|
export const startNextcloud = async function(branch: string = 'master'): Promise<any> {
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Pulling images
|
||||||
|
console.log('\nPulling images... ⏳')
|
||||||
|
await new Promise((resolve, reject): any => docker.pull(SERVER_IMAGE, (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
// https://github.com/apocas/dockerode/issues/357
|
||||||
|
docker.modem.followProgress(stream, onFinished)
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param err
|
||||||
|
*/
|
||||||
|
function onFinished(err) {
|
||||||
|
if (!err) {
|
||||||
|
resolve(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
console.log('└─ Done')
|
||||||
|
|
||||||
|
// Remove old container if exists
|
||||||
|
console.log('\nChecking running containers... 🔍')
|
||||||
|
try {
|
||||||
|
const oldContainer = docker.getContainer(CONTAINER_NAME)
|
||||||
|
const oldContainerData = await oldContainer.inspect()
|
||||||
|
if (oldContainerData) {
|
||||||
|
console.log('├─ Existing running container found')
|
||||||
|
console.log('├─ Removing... ⏳')
|
||||||
|
// Forcing any remnants to be removed just in case
|
||||||
|
await oldContainer.remove({ force: true })
|
||||||
|
console.log('└─ Done')
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log('└─ None found!')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Starting container
|
||||||
|
console.log('\nStarting Nextcloud container... 🚀')
|
||||||
|
console.log(`├─ Using branch '${branch}'`)
|
||||||
|
console.log(`├─ And binding app '${APP_NAME}' from '${APP_PATH}'`)
|
||||||
|
const container = await docker.createContainer({
|
||||||
|
Image: SERVER_IMAGE,
|
||||||
|
name: CONTAINER_NAME,
|
||||||
|
HostConfig: {
|
||||||
|
Binds: [
|
||||||
|
// TODO: improve local app directory detection
|
||||||
|
`${APP_PATH}/:/var/www/html/apps/${APP_NAME}`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Env: [
|
||||||
|
`BRANCH=${branch}`,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
await container.start()
|
||||||
|
|
||||||
|
// Get container's IP
|
||||||
|
const ip = await getContainerIP(container)
|
||||||
|
|
||||||
|
console.log(`├─ Nextcloud container's IP is ${ip} 🌏`)
|
||||||
|
return ip
|
||||||
|
} catch (err) {
|
||||||
|
console.log('└─ Unable to start the container 🛑')
|
||||||
|
console.log(err)
|
||||||
|
stopNextcloud()
|
||||||
|
throw new Error('Unable to start the container')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure Nextcloud
|
||||||
|
*/
|
||||||
|
export const configureNextcloud = async function(branch: string = 'master') {
|
||||||
|
console.log('\nConfiguring nextcloud...')
|
||||||
|
const container = docker.getContainer(CONTAINER_NAME)
|
||||||
|
await runExec(container, ['php', 'occ', '--version'], true)
|
||||||
|
|
||||||
|
// Clone the viewer app
|
||||||
|
await runExec(container, ['git', 'clone', '--depth', '1', '--branch', branch, 'https://github.com/nextcloud/viewer.git', '/var/www/html/apps/viewer'], true)
|
||||||
|
await runExec(container, ['php', 'occ', 'app:enable', 'social'], true)
|
||||||
|
|
||||||
|
// Be consistent for screenshots
|
||||||
|
await runExec(container, ['php', 'occ', 'config:system:set', 'default_language', '--value', 'en'], true)
|
||||||
|
await runExec(container, ['php', 'occ', 'config:system:set', 'force_language', '--value', 'en'], true)
|
||||||
|
await runExec(container, ['php', 'occ', 'config:system:set', 'default_locale', '--value', 'en_US'], true)
|
||||||
|
await runExec(container, ['php', 'occ', 'config:system:set', 'force_locale', '--value', 'en_US'], true)
|
||||||
|
await runExec(container, ['php', 'occ', 'config:system:set', 'enforce_theme', '--value', 'light'], true)
|
||||||
|
|
||||||
|
console.log('└─ Nextcloud is now ready to use 🎉')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force stop the testing container
|
||||||
|
*/
|
||||||
|
export const stopNextcloud = async function() {
|
||||||
|
try {
|
||||||
|
const container = docker.getContainer(CONTAINER_NAME)
|
||||||
|
console.log('Stopping Nextcloud container...')
|
||||||
|
container.remove({ force: true })
|
||||||
|
console.log('└─ Nextcloud container removed 🥀')
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the testing container's IP
|
||||||
|
*/
|
||||||
|
export const getContainerIP = async function(
|
||||||
|
container: Docker.Container = docker.getContainer(CONTAINER_NAME)
|
||||||
|
): Promise<string> {
|
||||||
|
let ip = ''
|
||||||
|
let tries = 0
|
||||||
|
while (ip === '' && tries < 10) {
|
||||||
|
tries++
|
||||||
|
|
||||||
|
await container.inspect(function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
ip = data?.NetworkSettings?.IPAddress || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
if (ip !== '') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(1000 * tries)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Would be simpler to start the container from cypress.config.ts,
|
||||||
|
// but when checking out different branches, it can take a few seconds
|
||||||
|
// Until we can properly configure the baseUrl retry intervals,
|
||||||
|
// We need to make sure the server is already running before cypress
|
||||||
|
// https://github.com/cypress-io/cypress/issues/22676
|
||||||
|
export const waitOnNextcloud = async function(ip: string) {
|
||||||
|
console.log('├─ Waiting for Nextcloud to be ready... ⏳')
|
||||||
|
await waitOn({ resources: [`http://${ip}/index.php`] })
|
||||||
|
console.log('└─ Done')
|
||||||
|
}
|
||||||
|
|
||||||
|
const runExec = async function(
|
||||||
|
container: Docker.Container,
|
||||||
|
command: string[],
|
||||||
|
verbose = false,
|
||||||
|
user = 'www-data'
|
||||||
|
) {
|
||||||
|
const exec = await container.exec({
|
||||||
|
Cmd: command,
|
||||||
|
AttachStdout: true,
|
||||||
|
AttachStderr: true,
|
||||||
|
User: user,
|
||||||
|
})
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec.start({}, (err, stream) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
}
|
||||||
|
if (stream) {
|
||||||
|
stream.setEncoding('utf-8')
|
||||||
|
stream.on('data', str => {
|
||||||
|
if (verbose && str.trim() !== '') {
|
||||||
|
console.log(`├─ ${str.trim().replace(/\n/gi, '\n├─ ')}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stream.on('end', resolve)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const sleep = function(milliseconds: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, milliseconds))
|
||||||
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
const userId = 'janedoe' + Date.now()
|
|
||||||
|
|
||||||
describe('Social app setup', function() {
|
|
||||||
before(function() {
|
|
||||||
cy.nextcloudCreateUser(userId, 'p4ssw0rd')
|
|
||||||
cy.login(userId, 'p4ssw0rd')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('See the welcome message', function() {
|
|
||||||
cy.visit('/apps/social/')
|
|
||||||
cy.get('.social__welcome').should('contain', 'Nextcloud becomes part of the federated social networks!')
|
|
||||||
cy.get('.social__welcome').find('.icon-close').click()
|
|
||||||
cy.get('.social__welcome').should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('See the home section in the sidebar', function() {
|
|
||||||
cy.get('.app-navigation').contains('Home').click()
|
|
||||||
cy.get('.emptycontent').should('be.visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('See the empty content illustration', function() {
|
|
||||||
cy.get('.app-navigation').contains('Direct messages').click()
|
|
||||||
cy.get('.emptycontent').should('be.visible').contains('No direct messages found')
|
|
||||||
cy.get('.app-navigation').contains('Profile').click()
|
|
||||||
cy.get('.emptycontent').should('be.visible').contains('You have not tooted yet')
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
|
@ -1,113 +0,0 @@
|
||||||
/*
|
|
||||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
|
||||||
*
|
|
||||||
* @author Julius Härtl <jus@bitgrid.net>
|
|
||||||
*
|
|
||||||
* @license GNU AGPL version 3 or any later version
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const userId = 'janedoe' + Date.now()
|
|
||||||
|
|
||||||
describe('Create posts', function() {
|
|
||||||
|
|
||||||
before(function() {
|
|
||||||
// ensure that the admin account is initialized for social
|
|
||||||
cy.login('admin', 'admin', '/apps/social/')
|
|
||||||
|
|
||||||
cy.nextcloudCreateUser(userId, 'p4ssw0rd')
|
|
||||||
cy.logout()
|
|
||||||
|
|
||||||
cy.login(userId, 'p4ssw0rd', '/apps/social/')
|
|
||||||
cy.get('.app-content').should('be.visible')
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(function() {
|
|
||||||
cy.screenshot()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('See the empty content illustration', function() {
|
|
||||||
cy.get('.emptycontent').should('be.visible').contains('No posts found')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Write a post to followers', function() {
|
|
||||||
cy.visit('/apps/social/')
|
|
||||||
cy.server()
|
|
||||||
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
|
|
||||||
cy.get('.new-post button[type=submit]')
|
|
||||||
.should('be.disabled')
|
|
||||||
cy.get('.new-post').find('[contenteditable]').type('Hello world')
|
|
||||||
cy.get('.new-post button[type=submit]')
|
|
||||||
.should('not.be.disabled')
|
|
||||||
cy.get('.new-post button[type=submit]')
|
|
||||||
.click()
|
|
||||||
cy.wait('@postMessage')
|
|
||||||
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('No longer see the empty content illustration', function() {
|
|
||||||
cy.get('.emptycontent').should('not.exist')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Write a post to followers with shift enter', function() {
|
|
||||||
cy.visit('/apps/social/')
|
|
||||||
cy.server()
|
|
||||||
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
|
|
||||||
cy.get('.new-post').find('[contenteditable]').type('Hello world 2{shift}{enter}')
|
|
||||||
cy.wait('@postMessage')
|
|
||||||
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world 2')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Write a post to @admin', function() {
|
|
||||||
cy.visit('/apps/social/')
|
|
||||||
cy.server()
|
|
||||||
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
|
|
||||||
cy.route('GET', '/index.php/apps/social/api/v1/global/accounts/search')
|
|
||||||
cy.get('.new-post').find('[contenteditable]').type('@adm', { delay: 500 })
|
|
||||||
cy.get('.tribute-container').should('be.visible')
|
|
||||||
cy.get('.tribute-container ul li:first').contains('admin')
|
|
||||||
cy.get('.new-post').find('[contenteditable]').type('{enter} Hello there', { delay: 100, force: true })
|
|
||||||
cy.get('.new-post button[type=submit]')
|
|
||||||
.click()
|
|
||||||
cy.wait('@postMessage')
|
|
||||||
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', '@admin')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Opens the menu and shows that followers is selected by default', function() {
|
|
||||||
cy.visit('/apps/social/')
|
|
||||||
cy.server()
|
|
||||||
cy.route('POST', '/index.php/apps/social/api/v1/post').as('postMessage')
|
|
||||||
cy.route('GET', '/index.php/apps/social/api/v1/global/accounts/search')
|
|
||||||
cy.get('.new-post').find('[contenteditable]').click({ force: true }).type('@adm{enter} Hello world', { delay: 500, force: true })
|
|
||||||
cy.wait(500)
|
|
||||||
cy.get('.new-post button[type=submit]').should('not.be.disabled')
|
|
||||||
const visibilityButton = cy.get('.new-post .options > div > button')
|
|
||||||
visibilityButton.should('have.class', 'icon-contacts-dark')
|
|
||||||
|
|
||||||
visibilityButton.click()
|
|
||||||
cy.get('.new-post-form .popovermenu').should('be.visible')
|
|
||||||
cy.get('.new-post-form .popovermenu .active').contains('Followers')
|
|
||||||
visibilityButton.click()
|
|
||||||
cy.get('.new-post-form .popovermenu').should('not.be.visible')
|
|
||||||
|
|
||||||
cy.get('.new-post button[type=submit]')
|
|
||||||
.click()
|
|
||||||
cy.wait('@postMessage')
|
|
||||||
cy.get('.social__timeline div.timeline-entry:first-child').should('contain', 'Hello world').should('contain', '@admin')
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @author Julius Härtl <jus@bitgrid.net>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { User } from "@nextcloud/cypress"
|
||||||
|
import { randHash } from "../utils"
|
||||||
|
|
||||||
|
const alice = new User(`alice_${randHash()}`)
|
||||||
|
|
||||||
|
describe('Create posts', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.createUser(alice)
|
||||||
|
cy.login(alice)
|
||||||
|
cy.visit('/apps/social')
|
||||||
|
cy.createRandomUser()
|
||||||
|
.then((user) => {
|
||||||
|
cy.login(user)
|
||||||
|
cy.visit('/apps/social')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('See the empty content illustration', () => {
|
||||||
|
cy.get('.social__welcome').find('.icon-close').click()
|
||||||
|
cy.get('.app-social .empty-content').should('be.visible').contains('No posts found')
|
||||||
|
cy.reload()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Write a post to followers', () => {
|
||||||
|
cy.intercept({ times: 1, method: 'POST', url: '/index.php/apps/social/api/v1/statuses' }).as('postMessage')
|
||||||
|
cy.get('.new-post button[type=submit]').should('be.disabled')
|
||||||
|
cy.get('.new-post').find('[contenteditable]').type('Hello world')
|
||||||
|
cy.get('.new-post button[type=submit]').should('not.be.disabled')
|
||||||
|
cy.get('.new-post button[type=submit]').click()
|
||||||
|
cy.wait('@postMessage')
|
||||||
|
cy.get('.social__timeline .timeline-entry:first-child').should('contain', 'Hello world')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('No longer see the empty content illustration', () => {
|
||||||
|
cy.get('.app-social .empty-content').should('not.exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Write a post to followers with ctrl+enter', () => {
|
||||||
|
cy.intercept({ times: 1, method: 'POST', url: '/index.php/apps/social/api/v1/statuses' }).as('postMessage')
|
||||||
|
cy.get('.new-post').find('[contenteditable]').type('Hello world 2{ctrl}{enter}')
|
||||||
|
cy.wait('@postMessage')
|
||||||
|
cy.get('.social__timeline .timeline-entry:first-child').should('contain', 'Hello world 2')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Write a post to @alice', () => {
|
||||||
|
cy.intercept({ times: 1, method: 'POST', url: '/index.php/apps/social/api/v1/statuses' }).as('postMessage')
|
||||||
|
cy.intercept({ times: 1, method: 'GET', url: '/index.php/apps/social/api/v1/global/accounts/search' })
|
||||||
|
cy.get('.new-post').find('[contenteditable]').type(`@${alice.userId}`)
|
||||||
|
cy.get('.tribute-container').should('be.visible')
|
||||||
|
cy.get('.tribute-container ul li:first').contains(alice.userId)
|
||||||
|
cy.get('.new-post').find('[contenteditable]').type('{enter} Hello there')
|
||||||
|
cy.get('.new-post button[type=submit]').click()
|
||||||
|
cy.wait('@postMessage')
|
||||||
|
cy.get('.social__timeline .timeline-entry:first-child').should('contain', `@${alice.userId}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('Opens the menu and shows that followers is selected by default', () => {
|
||||||
|
cy.intercept({ times: 1, method: 'POST', url: '/index.php/apps/social/api/v1/statuses' }).as('postMessage')
|
||||||
|
cy.intercept({ times: 1, method: 'GET', url: '/index.php/apps/social/api/v1/global/accounts/search' })
|
||||||
|
cy.get('.new-post').find('[contenteditable]').type(`@${alice.userId}{enter} Hello world`)
|
||||||
|
cy.wait(500)
|
||||||
|
cy.get('.new-post button[type=submit]').should('not.be.disabled')
|
||||||
|
const visibilityButton = cy.get('.new-post .options > .action-item > div > button')
|
||||||
|
visibilityButton.find('.material-design-icon').should('have.class', 'account-multiple-icon')
|
||||||
|
|
||||||
|
visibilityButton.click()
|
||||||
|
cy.get('.v-popper__popper ').should('be.visible')
|
||||||
|
cy.get('.v-popper__popper .selected-visibility').contains('Visible to followers only')
|
||||||
|
visibilityButton.click()
|
||||||
|
cy.get('.v-popper__popper ').should('not.be.visible')
|
||||||
|
|
||||||
|
cy.get('.new-post button[type=submit]').click()
|
||||||
|
cy.wait('@postMessage')
|
||||||
|
cy.get('.social__timeline .timeline-entry:first-child').should('contain', 'Hello world').should('contain', `@${alice.userId}`)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -0,0 +1,58 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2023 Louis Chmn <louis@chmn.me>
|
||||||
|
*
|
||||||
|
* @author Louis Chmn <louis@chmn.me>
|
||||||
|
*
|
||||||
|
* @license AGPL-3.0-or-later
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
describe('Social app setup', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.createRandomUser()
|
||||||
|
.then((user) => {
|
||||||
|
cy.login(user)
|
||||||
|
cy.visit('/apps/social')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('See the welcome message', () => {
|
||||||
|
cy.get('.social__welcome').should('contain', 'Nextcloud becomes part of the federated social networks!')
|
||||||
|
cy.get('.social__welcome').find('.icon-close').click()
|
||||||
|
cy.get('.social__welcome').should('not.exist')
|
||||||
|
cy.reload()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('See the home section in the sidebar', () => {
|
||||||
|
cy.get('.app-navigation').contains('Home').click()
|
||||||
|
cy.get('.app-social .empty-content').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('See the empty content illustration of Direct messages', () => {
|
||||||
|
cy.get('.app-navigation').contains('Direct messages').click()
|
||||||
|
cy.get('.app-social .empty-content').should('be.visible').contains('No direct messages found')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('See the empty content illustration of Profile', () => {
|
||||||
|
cy.intercept({ times: 1, method: 'GET', url: '**/apps/social/api/v1/accounts/*/statuses?*' }).as('accountStatuses')
|
||||||
|
|
||||||
|
cy.get('.app-navigation').contains('Profile').click()
|
||||||
|
cy.wait("@accountStatuses")
|
||||||
|
|
||||||
|
cy.get('.app-social .empty-content__title').scrollIntoView()
|
||||||
|
cy.get('.app-social .empty-content').should('be.visible').contains('You have not tooted yet')
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,12 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
echo "APP_NAME: $APP_NAME"
|
|
||||||
echo "BRANCH: $BRANCH"
|
|
||||||
|
|
||||||
chown -R www-data:www-data /var/www/html/data
|
|
||||||
|
|
||||||
su www-data -c "
|
|
||||||
php occ config:system:set force_language --value en
|
|
||||||
php occ app:enable $APP_NAME
|
|
||||||
php occ app:list
|
|
||||||
"
|
|
|
@ -1,19 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# RUN THIS SCRIPT FROM THE ROOT FOLDER OF YOUR APP
|
|
||||||
APP_NAME=${PWD##*/}
|
|
||||||
CYPRESS_baseUrl=http://127.0.0.1:8082/index.php
|
|
||||||
|
|
||||||
if [[ $APP_NAME == "cypress" ]]
|
|
||||||
then
|
|
||||||
echo "Please run this app from your app root folder."
|
|
||||||
else
|
|
||||||
echo "Launching docker server for the $APP_NAME app"
|
|
||||||
cd cypress
|
|
||||||
docker-compose pull
|
|
||||||
docker-compose up -d --force-recreate
|
|
||||||
npm run wait-on $CYPRESS_baseUrl
|
|
||||||
echo "Nextcloud successfully installed"
|
|
||||||
docker-compose exec --env APP_NAME=$APP_NAME -T nextcloud bash /initserver.sh
|
|
||||||
docker-compose exec -u www-data -T nextcloud php ./occ social:reset -n
|
|
||||||
echo "Nextcloud successfully configured"
|
|
||||||
fi
|
|
|
@ -1,12 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# RUN THIS SCRIPT FROM THE ROOT FOLDER OF YOUR APP
|
|
||||||
appname=${PWD##*/}
|
|
||||||
|
|
||||||
if [[ $appname == "cypress" ]]
|
|
||||||
then
|
|
||||||
echo "Please run this app from your app root folder."
|
|
||||||
else
|
|
||||||
echo "Killing server for the $appname app"
|
|
||||||
cd cypress
|
|
||||||
docker-compose down
|
|
||||||
fi
|
|
|
@ -21,62 +21,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
|
import { addCommands, User } from '@nextcloud/cypress'
|
||||||
|
import { basename } from 'path'
|
||||||
|
|
||||||
|
// Add custom commands
|
||||||
|
import 'cypress-wait-until'
|
||||||
|
addCommands()
|
||||||
|
|
||||||
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
|
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
|
||||||
Cypress.env('baseUrl', url)
|
Cypress.env('baseUrl', url)
|
||||||
|
|
||||||
Cypress.Commands.add('login', (user, password, route = '/apps/files') => {
|
|
||||||
Cypress.Cookies.defaults({
|
|
||||||
preserve: /^(oc|nc)/,
|
|
||||||
})
|
|
||||||
cy.visit(route)
|
|
||||||
cy.get('input[name=user]').type(user)
|
|
||||||
cy.get('input[name=password]').type(password)
|
|
||||||
cy.get('form[name=login] [type=submit]').click()
|
|
||||||
cy.url().should('include', route)
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('logout', () => {
|
|
||||||
cy.getCookies()
|
|
||||||
.then(cookies => {
|
|
||||||
if (cookies.length === 0) {
|
|
||||||
cy.log('Not logged, skipping logout...')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return cy.get('body')
|
|
||||||
.then($body => {
|
|
||||||
const $settingsButton = $body.find('#settings #expand')
|
|
||||||
if ($settingsButton.length === 0) {
|
|
||||||
cy.log('Not logged in.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
$settingsButton.click()
|
|
||||||
cy.contains('Log out').click()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('nextcloudCreateUser', (user, password) => {
|
|
||||||
cy.request({
|
|
||||||
method: 'POST',
|
|
||||||
url: `${Cypress.env('baseUrl')}/ocs/v1.php/cloud/users?format=json`,
|
|
||||||
form: true,
|
|
||||||
body: {
|
|
||||||
userid: user,
|
|
||||||
password,
|
|
||||||
},
|
|
||||||
auth: { user: 'admin', pass: 'admin' },
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
'OCS-ApiRequest': 'true',
|
|
||||||
Authorization: `Basic ${Buffer.from('admin:admin').toString('base64')}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
cy.clearCookies()
|
|
||||||
})
|
|
||||||
|
|
||||||
Cypress.Commands.add('uploadFile', (fileName, mimeType, path = '') => {
|
Cypress.Commands.add('uploadFile', (fileName, mimeType, path = '') => {
|
||||||
// get fixture
|
// get fixture
|
||||||
return cy.fixture(fileName, 'base64').then(file => {
|
return cy.fixture(fileName, 'base64').then(file => {
|
|
@ -14,4 +14,4 @@
|
||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
// Import commands.js using ES2015 syntax:
|
||||||
import './commands.js'
|
import './commands.ts'
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"include": ["./**/*.ts"],
|
||||||
|
}
|
|
@ -20,7 +20,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const getSearchParams = url => {
|
export function getSearchParams (url) {
|
||||||
return url
|
return url
|
||||||
.split(/[?&]/)
|
.split(/[?&]/)
|
||||||
.reduce((acc, cur) => {
|
.reduce((acc, cur) => {
|
||||||
|
@ -30,6 +30,6 @@ const getSearchParams = url => {
|
||||||
}, {})
|
}, {})
|
||||||
}
|
}
|
||||||
|
|
||||||
const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10)
|
export function randHash() {
|
||||||
|
return Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10)
|
||||||
export default { getSearchParams, randHash }
|
}
|
|
@ -700,8 +700,8 @@ class CoreRequestBuilder {
|
||||||
$qb->orderBy($pf . '.published_time', 'desc');
|
$qb->orderBy($pf . '.published_time', 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param IQueryBuilder $qb
|
* @param IQueryBuilder $qb
|
||||||
|
|
|
@ -207,13 +207,13 @@ class RequestQueueRequest extends RequestQueueRequestBuilder {
|
||||||
$qb->executeStatement();
|
$qb->executeStatement();
|
||||||
}
|
}
|
||||||
|
|
||||||
// public function moveAccount(string $actorId, string $newId, string $instance): void {
|
// public function moveAccount(string $actorId, string $newId, string $instance): void {
|
||||||
// $qb = $this->getRequestQueueUpdateSql();
|
// $qb = $this->getRequestQueueUpdateSql();
|
||||||
// $qb->set('author', $qb->createNamedParameter($newId))
|
// $qb->set('author', $qb->createNamedParameter($newId))
|
||||||
// ->set('author_prim', $qb->createNamedParameter($qb->prim($newId)))
|
// ->set('author_prim', $qb->createNamedParameter($qb->prim($newId)))
|
||||||
// ->set('instance', $qb->createNamedParameter($instance));
|
// ->set('instance', $qb->createNamedParameter($instance));
|
||||||
// $qb->limitToDBField('author_prim', $qb->prim($actorId));
|
// $qb->limitToDBField('author_prim', $qb->prim($actorId));
|
||||||
//
|
//
|
||||||
// $qb->execute();
|
// $qb->execute();
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,7 +142,7 @@ class StreamRequestBuilder extends CoreRequestBuilder {
|
||||||
protected function timelineHomeLinkCacheActor(
|
protected function timelineHomeLinkCacheActor(
|
||||||
SocialQueryBuilder $qb, string $alias = 'ca', string $aliasFollow = 'f'
|
SocialQueryBuilder $qb, string $alias = 'ca', string $aliasFollow = 'f'
|
||||||
) {
|
) {
|
||||||
$qb->linkToCacheActors($alias, 'attributed_to_prim');
|
$qb->linkToCacheActors($alias, 's.attributed_to_prim');
|
||||||
|
|
||||||
$expr = $qb->expr();
|
$expr = $qb->expr();
|
||||||
$orX = $expr->orX();
|
$orX = $expr->orX();
|
||||||
|
|
|
@ -279,24 +279,24 @@ class SocialClient implements IQueryRow, JsonSerializable {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @return string
|
// * @return string
|
||||||
// */
|
// */
|
||||||
// public function getAuthRedirectUri(): string {
|
// public function getAuthRedirectUri(): string {
|
||||||
// return $this->authRedirectUri;
|
// return $this->authRedirectUri;
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @param string $authRedirectUri
|
// * @param string $authRedirectUri
|
||||||
// *
|
// *
|
||||||
// * @return SocialClient
|
// * @return SocialClient
|
||||||
// */
|
// */
|
||||||
// public function setAuthRedirectUri(string $authRedirectUri): self {
|
// public function setAuthRedirectUri(string $authRedirectUri): self {
|
||||||
// $this->authRedirectUri = $authRedirectUri;
|
// $this->authRedirectUri = $authRedirectUri;
|
||||||
//
|
//
|
||||||
// return $this;
|
// return $this;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -337,23 +337,23 @@ class SocialClient implements IQueryRow, JsonSerializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// /**
|
// /**
|
||||||
// * @return array
|
// * @return array
|
||||||
// */
|
// */
|
||||||
// public function getTokenScopes(): array {
|
// public function getTokenScopes(): array {
|
||||||
// return $this->tokenScopes;
|
// return $this->tokenScopes;
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @param array $scopes
|
// * @param array $scopes
|
||||||
// *
|
// *
|
||||||
// * @return SocialClient
|
// * @return SocialClient
|
||||||
// */
|
// */
|
||||||
// public function setTokenScopes(array $scopes): self {
|
// public function setTokenScopes(array $scopes): self {
|
||||||
// $this->tokenScopes = $scopes;
|
// $this->tokenScopes = $scopes;
|
||||||
//
|
//
|
||||||
// return $this;
|
// return $this;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -30,6 +30,7 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace OCA\Social\Service;
|
namespace OCA\Social\Service;
|
||||||
|
|
||||||
|
use CurlHandle;
|
||||||
use Exception;
|
use Exception;
|
||||||
use OCA\Social\AP;
|
use OCA\Social\AP;
|
||||||
use OCA\Social\Exceptions\HostMetaException;
|
use OCA\Social\Exceptions\HostMetaException;
|
||||||
|
@ -105,9 +106,9 @@ class CurlService {
|
||||||
$account = $this->withoutBeginAt($account);
|
$account = $this->withoutBeginAt($account);
|
||||||
|
|
||||||
// we consider an account is like an email
|
// we consider an account is like an email
|
||||||
if (!filter_var($account, FILTER_VALIDATE_EMAIL)) {
|
// if (!filter_var($account, FILTER_VALIDATE_EMAIL)) {
|
||||||
throw new InvalidResourceException('account format is not valid');
|
// throw new InvalidResourceException('account format is not valid');
|
||||||
}
|
// }
|
||||||
|
|
||||||
$exploded = explode('@', $account);
|
$exploded = explode('@', $account);
|
||||||
|
|
||||||
|
@ -386,9 +387,9 @@ class CurlService {
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
*
|
*
|
||||||
* @return resource
|
* @return CurlHandle
|
||||||
*/
|
*/
|
||||||
private function initRequest(Request $request) {
|
private function initRequest(Request $request): CurlHandle {
|
||||||
$curl = $this->generateCurlRequest($request);
|
$curl = $this->generateCurlRequest($request);
|
||||||
$this->initRequestHeaders($curl, $request);
|
$this->initRequestHeaders($curl, $request);
|
||||||
|
|
||||||
|
@ -404,7 +405,8 @@ class CurlService {
|
||||||
|
|
||||||
curl_setopt($curl, CURLOPT_BUFFERSIZE, 128);
|
curl_setopt($curl, CURLOPT_BUFFERSIZE, 128);
|
||||||
curl_setopt($curl, CURLOPT_NOPROGRESS, false);
|
curl_setopt($curl, CURLOPT_NOPROGRESS, false);
|
||||||
curl_setopt($curl, CURLOPT_PROGRESSFUNCTION,
|
curl_setopt(
|
||||||
|
$curl, CURLOPT_PROGRESSFUNCTION,
|
||||||
/**
|
/**
|
||||||
* @param $downloadSize
|
* @param $downloadSize
|
||||||
* @param int $downloaded
|
* @param int $downloaded
|
||||||
|
@ -430,8 +432,10 @@ class CurlService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return CurlHandle
|
||||||
*/
|
*/
|
||||||
private function generateCurlRequest(Request $request) {
|
private function generateCurlRequest(Request $request): CurlHandle {
|
||||||
$url = $request->getUsedProtocol() . '://' . $request->getHost() . $request->getParsedUrl();
|
$url = $request->getUsedProtocol() . '://' . $request->getHost() . $request->getParsedUrl();
|
||||||
if ($request->getType() !== Request::TYPE_GET) {
|
if ($request->getType() !== Request::TYPE_GET) {
|
||||||
$curl = curl_init($url);
|
$curl = curl_init($url);
|
||||||
|
@ -467,10 +471,10 @@ class CurlService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $curl
|
* @param CurlHandle $curl
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
*/
|
*/
|
||||||
private function initRequestHeaders($curl, Request $request) {
|
private function initRequestHeaders(CurlHandle $curl, Request $request): void {
|
||||||
$headers = [];
|
$headers = [];
|
||||||
foreach ($request->getHeaders() as $name => $value) {
|
foreach ($request->getHeaders() as $name => $value) {
|
||||||
$headers[] = $name . ': ' . $value;
|
$headers[] = $name . ': ' . $value;
|
||||||
|
@ -481,14 +485,14 @@ class CurlService {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $curl
|
* @param CurlHandle $curl
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
*
|
*
|
||||||
* @throws RequestContentException
|
* @throws RequestContentException
|
||||||
* @throws RequestServerException
|
* @throws RequestServerException
|
||||||
* @throws RequestNetworkException
|
* @throws RequestNetworkException
|
||||||
*/
|
*/
|
||||||
private function parseRequestResult($curl, Request $request): void {
|
private function parseRequestResult(CurlHandle $curl, Request $request): void {
|
||||||
$this->parseRequestResultCurl($curl, $request);
|
$this->parseRequestResultCurl($curl, $request);
|
||||||
|
|
||||||
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||||
|
@ -499,12 +503,12 @@ class CurlService {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param resource $curl
|
* @param CurlHandle $curl
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
*
|
*
|
||||||
* @throws RequestNetworkException
|
* @throws RequestNetworkException
|
||||||
*/
|
*/
|
||||||
private function parseRequestResultCurl($curl, Request $request) {
|
private function parseRequestResultCurl(CurlHandle $curl, Request $request): void {
|
||||||
$errno = curl_errno($curl);
|
$errno = curl_errno($curl);
|
||||||
if ($errno > 0) {
|
if ($errno > 0) {
|
||||||
throw new RequestNetworkException(
|
throw new RequestNetworkException(
|
||||||
|
|
|
@ -197,83 +197,83 @@ class FediverseService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @param string $address
|
// * @param string $address
|
||||||
// *
|
// *
|
||||||
// * @throws Exception
|
// * @throws Exception
|
||||||
// */
|
// */
|
||||||
// public function blockAddress(string $address) {
|
// public function blockAddress(string $address) {
|
||||||
// if ($this->isBlocked($address)) {
|
// if ($this->isBlocked($address)) {
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// if ($this->isAllowed($address)) {
|
// if ($this->isAllowed($address)) {
|
||||||
// throw new Exception($address . ' is already in the whitelist');
|
// throw new Exception($address . ' is already in the whitelist');
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// $blackList = $this->getBlockedAddresses();
|
// $blackList = $this->getBlockedAddresses();
|
||||||
// array_push($blackList, $address);
|
// array_push($blackList, $address);
|
||||||
//
|
//
|
||||||
// $this->configService->setAppValue(ConfigService::SOCIAL_BLACKLIST, json_encode($blackList));
|
// $this->configService->setAppValue(ConfigService::SOCIAL_BLACKLIST, json_encode($blackList));
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @return array
|
// * @return array
|
||||||
// */
|
// */
|
||||||
// public function getBlockedAddresses(): array {
|
// public function getBlockedAddresses(): array {
|
||||||
// return json_decode($this->configService->getAppValue(ConfigService::SOCIAL_BLACKLIST));
|
// return json_decode($this->configService->getAppValue(ConfigService::SOCIAL_BLACKLIST));
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @param string $address
|
// * @param string $address
|
||||||
// *
|
// *
|
||||||
// * @return bool
|
// * @return bool
|
||||||
// */
|
// */
|
||||||
// public function isBlocked(string $address): bool {
|
// public function isBlocked(string $address): bool {
|
||||||
// return (in_array('ALL', $this->getBlockedAddresses())
|
// return (in_array('ALL', $this->getBlockedAddresses())
|
||||||
// || in_array($address, $this->getBlockedAddresses()));
|
// || in_array($address, $this->getBlockedAddresses()));
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @param string $address
|
// * @param string $address
|
||||||
// *
|
// *
|
||||||
// * @return void
|
// * @return void
|
||||||
// * @throws Exception
|
// * @throws Exception
|
||||||
// */
|
// */
|
||||||
// public function allowAddress(string $address) {
|
// public function allowAddress(string $address) {
|
||||||
// if ($this->isAllowed($address)) {
|
// if ($this->isAllowed($address)) {
|
||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// if ($this->isBlocked($address)) {
|
// if ($this->isBlocked($address)) {
|
||||||
// throw new Exception($address . ' is already in the blacklist');
|
// throw new Exception($address . ' is already in the blacklist');
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// $whiteList = $this->getAllowedAddresses();
|
// $whiteList = $this->getAllowedAddresses();
|
||||||
// array_push($whiteList, $address);
|
// array_push($whiteList, $address);
|
||||||
//
|
//
|
||||||
// $this->configService->setAppValue(ConfigService::SOCIAL_WHITELIST, json_encode($whiteList));
|
// $this->configService->setAppValue(ConfigService::SOCIAL_WHITELIST, json_encode($whiteList));
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @return array
|
// * @return array
|
||||||
// */
|
// */
|
||||||
// public function getAllowedAddresses(): array {
|
// public function getAllowedAddresses(): array {
|
||||||
// return json_decode($this->configService->getAppValue(ConfigService::SOCIAL_WHITELIST));
|
// return json_decode($this->configService->getAppValue(ConfigService::SOCIAL_WHITELIST));
|
||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @param string $address
|
// * @param string $address
|
||||||
// *
|
// *
|
||||||
// * @return bool
|
// * @return bool
|
||||||
// */
|
// */
|
||||||
// public function isAllowed(string $address): bool {
|
// public function isAllowed(string $address): bool {
|
||||||
// return (in_array('ALL', $this->getAllowedAddresses())
|
// return (in_array('ALL', $this->getAllowedAddresses())
|
||||||
// || in_array($address, $this->getAllowedAddresses()));
|
// || in_array($address, $this->getAllowedAddresses()));
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,16 +129,16 @@ class PushService {
|
||||||
// $pushHelper->toCallback($callback);
|
// $pushHelper->toCallback($callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @param $userId
|
// * @param $userId
|
||||||
// *
|
// *
|
||||||
// * @return IPushWrapper
|
// * @return IPushWrapper
|
||||||
// * @throws PushInstallException
|
// * @throws PushInstallException
|
||||||
// */
|
// */
|
||||||
// public function testOnAccount(string $userId): IPushWrapper {
|
// public function testOnAccount(string $userId): IPushWrapper {
|
||||||
//// $pushHelper = $this->pushManager->getPushHelper();
|
//// $pushHelper = $this->pushManager->getPushHelper();
|
||||||
////
|
////
|
||||||
//// return $pushHelper->test($userId);
|
//// return $pushHelper->test($userId);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -467,27 +467,27 @@ class ExtendedQueryBuilder extends QueryBuilder implements IExtendedQueryBuilder
|
||||||
return $expr->$comp($field, $qb->createNamedParameter('%"' . $value . '"%'));
|
return $expr->$comp($field, $qb->createNamedParameter('%"' . $value . '"%'));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// /**
|
// /**
|
||||||
// * @param IQueryBuilder $qb
|
// * @param IQueryBuilder $qb
|
||||||
// * @param string $field
|
// * @param string $field
|
||||||
// * @param string $value
|
// * @param string $value
|
||||||
// *
|
// *
|
||||||
// * @return string
|
// * @return string
|
||||||
// */
|
// */
|
||||||
// public function exprValueNotWithinJsonFormat(IQueryBuilder $qb, string $field, string $value): string {
|
// public function exprValueNotWithinJsonFormat(IQueryBuilder $qb, string $field, string $value): string {
|
||||||
// $dbConn = $this->getConnection();
|
// $dbConn = $this->getConnection();
|
||||||
// $expr = $qb->expr();
|
// $expr = $qb->expr();
|
||||||
// $func = $qb->func();
|
// $func = $qb->func();
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// return $expr->notLike(
|
// return $expr->notLike(
|
||||||
// $func->lower($field),
|
// $func->lower($field),
|
||||||
// $qb->createNamedParameter(
|
// $qb->createNamedParameter(
|
||||||
// '%"' . $func->lower($dbConn->escapeLikeParameter($value)) . '"%'
|
// '%"' . $func->lower($dbConn->escapeLikeParameter($value)) . '"%'
|
||||||
// )
|
// )
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -38,6 +38,7 @@ use OCP\AppFramework\Http;
|
||||||
use OCP\Http\WellKnown\IHandler;
|
use OCP\Http\WellKnown\IHandler;
|
||||||
use OCP\Http\WellKnown\IRequestContext;
|
use OCP\Http\WellKnown\IRequestContext;
|
||||||
use OCP\Http\WellKnown\IResponse;
|
use OCP\Http\WellKnown\IResponse;
|
||||||
|
use OCP\IRequest;
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
|
|
||||||
class WebfingerHandler implements IHandler {
|
class WebfingerHandler implements IHandler {
|
||||||
|
@ -111,7 +112,7 @@ class WebfingerHandler implements IHandler {
|
||||||
* @return IResponse|null
|
* @return IResponse|null
|
||||||
*/
|
*/
|
||||||
public function handleWebfinger(IRequestContext $context, ?IResponse $previousResponse): ?IResponse {
|
public function handleWebfinger(IRequestContext $context, ?IResponse $previousResponse): ?IResponse {
|
||||||
$subject = $context->getHttpRequest()->getParam('resource') ?? '';
|
$subject = $this->getSubjectFromRequest($context->getHttpRequest());
|
||||||
if (str_starts_with($subject, 'acct:')) {
|
if (str_starts_with($subject, 'acct:')) {
|
||||||
$subject = substr($subject, 5);
|
$subject = substr($subject, 5);
|
||||||
}
|
}
|
||||||
|
@ -222,4 +223,17 @@ class WebfingerHandler implements IHandler {
|
||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getSubjectFromRequest(IRequest $request): string {
|
||||||
|
$subject = $request->getParam('resource') ?? '';
|
||||||
|
if ($subject !== '') {
|
||||||
|
return $subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
// work around to extract resource:
|
||||||
|
// on some setup (i.e. tests) the data are not available from IRequest
|
||||||
|
parse_str(parse_url($request->getRequestUri(), PHP_URL_QUERY), $query);
|
||||||
|
|
||||||
|
return $query['resource'] ?? '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Plik diff jest za duży
Load Diff
13
package.json
13
package.json
|
@ -27,9 +27,11 @@
|
||||||
"stylelint:fix": "stylelint src --fix",
|
"stylelint:fix": "stylelint src --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:coverage": "jest --coverage",
|
"test:coverage": "jest --coverage",
|
||||||
"cypress": "./cypress/start.sh; cypress run; ./cypress/stop.sh",
|
"cypress": "npm run cypress:component && npm run cypress:e2e",
|
||||||
"cypress:gui": "./cypress/start.sh; cypress open; ./cypress/stop.sh",
|
"cypress:component": "cypress run --component",
|
||||||
"wait-on": "wait-on -i 500 -t 300000"
|
"cypress:e2e": "cypress run --e2e",
|
||||||
|
"cypress:gui": "cypress open",
|
||||||
|
"precypress:update-snapshots": "TESTING=true npm run dev"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nextcloud/auth": "^2.0.0",
|
"@nextcloud/auth": "^2.0.0",
|
||||||
|
@ -82,7 +84,10 @@
|
||||||
"@nextcloud/stylelint-config": "^2.3.0",
|
"@nextcloud/stylelint-config": "^2.3.0",
|
||||||
"@nextcloud/webpack-vue-config": "^5.5.1",
|
"@nextcloud/webpack-vue-config": "^5.5.1",
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^11.0.0",
|
||||||
"cypress": "^12.14.0",
|
"@nextcloud/cypress": "^1.0.0-beta.2",
|
||||||
|
"cypress-visual-regression": "^2.1.1",
|
||||||
|
"cypress-wait-until": "^1.7.2",
|
||||||
|
"dockerode": "^3.3.5",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-serializer-vue": "^3.1.0",
|
"jest-serializer-vue": "^3.1.0",
|
||||||
"vue-template-compiler": "^2.7.14",
|
"vue-template-compiler": "^2.7.14",
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<errorLevel type="suppress">
|
<errorLevel type="suppress">
|
||||||
<referencedClass name="OC" />
|
<referencedClass name="OC" />
|
||||||
<referencedClass name="OC\DB\Connection" />
|
<referencedClass name="OC\DB\Connection" />
|
||||||
|
<referencedClass name="Symfony\Component\EventDispatcher\GenericEvent" />
|
||||||
</errorLevel>
|
</errorLevel>
|
||||||
</UndefinedClass>
|
</UndefinedClass>
|
||||||
<UndefinedDocblockClass>
|
<UndefinedDocblockClass>
|
||||||
|
@ -35,6 +36,7 @@
|
||||||
<referencedClass name="Doctrine\DBAL\Driver\Statement" />
|
<referencedClass name="Doctrine\DBAL\Driver\Statement" />
|
||||||
<referencedClass name="Doctrine\DBAL\Schema\Table" />
|
<referencedClass name="Doctrine\DBAL\Schema\Table" />
|
||||||
<referencedClass name="OC\DB\Connection" />
|
<referencedClass name="OC\DB\Connection" />
|
||||||
|
<referencedClass name="Symfony\Component\EventDispatcher\EventDispatcherInterface" />
|
||||||
</errorLevel>
|
</errorLevel>
|
||||||
</UndefinedDocblockClass>
|
</UndefinedDocblockClass>
|
||||||
</issueHandlers>
|
</issueHandlers>
|
||||||
|
|
|
@ -1,89 +1,72 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<files psalm-version="5.4.0@62db5d4f6a7ae0a20f7cc5a4952d730272fc0863">
|
<files psalm-version="5.13.1@086b94371304750d1c673315321a55d15fc59015">
|
||||||
<file src="lib/Model/ActivityPub/ACore.php">
|
<file src="lib/Model/ActivityPub/ACore.php">
|
||||||
<InvalidArgument occurrences="1">
|
<InvalidArgument>
|
||||||
<code>['a', 'p', 'span', 'br']</code>
|
<code><![CDATA[['a', 'p', 'span', 'br']]]></code>
|
||||||
</InvalidArgument>
|
</InvalidArgument>
|
||||||
<InvalidClass occurrences="1">
|
<InvalidClass>
|
||||||
<code>Acore</code>
|
<code>Acore</code>
|
||||||
</InvalidClass>
|
</InvalidClass>
|
||||||
<InvalidNullableReturnType occurrences="1">
|
<InvalidNullableReturnType>
|
||||||
<code>ACore</code>
|
<code>ACore</code>
|
||||||
<code>ACore</code>
|
<code>ACore</code>
|
||||||
</InvalidNullableReturnType>
|
</InvalidNullableReturnType>
|
||||||
<InvalidPropertyAssignmentValue occurrences="1">
|
<InvalidPropertyAssignmentValue>
|
||||||
<code>$parent</code>
|
<code>$parent</code>
|
||||||
</InvalidPropertyAssignmentValue>
|
</InvalidPropertyAssignmentValue>
|
||||||
<NullableReturnStatement occurrences="1">
|
<NullableReturnStatement>
|
||||||
<code>$this->parent</code>
|
<code><![CDATA[$this->parent]]></code>
|
||||||
</NullableReturnStatement>
|
</NullableReturnStatement>
|
||||||
<TypeDoesNotContainNull occurrences="1">
|
<TypeDoesNotContainNull>
|
||||||
<code>$v === null</code>
|
<code>$v === null</code>
|
||||||
</TypeDoesNotContainNull>
|
</TypeDoesNotContainNull>
|
||||||
<TypeDoesNotContainType occurrences="2">
|
<TypeDoesNotContainType>
|
||||||
<code>$v === 0</code>
|
<code>$v === 0</code>
|
||||||
<code>$v === 0</code>
|
<code>$v === 0</code>
|
||||||
</TypeDoesNotContainType>
|
</TypeDoesNotContainType>
|
||||||
</file>
|
</file>
|
||||||
<file src="lib/Model/ActivityPub/Object/Announce.php">
|
|
||||||
<RedundantCondition occurrences="1">
|
|
||||||
<code>$object = $cache->getItem($this->getObjectId())</code>
|
|
||||||
</RedundantCondition>
|
|
||||||
</file>
|
|
||||||
<file src="lib/Db/CoreRequestBuilder.php">
|
<file src="lib/Db/CoreRequestBuilder.php">
|
||||||
<UndefinedMethod occurrences="3">
|
<UndefinedMethod>
|
||||||
<code>dropTable</code>
|
<code>dropTable</code>
|
||||||
<code>hasTable</code>
|
<code>hasTable</code>
|
||||||
<code>hasTable</code>
|
<code>hasTable</code>
|
||||||
</UndefinedMethod>
|
</UndefinedMethod>
|
||||||
<InvalidArgument occurrences="2"/>
|
<InvalidArgument>
|
||||||
|
<code>Server::get(Connection::class)</code>
|
||||||
|
<code>Server::get(Connection::class)</code>
|
||||||
|
</InvalidArgument>
|
||||||
</file>
|
</file>
|
||||||
<file src="lib/Service/CheckService.php">
|
<file src="lib/Service/CheckService.php">
|
||||||
<RedundantCast occurrences="1">
|
<RedundantCast>
|
||||||
<code>(bool)($this->cache->get(self::CACHE_PREFIX . 'wellknown') === 'true')</code>
|
<code><![CDATA[(bool)($this->cache->get(self::CACHE_PREFIX . 'wellknown') === 'true')]]></code>
|
||||||
</RedundantCast>
|
</RedundantCast>
|
||||||
<UndefinedClass occurrences="1">
|
<UndefinedClass>
|
||||||
<code>ClientException</code>
|
<code>ClientException</code>
|
||||||
</UndefinedClass>
|
</UndefinedClass>
|
||||||
</file>
|
</file>
|
||||||
<file src="lib/Service/CurlService.php">
|
<file src="lib/Service/CurlService.php">
|
||||||
<InvalidOperand occurrences="1">
|
<InvalidOperand>
|
||||||
<code>$this->configService->getAppValue(ConfigService::SOCIAL_MAX_SIZE)</code>
|
<code><![CDATA[$this->configService->getAppValue(ConfigService::SOCIAL_MAX_SIZE)]]></code>
|
||||||
</InvalidOperand>
|
</InvalidOperand>
|
||||||
<RedundantCondition occurrences="1">
|
<TypeDoesNotContainType>
|
||||||
<code>is_array($result)</code>
|
<code><![CDATA[$this->maxDownloadSizeReached === true]]></code>
|
||||||
</RedundantCondition>
|
|
||||||
<TypeDoesNotContainType occurrences="1">
|
|
||||||
<code>$this->maxDownloadSizeReached === true</code>
|
|
||||||
</TypeDoesNotContainType>
|
</TypeDoesNotContainType>
|
||||||
<InvalidArgument occurrences="8"/>
|
|
||||||
</file>
|
</file>
|
||||||
<file src="lib/Service/PostService.php">
|
<file src="lib/Service/PostService.php">
|
||||||
<UndefinedMethod occurrences="2">
|
<TypeDoesNotContainType>
|
||||||
<code>setAttributedTo</code>
|
<code><![CDATA[is_array($_FILES['attachments']['error'])]]></code>
|
||||||
<code>setContent</code>
|
|
||||||
</UndefinedMethod>
|
|
||||||
<TypeDoesNotContainType occurrences="1">
|
|
||||||
<code>is_array($_FILES['attachments']['error'])</code>
|
|
||||||
</TypeDoesNotContainType>
|
</TypeDoesNotContainType>
|
||||||
</file>
|
</file>
|
||||||
<file src="lib/Service/SearchService.php">
|
<file src="lib/Service/SearchService.php">
|
||||||
<InvalidOperand occurrences="3">
|
<InvalidOperand>
|
||||||
<code>!$type</code>
|
<code>!$type</code>
|
||||||
<code>!$type</code>
|
<code>!$type</code>
|
||||||
<code>!$type</code>
|
<code>!$type</code>
|
||||||
</InvalidOperand>
|
</InvalidOperand>
|
||||||
</file>
|
</file>
|
||||||
<file src="lib/Service/SignatureService.php">
|
<file src="lib/Service/SignatureService.php">
|
||||||
<RedundantCondition occurrences="1">
|
<RedundantCondition>
|
||||||
<code>$varr[0] !== null</code>
|
<code>$varr[0] !== null</code>
|
||||||
</RedundantCondition>
|
</RedundantCondition>
|
||||||
</file>
|
</file>
|
||||||
<file src="lib/Service/TestService.php">
|
|
||||||
<TypeDoesNotContainNull occurrences="3">
|
|
||||||
<code>$host === null</code>
|
|
||||||
<code>$username === null</code>
|
|
||||||
<code>$username === null</code>
|
|
||||||
</TypeDoesNotContainNull>
|
|
||||||
</file>
|
|
||||||
</files>
|
</files>
|
||||||
|
|
Ładowanie…
Reference in New Issue