Initial QSfera import
This commit is contained in:
@@ -0,0 +1,575 @@
|
||||
# Acceptance Testing
|
||||
|
||||
To run tests in the test suite you have two options. You may go the easy way and just run the test suite in docker. But for some tasks you could also need to install the test suite natively, which requires a little more setup since PHP and some dependencies need to be installed.
|
||||
|
||||
Both ways to run tests with the test suites are described here.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Running Test Suite in Docker](#running-test-suite-in-docker)
|
||||
- [Running API Tests](#running-api-tests)
|
||||
- [Run Tests With Required Services](#run-tests-with-required-services)
|
||||
- [Run Tests Only](#run-tests-only)
|
||||
- [Skip Local Image Build While Running Tests](#skip-local-image-build-while-running-tests)
|
||||
- [Check Test Logs](#check-test-logs)
|
||||
- [Cleanup the Setup](#cleanup-the-setup)
|
||||
- [Running WOPI Validator Tests](#running-wopi-validator-tests)
|
||||
- [Running Test Suite in Local Environment](#running-test-suite-in-local-environment)
|
||||
- [Running Tests With And Without `remote.php`](#running-tests-with-and-without-remotephp)
|
||||
- [Running ENV Config Tests (@env-Config)](#running-env-config-tests-env-config)
|
||||
- [Running Test Suite With Email Service (@email)](#running-test-suite-with-email-service-email)
|
||||
- [Running Test Suite With Tika Service (@tikaServiceNeeded)](#running-test-suite-with-tika-service-tikaserviceneeded)
|
||||
- [Running Test Suite With Antivirus Service (@antivirus)](#running-test-suite-with-antivirus-service-antivirus)
|
||||
- [Running Test Suite With Federated Sharing (@ocm)](#running-test-suite-with-federated-sharing-ocm)
|
||||
- [Running Text Preview Tests Containing Unicode Characters](#running-text-preview-tests-containing-unicode-characters)
|
||||
- [Running All API Tests Locally](#running-all-api-tests-locally)
|
||||
|
||||
## Running Test Suite in Docker
|
||||
|
||||
Check the available commands and environment variables with:
|
||||
|
||||
```bash
|
||||
make -C tests/acceptance/docker help
|
||||
```
|
||||
|
||||
### Running API Tests
|
||||
|
||||
#### Run Tests With Required Services
|
||||
|
||||
We can run a single feature or a single test suite with different storage drivers.
|
||||
|
||||
1. Run a specific feature file:
|
||||
|
||||
```bash
|
||||
BEHAT_FEATURE='tests/acceptance/features/apiGraphUserGroup/createUser.feature' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
or a single scenario in a feature:
|
||||
|
||||
```bash
|
||||
BEHAT_FEATURE='tests/acceptance/features/apiGraphUserGroup/createUser.feature:24' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
2. Run a specific test suite:
|
||||
|
||||
```bash
|
||||
BEHAT_SUITE='apiGraphUserGroup' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
3. Run with different storage driver (default is `posix`):
|
||||
|
||||
```bash
|
||||
STORAGE_DRIVER='posix' \
|
||||
BEHAT_SUITE='apiGraphUserGroup' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
4. Run the tests that require an email server (tests tagged with `@email`). Provide `START_EMAIL=true` while running the tests:
|
||||
|
||||
```bash
|
||||
START_EMAIL=true \
|
||||
BEHAT_FEATURE='tests/acceptance/features/apiNotification/emailNotification.feature' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
5. Run the tests that require tika service (tests tagged with `@tikaServiceNeeded`). Provide `START_TIKA=true` while running the tests:
|
||||
|
||||
```bash
|
||||
START_TIKA=true \
|
||||
BEHAT_FEATURE='tests/acceptance/features/apiSearchContent/contentSearch.feature' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
6. Run the tests that require an antivirus service (tests tagged with `@antivirus`). Provide `START_ANTIVIRUS=true` while running the tests:
|
||||
|
||||
```bash
|
||||
START_ANTIVIRUS=true \
|
||||
BEHAT_FEATURE='tests/acceptance/features/apiAntivirus/antivirus.feature' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
7. Run the wopi tests. Provide `ENABLE_WOPI=true` while running the tests:
|
||||
```bash
|
||||
ENABLE_WOPI=true \
|
||||
BEHAT_FEATURE='tests/acceptance/features/apiCollaboration/checkFileInfo.feature' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
#### Run Tests Only
|
||||
|
||||
If you want to re-run the tests because of some failures or any other reason, you can use the following command to run only the tests without starting the services again.
|
||||
Also, this command can be used to run the tests against the already hosted КуСфера server by providing the `TEST_SERVER_URL` and `USE_BEARER_TOKEN` environment variables.
|
||||
|
||||
> [!NOTE]
|
||||
> You can utilize the following environment variables:
|
||||
>
|
||||
> - `BEHAT_FEATURE`
|
||||
> - `BEHAT_SUITE`
|
||||
> - `USE_BEARER_TOKEN`
|
||||
> - `TEST_SERVER_URL`
|
||||
|
||||
```bash
|
||||
BEHAT_FEATURE='tests/acceptance/features/apiGraphUserGroup/createUser.feature:24' \
|
||||
make -C tests/acceptance/docker run-test-only
|
||||
```
|
||||
|
||||
#### Skip Local Image Build While Running Tests
|
||||
|
||||
While running the tests, qsfera docker image is built with `qsfera/qsfera:dev` tag. If you want to skip building the local image, you can use `OC_IMAGE_TAG` env which must contain an available docker tag of the [qsfera/qsfera registry on Docker Hub](https://hub.docker.com/r/qsfera/qsfera) (e.g. 'latest').
|
||||
|
||||
```bash
|
||||
OC_IMAGE_TAG=latest \
|
||||
BEHAT_FEATURE='tests/acceptance/features/apiGraphUserGroup/createUser.feature' \
|
||||
make -C tests/acceptance/docker run-api-tests
|
||||
```
|
||||
|
||||
#### Check Test Logs
|
||||
|
||||
While a test is running or when it is finished, you can attach to the logs generated by the tests.
|
||||
|
||||
```bash
|
||||
make -C tests/acceptance/docker show-test-logs
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The log output is opened in `less`. You can navigate up and down with your cursors. By pressing "F" you can follow the latest line of the output.
|
||||
|
||||
#### Cleanup the Setup
|
||||
|
||||
Run the following command to clean all the resources created while running the tests:
|
||||
|
||||
```bash
|
||||
make -C tests/acceptance/docker clean-all
|
||||
```
|
||||
|
||||
### Running WOPI Validator Tests
|
||||
|
||||
#### Available Test Groups
|
||||
|
||||
```text
|
||||
BaseWopiViewing
|
||||
CheckFileInfoSchema
|
||||
EditFlows
|
||||
Locks
|
||||
AccessTokens
|
||||
GetLock
|
||||
ExtendedLockLength
|
||||
FileVersion
|
||||
Features
|
||||
PutRelativeFile
|
||||
RenameFileIfCreateChildFileIsNotSupported
|
||||
```
|
||||
|
||||
#### Run Test
|
||||
|
||||
```bash
|
||||
TEST_GROUP=BaseWopiViewing docker compose -f tests/acceptance/docker/src/wopi-validator-test.yml up -d
|
||||
```
|
||||
|
||||
#### Run Test (macOS)
|
||||
|
||||
Use the arm image for macOS to run the validator tests.
|
||||
|
||||
```bash
|
||||
WOPI_VALIDATOR_IMAGE=scharfvi/wopi-validator \
|
||||
TEST_GROUP=BaseWopiViewing \
|
||||
docker compose -f tests/acceptance/docker/src/wopi-validator-test.yml up -d
|
||||
```
|
||||
|
||||
## Running Test Suite in Local Environment
|
||||
|
||||
### Run КуСфера
|
||||
|
||||
Create an up-to-date КуСфера binary by [building КуСфера]({{< ref "build" >}})
|
||||
|
||||
To start КуСфера:
|
||||
|
||||
```bash
|
||||
IDM_ADMIN_PASSWORD=admin \
|
||||
qsfera/bin/qsfera init --insecure true
|
||||
|
||||
OC_INSECURE=true PROXY_ENABLE_BASIC_AUTH=true \
|
||||
qsfera/bin/qsfera server
|
||||
```
|
||||
|
||||
`PROXY_ENABLE_BASIC_AUTH` will allow the acceptance tests to make requests against the provisioning api (and other endpoints) using basic auth.
|
||||
|
||||
#### Run Local КуСфера Tests (prefix `api`) and Tests Transferred From Core (prefix `coreApi`)
|
||||
|
||||
```bash
|
||||
make test-acceptance-api \
|
||||
TEST_SERVER_URL=https://localhost:9200 \
|
||||
```
|
||||
|
||||
Useful environment variables:
|
||||
|
||||
`TEST_SERVER_URL`: КуСфера server url. Please, adjust the server url according to your setup.
|
||||
|
||||
`BEHAT_FEATURE`: to run a single feature
|
||||
|
||||
Note:
|
||||
A specific scenario from a feature can be run by adding `:<line-number>` at the end of the feature file path. For example, to run the scenario at line 26 of the feature file `apiGraphUserGroup/createUser.feature`, simply add the line number like this: `apiGraphUserGroup/createUser.feature:26`. Note that the line numbers mentioned in the examples might not always point to a scenario, so always check the line numbers before running the test.
|
||||
|
||||
> Example:
|
||||
>
|
||||
> BEHAT_FEATURE=tests/acceptance/features/apiGraphUserGroup/createUser.feature
|
||||
>
|
||||
> Or
|
||||
>
|
||||
> BEHAT_FEATURE=tests/acceptance/features/apiGraphUserGroup/createUser.feature:13
|
||||
|
||||
`BEHAT_SUITE`: to run a single suite
|
||||
|
||||
> Example:
|
||||
>
|
||||
> BEHAT_SUITE=apiGraph
|
||||
|
||||
`STORAGE_DRIVER`: to run tests with a different user storage driver. Available options are `decomposed` (default), `owncloudsql` and `decomposeds3`
|
||||
|
||||
> Example:
|
||||
>
|
||||
> STORAGE_DRIVER=owncloudsql
|
||||
|
||||
`STOP_ON_FAILURE`: to stop running tests after the first failure
|
||||
|
||||
> Example:
|
||||
>
|
||||
> STOP_ON_FAILURE=true
|
||||
|
||||
### Use Existing Tests for BDD
|
||||
|
||||
As a lot of scenarios are written for core, we can use those tests for Behaviour driven development in КуСфера.
|
||||
Every scenario that does not work in КуСфера with `decomposed` storage, is listed in `tests/acceptance/expected-failures-decomposed-storage.md` with a link to the related issue.
|
||||
|
||||
Those scenarios are run in the ordinary acceptance test pipeline in CI. The scenarios that fail are checked against the
|
||||
expected failures. If there are any differences then the CI pipeline fails.
|
||||
|
||||
If you want to work on a specific issue
|
||||
|
||||
1. locally run each of the tests marked with that issue in the expected failures file.
|
||||
|
||||
E.g.:
|
||||
|
||||
```bash
|
||||
make test-acceptance-api \
|
||||
TEST_SERVER_URL=https://localhost:9200 \
|
||||
STORAGE_DRIVER=decomposed \
|
||||
BEHAT_FEATURE='tests/acceptance/features/coreApiVersions/fileVersions.feature:141'
|
||||
```
|
||||
|
||||
2. the tests will fail, try to understand how and why they are failing
|
||||
3. fix the code
|
||||
4. go back to 1. and repeat till the tests are passing.
|
||||
5. remove those tests from the expected failures file
|
||||
6. make a PR that has the fixed code, and the relevant lines removed from the expected failures file.
|
||||
|
||||
### Running Tests With And Without `remote.php`
|
||||
|
||||
By default, the tests are run with `remote.php` enabled. If you want to run the tests without `remote.php`, you can disable it by setting the environment variable `WITH_REMOTE_PHP=false` while running the tests.
|
||||
|
||||
```bash
|
||||
WITH_REMOTE_PHP=false \
|
||||
TEST_SERVER_URL="https://localhost:9200" \
|
||||
make test-acceptance-api
|
||||
```
|
||||
|
||||
### Running ENV Config Tests (@env-Config)
|
||||
|
||||
Test suites tagged with `@env-config` are used to test the environment variables that are used to configure КуСфера. These tests are special tests that require the КуСфера server to be run using [ocwrapper](https://github.com/qsfera/server/blob/main/tests/ocwrapper/README.md).
|
||||
|
||||
#### Run КуСфера With ocwrapper
|
||||
|
||||
```bash
|
||||
# working dir: КуСфера repo root dir
|
||||
|
||||
# init КуСфера
|
||||
IDM_ADMIN_PASSWORD=admin \
|
||||
qsfera/bin/qsfera init --insecure true
|
||||
|
||||
# build the wrapper
|
||||
cd tests/ocwrapper
|
||||
make build
|
||||
|
||||
# run КуСфера
|
||||
PROXY_ENABLE_BASIC_AUTH=true \
|
||||
./bin/ocwrapper serve --bin=../../qsfera/bin/qsfera
|
||||
```
|
||||
|
||||
#### Run the Tests
|
||||
|
||||
```bash
|
||||
OC_WRAPPER_URL=http://localhost:5200 \
|
||||
TEST_SERVER_URL="https://localhost:9200" \
|
||||
BEHAT_FEATURE=tests/acceptance/features/apiAsyncUpload/delayPostprocessing.feature \
|
||||
make test-acceptance-api
|
||||
```
|
||||
|
||||
#### Writing New ENV Config Tests
|
||||
|
||||
While writing tests for a new КуСфера ENV configuration, please make sure to follow these guidelines:
|
||||
|
||||
1. Tag the test suite (or test scenarios) with `@env-config`
|
||||
2. Use `OcConfigHelper.php` for helper functions - provides functions to reconfigure the running КуСфера instance.
|
||||
3. Recommended: add the new step implementations in `OcConfigContext.php`
|
||||
|
||||
### Running Test Suite With Email Service (@email)
|
||||
|
||||
Test suites that are tagged with `@email` require an email service. We use inbucket as the email service in our tests.
|
||||
|
||||
#### Setup Inbucket
|
||||
|
||||
Run the following command to setup inbucket
|
||||
|
||||
```bash
|
||||
docker run -d -p9000:9000 -p2500:2500 --name inbucket inbucket/inbucket
|
||||
```
|
||||
|
||||
#### Run КуСфера
|
||||
|
||||
Documentation for environment variables is available [here](https://docs.qsfera.eu/services/notifications/#environment-variables)
|
||||
|
||||
```bash
|
||||
# init КуСфера
|
||||
IDM_ADMIN_PASSWORD=admin \
|
||||
qsfera/bin/qsfera init --insecure true
|
||||
|
||||
# run КуСфера
|
||||
PROXY_ENABLE_BASIC_AUTH=true \
|
||||
OC_ADD_RUN_SERVICES=notifications \
|
||||
NOTIFICATIONS_SMTP_HOST=localhost \
|
||||
NOTIFICATIONS_SMTP_PORT=2500 \
|
||||
NOTIFICATIONS_SMTP_INSECURE=true \
|
||||
NOTIFICATIONS_SMTP_SENDER="КуСфера <noreply@example.com>" \
|
||||
qsfera/bin/qsfera server
|
||||
```
|
||||
|
||||
#### Run the Acceptance Test
|
||||
|
||||
Run the acceptance test with the following command:
|
||||
|
||||
```bash
|
||||
TEST_SERVER_URL="https://localhost:9200" \
|
||||
EMAIL_HOST="localhost" \
|
||||
EMAIL_PORT=9000 \
|
||||
BEHAT_FEATURE="tests/acceptance/features/apiNotification/emailNotification.feature" \
|
||||
make test-acceptance-api
|
||||
```
|
||||
|
||||
### Running Test Suite With Tika Service (@tikaServiceNeeded)
|
||||
|
||||
Test suites that are tagged with `@tikaServiceNeeded` require tika service.
|
||||
|
||||
#### Setup Tika Service
|
||||
|
||||
Run the following docker command to setup tika service
|
||||
|
||||
```bash
|
||||
docker run -d -p 127.0.0.1:9998:9998 apache/tika
|
||||
```
|
||||
|
||||
#### Run КуСфера
|
||||
|
||||
TODO: Documentation related to the content based search and tika extractor will be added later.
|
||||
|
||||
```bash
|
||||
# init КуСфера
|
||||
IDM_ADMIN_PASSWORD=admin \
|
||||
qsfera/bin/qsfera init --insecure true
|
||||
|
||||
# run КуСфера
|
||||
PROXY_ENABLE_BASIC_AUTH=true \
|
||||
OC_INSECURE=true \
|
||||
SEARCH_EXTRACTOR_TYPE=tika \
|
||||
SEARCH_EXTRACTOR_TIKA_TIKA_URL=http://localhost:9998 \
|
||||
SEARCH_EXTRACTOR_CS3SOURCE_INSECURE=true \
|
||||
qsfera/bin/qsfera server
|
||||
```
|
||||
|
||||
#### Run the Acceptance Test
|
||||
|
||||
Run the acceptance test with the following command:
|
||||
|
||||
```bash
|
||||
TEST_SERVER_URL="https://localhost:9200" \
|
||||
BEHAT_FEATURE="tests/acceptance/features/apiSearchContent/contentSearch.feature" \
|
||||
make test-acceptance-api
|
||||
```
|
||||
|
||||
### Running Test Suite With Antivirus Service (@antivirus)
|
||||
|
||||
Test suites that are tagged with `@antivirus` require antivirus service. TODO The available antivirus and the configuration related to them will be added latert. This documentation is only going to use `clamav` as antivirus.
|
||||
|
||||
#### Setup clamAV
|
||||
|
||||
**Option 1. Setup Locally**
|
||||
|
||||
Linux OS user:
|
||||
|
||||
Run the following command to set up calmAV and clamAV daemon
|
||||
|
||||
```bash
|
||||
sudo apt install clamav clamav-daemon -y
|
||||
```
|
||||
|
||||
Make sure that the clamAV daemon is up and running
|
||||
|
||||
```bash
|
||||
sudo service clamav-daemon status
|
||||
```
|
||||
|
||||
Note:
|
||||
The commands are ubuntu specific and may differ according to your system. You can find information related to installation of clamAV in their official documentation [here](https://docs.clamav.net/manual/Installing/Packages.html).
|
||||
|
||||
Mac OS user:
|
||||
|
||||
Install ClamAV using [here](https://gist.github.com/mendozao/3ea393b91f23a813650baab9964425b9)
|
||||
Start ClamAV daemon
|
||||
|
||||
```bash
|
||||
/your/location/to/brew/Cellar/clamav/1.1.0/sbin/clamd
|
||||
```
|
||||
|
||||
**Option 2. Setup clamAV With Docker**
|
||||
|
||||
Run `clamAV` through docker
|
||||
|
||||
```bash
|
||||
docker run -d -p 3310:3310 opencloudeu/clamav-ci:latest
|
||||
```
|
||||
|
||||
#### Run КуСфера
|
||||
|
||||
As `antivirus` service is not enabled by default we need to enable the service while running КуСфера server. We also need to enable `async upload` and as virus scan is performed in post-processing step, we need to set it as well. Documentation for environment variables related to antivirus is available [here](https://docs.qsfera.eu/services/antivirus/#environment-variables)
|
||||
|
||||
```bash
|
||||
# init КуСфера
|
||||
IDM_ADMIN_PASSWORD=admin \
|
||||
qsfera/bin/qsfera init --insecure true
|
||||
|
||||
# run КуСфера
|
||||
PROXY_ENABLE_BASIC_AUTH=true \
|
||||
ANTIVIRUS_SCANNER_TYPE="clamav" \
|
||||
ANTIVIRUS_CLAMAV_SOCKET="tcp://host.docker.internal:3310" \
|
||||
POSTPROCESSING_STEPS="virusscan" \
|
||||
OC_ASYNC_UPLOADS=true \
|
||||
OC_ADD_RUN_SERVICES="antivirus" \
|
||||
qsfera/bin/qsfera server
|
||||
```
|
||||
|
||||
Note:
|
||||
The value for `ANTIVIRUS_CLAMAV_SOCKET` is an example which needs adaption according your OS.
|
||||
|
||||
For antivirus running localy on Linux OS, use `ANTIVIRUS_CLAMAV_SOCKET= "/var/run/clamav/clamd.ctl"`.
|
||||
For antivirus running localy on Mac OS, use `ANTIVIRUS_CLAMAV_SOCKET= "/tmp/clamd.sock"`.
|
||||
For antivirus running with docker, use `ANTIVIRUS_CLAMAV_SOCKET= "tcp://host.docker.internal:3310"`
|
||||
|
||||
#### Run the Acceptance Test
|
||||
|
||||
Run the acceptance test with the following command:
|
||||
|
||||
```bash
|
||||
TEST_SERVER_URL="https://localhost:9200" \
|
||||
BEHAT_FEATURE="tests/acceptance/features/apiAntivirus/antivirus.feature" \
|
||||
make test-acceptance-api
|
||||
```
|
||||
|
||||
### Running Test Suite With Federated Sharing (@ocm)
|
||||
|
||||
Test suites that are tagged with `@ocm` require running two different КуСфера instances. TODO More detailed information and configuration related to it will be added later.
|
||||
|
||||
#### Setup First КуСфера Instance
|
||||
|
||||
```bash
|
||||
# init КуСфера
|
||||
IDM_ADMIN_PASSWORD=admin \
|
||||
qsfera/bin/qsfera init --insecure true
|
||||
|
||||
# run КуСфера
|
||||
OC_URL="https://localhost:9200" \
|
||||
PROXY_ENABLE_BASIC_AUTH=true \
|
||||
OC_ENABLE_OCM=true \
|
||||
OCM_OCM_PROVIDER_AUTHORIZER_PROVIDERS_FILE="tests/config/local/providers.json" \
|
||||
OC_ADD_RUN_SERVICES="ocm" \
|
||||
OCM_OCM_INVITE_MANAGER_INSECURE=true \
|
||||
OCM_OCM_SHARE_PROVIDER_INSECURE=true \
|
||||
OCM_OCM_STORAGE_PROVIDER_INSECURE=true \
|
||||
WEB_UI_CONFIG_FILE="tests/config/local/qsfera-web.json" \
|
||||
qsfera/bin/qsfera server
|
||||
```
|
||||
|
||||
The first КуСфера instance should be available at: https://localhost:9200/
|
||||
|
||||
#### Setup Second КуСфера Instance
|
||||
|
||||
You can run the second КуСфера instance in two ways:
|
||||
|
||||
**Option 1. Using `.vscode/launch.json`**
|
||||
|
||||
From the `Run and Debug` panel of VSCode, select `Fed КуСфера Server` and start the debugger.
|
||||
|
||||
**Option 2. Using env file**
|
||||
|
||||
```bash
|
||||
# init КуСфера
|
||||
source tests/config/local/.env-federation && qsfera/bin/qsfera init
|
||||
|
||||
# run КуСфера
|
||||
qsfera/bin/qsfera server
|
||||
```
|
||||
|
||||
The second КуСфера instance should be available at: https://localhost:10200/
|
||||
|
||||
Note:
|
||||
To enable ocm in the web interface, you need to set the following envs:
|
||||
`OC_ENABLE_OCM="true"`
|
||||
`OC_ADD_RUN_SERVICES="ocm"`
|
||||
|
||||
#### Run the Acceptance Test
|
||||
|
||||
Run the acceptance test with the following command:
|
||||
|
||||
```bash
|
||||
TEST_SERVER_URL="https://localhost:9200" \
|
||||
TEST_SERVER_FED_URL="https://localhost:10200" \
|
||||
BEHAT_FEATURE="tests/acceptance/features/apiOcm/ocm.feature" \
|
||||
make test-acceptance-api
|
||||
```
|
||||
|
||||
### Running Text Preview Tests Containing Unicode Characters
|
||||
|
||||
There are some tests that check the text preview of files containing Unicode characters. The КуСфера server by default cannot generate the thumbnail of such files correctly but it provides an environment variable to allow the use of custom fonts that support Unicode characters. So to run such tests successfully, we have to run the КуСфера server with this environment variable.
|
||||
|
||||
```bash
|
||||
...
|
||||
THUMBNAILS_TXT_FONTMAP_FILE="/path/to/fontsMap.json"
|
||||
qsfera/bin/qsfera server
|
||||
```
|
||||
|
||||
The sample `fontsMap.json` file is located in `tests/config/drone/fontsMap.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"defaultFont": "/path/to/qsfera/tests/config/drone/NotoSans.ttf"
|
||||
}
|
||||
```
|
||||
|
||||
## Running All API Tests Locally
|
||||
|
||||
### Build dev docker
|
||||
|
||||
```bash
|
||||
make -C qsfera dev-docker
|
||||
```
|
||||
|
||||
### Choose STORAGE_DRIVER
|
||||
|
||||
By default, the system uses `posix` storage. However, you can override this by setting the `STORAGE_DRIVER` environment variable.
|
||||
|
||||
### Run a script that starts the qsfera server in the docker and runs the API tests locally (for debugging purposes)
|
||||
|
||||
```bash
|
||||
STORAGE_DRIVER=posix ./tests/acceptance/run_api_tests.sh
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
output
|
||||
logs
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers\Asserts;
|
||||
|
||||
use PHPUnit\Framework\Assert;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* WebDAV related asserts
|
||||
*/
|
||||
class WebDav extends Assert {
|
||||
/**
|
||||
*
|
||||
* @param string|null $element exception|message|reason
|
||||
* @param string|null $expectedValue
|
||||
* @param array|null $responseXmlArray
|
||||
* @param string|null $extraErrorText
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function assertDavResponseElementIs(
|
||||
?string $element,
|
||||
?string $expectedValue,
|
||||
?array $responseXmlArray,
|
||||
?string $extraErrorText = ''
|
||||
): void {
|
||||
if ($extraErrorText !== '') {
|
||||
$extraErrorText = $extraErrorText . " ";
|
||||
}
|
||||
self::assertArrayHasKey(
|
||||
'value',
|
||||
$responseXmlArray,
|
||||
$extraErrorText . "responseXml does not have key 'value'"
|
||||
);
|
||||
if ($element === "exception") {
|
||||
$result = $responseXmlArray['value'][0]['value'];
|
||||
} elseif ($element === "message") {
|
||||
$result = $responseXmlArray['value'][1]['value'];
|
||||
} elseif ($element === "reason") {
|
||||
$result = $responseXmlArray['value'][3]['value'];
|
||||
} else {
|
||||
self::fail(
|
||||
__METHOD__ . " element must be one of exception, response or reason. But '$element' was passed in."
|
||||
);
|
||||
}
|
||||
|
||||
self::assertEquals(
|
||||
$expectedValue,
|
||||
$result,
|
||||
__METHOD__ . " " . $extraErrorText . "Expected '$expectedValue' in element $element got '$result'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param SimpleXMLElement $responseXmlObject
|
||||
* @param array|null $expectedShareTypes
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function assertResponseContainsShareTypes(
|
||||
SimpleXMLElement $responseXmlObject,
|
||||
?array $expectedShareTypes
|
||||
): void {
|
||||
foreach ($expectedShareTypes as $row) {
|
||||
$xmlPart = $responseXmlObject->xpath(
|
||||
"//d:prop/oc:share-types/oc:share-type[.=" . $row[0] . "]"
|
||||
);
|
||||
self::assertNotEmpty(
|
||||
$xmlPart,
|
||||
"cannot find share-type '" . $row[0] . "'"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Niraj Acharya <niraj@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Niraj Acharya niraj@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for managing Auth App API requests
|
||||
*/
|
||||
class AuthAppHelper {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getAuthAppEndpoint(): string {
|
||||
return "/auth-app/tokens";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public static function listAllAppAuthTokensForUser(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . self::getAuthAppEndpoint();
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
null,
|
||||
"GET",
|
||||
$user,
|
||||
$password,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param array $params
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public static function createAppAuthToken(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
array $params,
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . self::getAuthAppEndpoint() . "?"
|
||||
. http_build_query($params);
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
null,
|
||||
"POST",
|
||||
$user,
|
||||
$password,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $token
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public static function deleteAppAuthToken(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $token
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . self::getAuthAppEndpoint() . "?token=$token";
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
null,
|
||||
"DELETE",
|
||||
$user,
|
||||
$password,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
|
||||
use Behat\Behat\Context\Exception\ContextNotFoundException;
|
||||
use Behat\Behat\Hook\Scope\ScenarioScope;
|
||||
|
||||
/**
|
||||
* Helper for Behat environment configuration
|
||||
*
|
||||
*/
|
||||
class BehatHelper {
|
||||
/**
|
||||
* @param ScenarioScope $scope
|
||||
* @param InitializedContextEnvironment $environment
|
||||
* @param string $class
|
||||
*
|
||||
* @return Context
|
||||
*/
|
||||
public static function getContext(
|
||||
ScenarioScope $scope,
|
||||
InitializedContextEnvironment $environment,
|
||||
string $class
|
||||
): Context {
|
||||
try {
|
||||
return $environment->getContext($class);
|
||||
} catch (ContextNotFoundException $e) {
|
||||
$context = new $class();
|
||||
$environment->registerContext($context);
|
||||
if (\method_exists($context, 'before')) {
|
||||
$context->before($scope);
|
||||
}
|
||||
return $environment->getContext($class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Sajan Gurung sajan@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\OcConfigHelper;
|
||||
|
||||
/**
|
||||
* A helper class for running КуСфера CLI commands
|
||||
*/
|
||||
class CliHelper {
|
||||
/**
|
||||
* @param array $body
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function runCommand(array $body): ResponseInterface {
|
||||
$url = OcConfigHelper::getWrapperUrl() . "/command";
|
||||
return OcConfigHelper::sendRequest($url, "POST", \json_encode($body));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Amrita Shrestha <amrita@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Amrita Shrestha amrita@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for managing wopi requests
|
||||
*/
|
||||
class CollaborationHelper {
|
||||
/**
|
||||
* @param string $fileId
|
||||
* @param string $app
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string|null $viewMode
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendPOSTRequestToAppOpen(
|
||||
string $fileId,
|
||||
string $app,
|
||||
string $username,
|
||||
string $password,
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
?string $viewMode = null,
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . "/app/open?app_name=$app&file_id=$fileId";
|
||||
if ($viewMode) {
|
||||
$url .= "&view_mode=$viewMode";
|
||||
}
|
||||
|
||||
return HttpRequestHelper::post(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$username,
|
||||
$password,
|
||||
['Content-Type' => 'application/json']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $parentContainerId
|
||||
* @param string $file
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function createFile(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $parentContainerId,
|
||||
string $file,
|
||||
?array $headers = null
|
||||
): ResponseInterface {
|
||||
$url = $baseUrl . "/app/new?parent_container_id=$parentContainerId&filename=$file";
|
||||
return HttpRequestHelper::post(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Prajwol Amatya <prajwol@jankaritech.com>
|
||||
* @copyright Copyright (c) 2023 Prajwol Amatya prajwol@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for managing emails
|
||||
*/
|
||||
class EmailHelper {
|
||||
/**
|
||||
* @param string $emailAddress
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getMailBoxFromEmail(string $emailAddress): string {
|
||||
return explode("@", $emailAddress)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host and port where Email messages can be read and deleted
|
||||
* by the test runner.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLocalEmailUrl(): string {
|
||||
$localEmailHost = self::getLocalEmailHost();
|
||||
$emailPort = \getenv('EMAIL_PORT');
|
||||
if ($emailPort === false) {
|
||||
$emailPort = "9000";
|
||||
}
|
||||
return "http://$localEmailHost:$emailPort";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host name or address of the Email server as seen from the
|
||||
* point of view of the system-under-test.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getEmailHost(): string {
|
||||
$emailHost = \getenv('EMAIL_HOST');
|
||||
if ($emailHost === false) {
|
||||
$emailHost = "127.0.0.1";
|
||||
}
|
||||
return $emailHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the host name or address of the Email server as seen from the
|
||||
* point of view of the test runner.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getLocalEmailHost(): string {
|
||||
$localEmailHost = \getenv('LOCAL_EMAIL_HOST');
|
||||
if ($localEmailHost === false) {
|
||||
$localEmailHost = self::getEmailHost();
|
||||
}
|
||||
return $localEmailHost;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns general response information about the provided mailbox
|
||||
* A mailbox is created automatically in InBucket for every unique email sender|receiver
|
||||
*
|
||||
* @param string $mailBox
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return array
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getMailBoxInformation(string $mailBox, ?string $xRequestId = null): array {
|
||||
$response = HttpRequestHelper::get(
|
||||
self::getLocalEmailUrl() . "/api/v1/mailbox/" . $mailBox,
|
||||
$xRequestId,
|
||||
null,
|
||||
null,
|
||||
['Content-Type' => 'application/json']
|
||||
);
|
||||
return \json_decode($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* returns body content of a specific email (mailBox) with email ID (mailbox Id)
|
||||
*
|
||||
* @param string $mailBox
|
||||
* @param string $mailboxId
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return object
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getBodyOfAnEmailById(
|
||||
string $mailBox,
|
||||
string $mailboxId,
|
||||
?string $xRequestId = null
|
||||
): object {
|
||||
$response = HttpRequestHelper::get(
|
||||
self::getLocalEmailUrl() . "/api/v1/mailbox/" . $mailBox . "/" . $mailboxId,
|
||||
$xRequestId,
|
||||
null,
|
||||
null,
|
||||
['Content-Type' => 'application/json']
|
||||
);
|
||||
return \json_decode($response->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the body of the last received email for the provided receiver
|
||||
*
|
||||
* @param string $emailAddress
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getBodyOfLastEmail(
|
||||
string $emailAddress,
|
||||
string $xRequestId,
|
||||
): string {
|
||||
$mailBox = self::getMailBoxFromEmail($emailAddress);
|
||||
$emails = self::getMailboxInformation($mailBox, $xRequestId);
|
||||
if (!empty($emails)) {
|
||||
$emailId = \array_pop($emails)->id;
|
||||
$response = self::getBodyOfAnEmailById($mailBox, $emailId, $xRequestId);
|
||||
$body = \str_replace(
|
||||
"\r\n",
|
||||
"\n",
|
||||
\quoted_printable_decode($response->body->text . "\n" . $response->body->html)
|
||||
);
|
||||
return $body;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all the emails for the provided mailbox
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $mailBox
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function deleteAllEmails(
|
||||
string $url,
|
||||
string $mailBox,
|
||||
string $xRequestId,
|
||||
): ResponseInterface {
|
||||
return HttpRequestHelper::delete(
|
||||
$url . "/api/v1/mailbox/" . $mailBox,
|
||||
$xRequestId,
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,133 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2023, ownCloud GmbH
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Helper for logging HTTP requests and responses
|
||||
*/
|
||||
class HttpLogger {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getLogDir(): string {
|
||||
return __DIR__ . '/../logs';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getFailedLogPath(): string {
|
||||
return self::getLogDir() . "/failed.log";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getScenarioLogPath(): string {
|
||||
return self::getLogDir() . "/scenario.log";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $logFile
|
||||
* @param string $logMessage
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function writeLog(string $logFile, string $logMessage): void {
|
||||
$file = \fopen($logFile, 'a+') or die('Cannot open file: ' . $logFile);
|
||||
\fwrite($file, $logMessage);
|
||||
\fclose($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RequestInterface $request
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function logRequest(RequestInterface $request): void {
|
||||
$method = $request->getMethod();
|
||||
$path = $request->getUri()->getPath();
|
||||
$query = $request->getUri()->getQuery();
|
||||
$body = $request->getBody();
|
||||
|
||||
$headers = "";
|
||||
foreach ($request->getHeaders() as $key => $value) {
|
||||
$headers = $key . ": " . $value[0] . "\n";
|
||||
}
|
||||
|
||||
$logMessage = "\t\t_______________________________________________________________________\n\n";
|
||||
$logMessage .= "\t\t==> REQUEST [" . self::getCurrentDateTime() . "]\n";
|
||||
$logMessage .= "\t\t$method $path\n";
|
||||
$logMessage .= $query ? "\t\tQUERY: $query\n" : "";
|
||||
$logMessage .= "\t\t$headers";
|
||||
|
||||
if ($body->getSize() > 0) {
|
||||
$logMessage .= "\t\t==> REQ BODY\n";
|
||||
$logMessage .= "\t\t$body\n";
|
||||
}
|
||||
$logMessage .= "\n";
|
||||
self::writeLog(self::getScenarioLogPath(), $logMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function logResponse(ResponseInterface $response): void {
|
||||
$statusCode = $response->getStatusCode();
|
||||
$statusMessage = $response->getReasonPhrase();
|
||||
$body = $response->getBody();
|
||||
$headers = "";
|
||||
|
||||
foreach ($response->getHeaders() as $key => $value) {
|
||||
$headers = $key . ": " . $value[0] . "\n";
|
||||
}
|
||||
|
||||
$logMessage = "\t\t<== RESPONSE [" . self::getCurrentDateTime() . "]\n";
|
||||
$logMessage .= "\t\t$statusCode $statusMessage\n";
|
||||
$logMessage .= "\t\t$headers";
|
||||
|
||||
if ($body->getSize() > 0) {
|
||||
$logMessage .= "\t\t<== RES BODY\n";
|
||||
foreach (\explode("\n", \strval($body)) as $line) {
|
||||
$logMessage .= "\t\t$line\n";
|
||||
}
|
||||
}
|
||||
// rewind the body stream so that later code can read from the start.
|
||||
$response->getBody()->rewind();
|
||||
|
||||
$logMessage = \rtrim($logMessage) . "\n\n";
|
||||
self::writeLog(self::getScenarioLogPath(), $logMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current date and time in format: 1999-01-31T23:01:59
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getCurrentDateTime(): string {
|
||||
return date('Y-m-d\TG:i:s');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use SimpleXMLElement;
|
||||
use Sabre\Xml\LibXMLException;
|
||||
use Sabre\Xml\Reader;
|
||||
use GuzzleHttp\Pool;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Helper for HTTP requests
|
||||
*/
|
||||
class HttpRequestHelper {
|
||||
public const HTTP_TOO_EARLY = 425;
|
||||
public const HTTP_CONFLICT = 409;
|
||||
|
||||
/**
|
||||
* Some systems-under-test do async post-processing of operations like upload,
|
||||
* move, etc. If a client does a request on the resource before the post-processing
|
||||
* is finished, then the server should return HTTP_TOO_EARLY "425". Clients are
|
||||
* expected to retry the request "some time later" (tm).
|
||||
*
|
||||
* On such systems, when HTTP_TOO_EARLY status is received, the test code will
|
||||
* retry the request at 1-second intervals until either some other HTTP status
|
||||
* is received or the retry-limit is reached.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function numRetriesOnHttpTooEarly(): int {
|
||||
// Currently reva and КуСфера may return HTTP_TOO_EARLY
|
||||
// So try up to 10 times before giving up.
|
||||
return STANDARD_RETRY_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $method
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param Client|null $client
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendRequestOnce(
|
||||
string $url,
|
||||
?string $xRequestId = null,
|
||||
?string $method = 'GET',
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?Client $client = null,
|
||||
): ResponseInterface {
|
||||
$bearerToken = null;
|
||||
if (TokenHelper::useBearerToken() && $user && $user !== 'public') {
|
||||
$bearerToken = TokenHelper::getTokens($user, $password, $url)['access_token'];
|
||||
// check token is still valid
|
||||
$parsedUrl = parse_url($url);
|
||||
$baseUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
|
||||
$baseUrl .= isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : '';
|
||||
$testUrl = $baseUrl . "/graph/v1.0/me";
|
||||
if (OcHelper::isTestingOnReva()) {
|
||||
$url = $baseUrl . "/ocs/v2.php/cloud/users/$user";
|
||||
}
|
||||
// check token validity with a GET request
|
||||
$c = self::createClient(
|
||||
$user,
|
||||
$password,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream,
|
||||
$timeout,
|
||||
$bearerToken
|
||||
);
|
||||
$testReq = self::createRequest($testUrl, $xRequestId, 'GET');
|
||||
try {
|
||||
$testRes = $c->send($testReq);
|
||||
} catch (RequestException $ex) {
|
||||
$testRes = $ex->getResponse();
|
||||
if ($testRes && $testRes->getStatusCode() === Response::HTTP_UNAUTHORIZED) {
|
||||
// token is invalid or expired, get a new one
|
||||
echo "[INFO] Bearer token expired or invalid, getting a new one...\n";
|
||||
TokenHelper::clearAllTokens();
|
||||
$bearerToken = TokenHelper::getTokens($user, $password, $url)['access_token'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($client === null) {
|
||||
$client = self::createClient(
|
||||
$user,
|
||||
$password,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream,
|
||||
$timeout,
|
||||
$bearerToken
|
||||
);
|
||||
}
|
||||
|
||||
if (WebdavHelper::isDAVRequest($url) && \str_starts_with($url, OcHelper::getServerUrl())) {
|
||||
$urlHasRemotePhp = \str_contains($url, 'remote.php');
|
||||
if (!WebDavHelper::withRemotePhp() && $urlHasRemotePhp) {
|
||||
throw new Exception("remote.php is disabled but found in the URL: $url");
|
||||
}
|
||||
if (WebDavHelper::withRemotePhp() && !$urlHasRemotePhp) {
|
||||
throw new Exception("remote.php is enabled but not found in the URL: $url");
|
||||
}
|
||||
|
||||
if ($headers && \array_key_exists("Destination", $headers)) {
|
||||
if (!WebDavHelper::withRemotePhp() && $urlHasRemotePhp) {
|
||||
throw new Exception("remote.php is disabled but found in the URL: $url");
|
||||
}
|
||||
if (WebDavHelper::withRemotePhp() && !$urlHasRemotePhp) {
|
||||
throw new Exception("remote.php is enabled but not found in the URL: $url");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$request = self::createRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
|
||||
if ((\getenv('DEBUG_ACCEPTANCE_REQUESTS') !== false) || (\getenv('DEBUG_ACCEPTANCE_API_CALLS') !== false)) {
|
||||
$debugRequests = true;
|
||||
} else {
|
||||
$debugRequests = false;
|
||||
}
|
||||
|
||||
if ($debugRequests) {
|
||||
self::debugRequest($request, $user, $password);
|
||||
}
|
||||
|
||||
// The exceptions that might happen here include:
|
||||
// ConnectException - in that case there is no response. Don't catch the exception.
|
||||
// RequestException - if there is something in the response then pass it back.
|
||||
// Otherwise, re-throw the exception.
|
||||
// GuzzleException - something else unexpected happened. Don't catch the exception.
|
||||
try {
|
||||
$response = $client->send($request);
|
||||
} catch (RequestException $ex) {
|
||||
$response = $ex->getResponse();
|
||||
|
||||
//if the response was null for some reason do not return it but re-throw
|
||||
if ($response === null) {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
HttpLogger::logResponse($response);
|
||||
|
||||
// wait for post-processing to finish if applicable
|
||||
if (WebdavHelper::isDAVRequest($url)
|
||||
&& \str_starts_with($url, OcHelper::getServerUrl())
|
||||
&& \in_array($method, ["PUT", "MOVE", "COPY", "MKCOL"])
|
||||
&& \in_array($response->getStatusCode(), [Response::HTTP_CREATED, Response::HTTP_NO_CONTENT])
|
||||
&& OcConfigHelper::getPostProcessingDelay() === 0
|
||||
) {
|
||||
if (\in_array($method, ["MOVE", "COPY"])) {
|
||||
$url = $headers['Destination'];
|
||||
}
|
||||
WebDavHelper::waitForPostProcessingToFinish(
|
||||
$url,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $method
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param Client|null $client
|
||||
* @param bool|null $isGivenStep
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendRequest(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $method = 'GET',
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?Client $client = null,
|
||||
?bool $isGivenStep = false
|
||||
): ResponseInterface {
|
||||
if ((\getenv('DEBUG_ACCEPTANCE_RESPONSES') !== false) || (\getenv('DEBUG_ACCEPTANCE_API_CALLS') !== false)) {
|
||||
$debugResponses = true;
|
||||
} else {
|
||||
$debugResponses = false;
|
||||
}
|
||||
|
||||
$sendRetryLimit = self::numRetriesOnHttpTooEarly();
|
||||
$sendCount = 0;
|
||||
$sendExceptionHappened = false;
|
||||
do {
|
||||
$response = self::sendRequestOnce(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream,
|
||||
$timeout,
|
||||
$client,
|
||||
);
|
||||
|
||||
if ($response->getStatusCode() >= 400
|
||||
&& $response->getStatusCode() !== self::HTTP_TOO_EARLY
|
||||
&& $response->getStatusCode() !== self::HTTP_CONFLICT
|
||||
) {
|
||||
$sendExceptionHappened = true;
|
||||
}
|
||||
|
||||
if ($debugResponses) {
|
||||
self::debugResponse($response);
|
||||
}
|
||||
$sendCount = $sendCount + 1;
|
||||
// Here we check if the response has status code 425 or is a 409 gotten from a Given step
|
||||
// HTTP_TOO_EARLY (425) can happen if async processing of a previous request is still happening.
|
||||
// For example, if a test uploads a file and then immediately tries to download it.
|
||||
// HTTP_CONFLICT (409) can happen if the user has just been created in the previous step.
|
||||
// The OCS API might not "realize" yet that the user exists. A folder creation (MKCOL) or maybe even
|
||||
// a file upload might return 409.
|
||||
// In all these cases we can try the API request again after a short time.
|
||||
$loopAgain = !$sendExceptionHappened && ($response->getStatusCode() === self::HTTP_TOO_EARLY ||
|
||||
($response->getStatusCode() === self::HTTP_CONFLICT && $isGivenStep)) &&
|
||||
$sendCount <= $sendRetryLimit;
|
||||
if ($loopAgain) {
|
||||
// we need to repeat the send request, because we got HTTP_TOO_EARLY or HTTP_CONFLICT
|
||||
// wait 1 second before sending again, to give the server some time
|
||||
// to finish whatever post-processing it might be doing.
|
||||
echo "[INFO] Received '" . $response->getStatusCode() .
|
||||
"' status code, retrying request ($sendCount)...\n";
|
||||
\sleep(1);
|
||||
}
|
||||
} while ($loopAgain);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Print details about the request.
|
||||
*
|
||||
* @param RequestInterface|null $request
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function debugRequest(?RequestInterface $request, ?string $user, ?string $password): void {
|
||||
print("### AUTH: $user:$password\n");
|
||||
print("### REQUEST: " . $request->getMethod() . " " . $request->getUri() . "\n");
|
||||
self::printHeaders($request->getHeaders());
|
||||
self::printBody($request->getBody());
|
||||
print("\n### END REQUEST\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Print details about the response.
|
||||
*
|
||||
* @param ResponseInterface|null $response
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function debugResponse(?ResponseInterface $response): void {
|
||||
print("### RESPONSE\n");
|
||||
print("Status: " . $response->getStatusCode() . "\n");
|
||||
self::printHeaders($response->getHeaders());
|
||||
self::printBody($response->getBody());
|
||||
print("\n### END RESPONSE\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Print details about the headers.
|
||||
*
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function printHeaders(?array $headers): void {
|
||||
if ($headers) {
|
||||
print("Headers:\n");
|
||||
foreach ($headers as $header => $value) {
|
||||
if (\is_array($value)) {
|
||||
print($header . ": " . \implode(', ', $value) . "\n");
|
||||
} else {
|
||||
print($header . ": " . $value . "\n");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print("Headers: none\n");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print details about the body.
|
||||
*
|
||||
* @param StreamInterface|null $body
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function printBody(?StreamInterface $body): void {
|
||||
print("Body:\n");
|
||||
\var_dump($body->getContents());
|
||||
// Rewind the stream so that later code can read from the start.
|
||||
$body->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the requests to the server in parallel.
|
||||
* This function takes an array of requests and an optional client.
|
||||
* It will send all the requests to the server using the Pool object in guzzle.
|
||||
*
|
||||
* @param array|null $requests
|
||||
* @param Client|null $client
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function sendBatchRequest(
|
||||
?array $requests,
|
||||
?Client $client
|
||||
): array {
|
||||
return Pool::batch($client, $requests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Guzzle Client
|
||||
* This creates a client object that can be used later to send a request object(s)
|
||||
*
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param string|null $bearerToken
|
||||
*
|
||||
* @return Client
|
||||
*/
|
||||
public static function createClient(
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?string $bearerToken = null
|
||||
): Client {
|
||||
$options = [];
|
||||
if ($bearerToken !== null) {
|
||||
$options['headers']['Authorization'] = 'Bearer ' . $bearerToken;
|
||||
} elseif ($user !== null) {
|
||||
$options['auth'] = [$user, $password];
|
||||
}
|
||||
if ($config !== null) {
|
||||
$options['config'] = $config;
|
||||
}
|
||||
if ($cookies !== null) {
|
||||
$options['cookies'] = $cookies;
|
||||
}
|
||||
$options['stream'] = $stream;
|
||||
$options['verify'] = false;
|
||||
$options['timeout'] = $timeout ?: self::getRequestTimeout();
|
||||
return new Client($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an HTTP request based on given parameters.
|
||||
* This creates a RequestInterface object that can be used with a client to send a request.
|
||||
* This enables us to create multiple requests in advance so that we can send them to the server at once in parallel.
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $method
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param string|array $body either the actual string to send in the body,
|
||||
* or an array of key-value pairs to be converted
|
||||
* into a body with http_build_query.
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public static function createRequest(
|
||||
?string $url,
|
||||
?string $xRequestId = '',
|
||||
?string $method = 'GET',
|
||||
?array $headers = null,
|
||||
$body = null
|
||||
): RequestInterface {
|
||||
if ($headers === null) {
|
||||
$headers = [];
|
||||
}
|
||||
if ($xRequestId !== '') {
|
||||
$headers['X-Request-ID'] = $xRequestId;
|
||||
}
|
||||
if (\is_array($body)) {
|
||||
// When creating the client, it is possible to set 'form_params' and
|
||||
// the Client constructor sorts out doing this http_build_query stuff.
|
||||
// But 'new Request' does not have the flexibility to do that.
|
||||
// So we need to do it here.
|
||||
$body = \http_build_query($body, '', '&');
|
||||
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
||||
}
|
||||
|
||||
$request = new Request(
|
||||
$method,
|
||||
$url,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
HttpLogger::logRequest($request);
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* same as HttpRequestHelper::sendRequest() but with "GET" as method
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param boolean $stream
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @see HttpRequestHelper::sendRequest()
|
||||
*/
|
||||
public static function get(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false
|
||||
): ResponseInterface {
|
||||
return self::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'GET',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* same as HttpRequestHelper::sendRequest() but with "POST" as method
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param boolean $stream
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @see HttpRequestHelper::sendRequest()
|
||||
*/
|
||||
public static function post(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false
|
||||
): ResponseInterface {
|
||||
return self::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'POST',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* same as HttpRequestHelper::sendRequest() but with "PUT" as method
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param boolean $stream
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @see HttpRequestHelper::sendRequest()
|
||||
*/
|
||||
public static function put(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false
|
||||
): ResponseInterface {
|
||||
return self::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'PUT',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* same as HttpRequestHelper::sendRequest() but with "DELETE" as method
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param array|null $headers ['X-MyHeader' => 'value']
|
||||
* @param mixed $body
|
||||
* @param array|null $config
|
||||
* @param CookieJar|null $cookies
|
||||
* @param boolean $stream
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @see HttpRequestHelper::sendRequest()
|
||||
*
|
||||
*/
|
||||
public static function delete(
|
||||
?string $url,
|
||||
?string $xRequestId,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = null,
|
||||
$body = null,
|
||||
?array $config = null,
|
||||
?CookieJar $cookies = null,
|
||||
?bool $stream = false
|
||||
): ResponseInterface {
|
||||
return self::sendRequest(
|
||||
$url,
|
||||
$xRequestId,
|
||||
'DELETE',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
$cookies,
|
||||
$stream
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the response as XML and returns a SimpleXMLElement with these
|
||||
* registered namespaces:
|
||||
* | prefix | namespace |
|
||||
* | d | DAV: |
|
||||
* | oc | http://owncloud.org/ns |
|
||||
* | ocs | http://open-collaboration-services.org/ns |
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
* @param string|null $exceptionText text to put at the front of exception messages
|
||||
*
|
||||
* @return SimpleXMLElement
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getResponseXml(ResponseInterface $response, ?string $exceptionText = ''): SimpleXMLElement {
|
||||
// rewind just to make sure we can reparse it in case it was parsed already...
|
||||
$response->getBody()->rewind();
|
||||
$contents = $response->getBody()->getContents();
|
||||
try {
|
||||
$responseXmlObject = new SimpleXMLElement($contents);
|
||||
$responseXmlObject->registerXPathNamespace(
|
||||
'ocs',
|
||||
'http://open-collaboration-services.org/ns'
|
||||
);
|
||||
$responseXmlObject->registerXPathNamespace(
|
||||
'oc',
|
||||
'http://owncloud.org/ns'
|
||||
);
|
||||
$responseXmlObject->registerXPathNamespace(
|
||||
'd',
|
||||
'DAV:'
|
||||
);
|
||||
return $responseXmlObject;
|
||||
} catch (Exception $e) {
|
||||
if ($exceptionText !== '') {
|
||||
$exceptionText = $exceptionText . ' ';
|
||||
}
|
||||
if ($contents === '') {
|
||||
throw new Exception($exceptionText . "Received empty response where XML was expected");
|
||||
}
|
||||
$message = $exceptionText . "Exception parsing response body: \"" . $contents . "\"";
|
||||
throw new Exception($message, 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* parses the body content of $response and returns an array representing the XML
|
||||
* This function returns an array with the following three elements:
|
||||
* * name - The root element name.
|
||||
* * value - The value for the root element.
|
||||
* * attributes - An array of attributes.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function parseResponseAsXml(ResponseInterface $response): array {
|
||||
// rewind so that we can reparse it if it was parsed already
|
||||
$response->getBody()->rewind();
|
||||
$body = $response->getBody()->getContents();
|
||||
$parsedResponse = [];
|
||||
if ($body && \substr($body, 0, 1) === '<') {
|
||||
try {
|
||||
$reader = new Reader();
|
||||
$reader->xml($body);
|
||||
$parsedResponse = $reader->parse();
|
||||
} catch (LibXMLException $e) {
|
||||
// Sometimes the body can be a real page of HTML and text.
|
||||
// So it may not be a complete ordinary piece of XML.
|
||||
// The XML parse might fail with an exception message like:
|
||||
// Opening and ending tag mismatch: link line 31 and head.
|
||||
}
|
||||
}
|
||||
return $parsedResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getRequestTimeout(): int {
|
||||
$timeout = \getenv("REQUEST_TIMEOUT");
|
||||
return (int)$timeout ?: HTTP_REQUEST_TIMEOUT;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns json decoded body content of a json response as an object
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function getJsonDecodedResponseBodyContent(ResponseInterface $response): mixed {
|
||||
return json_decode($response->getBody()->getContents(), null, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function sendScenarioLineReferencesInXRequestId(): bool {
|
||||
return (\getenv("SEND_SCENARIO_LINE_REFERENCES") === "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getXRequestIdRegex(): string {
|
||||
if (self::sendScenarioLineReferencesInXRequestId()) {
|
||||
return '/^[a-zA-Z]+\/[a-zA-Z]+\.feature:\d+(-\d+)?$/';
|
||||
}
|
||||
$host = gethostname();
|
||||
return "/^$host\/.*$/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2023 Sajan Gurung sajan@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use GuzzleHttp\Psr7\Request;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for configuring КуСфера server
|
||||
*/
|
||||
class OcConfigHelper {
|
||||
public static $postProcessingDelay = 0;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getPostProcessingDelay(): int {
|
||||
return self::$postProcessingDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $postProcessingDelay
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setPostProcessingDelay(string $postProcessingDelay): void {
|
||||
// extract number from string
|
||||
$delay = (int) filter_var($postProcessingDelay, FILTER_SANITIZE_NUMBER_INT);
|
||||
self::$postProcessingDelay = $delay;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param ?string $body
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendRequest(
|
||||
string $url,
|
||||
string $method,
|
||||
?string $body = ""
|
||||
): ResponseInterface {
|
||||
$client = HttpRequestHelper::createClient();
|
||||
$request = new Request(
|
||||
$method,
|
||||
$url,
|
||||
[],
|
||||
$body
|
||||
);
|
||||
|
||||
try {
|
||||
$response = $client->send($request);
|
||||
} catch (ConnectException $e) {
|
||||
throw new \Error(
|
||||
"Cannot connect to the ocwrapper at the moment,"
|
||||
. "make sure that ocwrapper is running before proceeding with the test run.\n"
|
||||
. $e->getMessage()
|
||||
);
|
||||
} catch (GuzzleException $ex) {
|
||||
$response = $ex->getResponse();
|
||||
|
||||
if ($response === null) {
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getWrapperUrl(): string {
|
||||
$url = \getenv("OC_WRAPPER_URL");
|
||||
if ($url === false) {
|
||||
$url = "http://localhost:5200";
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $envs
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function reConfigureOc(array $envs): ResponseInterface {
|
||||
$url = self::getWrapperUrl() . "/config";
|
||||
return self::sendRequest($url, "PUT", \json_encode($envs));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function rollbackOc(): ResponseInterface {
|
||||
$url = self::getWrapperUrl() . "/rollback";
|
||||
return self::sendRequest($url, "DELETE");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function stopQsfera(): ResponseInterface {
|
||||
$url = self::getWrapperUrl() . "/stop";
|
||||
return self::sendRequest($url, "POST");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function startQsfera(): ResponseInterface {
|
||||
$url = self::getWrapperUrl() . "/start";
|
||||
return self::sendRequest($url, "POST");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2020 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
|
||||
/**
|
||||
* Class StorageDriver
|
||||
*
|
||||
* @package TestHelpers
|
||||
*/
|
||||
abstract class StorageDriver {
|
||||
public const DECOMPOSED = "DECOMPOSED";
|
||||
public const EOS = "EOS";
|
||||
public const OWNCLOUD = "OWNCLOUD";
|
||||
public const DECOMPOSEDS3 = "DECOMPOSEDS3";
|
||||
public const POSIX = "POSIX";
|
||||
}
|
||||
|
||||
/**
|
||||
* Class OcHelper
|
||||
*
|
||||
* Helper functions that are needed to run tests on КуСфера server
|
||||
*
|
||||
* @package TestHelpers
|
||||
*/
|
||||
class OcHelper {
|
||||
public const STORAGE_DRIVERS = [
|
||||
StorageDriver::DECOMPOSED,
|
||||
StorageDriver::EOS,
|
||||
StorageDriver::OWNCLOUD,
|
||||
StorageDriver::DECOMPOSEDS3,
|
||||
StorageDriver::POSIX
|
||||
];
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getServerUrl(): string {
|
||||
if (\getenv('TEST_SERVER_URL')) {
|
||||
return \getenv('TEST_SERVER_URL');
|
||||
}
|
||||
return 'https://localhost:9200';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getFederatedServerUrl(): string {
|
||||
if (\getenv('TEST_SERVER_FED_URL')) {
|
||||
return \getenv('TEST_SERVER_FED_URL');
|
||||
}
|
||||
return 'https://localhost:10200';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getCollaborationServiceUrl(): string {
|
||||
if (\getenv("COLLABORATION_SERVICE_URL")) {
|
||||
return \getenv("COLLABORATION_SERVICE_URL");
|
||||
}
|
||||
return "http://localhost:9300";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isTestingOnReva(): bool {
|
||||
return (\getenv("TEST_REVA") === "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUsingPreparedLdapUsers(): bool {
|
||||
return (\getenv("USE_PREPARED_LDAP_USERS") === "true");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool|string false if no command given or the command as string
|
||||
*/
|
||||
public static function getDeleteUserDataCommand() {
|
||||
$cmd = \getenv("DELETE_USER_DATA_CMD");
|
||||
if ($cmd === false || \trim($cmd) === "") {
|
||||
return false;
|
||||
}
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getStorageDriver(): string {
|
||||
$storageDriver = (\getenv("STORAGE_DRIVER"));
|
||||
if ($storageDriver === false) {
|
||||
return StorageDriver::DECOMPOSED;
|
||||
}
|
||||
$storageDriver = \strtoupper($storageDriver);
|
||||
if (!\in_array($storageDriver, self::STORAGE_DRIVERS)) {
|
||||
throw new Exception(
|
||||
"Invalid storage driver. " .
|
||||
"STORAGE_DRIVER must be '" . \join(", ", self::STORAGE_DRIVERS) . "'"
|
||||
);
|
||||
}
|
||||
return $storageDriver;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $users
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function deleteRevaUserData(?array $users = []): void {
|
||||
$deleteCmd = self::getDeleteUserDataCommand();
|
||||
|
||||
if (self::getStorageDriver() === StorageDriver::POSIX) {
|
||||
\exec($deleteCmd);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($users as $user) {
|
||||
if (\is_array($user)) {
|
||||
$user = $user["actualUsername"];
|
||||
}
|
||||
if ($deleteCmd === false) {
|
||||
if (self::getStorageDriver() === StorageDriver::OWNCLOUD) {
|
||||
self::recurseRmdir(self::getOcRevaDataRoot() . $user);
|
||||
}
|
||||
continue;
|
||||
} elseif (self::getStorageDriver() === StorageDriver::EOS) {
|
||||
$deleteCmd = \str_replace(
|
||||
"%s",
|
||||
$user[0] . '/' . $user,
|
||||
$deleteCmd
|
||||
);
|
||||
} else {
|
||||
$deleteCmd = \sprintf($deleteCmd, $user);
|
||||
}
|
||||
\exec($deleteCmd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for Recursive Copy of file/folder
|
||||
* For more info check this out https://gist.github.com/gserrano/4c9648ec9eb293b9377b
|
||||
*
|
||||
* @param string|null $source
|
||||
* @param string|null $destination
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function recurseCopy(?string $source, ?string $destination): void {
|
||||
$dir = \opendir($source);
|
||||
@\mkdir($destination);
|
||||
while (($file = \readdir($dir)) !== false) {
|
||||
if (($file != '.') && ($file != '..')) {
|
||||
if (\is_dir($source . '/' . $file)) {
|
||||
self::recurseCopy($source . '/' . $file, $destination . '/' . $file);
|
||||
} else {
|
||||
\copy($source . '/' . $file, $destination . '/' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
\closedir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public static function getLdapPort(): int {
|
||||
$port = \getenv("REVA_LDAP_PORT");
|
||||
return $port ? (int)$port : 636;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function useSsl(): bool {
|
||||
$useSsl = \getenv("REVA_LDAP_USESSL");
|
||||
if ($useSsl === false) {
|
||||
return (self::getLdapPort() === 636);
|
||||
} else {
|
||||
return $useSsl === "true";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getBaseDN(): string {
|
||||
$dn = \getenv("REVA_LDAP_BASE_DN");
|
||||
return $dn ?: "dc=example,dc=org";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getGroupsOU(): string {
|
||||
$ou = \getenv("REVA_LDAP_GROUPS_OU");
|
||||
return $ou ?: "TestGroups";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getUsersOU(): string {
|
||||
$ou = \getenv("REVA_LDAP_USERS_OU");
|
||||
return $ou ?: "TestUsers";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getGroupSchema(): string {
|
||||
$schema = \getenv("REVA_LDAP_GROUP_SCHEMA");
|
||||
return $schema ?: "rfc2307";
|
||||
}
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getHostname(): string {
|
||||
$hostname = \getenv("REVA_LDAP_HOSTNAME");
|
||||
return $hostname ?: "localhost";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getBindDN(): string {
|
||||
$dn = \getenv("REVA_LDAP_BIND_DN");
|
||||
return $dn ?: "cn=admin,dc=example,dc=org";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function getBindPassword(): string {
|
||||
$pw = \getenv("REVA_LDAP_BIND_PASSWORD");
|
||||
return $pw ?: "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getOcRevaDataRoot(): string {
|
||||
$root = \getenv("OC_REVA_DATA_ROOT");
|
||||
if ($root === false || $root === "") {
|
||||
$root = "/var/tmp/qsfera/qsfera/";
|
||||
}
|
||||
if (!\file_exists($root)) {
|
||||
echo "WARNING: reva data root folder ($root) does not exist\n";
|
||||
}
|
||||
return $root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $dir
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function recurseRmdir(?string $dir): bool {
|
||||
if (\file_exists($dir) === true) {
|
||||
$files = \array_diff(\scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
if (\is_dir("$dir/$file")) {
|
||||
self::recurseRmdir("$dir/$file");
|
||||
} else {
|
||||
\unlink("$dir/$file");
|
||||
}
|
||||
}
|
||||
return \rmdir($dir);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* On Eos storage backend when the user data is cleared after test run
|
||||
* Running another test immediately fails. So Send this request to create user home directory
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function createEOSStorageHome(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $xRequestId = ''
|
||||
): void {
|
||||
HttpRequestHelper::get(
|
||||
$baseUrl . "/ocs/v2.php/apps/notifications/api/v1/notifications",
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Viktor Scharf <scharf.vi@gmail.com>
|
||||
* @copyright Copyright (c) 2024 Viktor Scharf <scharf.vi@gmail.com>
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* A helper class for managing federation server requests
|
||||
*/
|
||||
class OcmHelper {
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getRequestHeaders(): array {
|
||||
return [
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getFullUrl(string $baseUrl, string $path): string {
|
||||
$fullUrl = $baseUrl;
|
||||
if (\substr($fullUrl, -1) !== '/') {
|
||||
$fullUrl .= '/';
|
||||
}
|
||||
$fullUrl .= 'sciencemesh/' . $path;
|
||||
return $fullUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string|null $email
|
||||
* @param string|null $description
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function createInvitation(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
?string $email = null,
|
||||
?string $description = null
|
||||
): ResponseInterface {
|
||||
$body = [
|
||||
"description" => $description,
|
||||
"recipient" => $email
|
||||
];
|
||||
$url = self::getFullUrl($baseUrl, 'generate-invite');
|
||||
return HttpRequestHelper::post(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders(),
|
||||
\json_encode($body)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $token
|
||||
* @param string $providerDomain
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function acceptInvitation(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $token,
|
||||
string $providerDomain
|
||||
): ResponseInterface {
|
||||
$body = [
|
||||
"token" => $token,
|
||||
"providerDomain" => $providerDomain
|
||||
];
|
||||
$url = self::getFullUrl($baseUrl, 'accept-invite');
|
||||
return HttpRequestHelper::post(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders(),
|
||||
\json_encode($body)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function findAcceptedUsers(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, 'find-accepted-users');
|
||||
return HttpRequestHelper::get(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function listInvite(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, 'list-invite');
|
||||
return HttpRequestHelper::get(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $xRequestId
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $userId
|
||||
* @param string $idp
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function deleteConnection(
|
||||
string $baseUrl,
|
||||
string $xRequestId,
|
||||
string $user,
|
||||
string $password,
|
||||
string $userId,
|
||||
string $idp
|
||||
): ResponseInterface {
|
||||
$url = self::getFullUrl($baseUrl, 'delete-accepted-user');
|
||||
$body = [
|
||||
"idp" => $idp,
|
||||
"user_id" => $userId
|
||||
];
|
||||
return HttpRequestHelper::delete(
|
||||
$url,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
self::getRequestHeaders(),
|
||||
\json_encode($body)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Helper to make requests to the OCS API
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class OcsApiHelper {
|
||||
/**
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user if set to null no authentication header will be sent
|
||||
* @param string|null $password
|
||||
* @param string|null $method HTTP Method
|
||||
* @param string|null $path
|
||||
* @param string|null $xRequestId
|
||||
* @param mixed $body array of key, value pairs e.g ['value' => 'yes']
|
||||
* @param int|null $ocsApiVersion (1|2) default 2
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function sendRequest(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $method,
|
||||
?string $path,
|
||||
?string $xRequestId = '',
|
||||
$body = [],
|
||||
?int $ocsApiVersion = 2,
|
||||
?array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = $baseUrl;
|
||||
if (\substr($fullUrl, -1) !== '/') {
|
||||
$fullUrl .= '/';
|
||||
}
|
||||
$fullUrl .= "ocs/v$ocsApiVersion.php" . $path;
|
||||
$headers['OCS-APIREQUEST'] = true;
|
||||
return HttpRequestHelper::sendRequest($fullUrl, $xRequestId, $method, $user, $password, $headers, $body);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $method HTTP Method
|
||||
* @param string|null $path
|
||||
* @param string|null $xRequestId
|
||||
* @param mixed $body array of key, value pairs e.g ['value' => 'yes']
|
||||
* @param int|null $ocsApiVersion (1|2) default 2
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return RequestInterface
|
||||
*/
|
||||
public static function createOcsRequest(
|
||||
?string $baseUrl,
|
||||
?string $method,
|
||||
?string $path,
|
||||
?string $xRequestId = '',
|
||||
$body = [],
|
||||
?int $ocsApiVersion = 2,
|
||||
?array $headers = []
|
||||
): RequestInterface {
|
||||
$fullUrl = $baseUrl;
|
||||
if (\substr($fullUrl, -1) !== '/') {
|
||||
$fullUrl .= '/';
|
||||
}
|
||||
$fullUrl .= "ocs/v$ocsApiVersion.php" . $path;
|
||||
return HttpRequestHelper::createRequest(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Sajan Gurung sajan@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
/**
|
||||
* A helper class for КуСфера settings
|
||||
*/
|
||||
class SettingsHelper {
|
||||
private static string $settingsEndpoint = '/api/v0/settings/';
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function buildFullUrl(string $baseUrl, string $path): string {
|
||||
return $baseUrl . self::$settingsEndpoint . $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getBundlesList(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "bundles-list");
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
"{}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $bundleName
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getBundleByName(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $bundleName,
|
||||
string $xRequestId
|
||||
): array {
|
||||
$response = self::getBundlesList($baseUrl, $user, $password, $xRequestId);
|
||||
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get bundles list");
|
||||
|
||||
$bundlesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
|
||||
foreach ($bundlesList->bundles as $value) {
|
||||
if ($value->displayName === $bundleName) {
|
||||
return json_decode(json_encode($value), true);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getRolesList(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "roles-list");
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
"{}"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $assigneeId
|
||||
* @param string $roleId
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function assignRoleToUser(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $assigneeId,
|
||||
string $roleId,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "assignments-add");
|
||||
$body = json_encode(["account_uuid" => $assigneeId, "role_id" => $roleId], JSON_THROW_ON_ERROR);
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $userId
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getAssignmentsList(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $userId,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "assignments-list");
|
||||
$body = json_encode(["account_uuid" => $userId], JSON_THROW_ON_ERROR);
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getValuesList(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "values-list");
|
||||
$body = json_encode(["account_uuid" => "me"], JSON_THROW_ON_ERROR);
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getAutoAcceptSharesSettingValue(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId
|
||||
): bool {
|
||||
$response = self::getValuesList($baseUrl, $user, $password, $xRequestId);
|
||||
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get values list");
|
||||
|
||||
$valuesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
|
||||
|
||||
if (empty($valuesList)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($valuesList->values as $value) {
|
||||
if ($value->identifier->setting === "auto-accept-shares") {
|
||||
return $value->value->boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getLanguageSettingValue(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId
|
||||
): string {
|
||||
$response = self::getValuesList($baseUrl, $user, $password, $xRequestId);
|
||||
Assert::assertEquals(201, $response->getStatusCode(), "Failed to get values list");
|
||||
|
||||
$valuesList = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
|
||||
|
||||
// if no language is set, the request body is empty return English as the default language
|
||||
if (empty($valuesList)) {
|
||||
return "en";
|
||||
}
|
||||
foreach ($valuesList->values as $value) {
|
||||
if ($value->identifier->setting === "language") {
|
||||
return $value->value->listValue->values[0]->stringValue;
|
||||
}
|
||||
}
|
||||
// if a language setting was still not found, return English
|
||||
return "en";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $body
|
||||
* @param string $xRequestId
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function updateSettings(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $body,
|
||||
string $xRequestId,
|
||||
array $headers = []
|
||||
): ResponseInterface {
|
||||
$fullUrl = self::buildFullUrl($baseUrl, "values-save");
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$body
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
|
||||
use Behat\Testwork\Hook\Scope\HookScope;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Exception;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* Helper to set up UI / Integration tests
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class SetupHelper extends \PHPUnit\Framework\Assert {
|
||||
private static ?string $baseUrl = null;
|
||||
private static ?string $adminUsername = null;
|
||||
private static ?string $adminPassword = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param HookScope $scope
|
||||
*
|
||||
* @return array of suite context parameters
|
||||
*/
|
||||
public static function getSuiteParameters(HookScope $scope): array {
|
||||
return $scope->getEnvironment()->getSuite()
|
||||
->getSettings() ['context'] ['parameters'];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $baseUrl
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init(
|
||||
?string $adminUsername,
|
||||
?string $adminPassword,
|
||||
?string $baseUrl
|
||||
): void {
|
||||
foreach (\func_get_args() as $variableToCheck) {
|
||||
if (!\is_string($variableToCheck)) {
|
||||
throw new \InvalidArgumentException(
|
||||
"mandatory argument missing or wrong type ($variableToCheck => "
|
||||
. \gettype($variableToCheck) . ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
self::$adminUsername = $adminUsername;
|
||||
self::$adminPassword = $adminPassword;
|
||||
self::$baseUrl = \rtrim($baseUrl, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return SimpleXMLElement
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getSysInfo(
|
||||
?string $baseUrl,
|
||||
?string $adminUsername,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = ''
|
||||
): SimpleXMLElement {
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"GET",
|
||||
"/apps/testing/api/v1/sysinfo",
|
||||
$xRequestId
|
||||
);
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not get sysinfo " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
return HttpRequestHelper::getResponseXml($result, __METHOD__)->data;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getServerRoot(
|
||||
?string $baseUrl,
|
||||
?string $adminUsername,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = ''
|
||||
): string {
|
||||
$sysInfo = self::getSysInfo(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
$xRequestId
|
||||
);
|
||||
// server_root is a SimpleXMLElement object that "wraps" a string.
|
||||
/// We want the bare string.
|
||||
return (string) $sysInfo->server_root;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $callerName
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function checkAdminUsername(?string $adminUsername, ?string $callerName): ?string {
|
||||
if (self::$adminUsername === null
|
||||
&& $adminUsername === null
|
||||
) {
|
||||
throw new Exception(
|
||||
"$callerName called without adminUsername - pass the username or call SetupHelper::init"
|
||||
);
|
||||
}
|
||||
if ($adminUsername === null) {
|
||||
$adminUsername = self::$adminUsername;
|
||||
}
|
||||
return $adminUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $callerName
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function checkAdminPassword(?string $adminPassword, ?string $callerName): string {
|
||||
if (self::$adminPassword === null
|
||||
&& $adminPassword === null
|
||||
) {
|
||||
throw new Exception(
|
||||
"$callerName called without adminPassword - pass the password or call SetupHelper::init"
|
||||
);
|
||||
}
|
||||
if ($adminPassword === null) {
|
||||
$adminPassword = self::$adminPassword;
|
||||
}
|
||||
return $adminPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $callerName
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function checkBaseUrl(?string $baseUrl, ?string $callerName): ?string {
|
||||
if (self::$baseUrl === null
|
||||
&& $baseUrl === null
|
||||
) {
|
||||
throw new Exception(
|
||||
"$callerName called without baseUrl - pass the baseUrl or call SetupHelper::init"
|
||||
);
|
||||
}
|
||||
if ($baseUrl === null) {
|
||||
$baseUrl = self::$baseUrl;
|
||||
}
|
||||
return $baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $dirPathFromServerRoot e.g. 'apps2/myapp/appinfo'
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function mkDirOnServer(
|
||||
?string $dirPathFromServerRoot,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): void {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "mkDirOnServer");
|
||||
$adminUsername = self::checkAdminUsername($adminUsername, "mkDirOnServer");
|
||||
$adminPassword = self::checkAdminPassword($adminPassword, "mkDirOnServer");
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"POST",
|
||||
"/apps/testing/api/v1/dir",
|
||||
$xRequestId,
|
||||
['dir' => $dirPathFromServerRoot]
|
||||
);
|
||||
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not create directory $dirPathFromServerRoot " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $dirPathFromServerRoot e.g. 'apps2/myapp/appinfo'
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function rmDirOnServer(
|
||||
?string $dirPathFromServerRoot,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): void {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "rmDirOnServer");
|
||||
$adminUsername = self::checkAdminUsername($adminUsername, "rmDirOnServer");
|
||||
$adminPassword = self::checkAdminPassword($adminPassword, "rmDirOnServer");
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"DELETE",
|
||||
"/apps/testing/api/v1/dir",
|
||||
$xRequestId,
|
||||
['dir' => $dirPathFromServerRoot]
|
||||
);
|
||||
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not delete directory $dirPathFromServerRoot " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $filePathFromServerRoot e.g. 'app2/myapp/appinfo/info.xml'
|
||||
* @param string|null $content
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function createFileOnServer(
|
||||
?string $filePathFromServerRoot,
|
||||
?string $content,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): void {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "createFileOnServer");
|
||||
$adminUsername = self::checkAdminUsername($adminUsername, "createFileOnServer");
|
||||
$adminPassword = self::checkAdminPassword($adminPassword, "createFileOnServer");
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"POST",
|
||||
"/apps/testing/api/v1/file",
|
||||
$xRequestId,
|
||||
[
|
||||
'file' => $filePathFromServerRoot,
|
||||
'content' => $content
|
||||
]
|
||||
);
|
||||
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not create file $filePathFromServerRoot " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $filePathFromServerRoot e.g. 'app2/myapp/appinfo/info.xml'
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function deleteFileOnServer(
|
||||
?string $filePathFromServerRoot,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): void {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "deleteFileOnServer");
|
||||
$adminUsername = self::checkAdminUsername($adminUsername, "deleteFileOnServer");
|
||||
$adminPassword = self::checkAdminPassword($adminPassword, "deleteFileOnServer");
|
||||
$result = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
"DELETE",
|
||||
"/apps/testing/api/v1/file",
|
||||
$xRequestId,
|
||||
[
|
||||
'file' => $filePathFromServerRoot
|
||||
]
|
||||
);
|
||||
|
||||
if ($result->getStatusCode() !== 200) {
|
||||
throw new \Exception(
|
||||
"could not delete file $filePathFromServerRoot " . $result->getReasonPhrase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $fileInCore e.g. 'app2/myapp/appinfo/info.xml'
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUsername
|
||||
* @param string|null $adminPassword
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function readFileFromServer(
|
||||
?string $fileInCore,
|
||||
?string $xRequestId = '',
|
||||
?string $baseUrl = null,
|
||||
?string $adminUsername = null,
|
||||
?string $adminPassword = null
|
||||
): string {
|
||||
$baseUrl = self::checkBaseUrl($baseUrl, "readFile");
|
||||
$adminUsername = self::checkAdminUsername(
|
||||
$adminUsername,
|
||||
"readFile"
|
||||
);
|
||||
$adminPassword = self::checkAdminPassword(
|
||||
$adminPassword,
|
||||
"readFile"
|
||||
);
|
||||
|
||||
$response = OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUsername,
|
||||
$adminPassword,
|
||||
'GET',
|
||||
"/apps/testing/api/v1/file?file=$fileInCore",
|
||||
$xRequestId
|
||||
);
|
||||
self::assertSame(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to read the file $fileInCore"
|
||||
);
|
||||
$localContent = HttpRequestHelper::getResponseXml($response, __METHOD__);
|
||||
$localContent = (string)$localContent->data->element->contentUrlEncoded;
|
||||
return \urldecode($localContent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use InvalidArgumentException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use SimpleXMLElement;
|
||||
|
||||
/**
|
||||
* manage Shares via OCS API
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class SharingHelper {
|
||||
public const PERMISSION_TYPES = [
|
||||
'read' => 1,
|
||||
'update' => 2,
|
||||
'create' => 4,
|
||||
'delete' => 8,
|
||||
'invite' => 0
|
||||
];
|
||||
|
||||
public const SHARE_TYPES = [
|
||||
'user' => 0,
|
||||
'group' => 1,
|
||||
'public_link' => 3,
|
||||
'federated' => 6,
|
||||
];
|
||||
|
||||
public const SHARE_STATES = [
|
||||
'accepted' => 0,
|
||||
'pending' => 1,
|
||||
'rejected' => 2,
|
||||
'declined' => 2, // declined is a synonym for rejected.
|
||||
];
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $baseUrl baseURL of the КуСфера installation without /ocs.
|
||||
* @param string $user user that creates the share.
|
||||
* @param string $password password of the user that creates the share.
|
||||
* @param string $path The path to the file or folder which should be shared.
|
||||
* @param string|int $shareType The type of the share. This can be one of:
|
||||
* 0 = user, 1 = group, 3 = public (link),
|
||||
* 6 = federated (cloud share).
|
||||
* Pass either the number or the keyword.
|
||||
* @param string $xRequestId
|
||||
* @param string|null $shareWith The user or group id with which the file should
|
||||
* be shared.
|
||||
* @param boolean|null $publicUpload Whether to allow public upload to a public
|
||||
* shared folder.
|
||||
* @param string|null $sharePassword The password to protect the public link
|
||||
* share with.
|
||||
* @param string|int|string[]|int[]|null $permissions The permissions to set on the share.
|
||||
* 1 = read; 2 = update; 4 = create;
|
||||
* 8 = delete; 16 = share
|
||||
* (default: 31, for public shares: 1)
|
||||
* Pass either the (total) number or array of numbers,
|
||||
* or any of the above keywords or array of keywords.
|
||||
* @param string|null $linkName A (human-readable) name for the share,
|
||||
* which can be up to 64 characters in length.
|
||||
* @param string|null $expireDate An expire date for public link shares.
|
||||
* This argument expects a date string
|
||||
* in the format 'YYYY-MM-DD'.
|
||||
* @param string|null $space_ref
|
||||
* @param int $ocsApiVersion
|
||||
* @param int $sharingApiVersion
|
||||
* @param string $sharingApp
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws InvalidArgumentException|GuzzleException
|
||||
*/
|
||||
public static function createShare(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $path,
|
||||
$shareType,
|
||||
string $xRequestId = '',
|
||||
?string $shareWith = null,
|
||||
?bool $publicUpload = false,
|
||||
?string $sharePassword = null,
|
||||
$permissions = null,
|
||||
?string $linkName = null,
|
||||
?string $expireDate = null,
|
||||
?string $space_ref = null,
|
||||
int $ocsApiVersion = 1,
|
||||
int $sharingApiVersion = 1,
|
||||
string $sharingApp = 'files_sharing'
|
||||
): ResponseInterface {
|
||||
$fd = [];
|
||||
foreach ([$path, $baseUrl, $user, $password] as $variableToCheck) {
|
||||
if (!\is_string($variableToCheck)) {
|
||||
throw new InvalidArgumentException(
|
||||
"mandatory argument missing or wrong type ($variableToCheck => "
|
||||
. \gettype($variableToCheck) . ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($permissions !== null) {
|
||||
$fd['permissions'] = self::getPermissionSum($permissions);
|
||||
} elseif ($shareType !== "public_link") {
|
||||
// sharing without permissions should automatically set the permission to 15 for share and 1 for public link
|
||||
$fd['permissions'] = 15;
|
||||
}
|
||||
|
||||
if (!\in_array($ocsApiVersion, [1, 2], true)) {
|
||||
throw new InvalidArgumentException(
|
||||
"invalid ocsApiVersion ($ocsApiVersion)"
|
||||
);
|
||||
}
|
||||
if (!\in_array($sharingApiVersion, [1, 2], true)) {
|
||||
throw new InvalidArgumentException(
|
||||
"invalid sharingApiVersion ($sharingApiVersion)"
|
||||
);
|
||||
}
|
||||
|
||||
$fullUrl = $baseUrl;
|
||||
if (\substr($fullUrl, -1) !== '/') {
|
||||
$fullUrl .= '/';
|
||||
}
|
||||
$fullUrl .= "ocs/v$ocsApiVersion.php/apps/$sharingApp/api/v$sharingApiVersion/shares";
|
||||
|
||||
$fd['path'] = $path;
|
||||
$fd['shareType'] = self::getShareType($shareType);
|
||||
|
||||
if ($shareWith !== null) {
|
||||
$fd['shareWith'] = $shareWith;
|
||||
}
|
||||
if ($publicUpload !== null) {
|
||||
$fd['publicUpload'] = $publicUpload;
|
||||
}
|
||||
if ($sharePassword !== null) {
|
||||
$fd['password'] = $sharePassword;
|
||||
}
|
||||
if ($linkName !== null) {
|
||||
$fd['name'] = $linkName;
|
||||
}
|
||||
if ($expireDate !== null) {
|
||||
$fd['expireDate'] = $expireDate;
|
||||
}
|
||||
if ($space_ref !== null) {
|
||||
$fd['space_ref'] = $space_ref;
|
||||
}
|
||||
$headers = ['OCS-APIREQUEST' => 'true'];
|
||||
return HttpRequestHelper::post(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$fd
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the permission sum (int) from the given permissions.
|
||||
* Permissions can be passed in as int, string or array of int or string
|
||||
* 'read' => 1
|
||||
* 'update' => 2
|
||||
* 'create' => 4
|
||||
* 'delete' => 8
|
||||
* 'share' => 16
|
||||
* 'invite' => 0
|
||||
*
|
||||
* @param string[]|string|int|int[] $permissions
|
||||
*
|
||||
* @return int
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
*/
|
||||
public static function getPermissionSum($permissions): int {
|
||||
if (\is_numeric($permissions)) {
|
||||
// Allow any permission number so that test scenarios can
|
||||
// specifically test invalid permission values
|
||||
return (int) $permissions;
|
||||
}
|
||||
if (!\is_array($permissions)) {
|
||||
$permissions = [$permissions];
|
||||
}
|
||||
$permissionSum = 0;
|
||||
foreach ($permissions as $permission) {
|
||||
if (\array_key_exists($permission, self::PERMISSION_TYPES)) {
|
||||
$permissionSum += self::PERMISSION_TYPES[$permission];
|
||||
} elseif (\in_array($permission, self::PERMISSION_TYPES, true)) {
|
||||
$permissionSum += $permission;
|
||||
} else {
|
||||
throw new InvalidArgumentException(
|
||||
"invalid permission type ($permission)"
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($permissionSum < 0 || $permissionSum > 31) {
|
||||
throw new InvalidArgumentException(
|
||||
"invalid permission total ($permissionSum)"
|
||||
);
|
||||
}
|
||||
return $permissionSum;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the share type number corresponding to the share type keyword
|
||||
*
|
||||
* @param string|int $shareType a keyword from SHARE_TYPES or a share type number
|
||||
*
|
||||
* @return int
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
*/
|
||||
public static function getShareType($shareType): int {
|
||||
if (\array_key_exists($shareType, self::SHARE_TYPES)) {
|
||||
return self::SHARE_TYPES[$shareType];
|
||||
} else {
|
||||
if (\ctype_digit($shareType)) {
|
||||
$shareType = (int) $shareType;
|
||||
}
|
||||
$key = \array_search($shareType, self::SHARE_TYPES, true);
|
||||
if ($key !== false) {
|
||||
return self::SHARE_TYPES[$key];
|
||||
}
|
||||
throw new InvalidArgumentException(
|
||||
"invalid share type ($shareType)"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param SimpleXMLElement $responseXmlObject
|
||||
* @param string $errorMessage
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
public static function getLastShareIdFromResponse(
|
||||
SimpleXMLElement $responseXmlObject,
|
||||
string $errorMessage = "cannot find share id in response"
|
||||
): string {
|
||||
$xmlPart = $responseXmlObject->xpath("//data/element[last()]/id");
|
||||
|
||||
if (!\is_array($xmlPart) || (\count($xmlPart) === 0)) {
|
||||
throw new Exception($errorMessage);
|
||||
}
|
||||
return $xmlPart[0]->__toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Sagar Gurung <sagar@jankatitech.com>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class SpaceNotFoundException
|
||||
* Exception when space id for a user is not found
|
||||
*/
|
||||
class SpaceNotFoundException extends Exception {
|
||||
}
|
||||
@@ -0,0 +1,373 @@
|
||||
<?php
|
||||
/**
|
||||
* @author Viktor Scharf <v.scharf@qsfera.eu>
|
||||
* @copyright Copyright (c) 2025 Viktor Scharf <v.scharf@qsfera.eu>
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
/**
|
||||
* Helper for obtaining bearer tokens for users
|
||||
*/
|
||||
class TokenHelper {
|
||||
private const LOGON_URL = '/signin/v1/identifier/_/logon';
|
||||
private const REDIRECT_URL = '/oidc-callback.html';
|
||||
private const TOKEN_URL = '/konnect/v1/token';
|
||||
|
||||
// Static cache [username => token_data]
|
||||
private static array $tokenCache = [];
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function useBearerToken(): bool {
|
||||
return \getenv('USE_BEARER_TOKEN') === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts base URL from a full URL
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string the base URL
|
||||
*/
|
||||
private static function extractBaseUrl(string $url): string {
|
||||
return preg_replace('#(https?://[^/]+).*#', '$1', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get access and refresh tokens for a user
|
||||
* Uses cache to avoid unnecessary requests
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $url
|
||||
*
|
||||
* @return array ['access_token' => string, 'refresh_token' => string, 'expires_at' => int]
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getTokens(string $username, string $password, string $url): array {
|
||||
// Extract base URL. I need to send $url to get correct server in case of multiple servers (ocm suite)
|
||||
$baseUrl = self::extractBaseUrl($url);
|
||||
$cacheKey = $username . '|' . $baseUrl;
|
||||
|
||||
// Check cache
|
||||
if (isset(self::$tokenCache[$cacheKey])) {
|
||||
$cachedToken = self::$tokenCache[$cacheKey];
|
||||
|
||||
// Check if access token has expired
|
||||
if (time() < $cachedToken['expires_at']) {
|
||||
return $cachedToken;
|
||||
}
|
||||
|
||||
$refreshedToken = self::refreshToken($cachedToken['refresh_token'], $baseUrl);
|
||||
$tokenData = [
|
||||
'access_token' => $refreshedToken['access_token'],
|
||||
'refresh_token' => $refreshedToken['refresh_token'],
|
||||
// set expiry to 240 (4 minutes) seconds to allow for some buffer
|
||||
// token actually expires in 300 seconds (5 minutes)
|
||||
'expires_at' => time() + 240
|
||||
];
|
||||
self::$tokenCache[$cacheKey] = $tokenData;
|
||||
return $tokenData;
|
||||
}
|
||||
|
||||
// Get new tokens
|
||||
$cookieJar = new CookieJar();
|
||||
|
||||
$continueUrl = self::getAuthorizedEndPoint($username, $password, $baseUrl, $cookieJar);
|
||||
$code = self::getCode($continueUrl, $baseUrl, $cookieJar);
|
||||
$tokens = self::getToken($code, $baseUrl, $cookieJar);
|
||||
|
||||
$tokenData = [
|
||||
'access_token' => $tokens['access_token'],
|
||||
'refresh_token' => $tokens['refresh_token'],
|
||||
// set expiry to 240 (4 minutes) seconds to allow for some buffer
|
||||
// token actually expires in 300 seconds (5 minutes)
|
||||
'expires_at' => time() + 240
|
||||
];
|
||||
|
||||
// Save to cache
|
||||
self::$tokenCache[$cacheKey] = $tokenData;
|
||||
|
||||
return $tokenData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh token
|
||||
*
|
||||
* @param string $refreshToken
|
||||
* @param string $baseUrl
|
||||
*
|
||||
* @return array
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function refreshToken(string $refreshToken, string $baseUrl): array {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false
|
||||
]
|
||||
);
|
||||
|
||||
$response = $client->post(
|
||||
$baseUrl . self::TOKEN_URL,
|
||||
[
|
||||
'form_params' => [
|
||||
'client_id' => 'web',
|
||||
'refresh_token' => $refreshToken,
|
||||
'grant_type' => 'refresh_token'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
'Token refresh failed: Expected status code 200 but received ' . $response->getStatusCode()
|
||||
);
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
Assert::assertArrayHasKey('access_token', $data, 'Missing access_token in refresh response');
|
||||
Assert::assertArrayHasKey('refresh_token', $data, 'Missing refresh_token in refresh response');
|
||||
|
||||
return [
|
||||
'access_token' => $data['access_token'],
|
||||
'refresh_token' => $data['refresh_token']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cached tokens for a specific user
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $url
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearUserTokens(string $username, string $url): void {
|
||||
$baseUrl = self::extractBaseUrl($url);
|
||||
$cacheKey = $username . '|' . $baseUrl;
|
||||
unset(self::$tokenCache[$cacheKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all cached tokens
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearAllTokens(): void {
|
||||
self::$tokenCache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return \Psr\Http\Message\ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function makeLoginRequest(
|
||||
string $username,
|
||||
string $password,
|
||||
string $baseUrl,
|
||||
CookieJar $cookieJar
|
||||
): \Psr\Http\Message\ResponseInterface {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false,
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
return $client->post(
|
||||
$baseUrl . self::LOGON_URL,
|
||||
[
|
||||
'headers' => [
|
||||
'Kopano-Konnect-XSRF' => '1',
|
||||
'Referer' => $baseUrl,
|
||||
'Content-Type' => 'application/json'
|
||||
],
|
||||
'json' => [
|
||||
'params' => [$username, $password, '1'],
|
||||
'hello' => [
|
||||
'scope' => 'openid profile offline_access email',
|
||||
'client_id' => 'web',
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'flow' => 'oidc'
|
||||
]
|
||||
]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 1: Login and get continue_uri
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getAuthorizedEndPoint(
|
||||
string $username,
|
||||
string $password,
|
||||
string $baseUrl,
|
||||
CookieJar $cookieJar
|
||||
): string {
|
||||
$response = self::makeLoginRequest($username, $password, $baseUrl, $cookieJar);
|
||||
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
'Logon failed: Expected status code 200 but received: ' . $response->getStatusCode()
|
||||
);
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
|
||||
Assert::assertArrayHasKey(
|
||||
'hello',
|
||||
$data,
|
||||
'Logon response does not contain "hello" object'
|
||||
);
|
||||
|
||||
Assert::assertArrayHasKey(
|
||||
'continue_uri',
|
||||
$data['hello'],
|
||||
'Missing continue_uri in logon response'
|
||||
);
|
||||
|
||||
return $data['hello']['continue_uri'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 2: Authorization and get code
|
||||
*
|
||||
* @param string $continueUrl
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function getCode(string $continueUrl, string $baseUrl, CookieJar $cookieJar): string {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false, // Disable automatic redirects
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
$params = [
|
||||
'client_id' => 'web',
|
||||
'prompt' => 'none',
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'response_mode' => 'query',
|
||||
'response_type' => 'code',
|
||||
'scope' => 'openid profile offline_access email'
|
||||
];
|
||||
|
||||
$response = $client->get(
|
||||
$continueUrl,
|
||||
[
|
||||
'query' => $params
|
||||
]
|
||||
);
|
||||
|
||||
Assert::assertEquals(
|
||||
302,
|
||||
$response->getStatusCode(),
|
||||
'Authorization request failed: Expected status code 302 but received: ' . $response->getStatusCode()
|
||||
);
|
||||
|
||||
$location = $response->getHeader('Location')[0] ?? '';
|
||||
Assert::assertNotEmpty($location, 'Missing Location header in authorization response');
|
||||
|
||||
parse_str(parse_url($location, PHP_URL_QUERY), $queryParams);
|
||||
Assert::assertArrayHasKey('code', $queryParams, 'Missing code parameter in redirect URL');
|
||||
return $queryParams['code'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Step 3: Get token
|
||||
*
|
||||
* @param string $code
|
||||
* @param string $baseUrl
|
||||
* @param CookieJar $cookieJar
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*
|
||||
*/
|
||||
private static function getToken(string $code, string $baseUrl, CookieJar $cookieJar): array {
|
||||
$client = new Client(
|
||||
[
|
||||
'verify' => false,
|
||||
'http_errors' => false,
|
||||
'allow_redirects' => false,
|
||||
'cookies' => $cookieJar
|
||||
]
|
||||
);
|
||||
|
||||
$response = $client->post(
|
||||
$baseUrl . self::TOKEN_URL,
|
||||
[
|
||||
'form_params' => [
|
||||
'client_id' => 'web',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $baseUrl . self::REDIRECT_URL,
|
||||
'grant_type' => 'authorization_code'
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
'Token request failed: Expected status code 200 but received: ' . $response->getStatusCode()
|
||||
);
|
||||
|
||||
$data = json_decode($response->getBody()->getContents(), true);
|
||||
Assert::assertArrayHasKey('access_token', $data, 'Missing access_token in token response');
|
||||
Assert::assertArrayHasKey('refresh_token', $data, 'Missing refresh_token in token response');
|
||||
|
||||
return [
|
||||
'access_token' => $data['access_token'],
|
||||
'refresh_token' => $data['refresh_token']
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Talank Baral <talank@jankaritech.com>
|
||||
* @copyright Copyright (c) 2021 Talank Baral talank@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace TestHelpers;
|
||||
|
||||
/**
|
||||
* Class TranslationHelper
|
||||
*
|
||||
* Helper functions that are needed to run tests in different languages
|
||||
*
|
||||
* @package TestHelpers
|
||||
*/
|
||||
class TranslationHelper {
|
||||
/**
|
||||
* @param string|null $language
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getLanguage(?string $language): ?string {
|
||||
if (!isset($language)) {
|
||||
if (\getenv('OC_LANGUAGE') !== false) {
|
||||
$language = \getenv('OC_LANGUAGE');
|
||||
}
|
||||
}
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Helper for Uploads
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class UploadHelper extends Assert {
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl URL of КуСфера
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $source
|
||||
* @param string|null $destination
|
||||
* @param string|null $xRequestId
|
||||
* @param array|null $headers
|
||||
* @param int|null $davPathVersionToUse (1|2)
|
||||
* @param bool $doChunkUpload
|
||||
* @param int|null $noOfChunks how many chunks to upload
|
||||
* @param bool|null $isGivenStep
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function upload(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $source,
|
||||
?string $destination,
|
||||
?string $xRequestId = '',
|
||||
?array $headers = [],
|
||||
?int $davPathVersionToUse = 1,
|
||||
bool $doChunkUpload = false,
|
||||
?int $noOfChunks = 1,
|
||||
?bool $isGivenStep = false,
|
||||
): ResponseInterface {
|
||||
if (!$doChunkUpload) {
|
||||
$data = \file_get_contents($source);
|
||||
return WebDavHelper::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PUT",
|
||||
$destination,
|
||||
$headers,
|
||||
null,
|
||||
$xRequestId,
|
||||
$data,
|
||||
$davPathVersionToUse,
|
||||
"files",
|
||||
null,
|
||||
"basic",
|
||||
false,
|
||||
0,
|
||||
null,
|
||||
[],
|
||||
null,
|
||||
$isGivenStep
|
||||
);
|
||||
}
|
||||
|
||||
//prepare chunking
|
||||
$chunks = self::chunkFile($source, $noOfChunks);
|
||||
$chunkingId = 'chunking-' . \rand(1000, 9999);
|
||||
$result = null;
|
||||
|
||||
//upload chunks
|
||||
foreach ($chunks as $index => $chunk) {
|
||||
$filename = $destination . "-" . $chunkingId . "-" . \count($chunks) . '-' . $index;
|
||||
$result = WebDavHelper::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PUT",
|
||||
$filename,
|
||||
$headers,
|
||||
null,
|
||||
$xRequestId,
|
||||
$chunk,
|
||||
$davPathVersionToUse,
|
||||
"files",
|
||||
null,
|
||||
"basic",
|
||||
false,
|
||||
0,
|
||||
null,
|
||||
[],
|
||||
null,
|
||||
$isGivenStep
|
||||
);
|
||||
if ($result->getStatusCode() >= 400) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
self::assertNotNull($result, __METHOD__ . " chunking was requested but no upload was done.");
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* cut the file in multiple chunks
|
||||
* returns an array of chunks with the content of the file
|
||||
*
|
||||
* @param string|null $file
|
||||
* @param int|null $noOfChunks
|
||||
*
|
||||
* @return array $string
|
||||
*/
|
||||
public static function chunkFile(?string $file, ?int $noOfChunks = 1): array {
|
||||
$size = \filesize($file);
|
||||
$chunkSize = \ceil($size / $noOfChunks);
|
||||
$chunks = [];
|
||||
$fp = \fopen($file, 'r');
|
||||
while (!\feof($fp) && \ftell($fp) < $size) {
|
||||
$chunks[] = \fread($fp, (int)$chunkSize);
|
||||
}
|
||||
\fclose($fp);
|
||||
if (\count($chunks) === 0) {
|
||||
// chunk an empty file
|
||||
$chunks[] = '';
|
||||
}
|
||||
return $chunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a File with a specific size
|
||||
*
|
||||
* @param string|null $name full path of the file to create
|
||||
* @param int|null $size
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createFileSpecificSize(?string $name, ?int $size): void {
|
||||
if (\file_exists($name)) {
|
||||
\unlink($name);
|
||||
}
|
||||
$file = \fopen($name, 'w');
|
||||
\fseek($file, \max($size - 1, 0), SEEK_CUR);
|
||||
if ($size) {
|
||||
\fwrite($file, 'a'); // write a dummy char at SIZE position
|
||||
}
|
||||
\fclose($file);
|
||||
self::assertEquals(
|
||||
1,
|
||||
\file_exists($name)
|
||||
);
|
||||
self::assertEquals(
|
||||
$size,
|
||||
\filesize($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* creates a File with a specific text content
|
||||
*
|
||||
* @param string|null $name full path of the file to create
|
||||
* @param string|null $text
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function createFileWithText(?string $name, ?string $text): void {
|
||||
$file = \fopen($name, 'w');
|
||||
\fwrite($file, $text);
|
||||
\fclose($file);
|
||||
self::assertEquals(
|
||||
1,
|
||||
\file_exists($name)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the path of the acceptance tests directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getAcceptanceTestsDir(): string {
|
||||
return \dirname(__FILE__) . "/../";
|
||||
}
|
||||
|
||||
/**
|
||||
* get the path of the filesForUpload directory
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getFilesForUploadDir(): string {
|
||||
return \dirname(__FILE__) . "/../filesForUpload/";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* Helper to administrate users (and groups) through the provisioning API
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class UserHelper {
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string $user
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
* @param string $adminUser
|
||||
* @param string $adminPassword
|
||||
* @param string $xRequestId
|
||||
* @param int|null $ocsApiVersion
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function editUser(
|
||||
?string $baseUrl,
|
||||
string $user,
|
||||
string $key,
|
||||
string $value,
|
||||
string $adminUser,
|
||||
string $adminPassword,
|
||||
string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"PUT",
|
||||
"/cloud/users/" . $user,
|
||||
$xRequestId,
|
||||
["key" => $key, "value" => $value],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send batch requests to edit the user.
|
||||
* This will send multiple requests in parallel to the server which will be faster in comparison to waiting for each request to complete.
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param array|null $editData ['user' => '', 'key' => '', 'value' => '']
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function editUserBatch(
|
||||
?string $baseUrl,
|
||||
?array $editData,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): array {
|
||||
$requests = [];
|
||||
$client = HttpRequestHelper::createClient(
|
||||
$adminUser,
|
||||
$adminPassword
|
||||
);
|
||||
|
||||
foreach ($editData as $data) {
|
||||
$path = "/cloud/users/" . $data['user'];
|
||||
$body = ["key" => $data['key'], 'value' => $data["value"]];
|
||||
// Create the OCS API requests and push them to an array.
|
||||
$requests[] = OcsApiHelper::createOcsRequest(
|
||||
$baseUrl,
|
||||
'PUT',
|
||||
$path,
|
||||
$xRequestId,
|
||||
$body
|
||||
);
|
||||
}
|
||||
// Send the array of requests at once in parallel.
|
||||
$results = HttpRequestHelper::sendBatchRequest($requests, $client);
|
||||
|
||||
foreach ($results as $e) {
|
||||
if ($e instanceof ClientException) {
|
||||
$httpStatusCode = $e->getResponse()->getStatusCode();
|
||||
$reasonPhrase = $e->getResponse()->getReasonPhrase();
|
||||
throw new Exception(
|
||||
"Unexpected failure when editing a user: HTTP status $httpStatusCode HTTP reason $reasonPhrase"
|
||||
);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $userName
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getUser(
|
||||
?string $baseUrl,
|
||||
?string $userName,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"GET",
|
||||
"/cloud/users/" . $userName,
|
||||
$xRequestId,
|
||||
[],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $userName
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function deleteUser(
|
||||
?string $baseUrl,
|
||||
?string $userName,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"DELETE",
|
||||
"/cloud/users/" . $userName,
|
||||
$xRequestId,
|
||||
[],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $group
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion (1|2)
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function addUserToGroup(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $group,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"POST",
|
||||
"/cloud/users/" . $user . "/groups",
|
||||
$xRequestId,
|
||||
['groupid' => $group],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $group
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $ocsApiVersion (1|2)
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function removeUserFromGroup(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $group,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId,
|
||||
?int $ocsApiVersion = 2
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"DELETE",
|
||||
"/cloud/users/" . $user . "/groups",
|
||||
$xRequestId,
|
||||
['groupid' => $group],
|
||||
$ocsApiVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $search
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getGroups(
|
||||
?string $baseUrl,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?string $search =""
|
||||
): ResponseInterface {
|
||||
return OcsApiHelper::sendRequest(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
"GET",
|
||||
"/cloud/groups?search=" . $search,
|
||||
$xRequestId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $adminUser
|
||||
* @param string|null $adminPassword
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $search
|
||||
*
|
||||
* @return string[]
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public static function getGroupsAsArray(
|
||||
?string $baseUrl,
|
||||
?string $adminUser,
|
||||
?string $adminPassword,
|
||||
?string $xRequestId = '',
|
||||
?string $search = ""
|
||||
): array {
|
||||
$result = self::getGroups(
|
||||
$baseUrl,
|
||||
$adminUser,
|
||||
$adminPassword,
|
||||
$xRequestId,
|
||||
$search
|
||||
);
|
||||
$groups = HttpRequestHelper::getResponseXml($result, __METHOD__)->xpath(".//groups")[0];
|
||||
$return = [];
|
||||
foreach ($groups as $group) {
|
||||
$return[] = $group->__toString();
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,967 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2017 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
namespace TestHelpers;
|
||||
|
||||
use Exception;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* Helper to make WebDav Requests
|
||||
*
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
*/
|
||||
class WebDavHelper {
|
||||
public const DAV_VERSION_OLD = 1;
|
||||
public const DAV_VERSION_NEW = 2;
|
||||
public const DAV_VERSION_SPACES = 3;
|
||||
|
||||
/**
|
||||
* @var array of users with their different space ids
|
||||
*/
|
||||
public static array $spacesIdRef = [];
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function withRemotePhp(): bool {
|
||||
// use remote.php by default
|
||||
return \getenv("WITH_REMOTE_PHP") !== "false";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $urlPath
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function prefixRemotePhp(string $urlPath): string {
|
||||
if (self::withRemotePhp()) {
|
||||
return "remote.php/$urlPath";
|
||||
}
|
||||
return $urlPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isDAVRequest(string $url): bool {
|
||||
$found = \preg_match("/(\bwebdav\b|\bdav\b)/", $url);
|
||||
return (bool)$found;
|
||||
}
|
||||
|
||||
/**
|
||||
* clear space id reference for user
|
||||
*
|
||||
* @param string|null $user
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function removeSpaceIdReferenceForUser(
|
||||
?string $user
|
||||
): void {
|
||||
if (\array_key_exists($user, self::$spacesIdRef)) {
|
||||
unset(self::$spacesIdRef[$user]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespaceString
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public static function parseNamespace(string $namespaceString): object {
|
||||
//calculate the namespace prefix and namespace
|
||||
$matches = [];
|
||||
\preg_match("/^(.*)='(.*)'$/", $namespaceString, $matches);
|
||||
return (object)["namespace" => $matches[2], "prefix" => $matches[1]];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the id of a file
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $path
|
||||
* @param string|null $spaceId
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $davPathVersionToUse
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public static function getFileIdForPath(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $path,
|
||||
?string $spaceId = null,
|
||||
?string $xRequestId = '',
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW
|
||||
): string {
|
||||
$body
|
||||
= '<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:prop>
|
||||
<oc:fileid />
|
||||
</d:prop>
|
||||
</d:propfind>';
|
||||
$response = self::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPFIND",
|
||||
$path,
|
||||
null,
|
||||
$spaceId,
|
||||
$xRequestId,
|
||||
$body,
|
||||
$davPathVersionToUse
|
||||
);
|
||||
\preg_match(
|
||||
'/\<oc:fileid\>([^\<]*)\<\/oc:fileid\>/',
|
||||
$response->getBody()->getContents(),
|
||||
$matches
|
||||
);
|
||||
|
||||
if (!isset($matches[1])) {
|
||||
throw new Exception("could not find fileId of $path");
|
||||
}
|
||||
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns body for propfind
|
||||
*
|
||||
* @param array|null $properties
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getBodyForPropfind(?array $properties): string {
|
||||
$propertyBody = "";
|
||||
$extraNamespaces = "";
|
||||
foreach ($properties as $namespaceString => $property) {
|
||||
if (\is_int($namespaceString)) {
|
||||
//default namespace prefix if the property has no array key
|
||||
//also used if no prefix is given in the property value
|
||||
$namespacePrefix = null;
|
||||
} else {
|
||||
$ns = self::parseNamespace($namespaceString);
|
||||
$namespacePrefix = $ns->prefix;
|
||||
$extraNamespaces .= " xmlns:$namespacePrefix=\"$ns->namespace\" ";
|
||||
}
|
||||
//if a namespace prefix is given in the property value use that
|
||||
if (\strpos($property, ":") !== false) {
|
||||
$propertyParts = \explode(":", $property);
|
||||
$namespacePrefix = $propertyParts[0];
|
||||
$property = $propertyParts[1];
|
||||
}
|
||||
|
||||
if ($namespacePrefix) {
|
||||
$propertyBody .= "<$namespacePrefix:$property/>";
|
||||
} else {
|
||||
$propertyBody .= "<$property/>";
|
||||
}
|
||||
}
|
||||
$body = "<?xml version=\"1.0\"?>
|
||||
<d:propfind
|
||||
xmlns:d=\"DAV:\"
|
||||
xmlns:oc=\"http://owncloud.org/ns\"
|
||||
xmlns:ocs=\"http://open-collaboration-services.org/ns\"
|
||||
$extraNamespaces>
|
||||
<d:prop>$propertyBody</d:prop>
|
||||
</d:propfind>";
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* sends a PROPFIND request
|
||||
* with these registered namespaces:
|
||||
* | prefix | namespace |
|
||||
* | d | DAV: |
|
||||
* | oc | http://owncloud.org/ns |
|
||||
* | ocs | http://open-collaboration-services.org/ns |
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $path
|
||||
* @param string[] $properties
|
||||
* string can contain namespace prefix,
|
||||
* if no prefix is given 'd:' is used as prefix
|
||||
* if an associative array is used, then the key will be used as namespace
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $folderDepth
|
||||
* @param string|null $spaceId
|
||||
* @param string|null $type
|
||||
* @param int|null $davPathVersionToUse
|
||||
* @param string|null $doDavRequestAsUser
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function propfind(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $path,
|
||||
?array $properties,
|
||||
?string $xRequestId = '',
|
||||
?string $folderDepth = '1',
|
||||
?string $spaceId = null,
|
||||
?string $type = "files",
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
|
||||
?string $doDavRequestAsUser = null,
|
||||
?array $headers = []
|
||||
): ResponseInterface {
|
||||
$body = self::getBodyForPropfind($properties);
|
||||
$folderDepth = (string) $folderDepth;
|
||||
if ($folderDepth !== '0' && $folderDepth !== '1' && $folderDepth !== 'infinity') {
|
||||
if ($folderDepth !== '') {
|
||||
throw new InvalidArgumentException('Invalid depth value ' . $folderDepth);
|
||||
}
|
||||
$folderDepth = '1'; // КуСфера server's default value
|
||||
}
|
||||
$headers['Depth'] = $folderDepth;
|
||||
return self::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPFIND",
|
||||
$path,
|
||||
$headers,
|
||||
$spaceId,
|
||||
$xRequestId,
|
||||
$body,
|
||||
$davPathVersionToUse,
|
||||
$type,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
null,
|
||||
null,
|
||||
[],
|
||||
$doDavRequestAsUser
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $path
|
||||
* @param string|null $propertyName
|
||||
* @param string|null $propertyValue
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null $namespaceString string containing prefix and namespace
|
||||
* e.g "x1='http://whatever.org/ns'"
|
||||
* @param int|null $davPathVersionToUse
|
||||
* @param string|null $type
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function proppatch(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $path,
|
||||
?string $propertyName,
|
||||
?string $propertyValue,
|
||||
?string $xRequestId = '',
|
||||
?string $namespaceString = null,
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
|
||||
?string $type="files",
|
||||
?string $spaceId = null,
|
||||
): ResponseInterface {
|
||||
if ($namespaceString !== null) {
|
||||
$ns = self::parseNamespace($namespaceString);
|
||||
$propertyBody = "<$ns->prefix:$propertyName" .
|
||||
" xmlns:$ns->prefix=\"$ns->namespace\">" .
|
||||
"$propertyValue" .
|
||||
"</$ns->prefix:$propertyName>";
|
||||
} else {
|
||||
$propertyBody = "<$propertyName>$propertyValue</$propertyName>";
|
||||
}
|
||||
$body = "<?xml version=\"1.0\"?>
|
||||
<d:propertyupdate xmlns:d=\"DAV:\"
|
||||
xmlns:oc=\"http://owncloud.org/ns\">
|
||||
<d:set>
|
||||
<d:prop>$propertyBody</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>";
|
||||
return self::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPPATCH",
|
||||
$path,
|
||||
[],
|
||||
$spaceId,
|
||||
$xRequestId,
|
||||
$body,
|
||||
$davPathVersionToUse,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets namespace-prefix, namespace url and propName from provided namespaceString or property
|
||||
* or otherwise use default
|
||||
*
|
||||
* @param string $namespaceString
|
||||
* @param string $property
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getPropertyWithNamespaceInfo(string $namespaceString = "", string $property = ""): array {
|
||||
$namespace = "";
|
||||
$namespacePrefix = "";
|
||||
if (\is_int($namespaceString)) {
|
||||
//default namespace prefix if the property has no array key
|
||||
//also used if no prefix is given in the property value
|
||||
$namespacePrefix = "d";
|
||||
$namespace = "DAV:";
|
||||
} elseif ($namespaceString) {
|
||||
$ns = self::parseNamespace($namespaceString);
|
||||
$namespacePrefix = $ns->prefix;
|
||||
$namespace = $ns->namespace;
|
||||
}
|
||||
//if a namespace prefix is given in the property value use that
|
||||
if ($property && \strpos($property, ":")) {
|
||||
$propertyParts = \explode(":", $property);
|
||||
$namespacePrefix = $propertyParts[0];
|
||||
$property = $propertyParts[1];
|
||||
}
|
||||
return [$namespacePrefix, $namespace, $property];
|
||||
}
|
||||
|
||||
/**
|
||||
* sends HTTP request PROPPATCH method with multiple properties
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string $path
|
||||
* @param array|null $propertiesArray
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $davPathVersion
|
||||
* @param string|null $namespaceString
|
||||
* @param string|null $type
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function proppatchWithMultipleProps(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
string $path,
|
||||
?array $propertiesArray,
|
||||
?string $xRequestId = '',
|
||||
?int $davPathVersion = null,
|
||||
?string $namespaceString = null,
|
||||
?string $type="files"
|
||||
): ResponseInterface {
|
||||
$propertyBody = "";
|
||||
foreach ($propertiesArray as $propertyArray) {
|
||||
$property = $propertyArray["propertyName"];
|
||||
$value = $propertyArray["propertyValue"];
|
||||
|
||||
if ($namespaceString !== null) {
|
||||
$matches = [];
|
||||
[$namespacePrefix, $namespace, $property] = self::getPropertyWithNamespaceInfo(
|
||||
$namespaceString,
|
||||
$property
|
||||
);
|
||||
$propertyBody .= "\n\t<$namespacePrefix:$property>" .
|
||||
"$value" .
|
||||
"</$namespacePrefix:$property>";
|
||||
} else {
|
||||
$propertyBody .= "<$property>$value</$property>";
|
||||
}
|
||||
}
|
||||
$body = "<?xml version=\"1.0\"?>
|
||||
<d:propertyupdate xmlns:d=\"DAV:\"
|
||||
xmlns:oc=\"http://owncloud.org/ns\">
|
||||
<d:set>
|
||||
<d:prop>$propertyBody
|
||||
</d:prop>
|
||||
</d:set>
|
||||
</d:propertyupdate>";
|
||||
return self::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPPATCH",
|
||||
$path,
|
||||
[],
|
||||
null,
|
||||
$xRequestId,
|
||||
$body,
|
||||
$davPathVersion,
|
||||
$type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the response to listing a folder (collection)
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $path
|
||||
* @param string|null $folderDepth
|
||||
* @param string|null $spaceId
|
||||
* @param string|null $xRequestId
|
||||
* @param string[] $properties
|
||||
* @param string|null $type
|
||||
* @param int|null $davPathVersionToUse
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function listFolder(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $path,
|
||||
?string $folderDepth,
|
||||
?string $spaceId = null,
|
||||
?string $xRequestId = '',
|
||||
?array $properties = null,
|
||||
?string $type = "files",
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW
|
||||
): ResponseInterface {
|
||||
if (!$properties) {
|
||||
$properties = [
|
||||
'd:getetag', 'd:resourcetype'
|
||||
];
|
||||
}
|
||||
return self::propfind(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
$path,
|
||||
$properties,
|
||||
$xRequestId,
|
||||
$folderDepth,
|
||||
$spaceId,
|
||||
$type,
|
||||
$davPathVersionToUse
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates UUIDv4
|
||||
* Example: 123e4567-e89b-12d3-a456-426614174000
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function generateUUIDv4(): string {
|
||||
// generate 16 bytes (128 bits) of random data or use the data passed into the function.
|
||||
$data = random_bytes(16);
|
||||
\assert(\strlen($data) == 16);
|
||||
|
||||
$data[6] = \chr(\ord($data[6]) & 0x0f | 0x40); // set version to 0100
|
||||
$data[8] = \chr(\ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
|
||||
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
|
||||
/**
|
||||
* fetches personal space id for provided user
|
||||
*
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getPersonalSpaceIdForUser(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId
|
||||
): string {
|
||||
if (\array_key_exists($user, self::$spacesIdRef) && \array_key_exists("personal", self::$spacesIdRef[$user])) {
|
||||
return self::$spacesIdRef[$user]["personal"];
|
||||
}
|
||||
|
||||
$personalSpaceId = '';
|
||||
if (!OcHelper::isTestingOnReva()) {
|
||||
$response = GraphHelper::getMySpaces($baseUrl, $user, $password, '', $xRequestId);
|
||||
Assert::assertEquals(200, $response->getStatusCode(), "Cannot list drives for user '$user'");
|
||||
|
||||
$drives = HttpRequestHelper::getJsonDecodedResponseBodyContent($response);
|
||||
foreach ($drives->value as $drive) {
|
||||
if ($drive->driveType === "personal") {
|
||||
$personalSpaceId = $drive->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$personalSpaceId) {
|
||||
// the graph endpoint did not give a useful answer
|
||||
// try getting the information from the webdav endpoint
|
||||
$fullUrl = "$baseUrl/" . self::getDavPath(self::DAV_VERSION_NEW, $user);
|
||||
$response = HttpRequestHelper::sendRequest(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
'PROPFIND',
|
||||
$user,
|
||||
$password
|
||||
);
|
||||
Assert::assertEquals(
|
||||
207,
|
||||
$response->getStatusCode(),
|
||||
"PROPFIND for user '$user' failed so the personal space id cannot be discovered"
|
||||
);
|
||||
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("/d:multistatus/d:response[1]/d:propstat/d:prop/oc:spaceid");
|
||||
Assert::assertNotEmpty(
|
||||
$xmlPart,
|
||||
"The 'oc:spaceid' for user '$user' was not found in the PROPFIND response"
|
||||
);
|
||||
|
||||
$personalSpaceId = $xmlPart[0]->__toString();
|
||||
}
|
||||
|
||||
Assert::assertNotEmpty($personalSpaceId, "The personal space id for user '$user' was not found");
|
||||
|
||||
self::$spacesIdRef[$user] = [];
|
||||
self::$spacesIdRef[$user]["personal"] = $personalSpaceId;
|
||||
return $personalSpaceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* First checks if a user exists to return its space ID
|
||||
* In case of any exception, it returns a fake space ID
|
||||
*
|
||||
* @param string $baseUrl
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
* @param string $xRequestId
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public static function getPersonalSpaceIdForUserOrFakeIfNotFound(
|
||||
string $baseUrl,
|
||||
string $user,
|
||||
string $password,
|
||||
string $xRequestId
|
||||
): string {
|
||||
if (\str_starts_with($user, "non-exist") || \str_starts_with($user, "nonexist")) {
|
||||
return self::generateUUIDv4();
|
||||
}
|
||||
|
||||
return self::getPersonalSpaceIdForUser(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
$xRequestId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* sends a DAV request
|
||||
*
|
||||
* @param string|null $baseUrl URL of КуСфера e.g. http://localhost:8080
|
||||
* @param string|null $user
|
||||
* @param string|null $password or token when bearer auth is used
|
||||
* @param string|null $method PUT, GET, DELETE, etc.
|
||||
* @param string|null $path
|
||||
* @param array|null $headers
|
||||
* @param string|null $spaceId
|
||||
* @param string|null $xRequestId
|
||||
* @param string|null|resource|StreamInterface $body
|
||||
* @param int|null $davPathVersionToUse (1|2|3)
|
||||
* @param string|null $type of request
|
||||
* @param string|null $sourceIpAddress to initiate the request from
|
||||
* @param string|null $authType basic|bearer
|
||||
* @param bool $stream Set to true to stream a response rather
|
||||
* than download it all up-front.
|
||||
* @param int|null $timeout
|
||||
* @param Client|null $client
|
||||
* @param array|null $urlParameter to concatenate with path
|
||||
* @param string|null $doDavRequestAsUser run the DAV as this user, if null it is the same as $user
|
||||
* @param bool $isGivenStep is set to true if makeDavRequest is called from a "given" step
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function makeDavRequest(
|
||||
?string $baseUrl,
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $method,
|
||||
?string $path,
|
||||
?array $headers,
|
||||
?string $spaceId = null,
|
||||
?string $xRequestId = '',
|
||||
$body = null,
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_OLD,
|
||||
?string $type = "files",
|
||||
?string $sourceIpAddress = null,
|
||||
?string $authType = "basic",
|
||||
?bool $stream = false,
|
||||
?int $timeout = 0,
|
||||
?Client $client = null,
|
||||
?array $urlParameter = [],
|
||||
?string $doDavRequestAsUser = null,
|
||||
?bool $isGivenStep = false,
|
||||
): ResponseInterface {
|
||||
$baseUrl = self::sanitizeUrl($baseUrl, true);
|
||||
|
||||
// We need to manipulate and use path as a string.
|
||||
// So ensure that it is a string to avoid any type-conversion errors.
|
||||
if ($path === null) {
|
||||
$path = "";
|
||||
}
|
||||
|
||||
// get space id if testing with spaces dav
|
||||
if ($spaceId === null && $davPathVersionToUse === self::DAV_VERSION_SPACES
|
||||
&& !\in_array($type, ["public-files", "versions"])
|
||||
) {
|
||||
$path = \ltrim($path, "/");
|
||||
if (\str_starts_with($path, "Shares/")) {
|
||||
$spaceId = GraphHelper::SHARES_SPACE_ID;
|
||||
$path = "/" . preg_replace("/^Shares\//", "", $path);
|
||||
} else {
|
||||
$spaceId = self::getPersonalSpaceIdForUserOrFakeIfNotFound(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
$xRequestId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$suffixPath = $user;
|
||||
if ($davPathVersionToUse === self::DAV_VERSION_SPACES
|
||||
&& !\in_array($type, ["archive", "versions", "public-files"])
|
||||
) {
|
||||
$suffixPath = $spaceId;
|
||||
} elseif ($type === "versions") {
|
||||
// $path is file-id in case of versions
|
||||
$suffixPath = $path;
|
||||
}
|
||||
|
||||
$davPath = self::getDavPath($davPathVersionToUse, $suffixPath, $type);
|
||||
|
||||
//replace %, # and ? and in the path, Guzzle will not encode them
|
||||
$urlSpecialChar = [['%', '#', '?'], ['%25', '%23', '%3F']];
|
||||
$path = \str_replace($urlSpecialChar[0], $urlSpecialChar[1], $path);
|
||||
|
||||
if (!empty($urlParameter)) {
|
||||
$urlParameter = \http_build_query($urlParameter, '', '&');
|
||||
$path .= '?' . $urlParameter;
|
||||
}
|
||||
$fullUrl = self::sanitizeUrl("$baseUrl/$davPath");
|
||||
// NOTE: no need to append path for archive and versions endpoints
|
||||
if (!\in_array($type, ["archive", "versions"])) {
|
||||
$fullUrl .= "/" . \ltrim($path, "/");
|
||||
}
|
||||
|
||||
if ($authType === 'bearer') {
|
||||
$headers['Authorization'] = 'Bearer ' . $password;
|
||||
$user = null;
|
||||
$password = null;
|
||||
}
|
||||
if ($type === "public-files") {
|
||||
if ($password === null || $password === "") {
|
||||
$user = null;
|
||||
} else {
|
||||
$user = "public";
|
||||
}
|
||||
}
|
||||
$config = null;
|
||||
if ($sourceIpAddress !== null) {
|
||||
$config = [ 'curl' => [ CURLOPT_INTERFACE => $sourceIpAddress ]];
|
||||
}
|
||||
|
||||
if ($headers !== null) {
|
||||
foreach ($headers as $key => $value) {
|
||||
//? and # need to be encoded in the Destination URL
|
||||
if ($key === "Destination") {
|
||||
$headers[$key] = \str_replace(
|
||||
$urlSpecialChar[0],
|
||||
$urlSpecialChar[1],
|
||||
$value
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$fullUrl,
|
||||
$xRequestId,
|
||||
$method,
|
||||
$doDavRequestAsUser ?? $user,
|
||||
$password,
|
||||
$headers,
|
||||
$body,
|
||||
$config,
|
||||
null,
|
||||
$stream,
|
||||
$timeout,
|
||||
$client,
|
||||
$isGivenStep
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the dav path
|
||||
*
|
||||
* @param int $davPathVersion (1|2|3)
|
||||
* @param string|null $userOrItemIdOrSpaceIdOrToken 'user' or 'file-id' or 'space-id' or 'public-token'
|
||||
* @param string|null $type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getDavPath(
|
||||
int $davPathVersion,
|
||||
?string $userOrItemIdOrSpaceIdOrToken = null,
|
||||
?string $type = "files"
|
||||
): string {
|
||||
switch ($type) {
|
||||
case 'archive':
|
||||
return self::prefixRemotePhp("dav/archive/$userOrItemIdOrSpaceIdOrToken/files");
|
||||
case 'versions':
|
||||
return self::prefixRemotePhp("dav/meta/$userOrItemIdOrSpaceIdOrToken/v");
|
||||
case 'comments':
|
||||
return self::prefixRemotePhp("dav/comments/files");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ($davPathVersion === self::DAV_VERSION_SPACES) {
|
||||
if ($type === "trash-bin") {
|
||||
if ($userOrItemIdOrSpaceIdOrToken === null) {
|
||||
throw new InvalidArgumentException("Space ID is required for trash-bin endpoint");
|
||||
}
|
||||
return self::prefixRemotePhp("dav/spaces/trash-bin/$userOrItemIdOrSpaceIdOrToken");
|
||||
} elseif ($type === "public-files") {
|
||||
// spaces DAV path doesn't have own public-files endpoint
|
||||
return self::prefixRemotePhp("dav/public-files/$userOrItemIdOrSpaceIdOrToken");
|
||||
}
|
||||
// return spaces root path if spaceid is null
|
||||
// REPORT request uses spaces root path
|
||||
if ($userOrItemIdOrSpaceIdOrToken === null) {
|
||||
return self::prefixRemotePhp("dav/spaces");
|
||||
}
|
||||
return self::prefixRemotePhp("dav/spaces/$userOrItemIdOrSpaceIdOrToken");
|
||||
} else {
|
||||
if ($type === "trash-bin") {
|
||||
// Since there is no trash bin endpoint for old dav version,
|
||||
// new dav version's endpoint is used here.
|
||||
return self::prefixRemotePhp("dav/trash-bin/$userOrItemIdOrSpaceIdOrToken");
|
||||
}
|
||||
if ($davPathVersion === self::DAV_VERSION_OLD) {
|
||||
return self::prefixRemotePhp("webdav");
|
||||
} elseif ($davPathVersion === self::DAV_VERSION_NEW) {
|
||||
if ($type === "files") {
|
||||
return self::prefixRemotePhp("dav/files/$userOrItemIdOrSpaceIdOrToken");
|
||||
} elseif ($type === "public-files") {
|
||||
return self::prefixRemotePhp("dav/public-files/$userOrItemIdOrSpaceIdOrToken");
|
||||
}
|
||||
return self::prefixRemotePhp("dav");
|
||||
}
|
||||
throw new InvalidArgumentException("Invalid DAV path: $davPathVersion");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* make sure there are no double-slashes in the URL
|
||||
*
|
||||
* @param string|null $url
|
||||
* @param bool|null $trailingSlash forces a trailing slash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function sanitizeUrl(?string $url, ?bool $trailingSlash = false): string {
|
||||
if ($trailingSlash === true) {
|
||||
$url = $url . "/";
|
||||
} else {
|
||||
$url = \rtrim($url, "/");
|
||||
}
|
||||
return \preg_replace("/([^:]\/)\/+/", '$1', $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* get Mtime of File in a public link share
|
||||
*
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $fileName
|
||||
* @param string|null $token
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $davVersionToUse
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public static function getMtimeOfFileInPublicLinkShare(
|
||||
?string $baseUrl,
|
||||
?string $fileName,
|
||||
?string $token,
|
||||
?string $xRequestId = '',
|
||||
?int $davVersionToUse = self::DAV_VERSION_NEW
|
||||
): string {
|
||||
$response = self::propfind(
|
||||
$baseUrl,
|
||||
null,
|
||||
null,
|
||||
"$token/$fileName",
|
||||
['d:getlastmodified'],
|
||||
$xRequestId,
|
||||
'1',
|
||||
null,
|
||||
"public-files",
|
||||
$davVersionToUse
|
||||
);
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("//d:getlastmodified");
|
||||
|
||||
return $xmlPart[0]->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* get Mtime of a resource
|
||||
*
|
||||
* @param string|null $user
|
||||
* @param string|null $password
|
||||
* @param string|null $baseUrl
|
||||
* @param string|null $resource
|
||||
* @param string|null $xRequestId
|
||||
* @param int|null $davPathVersionToUse
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public static function getMtimeOfResource(
|
||||
?string $user,
|
||||
?string $password,
|
||||
?string $baseUrl,
|
||||
?string $resource,
|
||||
?string $xRequestId = '',
|
||||
?int $davPathVersionToUse = self::DAV_VERSION_NEW,
|
||||
?string $spaceId = null,
|
||||
): string {
|
||||
$response = self::propfind(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
$resource,
|
||||
["d:getlastmodified"],
|
||||
$xRequestId,
|
||||
"0",
|
||||
$spaceId,
|
||||
"files",
|
||||
$davPathVersionToUse
|
||||
);
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("//d:getlastmodified");
|
||||
Assert::assertArrayHasKey(
|
||||
0,
|
||||
$xmlPart,
|
||||
__METHOD__
|
||||
. " XML part does not have key 0. Expected a value at index 0 of 'xmlPart' but, found: "
|
||||
. json_encode($xmlPart)
|
||||
);
|
||||
$mtime = new DateTime($xmlPart[0]->__toString());
|
||||
return $mtime->format('U');
|
||||
}
|
||||
|
||||
/**
|
||||
* wait until the reqeust doesn't return 425 anymore
|
||||
*
|
||||
* @param string $url
|
||||
* @param ?string $user
|
||||
* @param ?string $password
|
||||
* @param ?array $headers
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function waitForPostProcessingToFinish(
|
||||
string $url,
|
||||
?string $user = null,
|
||||
?string $password = null,
|
||||
?array $headers = [],
|
||||
): void {
|
||||
$retried = 0;
|
||||
do {
|
||||
$response = HttpRequestHelper::sendRequest(
|
||||
$url,
|
||||
'check-425-status',
|
||||
'GET',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
);
|
||||
$statusCode = $response->getStatusCode();
|
||||
if ($statusCode !== 425) {
|
||||
return;
|
||||
}
|
||||
$tryAgain = $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
|
||||
if ($tryAgain) {
|
||||
$retried += 1;
|
||||
echo "[INFO] Waiting for post processing to finish, attempt ($retried)...\n";
|
||||
// wait 1s and try again
|
||||
\sleep(1);
|
||||
}
|
||||
} while ($tryAgain);
|
||||
echo "[ERROR] 10 seconds timeout! Post processing did not finish in time.\n";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2021 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use splitbrain\PHPArchive\Tar;
|
||||
use splitbrain\PHPArchive\Zip;
|
||||
use splitbrain\PHPArchive\Archive;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Context for Archiver specific steps
|
||||
*/
|
||||
class ArchiverContext implements Context {
|
||||
/**
|
||||
* @var FeatureContext
|
||||
*/
|
||||
private FeatureContext $featureContext;
|
||||
private SpacesContext $spacesContext;
|
||||
|
||||
/**
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
$this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $query
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getArchiverUrl(string $query): string {
|
||||
return $this->featureContext->getBaseUrl() . '/archiver?' . $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
* @return Archive
|
||||
*/
|
||||
public function getArchiveClass(string $type): Archive {
|
||||
if ($type === 'zip') {
|
||||
return new Zip();
|
||||
} elseif ($type === 'tar') {
|
||||
return new Tar();
|
||||
} else {
|
||||
throw new Exception('Unknown archive type: ' . $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $dir
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeDir(string $dir): void {
|
||||
$items = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($items as $item) {
|
||||
$itemPath = $dir . DIRECTORY_SEPARATOR . $item;
|
||||
if (\is_dir($itemPath)) {
|
||||
$this->removeDir($itemPath);
|
||||
} else {
|
||||
\unlink($itemPath);
|
||||
}
|
||||
}
|
||||
\rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $resource
|
||||
* @param string $addressType id|ids|path|paths
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getArchiverQueryString(
|
||||
string $user,
|
||||
string $resource,
|
||||
string $addressType
|
||||
): string {
|
||||
switch ($addressType) {
|
||||
case 'id':
|
||||
case 'ids':
|
||||
return 'id=' . $this->featureContext->getFileIdForPath($user, $resource);
|
||||
case 'remoteItemIds':
|
||||
return 'id=' . $this->spacesContext->getSharesRemoteItemId($user, $resource);
|
||||
case 'path':
|
||||
case 'paths':
|
||||
return 'path=' . $resource;
|
||||
default:
|
||||
throw new Exception(
|
||||
'"' . $addressType .
|
||||
'" is not a legal value for $addressType, must be id|ids|remoteItemIds|path|paths'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" downloads the (zip|tar) archive of "([^"]*)" using the resource (id|ids|path|paths) and setting these headers:$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $archiveType
|
||||
* @param string $resource
|
||||
* @param string $addressType id|path
|
||||
* @param TableNode $headersTable
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userDownloadsTheZipOrTarArchiveOfResourceUsingResourceIdOrPathAndSettingTheseHeaders(
|
||||
string $user,
|
||||
string $archiveType,
|
||||
string $resource,
|
||||
string $addressType,
|
||||
TableNode $headersTable
|
||||
): void {
|
||||
$this->featureContext->verifyTableNodeColumns(
|
||||
$headersTable,
|
||||
['header', 'value']
|
||||
);
|
||||
$headers = [];
|
||||
foreach ($headersTable as $row) {
|
||||
$headers[$row['header']] = $row ['value'];
|
||||
}
|
||||
$this->featureContext->setResponse(
|
||||
$this->downloadArchive($user, $resource, $addressType, $archiveType, null, $headers)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :downloader downloads the archive of :item of user :owner using the resource :addressType
|
||||
*
|
||||
* @param string $downloader Who sends the request
|
||||
* @param string $resource
|
||||
* @param string $owner Who is the real owner of the file
|
||||
* @param string $addressType id|path
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userDownloadsTheArchiveOfItemOfUser(
|
||||
string $downloader,
|
||||
string $resource,
|
||||
string $owner,
|
||||
string $addressType
|
||||
): void {
|
||||
$this->featureContext->setResponse($this->downloadArchive($downloader, $resource, $addressType, null, $owner));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $downloader
|
||||
* @param string $resource
|
||||
* @param string $addressType
|
||||
* @param string|null $archiveType
|
||||
* @param string|null $owner
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function downloadArchive(
|
||||
string $downloader,
|
||||
string $resource,
|
||||
string $addressType,
|
||||
?string $archiveType = null,
|
||||
?string $owner = null,
|
||||
?array $headers = null
|
||||
): ResponseInterface {
|
||||
$owner = $owner ?? $downloader;
|
||||
$downloader = $this->featureContext->getActualUsername($downloader);
|
||||
$queryString = $this->getArchiverQueryString($owner, $resource, $addressType);
|
||||
if ($archiveType !== null) {
|
||||
$queryString .= '&output-format=' . $archiveType;
|
||||
}
|
||||
return HttpRequestHelper::get(
|
||||
$this->getArchiverUrl($queryString),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$downloader,
|
||||
$this->featureContext->getPasswordForUser($downloader),
|
||||
$headers
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user downloads the archive of these items using the resource :addressType
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $addressType ids|paths
|
||||
* @param TableNode $items
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException|Exception
|
||||
*/
|
||||
public function userDownloadsTheArchiveOfTheseItems(
|
||||
string $user,
|
||||
string $addressType,
|
||||
TableNode $items
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$queryString = [];
|
||||
foreach ($items->getRows() as $item) {
|
||||
$queryString[] = $this->getArchiverQueryString($user, $item[0], $addressType);
|
||||
}
|
||||
$queryString = \join('&', $queryString);
|
||||
|
||||
$this->featureContext->setResponse(
|
||||
HttpRequestHelper::get(
|
||||
$this->getArchiverUrl($queryString),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the downloaded :type archive should contain these files:
|
||||
*
|
||||
* @param string $type
|
||||
* @param TableNode $expectedFiles
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theDownloadedArchiveShouldContainTheseFiles(string $type, TableNode $expectedFiles): void {
|
||||
$this->featureContext->verifyTableNodeColumns($expectedFiles, ['name', 'content']);
|
||||
$contents = $this->featureContext->getResponse()->getBody()->getContents();
|
||||
$tempFile = \tempnam(\sys_get_temp_dir(), 'OcAcceptanceTests_');
|
||||
$tempExtractFolder = $tempFile;
|
||||
\unlink($tempFile); // we only need the name
|
||||
$tempFile = $tempFile . '.' . $type; // it needs the extension
|
||||
\file_put_contents($tempFile, $contents);
|
||||
|
||||
// open the archive
|
||||
$tar = $this->getArchiveClass($type);
|
||||
$tar->open($tempFile);
|
||||
$archiveData = $tar->contents();
|
||||
|
||||
// extract the archive
|
||||
$tar->open($tempFile);
|
||||
$tar->extract($tempExtractFolder);
|
||||
$tar->close();
|
||||
|
||||
foreach ($expectedFiles->getHash() as $expectedItem) {
|
||||
$expectedPath = trim($expectedItem['name'], "/");
|
||||
$found = false;
|
||||
foreach ($archiveData as $info) {
|
||||
// get only the parent folder path for the given item
|
||||
$actualPath = $info->getPath();
|
||||
|
||||
if ($expectedPath === $actualPath) {
|
||||
if (!$info->getIsdir()) {
|
||||
$fileFullPath = "$tempExtractFolder/$actualPath";
|
||||
$fileMimeType = \mime_content_type($fileFullPath);
|
||||
|
||||
if ($fileMimeType === "text/plain") {
|
||||
$fileContent = \file_get_contents($fileFullPath);
|
||||
Assert::assertEquals(
|
||||
$expectedItem['content'],
|
||||
$fileContent,
|
||||
__METHOD__ . " content of '" . $expectedPath . "' not as expected"
|
||||
);
|
||||
} else {
|
||||
Assert::assertFileExists(
|
||||
$fileFullPath,
|
||||
__METHOD__ . " File '" . $expectedPath . "' is not in the downloaded archive."
|
||||
);
|
||||
}
|
||||
}
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$found) {
|
||||
Assert::fail("Resource '" . $expectedPath . "' is not in the downloaded archive.");
|
||||
}
|
||||
}
|
||||
\unlink($tempFile);
|
||||
$this->removeDir($tempExtractFolder);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Niraj Acharya <niraj@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Niraj Acharya niraj@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use TestHelpers\BehatHelper;
|
||||
use TestHelpers\AuthAppHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* AuthApp context
|
||||
*/
|
||||
class AuthAppContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
|
||||
/**
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user creates app token with expiration time :expiration using the auth-app API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $expiration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userCreatesAppTokenWithExpirationTimeUsingTheAuthAppApi(string $user, string $expiration): void {
|
||||
$this->featureContext->setResponse(
|
||||
AuthAppHelper::createAppAuthToken(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
["expiry" => $expiration],
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has created app token with expiration time :expiration using the auth-app API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $expiration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userHasCreatedAppTokenWithExpirationTime(string $user, string $expiration): void {
|
||||
$response = AuthAppHelper::createAppAuthToken(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
["expiry" => $expiration]
|
||||
);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user lists all created tokens using the auth-app API
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userListsAllCreatedTokensUsingTheAuthAppApi(string $user): void {
|
||||
$this->featureContext->setResponse(
|
||||
AuthAppHelper::listAllAppAuthTokensForUser(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the administrator has created app token for user :impersonatedUser with expiration time :expiration using the auth-app API
|
||||
*
|
||||
* @param string $impersonatedUser
|
||||
* @param string $expiration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorHasCreatedAppTokenWithExpirationTimeImpersonatingUserUsingTheAuthAppApi(
|
||||
string $impersonatedUser,
|
||||
string $expiration,
|
||||
): void {
|
||||
$response = AuthAppHelper::createAppAuthToken(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getAdminUsername(),
|
||||
$this->featureContext->getAdminPassword(),
|
||||
[
|
||||
"expiry" => $expiration,
|
||||
"userName" => $this->featureContext->getActualUsername($impersonatedUser)
|
||||
],
|
||||
);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
200,
|
||||
"Failed creating auth-app token\n"
|
||||
. "HTTP status code 200 is not the expected value " . $response->getStatusCode(),
|
||||
$response
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates app token for user :impersonatedUser with expiration time :expiration using the auth-app API
|
||||
*
|
||||
* @param string $impersonatedUser
|
||||
* @param string $expiration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCreatesAppTokenForUserWithExpirationTimeViaAuthAppApi(
|
||||
string $impersonatedUser,
|
||||
string $expiration,
|
||||
): void {
|
||||
$this->featureContext->setResponse(
|
||||
AuthAppHelper::createAppAuthToken(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getAdminUsername(),
|
||||
$this->featureContext->getAdminPassword(),
|
||||
[
|
||||
"expiry" => $expiration,
|
||||
"userName" => $this->featureContext->getActualUsername($impersonatedUser)
|
||||
],
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,741 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Christoph Wurst <christoph@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2018, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use Behat\Behat\Context\Context;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
use TestHelpers\TokenHelper;
|
||||
use TestHelpers\WebDavHelper;
|
||||
|
||||
/**
|
||||
* Authentication functions
|
||||
*/
|
||||
class AuthContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
|
||||
/**
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function createBasicAuthHeader(string $user, string $password): array {
|
||||
$header = [];
|
||||
$authString = \base64_encode("$user:$password");
|
||||
$header["Authorization"] = "basic $authString";
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param string|null $body
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function sendRequest(
|
||||
string $url,
|
||||
string $method,
|
||||
?string $body = null,
|
||||
?array $headers = []
|
||||
): ResponseInterface {
|
||||
// NOTE: preserving '/' for tests with special cases
|
||||
// E.g: coreApiAuth/webDavSpecialURLs.feature
|
||||
$url = \substr($url, 1);
|
||||
$trimmedUrl = \ltrim($url, '/');
|
||||
$slashCount = \strlen($url) - \strlen($trimmedUrl);
|
||||
if (WebdavHelper::isDAVRequest($url)) {
|
||||
$url = WebdavHelper::prefixRemotePhp($trimmedUrl);
|
||||
}
|
||||
$url = \str_repeat("/", $slashCount) . $url;
|
||||
$fullUrl = $this->featureContext->getBaseUrl() . "/$url";
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$fullUrl,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$method,
|
||||
null,
|
||||
null,
|
||||
$headers,
|
||||
$body,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param string|null $body
|
||||
* @param array|null $headers
|
||||
* @param string|null $property
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function requestUrlWithBasicAuth(
|
||||
string $user,
|
||||
string $url,
|
||||
string $method,
|
||||
?string $body = null,
|
||||
?array $headers = null,
|
||||
?string $property = null
|
||||
): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$url = $this->featureContext->substituteInLineCodes(
|
||||
$url,
|
||||
$user
|
||||
);
|
||||
$authHeader = $this->createBasicAuthHeader($user, $this->featureContext->getPasswordForUser($user));
|
||||
$headers = \array_merge($headers ?? [], $authHeader);
|
||||
|
||||
if ($property !== null) {
|
||||
$body = $this->featureContext->getBodyForOCSRequest($method, $property);
|
||||
}
|
||||
|
||||
return $this->sendRequest(
|
||||
$url,
|
||||
$method,
|
||||
$body,
|
||||
$headers
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When a user requests :url with :method and no authentication
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userRequestsURLWithNoAuth(string $url, string $method): void {
|
||||
$this->featureContext->setResponse($this->sendRequest($url, $method));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When a user requests these endpoints with :method with body :body and no authentication about user :user
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $body
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userRequestsEndpointsWithBodyAndNoAuthThenStatusCodeAboutUser(
|
||||
string $method,
|
||||
string $body,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$ofUser = \strtolower($this->featureContext->getActualUsername($ofUser));
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
$response = $this->sendRequest($row['endpoint'], $method, $body);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When a user requests these endpoints with :method with no authentication about user :user
|
||||
*
|
||||
* @param string $method
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsEndpointsWithoutBodyAndNoAuthAboutUser(
|
||||
string $method,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$ofUser = \strtolower($this->featureContext->getActualUsername($ofUser));
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
$response = $this->sendRequest($row['endpoint'], $method);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When a user requests these endpoints with :method and no authentication
|
||||
*
|
||||
* @param string $method
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsEndpointsWithNoAuthentication(string $method, TableNode $table): void {
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
foreach ($table->getHash() as $row) {
|
||||
$this->featureContext->setResponse(
|
||||
$this->sendRequest(
|
||||
$this->featureContext->substituteInLineCodes($row['endpoint']),
|
||||
$method
|
||||
)
|
||||
);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When a user requests these URLs with :method and no authentication
|
||||
*
|
||||
* @param $method
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function aUserRequestsTheseUrlsWithAndNoAuthentication($method, TableNode $table): void {
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint'], ['service']);
|
||||
foreach ($table->getHash() as $row) {
|
||||
$this->featureContext->setResponse(
|
||||
HttpRequestHelper::sendRequest(
|
||||
$this->featureContext->substituteInLineCodes($row['endpoint']),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$method
|
||||
)
|
||||
);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the user :user requests these endpoints with :method with basic auth
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $method
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsEndpointsWithBasicAuth(string $user, string $method, TableNode $table): void {
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
foreach ($table->getHash() as $row) {
|
||||
$response = $this->requestUrlWithBasicAuth($user, $row['endpoint'], $method);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" requests these endpoints with "([^"]*)" to (?:get|set) property "([^"]*)" about user "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $method
|
||||
* @param string $property
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theUserRequestsTheseEndpointsToGetOrSetPropertyAboutUser(
|
||||
string $user,
|
||||
string $method,
|
||||
string $property,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
$response = $this->requestUrlWithBasicAuth($user, $row['endpoint'], $method, null, null, $property);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator requests these endpoints with :method
|
||||
*
|
||||
* @param string $method
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theAdminRequestsTheseEndpointsWithMethod(string $method, TableNode $table): void {
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
foreach ($table->getHash() as $row) {
|
||||
$response = $this->requestUrlWithBasicAuth(
|
||||
$this->featureContext->getAdminUsername(),
|
||||
$row['endpoint'],
|
||||
$method
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests :url with :method using basic auth
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userRequestsURLUsingBasicAuth(
|
||||
string $user,
|
||||
string $url,
|
||||
string $method
|
||||
): void {
|
||||
$response = $this->requestUrlWithBasicAuth($user, $url, $method);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests :url with :method using basic auth and with headers
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $url
|
||||
* @param string $method
|
||||
* @param TableNode $headersTable
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsURLWithUsingBasicAuthAndDepthHeader(
|
||||
string $user,
|
||||
string $url,
|
||||
string $method,
|
||||
TableNode $headersTable
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$url = $this->featureContext->substituteInLineCodes(
|
||||
$url,
|
||||
$user
|
||||
);
|
||||
$this->featureContext->verifyTableNodeColumns(
|
||||
$headersTable,
|
||||
['header', 'value']
|
||||
);
|
||||
$headers = [];
|
||||
foreach ($headersTable as $row) {
|
||||
$headers[$row['header']] = $row ['value'];
|
||||
}
|
||||
$this->featureContext->setResponse(
|
||||
$this->requestUrlWithBasicAuth(
|
||||
$user,
|
||||
$url,
|
||||
$method,
|
||||
null,
|
||||
$headers
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests these endpoints with :method using password :password
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $method
|
||||
* @param string $password
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsTheseEndpointsWithPassword(
|
||||
string $user,
|
||||
string $method,
|
||||
string $password,
|
||||
TableNode $table
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
|
||||
$authHeader = $this->createBasicAuthHeader($user, $this->featureContext->getActualPassword($password));
|
||||
|
||||
foreach ($table->getHash() as $row) {
|
||||
$response = $this->sendRequest($row['endpoint'], $method, null, $authHeader);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests these endpoints with :method using password :password about user :ofUser
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $method
|
||||
* @param string $password
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsTheseEndpointsUsingPasswordAboutUser(
|
||||
string $user,
|
||||
string $method,
|
||||
string $password,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$ofUser = $this->featureContext->getActualUsername($ofUser);
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint'], ['destination']);
|
||||
|
||||
$headers = $this->createBasicAuthHeader($user, $this->featureContext->getActualPassword($password));
|
||||
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
if (isset($row['destination'])) {
|
||||
$destination = $this->featureContext->substituteInLineCodes(
|
||||
$row['destination'],
|
||||
$ofUser
|
||||
);
|
||||
$headers['Destination'] = $this->featureContext->getBaseUrl()
|
||||
. "/" . WebdavHelper::prefixRemotePhp(\ltrim($destination, "/"));
|
||||
}
|
||||
$response = $this->sendRequest(
|
||||
$row['endpoint'],
|
||||
$method,
|
||||
null,
|
||||
$headers
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests these endpoints with :method including body :body using password :password about user :ofUser
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $method
|
||||
* @param string $body
|
||||
* @param string $password
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsTheseEndpointsWithBodyUsingPasswordAboutUser(
|
||||
string $user,
|
||||
string $method,
|
||||
string $body,
|
||||
string $password,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$ofUser = $this->featureContext->getActualUsername($ofUser);
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint'], ['destination']);
|
||||
|
||||
$headers = $this->createBasicAuthHeader($user, $this->featureContext->getActualPassword($password));
|
||||
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
if (isset($row['destination'])) {
|
||||
$destination = $this->featureContext->substituteInLineCodes(
|
||||
$row['destination'],
|
||||
$ofUser
|
||||
);
|
||||
$headers['Destination'] = $this->featureContext->getBaseUrl()
|
||||
. "/" . WebdavHelper::prefixRemotePhp(\ltrim($destination, "/"));
|
||||
}
|
||||
$response = $this->sendRequest(
|
||||
$row['endpoint'],
|
||||
$method,
|
||||
$body,
|
||||
$headers
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests these endpoints with :method including body :body about user :ofUser
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $method
|
||||
* @param string $body
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsTheseEndpointsIncludingBodyAboutUser(
|
||||
string $user,
|
||||
string $method,
|
||||
string $body,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$ofUser = $this->featureContext->getActualUsername($ofUser);
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
|
||||
$headers = [];
|
||||
if ($method === 'MOVE' || $method === 'COPY') {
|
||||
$headers['Destination'] = '/path/to/destination';
|
||||
}
|
||||
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
$response = $this->requestUrlWithBasicAuth(
|
||||
$user,
|
||||
$row['endpoint'],
|
||||
$method,
|
||||
$body,
|
||||
$headers
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :asUser requests these endpoints with :method using the password of user :ofUser
|
||||
*
|
||||
* @param string $asUser
|
||||
* @param string $method
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsTheseEndpointsWithoutBodyUsingThePasswordOfUser(
|
||||
string $asUser,
|
||||
string $method,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$asUser = $this->featureContext->getActualUsername($asUser);
|
||||
$ofUser = $this->featureContext->getActualUsername($ofUser);
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
|
||||
// do request as $asUser using password of $ofUser
|
||||
$authHeader = $this->createBasicAuthHeader($asUser, $this->featureContext->getPasswordForUser($ofUser));
|
||||
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
$response = $this->sendRequest(
|
||||
$row['endpoint'],
|
||||
$method,
|
||||
null,
|
||||
$authHeader
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :asUser requests these endpoints with :method including body :body using the password of user :user
|
||||
*
|
||||
* @param string $asUser
|
||||
* @param string $method
|
||||
* @param string|null $body
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userRequestsTheseEndpointsIncludingBodyUsingPasswordOfUser(
|
||||
string $asUser,
|
||||
string $method,
|
||||
?string $body,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$asUser = $this->featureContext->getActualUsername($asUser);
|
||||
$ofUser = $this->featureContext->getActualUsername($ofUser);
|
||||
$this->featureContext->verifyTableNodeColumns($table, ['endpoint']);
|
||||
|
||||
// do request as $asUser using password of $ofUser
|
||||
$authHeader = $this->createBasicAuthHeader($asUser, $this->featureContext->getPasswordForUser($ofUser));
|
||||
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
$response = $this->sendRequest(
|
||||
$row['endpoint'],
|
||||
$method,
|
||||
$body,
|
||||
$authHeader
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests these endpoints with :method about user :ofUser
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $method
|
||||
* @param string $ofUser
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRequestsTheseEndpointsAboutUser(
|
||||
string $user,
|
||||
string $method,
|
||||
string $ofUser,
|
||||
TableNode $table
|
||||
): void {
|
||||
$headers = [];
|
||||
if ($method === 'MOVE' || $method === 'COPY') {
|
||||
$baseUrl = $this->featureContext->getBaseUrl();
|
||||
$suffix = $user;
|
||||
if ($this->featureContext->getDavPathVersion() === WebDavHelper::DAV_VERSION_SPACES) {
|
||||
$suffix = $this->featureContext->getPersonalSpaceIdForUser($user);
|
||||
}
|
||||
$davPath = WebDavHelper::getDavPath($this->featureContext->getDavPathVersion(), $suffix);
|
||||
$headers['Destination'] = "$baseUrl/$davPath/moved";
|
||||
}
|
||||
|
||||
foreach ($table->getHash() as $row) {
|
||||
$row['endpoint'] = $this->featureContext->substituteInLineCodes(
|
||||
$row['endpoint'],
|
||||
$ofUser
|
||||
);
|
||||
$response = $this->requestUrlWithBasicAuth(
|
||||
$user,
|
||||
$row['endpoint'],
|
||||
$method,
|
||||
null,
|
||||
$headers
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastStatusCodesArrays();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests :endpoint with :method without retrying
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $endpoint
|
||||
* @param string $method
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userRequestsURLWithoutRetry(
|
||||
string $user,
|
||||
string $endpoint,
|
||||
string $method
|
||||
): void {
|
||||
$username = $this->featureContext->getActualUsername($user);
|
||||
$endpoint = $this->featureContext->substituteInLineCodes(
|
||||
$endpoint,
|
||||
$username
|
||||
);
|
||||
$endpoint = \ltrim($endpoint, '/');
|
||||
if (WebdavHelper::isDAVRequest($endpoint)) {
|
||||
$endpoint = WebdavHelper::prefixRemotePhp($endpoint);
|
||||
}
|
||||
$fullUrl = $this->featureContext->getBaseUrl() . "/$endpoint";
|
||||
$response = HttpRequestHelper::sendRequestOnce(
|
||||
$fullUrl,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$method,
|
||||
$username,
|
||||
$this->featureContext->getPasswordForUser($user)
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user should not be able to log in with wrong password :password
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userShouldNotBeAbleToLogInWithWrongPassword(
|
||||
string $user,
|
||||
string $password
|
||||
): void {
|
||||
TokenHelper::clearUserTokens($user, $this->featureContext->getBaseUrl());
|
||||
$response = TokenHelper::makeLoginRequest(
|
||||
$user,
|
||||
$password,
|
||||
$this->featureContext->getBaseUrl(),
|
||||
new \GuzzleHttp\Cookie\CookieJar()
|
||||
);
|
||||
// why is not 401 returned?
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(204, 'should not be able to log in', $response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author Sergio Bertolin <sbertolin@owncloud.com>
|
||||
* @author Phillip Davis <phil@jankaritech.com>
|
||||
* @copyright Copyright (c) 2018, ownCloud GmbH
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use TestHelpers\OcsApiHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Capabilities context.
|
||||
*/
|
||||
class CapabilitiesContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $username
|
||||
* @param boolean $formatJson // if true then formats the response in json
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userGetsCapabilities(string $username, ?bool $formatJson = false): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($username);
|
||||
$password = $this->featureContext->getPasswordForUser($user);
|
||||
return OcsApiHelper::sendRequest(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$password,
|
||||
'GET',
|
||||
'/cloud/capabilities' . ($formatJson ? '?format=json' : ''),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
[],
|
||||
$this->featureContext->getOcsApiVersion()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public function getAdminUsernameForCapabilitiesCheck(): string {
|
||||
if (\TestHelpers\OcHelper::isTestingOnReva()) {
|
||||
// When testing on reva we don't have a user called "admin" to use
|
||||
// to access the capabilities. So create an ordinary user on-the-fly
|
||||
// with a default password. That user should be able to get a
|
||||
// capabilities response that the test can process.
|
||||
$adminUsername = "PseudoAdminForRevaTest";
|
||||
$createdUsers = $this->featureContext->getCreatedUsers();
|
||||
if (!\array_key_exists($adminUsername, $createdUsers)) {
|
||||
$this->featureContext->userHasBeenCreated(["userName" => $adminUsername]);
|
||||
}
|
||||
} else {
|
||||
$adminUsername = $this->featureContext->getAdminUsername();
|
||||
}
|
||||
return $adminUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SimpleXMLElement $xml of the capabilities
|
||||
* @param string $capabilitiesApp the "app" name in the capabilities response
|
||||
* @param string $capabilitiesPath the path to the element
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getParameterValueFromXml(
|
||||
SimpleXMLElement $xml,
|
||||
string $capabilitiesApp,
|
||||
string $capabilitiesPath
|
||||
): string {
|
||||
$path_to_element = \explode('@@@', $capabilitiesPath);
|
||||
$answeredValue = $xml->{$capabilitiesApp};
|
||||
foreach ($path_to_element as $element) {
|
||||
$nameIndexParts = \explode('[', $element);
|
||||
if (isset($nameIndexParts[1])) {
|
||||
// This part of the path should be something like "some_element[1]"
|
||||
// Separately extract the name and the index
|
||||
$name = $nameIndexParts[0];
|
||||
$index = (int) \explode(']', $nameIndexParts[1])[0];
|
||||
// and use those to construct the reference into the next XML level
|
||||
$answeredValue = $answeredValue->{$name}[$index];
|
||||
} else {
|
||||
if ($element !== "") {
|
||||
$answeredValue = $answeredValue->{$element};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (string) $answeredValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :username retrieves the capabilities using the capabilities API
|
||||
*
|
||||
* @param string $username
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userRetrievesCapabilities(string $username): void {
|
||||
$user = $this->featureContext->getActualUsername($username);
|
||||
$this->featureContext->setResponse($this->userGetsCapabilities($user, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator retrieves the capabilities using the capabilities API
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorGetsCapabilities(): void {
|
||||
$user = $this->getAdminUsernameForCapabilitiesCheck();
|
||||
$this->featureContext->setResponse($this->userGetsCapabilities($user, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the major-minor-micro version data in the response should match the version string
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function checkVersionMajorMinorMicroResponse(): void {
|
||||
$jsonResponse = $this->featureContext->getJsonDecodedResponseBodyContent();
|
||||
$versionData = $jsonResponse->ocs->data->version;
|
||||
$versionString = (string) $versionData->string;
|
||||
// We expect that versionString will be in a format like "10.9.2 beta" or "10.9.2-alpha" or "10.9.2"
|
||||
$result = \preg_match('/^[0-9]+\.[0-9]+\.[0-9]+/', $versionString, $matches);
|
||||
Assert::assertSame(
|
||||
1,
|
||||
$result,
|
||||
__METHOD__ . " version string '$versionString' does not start with a semver version"
|
||||
);
|
||||
// semVerParts should have an array with the 3 semver components of the version, e.g. "1", "9" and "2".
|
||||
$semVerParts = \explode('.', $matches[0]);
|
||||
$expectedMajor = $semVerParts[0];
|
||||
$expectedMinor = $semVerParts[1];
|
||||
$expectedMicro = $semVerParts[2];
|
||||
$actualMajor = (string) $versionData->major;
|
||||
$actualMinor = (string) $versionData->minor;
|
||||
$actualMicro = (string) $versionData->micro;
|
||||
Assert::assertSame(
|
||||
$expectedMajor,
|
||||
$actualMajor,
|
||||
__METHOD__ . "'major' data item does not match with major version in string '$versionString'"
|
||||
);
|
||||
Assert::assertSame(
|
||||
$expectedMinor,
|
||||
$actualMinor,
|
||||
__METHOD__ . "'minor' data item does not match with minor version in string '$versionString'"
|
||||
);
|
||||
Assert::assertSame(
|
||||
$expectedMicro,
|
||||
$actualMicro,
|
||||
__METHOD__ . "'micro' data item does not match with micro (patch) version in string '$versionString'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the status.php response should include
|
||||
*
|
||||
* @param PyStringNode $jsonExpected
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function statusPhpRespondedShouldMatch(PyStringNode $jsonExpected): void {
|
||||
$jsonExpectedDecoded = \json_decode($jsonExpected->getRaw(), true);
|
||||
$jsonRespondedDecoded = $this->featureContext->getJsonDecodedResponse();
|
||||
|
||||
$response = $this->userGetsCapabilities($this->getAdminUsernameForCapabilitiesCheck());
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml($response, __METHOD__)->data->capabilities;
|
||||
$edition = $this->getParameterValueFromXml(
|
||||
$responseXmlObject,
|
||||
'core',
|
||||
'status@@@edition'
|
||||
);
|
||||
|
||||
$product = $this->getParameterValueFromXml(
|
||||
$responseXmlObject,
|
||||
'core',
|
||||
'status@@@product'
|
||||
);
|
||||
if (!\strlen($product)) {
|
||||
Assert::fail(
|
||||
"Cannot get product from core capabilities"
|
||||
);
|
||||
}
|
||||
|
||||
$productName = $this->getParameterValueFromXml(
|
||||
$responseXmlObject,
|
||||
'core',
|
||||
'status@@@productname'
|
||||
);
|
||||
|
||||
if (!\strlen($productName)) {
|
||||
Assert::fail(
|
||||
"Cannot get productname from core capabilities"
|
||||
);
|
||||
}
|
||||
|
||||
$jsonExpectedDecoded['edition'] = $edition;
|
||||
$jsonExpectedDecoded['product'] = $product;
|
||||
$jsonExpectedDecoded['productname'] = $productName;
|
||||
|
||||
// We are on КуСфера or reva or some other implementation. We cannot do "occ status".
|
||||
// So get the expected version values by looking in the capabilities response.
|
||||
$version = $this->getParameterValueFromXml(
|
||||
$responseXmlObject,
|
||||
'core',
|
||||
'status@@@version'
|
||||
);
|
||||
|
||||
if (!\strlen($version)) {
|
||||
Assert::fail(
|
||||
"Cannot get version from core capabilities"
|
||||
);
|
||||
}
|
||||
|
||||
$versionString = $this->getParameterValueFromXml(
|
||||
$responseXmlObject,
|
||||
'core',
|
||||
'status@@@versionstring'
|
||||
);
|
||||
|
||||
if (!\strlen($versionString)) {
|
||||
Assert::fail(
|
||||
"Cannot get versionstring from core capabilities"
|
||||
);
|
||||
}
|
||||
|
||||
$jsonExpectedDecoded['version'] = $version;
|
||||
$jsonExpectedDecoded['versionstring'] = $versionString;
|
||||
$errorMessage = "";
|
||||
$errorFound = false;
|
||||
foreach ($jsonExpectedDecoded as $key => $expectedValue) {
|
||||
if (\array_key_exists($key, $jsonRespondedDecoded)) {
|
||||
$actualValue = $jsonRespondedDecoded[$key];
|
||||
if ($actualValue !== $expectedValue) {
|
||||
$errorMessage .= "$key expected value was $expectedValue but actual value was $actualValue\n";
|
||||
$errorFound = true;
|
||||
}
|
||||
} else {
|
||||
$errorMessage .= "$key was not found in the status response\n";
|
||||
$errorFound = true;
|
||||
}
|
||||
}
|
||||
Assert::assertFalse($errorFound, $errorMessage);
|
||||
// We have checked that the status.php response has data that matches up with
|
||||
// data found in the capabilities response and/or the "occ status" command output.
|
||||
// But the output might be reported wrongly in all of these in the same way.
|
||||
// So check that the values also seem "reasonable".
|
||||
$version = $jsonExpectedDecoded['version'];
|
||||
$versionString = $jsonExpectedDecoded['versionstring'];
|
||||
Assert::assertMatchesRegularExpression(
|
||||
"/^\d+\.\d+\.\d+\.\d+$/",
|
||||
$version,
|
||||
"version should be in a form like 10.9.8.1 but is $version"
|
||||
);
|
||||
if (\preg_match("/^(\d+\.\d+\.\d+)\.\d+(-[0-9A-Za-z-]+)?(\+[0-9A-Za-z-]+)?$/", $version, $matches)) {
|
||||
// We should have matched something like 10.9.8 - the first 3 numbers in the version.
|
||||
// Ignore pre-releases and meta information
|
||||
Assert::assertArrayHasKey(
|
||||
1,
|
||||
$matches,
|
||||
"version $version could not match the pattern Major.Minor.Patch"
|
||||
);
|
||||
$majorMinorPatchVersion = $matches[1];
|
||||
} else {
|
||||
Assert::fail("version '$version' does not start in a form like 10.9.8");
|
||||
}
|
||||
Assert::assertStringStartsWith(
|
||||
$majorMinorPatchVersion,
|
||||
$versionString,
|
||||
"version string should start with $majorMinorPatchVersion but is $versionString"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Roeland Jago Douma <rullzer@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2018, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
use TestHelpers\UploadHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Checksum functions
|
||||
*/
|
||||
class ChecksumContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string $checksum
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function uploadFileToWithChecksumUsingTheAPI(
|
||||
string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
string $checksum
|
||||
): ResponseInterface {
|
||||
$file = \file_get_contents(
|
||||
UploadHelper::getAcceptanceTestsDir() . $source
|
||||
);
|
||||
return $this->featureContext->makeDavRequest(
|
||||
$user,
|
||||
'PUT',
|
||||
$destination,
|
||||
['OC-Checksum' => $checksum],
|
||||
$file
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user uploads file :source to :destination with checksum :checksum using the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string $checksum
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userUploadsFileToWithChecksumUsingTheAPI(
|
||||
string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
string $checksum
|
||||
): void {
|
||||
$this->featureContext->setResponse(
|
||||
$this->uploadFileToWithChecksumUsingTheAPI($user, $source, $destination, $checksum)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has uploaded file :source to :destination with checksum :checksum
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string $checksum
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userHasUploadedFileToWithChecksumUsingTheAPI(
|
||||
string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
string $checksum
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$response = $this->uploadFileToWithChecksumUsingTheAPI(
|
||||
$user,
|
||||
$source,
|
||||
$destination,
|
||||
$checksum
|
||||
);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe([201,204], '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param string $checksum
|
||||
* @param string $destination
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function uploadFileWithContentAndChecksumToUsingTheAPI(
|
||||
string $user,
|
||||
string $content,
|
||||
string $checksum,
|
||||
string $destination
|
||||
): ResponseInterface {
|
||||
return $this->featureContext->makeDavRequest(
|
||||
$user,
|
||||
'PUT',
|
||||
$destination,
|
||||
['OC-Checksum' => $checksum],
|
||||
$content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has uploaded file with content :content and checksum :checksum to :destination
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param string $checksum
|
||||
* @param string $destination
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userHasUploadedFileWithContentAndChecksumToUsingTheAPI(
|
||||
string $user,
|
||||
string $content,
|
||||
string $checksum,
|
||||
string $destination
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$response = $this->uploadFileWithContentAndChecksumToUsingTheAPI($user, $content, $checksum, $destination);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(201, '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user requests the checksum of :path via propfind
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userRequestsTheChecksumOfViaPropfind(string $user, string $path): void {
|
||||
$this->featureContext->setResponse($this->propfindResourceChecksum($user, $path));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function propfindResourceChecksum(string $user, string $path, ?string $spaceId = null): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$body = '<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:prop>
|
||||
<oc:checksums />
|
||||
</d:prop>
|
||||
</d:propfind>';
|
||||
$password = $this->featureContext->getPasswordForUser($user);
|
||||
return WebDavHelper::makeDavRequest(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$password,
|
||||
'PROPFIND',
|
||||
$path,
|
||||
null,
|
||||
$spaceId,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$body,
|
||||
$this->featureContext->getDavPathVersion()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the webdav checksum should match :expectedChecksum
|
||||
*
|
||||
* @param string $expectedChecksum
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theWebdavChecksumShouldMatch(string $expectedChecksum): void {
|
||||
$bodyContents = $this->featureContext->getResponse()->getBody()->getContents();
|
||||
$this->validateChecksum($bodyContents, $expectedChecksum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then as user :user the webdav checksum of :path via propfind should match :expectedChecksum
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
* @param string $expectedChecksum
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function asUserTheWebdavChecksumOfPathViaPropfindShouldMatch(
|
||||
string $user,
|
||||
string $path,
|
||||
string $expectedChecksum
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$resource = $this->propfindResourceChecksum($user, $path);
|
||||
$bodyContents = $resource->getBody()->getContents();
|
||||
$this->validateChecksum($bodyContents, $expectedChecksum);
|
||||
}
|
||||
/**
|
||||
* @param string $bodyContents
|
||||
* @param string $expectedChecksum
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function validateChecksum(string $bodyContents, string $expectedChecksum): void {
|
||||
$service = new Sabre\Xml\Service();
|
||||
$parsed = $service->parse($bodyContents);
|
||||
|
||||
/*
|
||||
* Fetch the checksum array
|
||||
* The checksums are way down in the array:
|
||||
* $checksums = $parsed[0]['value'][1]['value'][0]['value'][0];
|
||||
* And inside is the actual checksum string:
|
||||
* $checksums['value'][0]['value']
|
||||
* The Asserts below check the existence of the expected key at every level
|
||||
* of the nested array. This helps to see what happened if a test fails
|
||||
* because the response structure is not as expected.
|
||||
*/
|
||||
|
||||
Assert::assertIsArray(
|
||||
$parsed,
|
||||
__METHOD__
|
||||
. " could not parse response as XML. Expected parsed XML to be an array but found " . $bodyContents
|
||||
);
|
||||
Assert::assertArrayHasKey(
|
||||
0,
|
||||
$parsed,
|
||||
__METHOD__ . " parsed XML does not have key 0"
|
||||
);
|
||||
$parsed0 = $parsed[0];
|
||||
Assert::assertArrayHasKey(
|
||||
'value',
|
||||
$parsed0,
|
||||
__METHOD__ . " parsed XML parsed0 does not have key value"
|
||||
);
|
||||
$parsed0Value = $parsed0['value'];
|
||||
Assert::assertArrayHasKey(
|
||||
1,
|
||||
$parsed0Value,
|
||||
__METHOD__ . " parsed XML parsed0Value does not have key 1"
|
||||
);
|
||||
$parsed0Value1 = $parsed0Value[1];
|
||||
Assert::assertArrayHasKey(
|
||||
'value',
|
||||
$parsed0Value1,
|
||||
__METHOD__ . " parsed XML parsed0Value1 does not have key value after key 1"
|
||||
);
|
||||
$parsed0Value1Value = $parsed0Value1['value'];
|
||||
Assert::assertArrayHasKey(
|
||||
0,
|
||||
$parsed0Value1Value,
|
||||
__METHOD__ . " parsed XML parsed0Value1Value does not have key 0"
|
||||
);
|
||||
$parsed0Value1Value0 = $parsed0Value1Value[0];
|
||||
Assert::assertArrayHasKey(
|
||||
'value',
|
||||
$parsed0Value1Value0,
|
||||
__METHOD__ . " parsed XML parsed0Value1Value0 does not have key value"
|
||||
);
|
||||
$parsed0Value1Value0Value = $parsed0Value1Value0['value'];
|
||||
Assert::assertArrayHasKey(
|
||||
0,
|
||||
$parsed0Value1Value0Value,
|
||||
__METHOD__ . " parsed XML parsed0Value1Value0Value does not have key 0"
|
||||
);
|
||||
$checksums = $parsed0Value1Value0Value[0];
|
||||
Assert::assertArrayHasKey(
|
||||
'value',
|
||||
$checksums,
|
||||
__METHOD__ . " parsed XML checksums does not have key value"
|
||||
);
|
||||
$checksumsValue = $checksums['value'];
|
||||
Assert::assertArrayHasKey(
|
||||
0,
|
||||
$checksumsValue,
|
||||
__METHOD__ . " parsed XML checksumsValue does not have key 0"
|
||||
);
|
||||
$checksumsValue0 = $checksumsValue[0];
|
||||
Assert::assertArrayHasKey(
|
||||
'value',
|
||||
$checksumsValue0,
|
||||
__METHOD__ . " parsed XML checksumsValue0 does not have key value"
|
||||
);
|
||||
$actualChecksum = $checksumsValue0['value'];
|
||||
Assert::assertEquals(
|
||||
$expectedChecksum,
|
||||
$actualChecksum,
|
||||
"Expected: webDav checksum should be $expectedChecksum but got $actualChecksum"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the header checksum should match :expectedChecksum
|
||||
*
|
||||
* @param string $expectedChecksum
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theHeaderChecksumShouldMatch(string $expectedChecksum): void {
|
||||
$headerChecksums
|
||||
= $this->featureContext->getResponse()->getHeader('OC-Checksum');
|
||||
|
||||
Assert::assertIsArray(
|
||||
$headerChecksums,
|
||||
__METHOD__ . " getHeader('OC-Checksum') did not return an array"
|
||||
);
|
||||
|
||||
Assert::assertNotEmpty(
|
||||
$headerChecksums,
|
||||
__METHOD__ . " getHeader('OC-Checksum') returned an empty array. No checksum header was found."
|
||||
);
|
||||
|
||||
$checksumCount = \count($headerChecksums);
|
||||
|
||||
Assert::assertTrue(
|
||||
$checksumCount === 1,
|
||||
__METHOD__ . " Expected 1 checksum in the header but found $checksumCount checksums"
|
||||
);
|
||||
|
||||
$headerChecksum
|
||||
= $headerChecksums[0];
|
||||
Assert::assertEquals(
|
||||
$expectedChecksum,
|
||||
$headerChecksum,
|
||||
"Expected: header checksum should match $expectedChecksum but got $headerChecksum"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the header checksum when user :arg1 downloads file :arg2 using the WebDAV API should match :arg3
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileName
|
||||
* @param string $expectedChecksum
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theHeaderChecksumWhenUserDownloadsFileUsingTheWebdavApiShouldMatch(
|
||||
string $user,
|
||||
string $fileName,
|
||||
string $expectedChecksum
|
||||
): void {
|
||||
$response = $this->featureContext->downloadFileAsUserUsingPassword($user, $fileName);
|
||||
$headerChecksums = $response->getHeader('OC-Checksum');
|
||||
|
||||
Assert::assertIsArray(
|
||||
$headerChecksums,
|
||||
__METHOD__ . " getHeader('OC-Checksum') did not return an array"
|
||||
);
|
||||
|
||||
Assert::assertNotEmpty(
|
||||
$headerChecksums,
|
||||
__METHOD__ . " getHeader('OC-Checksum') returned an empty array. No checksum header was found."
|
||||
);
|
||||
|
||||
$checksumCount = \count($headerChecksums);
|
||||
|
||||
Assert::assertTrue(
|
||||
$checksumCount === 1,
|
||||
__METHOD__ . " Expected 1 checksum in the header but found $checksumCount checksums"
|
||||
);
|
||||
|
||||
$headerChecksum
|
||||
= $headerChecksums[0];
|
||||
Assert::assertEquals(
|
||||
$expectedChecksum,
|
||||
$headerChecksum,
|
||||
"Expected: header checksum should match $expectedChecksum but got $headerChecksum"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param int $num
|
||||
* @param int $total
|
||||
* @param string $data
|
||||
* @param string $destination
|
||||
* @param string $expectedChecksum
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function uploadChunkFileOfWithToWithChecksum(
|
||||
string $user,
|
||||
int $num,
|
||||
int $total,
|
||||
string $data,
|
||||
string $destination,
|
||||
string $expectedChecksum
|
||||
): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$num -= 1;
|
||||
$file = "$destination-chunking-42-$total-$num";
|
||||
return $this->featureContext->makeDavRequest(
|
||||
$user,
|
||||
'PUT',
|
||||
$file,
|
||||
['OC-Checksum' => $expectedChecksum, 'OC-Chunked' => '1'],
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user uploads chunk file :num of :total with :data to :destination with checksum :expectedChecksum using the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param int $num
|
||||
* @param int $total
|
||||
* @param string $data
|
||||
* @param string $destination
|
||||
* @param string $expectedChecksum
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userUploadsChunkFileOfWithToWithChecksum(
|
||||
string $user,
|
||||
int $num,
|
||||
int $total,
|
||||
string $data,
|
||||
string $destination,
|
||||
string $expectedChecksum
|
||||
): void {
|
||||
$response = $this->uploadChunkFileOfWithToWithChecksum(
|
||||
$user,
|
||||
$num,
|
||||
$total,
|
||||
$data,
|
||||
$destination,
|
||||
$expectedChecksum
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has uploaded chunk file :num of :total with :data to :destination with checksum :expectedChecksum
|
||||
*
|
||||
* @param string $user
|
||||
* @param int $num
|
||||
* @param int $total
|
||||
* @param string $data
|
||||
* @param string $destination
|
||||
* @param string $expectedChecksum
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userHasUploadedChunkFileOfWithToWithChecksum(
|
||||
string $user,
|
||||
int $num,
|
||||
int $total,
|
||||
string $data,
|
||||
string $destination,
|
||||
string $expectedChecksum
|
||||
): void {
|
||||
$response = $this->uploadChunkFileOfWithToWithChecksum(
|
||||
$user,
|
||||
$num,
|
||||
$total,
|
||||
$data,
|
||||
$destination,
|
||||
$expectedChecksum
|
||||
);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
[201, 206],
|
||||
'',
|
||||
$response
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,915 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Sajan Gurung sajan@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use TestHelpers\CliHelper;
|
||||
use TestHelpers\OcConfigHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
* CLI context
|
||||
*/
|
||||
class CliContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
private SpacesContext $spacesContext;
|
||||
|
||||
/**
|
||||
* qsfera users storage path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getUsersStoragePath(): string {
|
||||
$path = getenv('OC_STORAGE_PATH') ?: '/var/lib/qsfera/storage/users';
|
||||
// need for CI
|
||||
$home = getenv('HOME');
|
||||
$path = preg_replace('#^~/#', $home . '/', $path);
|
||||
$path = str_replace('$HOME', $home, $path);
|
||||
|
||||
return rtrim($path, '/') . '/users';
|
||||
}
|
||||
|
||||
/**
|
||||
* qsfera project spaces storage path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getProjectsStoragePath(): string {
|
||||
$path = getenv('OC_STORAGE_PATH') ?: '/var/lib/qsfera/storage/users';
|
||||
return $path . '/projects';
|
||||
}
|
||||
|
||||
/**
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
$this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* expects a file to exist at the given path
|
||||
*
|
||||
* @param string $path
|
||||
* @param string|null $sizeGb
|
||||
* @param int $maxSeconds
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function waitForPath(string $path, ?string $sizeGb = null, int $maxSeconds = 10): void {
|
||||
$escapedPath = escapeshellarg($path);
|
||||
|
||||
for ($i = 0; $i < $maxSeconds * 5; $i++) {
|
||||
if ($sizeGb) {
|
||||
if (!preg_match('/^(\d+)gb$/i', $sizeGb, $matches)) {
|
||||
throw new \InvalidArgumentException("Invalid size format: $sizeGb. Use formats like 1gb, 5gb.");
|
||||
}
|
||||
$targetBytes = (int)$matches[1] * 1024 * 1024 * 1024;
|
||||
$body = [
|
||||
"command" => "[ -f $escapedPath ] && stat -c%s $escapedPath || echo 0",
|
||||
"raw" => true
|
||||
];
|
||||
$data = json_decode((string)CliHelper::runCommand($body)->getBody(), true);
|
||||
|
||||
if (isset($data['message']) && (int)trim($data['message']) >= $targetBytes) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$body = [
|
||||
"command" => "ls $escapedPath >/dev/null 2>&1 && echo exists || echo not_exists",
|
||||
"raw" => true
|
||||
];
|
||||
$response = CliHelper::runCommand($body);
|
||||
$data = json_decode((string)$response->getBody(), true);
|
||||
|
||||
if (isset($data['message']) && trim($data['message']) === 'exists') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
usleep(200000);
|
||||
}
|
||||
|
||||
throw new \Exception("Timeout waiting for: $path");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the administrator has stopped the server
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorHasStoppedTheServer(): void {
|
||||
$response = OcConfigHelper::stopQsfera();
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^the administrator (?:starts|has started) the server$/
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorHasStartedTheServer(): void {
|
||||
$response = OcConfigHelper::startQsfera();
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^the administrator resets the password of (non-existing|existing) user "([^"]*)" to "([^"]*)" using the CLI$/
|
||||
*
|
||||
* @param string $status
|
||||
* @param string $user
|
||||
* @param string $password
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorResetsThePasswordOfUserUsingTheCLI(
|
||||
string $status,
|
||||
string $user,
|
||||
string $password
|
||||
): void {
|
||||
$command = "idm resetpassword -u $user";
|
||||
$body = [
|
||||
"command" => $command,
|
||||
"inputs" => [$password, $password]
|
||||
];
|
||||
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
if ($status === "non-existing") {
|
||||
return;
|
||||
}
|
||||
$this->featureContext->updateUserPassword($user, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator deletes the empty trashbin folders using the CLI
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorDeletesEmptyTrashbinFoldersUsingTheCli(): void {
|
||||
$path = $this->featureContext->getStorageUsersRoot();
|
||||
$command = "trash purge-empty-dirs -p $path --dry-run=false";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator checks the backup consistency using the CLI
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorChecksTheBackupConsistencyUsingTheCli(): void {
|
||||
$path = $this->featureContext->getStorageUsersRoot();
|
||||
$command = "backup consistency -p $path";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates app token for user :user with expiration time :expirationTime using the auth-app CLI
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $expirationTime
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCreatesAppTokenForUserWithExpirationTimeUsingTheAuthAppCLI(
|
||||
string $user,
|
||||
string $expirationTime
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUserName($user);
|
||||
$command = "auth-app create --user-name=$user --expiration=$expirationTime";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has created app token with expiration time :expirationTime using the auth-app CLI
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $expirationTime
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userHasCreatedAppTokenWithExpirationTimeUsingTheAuthAppCLI(
|
||||
string $user,
|
||||
string $expirationTime
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUserName($user);
|
||||
$command = "auth-app create --user-name=$user --expiration=$expirationTime";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
|
||||
$response = CliHelper::runCommand($body);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
$jsonResponse = $this->featureContext->getJsonDecodedResponse($response);
|
||||
Assert::assertSame("OK", $jsonResponse["status"]);
|
||||
Assert::assertSame(
|
||||
0,
|
||||
$jsonResponse["exitCode"],
|
||||
"Expected exit code to be 0, but got " . $jsonResponse["exitCode"]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator removes all the file versions using the CLI
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorRemovesAllVersionsOfResources() {
|
||||
$path = $this->featureContext->getStorageUsersRoot();
|
||||
$command = "revisions purge -p $path --dry-run=false";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator removes the versions of file :file of user :user from space :space using the CLI
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $user
|
||||
* @param string $space
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorRemovesTheVersionsOfFileUsingFileId($file, $user, $space) {
|
||||
$path = $this->featureContext->getStorageUsersRoot();
|
||||
$fileId = $this->spacesContext->getFileId($user, $space, $file);
|
||||
$command = "revisions purge -p $path -r $fileId --dry-run=false";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^the administrator reindexes all spaces using the CLI$/
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorReindexesAllSpacesUsingTheCli(): void {
|
||||
$endpoint = $this->featureContext->getBaseUrlHostName();
|
||||
$command = "search index --all-spaces --endpoint $endpoint:9220 --insecure";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^the administrator reindexes a space "([^"]*)" using the CLI$/
|
||||
*
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorReindexesASpaceUsingTheCli(string $spaceName): void {
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($this->featureContext->getAdminUsername(), $spaceName);
|
||||
$endpoint = $this->featureContext->getBaseUrlHostName();
|
||||
$command = "search index --space $spaceId --endpoint $endpoint:9220 --insecure";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator removes the file versions of space :space using the CLI
|
||||
*
|
||||
* @param string $space
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorRemovesTheVersionsOfFilesInSpaceUsingSpaceId(string $space): void {
|
||||
$path = $this->featureContext->getStorageUsersRoot();
|
||||
$adminUsername = $this->featureContext->getAdminUsername();
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($adminUsername, $space);
|
||||
$command = "revisions purge -p $path -r $spaceId --dry-run=false";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the command should be successful
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theCommandShouldBeSuccessful(): void {
|
||||
$response = $this->featureContext->getResponse();
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
|
||||
$jsonResponse = $this->featureContext->getJsonDecodedResponse($response);
|
||||
|
||||
Assert::assertSame("OK", $jsonResponse["status"], $jsonResponse["message"]);
|
||||
Assert::assertSame(
|
||||
0,
|
||||
$jsonResponse["exitCode"],
|
||||
"Expected exit code to be 0, but got " . $jsonResponse["exitCode"]
|
||||
. ". Message: " . $jsonResponse["message"]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the command output (should|should not) contain "([^"]*)"$/
|
||||
*
|
||||
* @param string $shouldOrNot
|
||||
* @param string $output
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theCommandOutputShouldContain(string $shouldOrNot, string $output): void {
|
||||
$response = $this->featureContext->getResponse();
|
||||
$jsonResponse = $this->featureContext->getJsonDecodedResponse($response);
|
||||
$output = $this->featureContext->substituteInLineCodes($output);
|
||||
|
||||
if ($shouldOrNot === "should") {
|
||||
Assert::assertStringContainsString($output, $jsonResponse["message"]);
|
||||
} else {
|
||||
Assert::assertStringNotContainsString($output, $jsonResponse["message"]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator lists all the upload sessions
|
||||
* @When the administrator lists all the upload sessions with flag :flag
|
||||
*
|
||||
* @param string|null $flag
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorListsAllTheUploadSessions(?string $flag = null): void {
|
||||
if ($flag) {
|
||||
$flag = "--$flag";
|
||||
}
|
||||
$command = "storage-users uploads sessions --json $flag";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator cleans upload sessions with the following flags:
|
||||
*
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCleansUploadSessionsWithTheFollowingFlags(TableNode $table): void {
|
||||
$flag = "";
|
||||
foreach ($table->getRows() as $row) {
|
||||
$flag .= "--$row[0] ";
|
||||
}
|
||||
$flagString = trim($flag);
|
||||
$command = "storage-users uploads sessions $flagString --clean --json";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator restarts the upload sessions that are in postprocessing
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorRestartsTheUploadSessionsThatAreInPostprocessing(): void {
|
||||
$command = "storage-users uploads sessions --processing --restart --json";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator restarts the upload sessions of file :file
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function theAdministratorRestartsUploadSessionsOfFile(string $file): void {
|
||||
$response = CliHelper::runCommand(["command" => "storage-users uploads sessions --json"]);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
$responseArray = $this->getJSONDecodedCliMessage($response);
|
||||
|
||||
foreach ($responseArray as $item) {
|
||||
if ($item->filename === $file) {
|
||||
$uploadId = $item->id;
|
||||
}
|
||||
}
|
||||
|
||||
$command = "storage-users uploads sessions --id=$uploadId --restart --json";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the CLI response (should|should not) contain these entries:$/
|
||||
*
|
||||
* @param string $shouldOrNot
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function theCLIResponseShouldContainTheseEntries(string $shouldOrNot, TableNode $table): void {
|
||||
$expectedFiles = $table->getColumn(0);
|
||||
$responseArray = $this->getJSONDecodedCliMessage($this->featureContext->getResponse());
|
||||
|
||||
$resourceNames = [];
|
||||
foreach ($responseArray as $item) {
|
||||
if (isset($item->filename)) {
|
||||
$resourceNames[] = $item->filename;
|
||||
}
|
||||
}
|
||||
|
||||
if ($shouldOrNot === "should not") {
|
||||
foreach ($expectedFiles as $expectedFile) {
|
||||
Assert::assertNotTrue(
|
||||
\in_array($expectedFile, $resourceNames),
|
||||
"The resource '$expectedFile' was found in the response."
|
||||
);
|
||||
}
|
||||
} else {
|
||||
foreach ($expectedFiles as $expectedFile) {
|
||||
Assert::assertTrue(
|
||||
\in_array($expectedFile, $resourceNames),
|
||||
"The resource '$expectedFile' was not found in the response."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return array
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function getJSONDecodedCliMessage(ResponseInterface $response): array {
|
||||
$responseBody = $this->featureContext->getJsonDecodedResponse($response);
|
||||
|
||||
// $responseBody["message"] contains a message info with the array of output json of the upload sessions command
|
||||
// Example Output: "INFO memory is not limited, skipping package=github.com/KimMachineGun/automemlimit/memlimit [{<output-json>}]"
|
||||
// So, only extracting the array of output json from the message
|
||||
\preg_match('/(\[.*\])/', $responseBody["message"], $matches);
|
||||
return \json_decode($matches[1], null, 512, JSON_THROW_ON_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @AfterScenario @cli-uploads-sessions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function cleanUploadsSessions(): void {
|
||||
$command = "storage-users uploads sessions --clean";
|
||||
$body = [
|
||||
"command" => $command
|
||||
];
|
||||
$response = CliHelper::runCommand($body);
|
||||
Assert::assertEquals("200", $response->getStatusCode(), "Failed to clean upload sessions");
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates the folder :folder for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $folder
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCreatesFolder(string $folder, string $user): void {
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
$fullPath = "$storagePath/$userUuid/$folder";
|
||||
|
||||
$body = [
|
||||
"command" => "mkdir -p $fullPath",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
$this->waitForPath($fullPath);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator lists the content of the POSIX storage folder of user :user
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCheckUsersFolder(string $user): void {
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
$body = [
|
||||
"command" => "ls -la $storagePath/$userUuid",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates the file :file with content :content for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $content
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCreatesFile(string $file, string $content, string $user): void {
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
$fullPath = "$storagePath/$userUuid/$file";
|
||||
$safeContent = escapeshellarg($content);
|
||||
$body = [
|
||||
"command" => "echo -n $safeContent > $fullPath",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
$this->waitForPath($fullPath);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator has created the file :file with content :content for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $content
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorHasCreatedFile(string $file, string $content, string $user): void {
|
||||
$this->theAdministratorCreatesFile($file, $content, $user);
|
||||
$this->theCommandShouldBeSuccessful();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates the file :file with size :size for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $size Example: "1gb", "5gb"
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCreatesLargeFileWithSize(string $file, string $size, string $user): void {
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
|
||||
$size = strtolower($size);
|
||||
if (!preg_match('/^(\d+)gb$/', $size, $matches)) {
|
||||
throw new \InvalidArgumentException("Invalid size format: $size. Use formats like 1gb, 5gb.");
|
||||
}
|
||||
|
||||
$count = (int)$matches[1] * 1024; // 1GB = 1024M
|
||||
$bs = '1M';
|
||||
$filePath = "$storagePath/$userUuid/$file";
|
||||
|
||||
$body = [
|
||||
"command" => "dd if=/dev/zero of=$filePath bs=$bs count=$count",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
$this->waitForPath($filePath, $size, 15);
|
||||
sleep(7);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates :count files sequentially in the directory :dir for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param int $count
|
||||
* @param string $dir
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCreatesFilesSequentially(int $count, string $dir, string $user): void {
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath() . "/$userUuid/$dir";
|
||||
$cmd = '';
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
$cmd .= "echo -n \"file $i content\" > $storagePath/file_$i.txt; ";
|
||||
}
|
||||
$body = [
|
||||
"command" => $cmd,
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
$this->waitForPath("$storagePath/file_$count.txt");
|
||||
sleep(3);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates :count files in parallel in the directory :dir for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param int $count
|
||||
* @param string $dir
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCreatesFilesInParallel(int $count, string $dir, string $user): void {
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath() . "/$userUuid/$dir";
|
||||
$cmd = "mkdir -p $storagePath; ";
|
||||
for ($i = 1; $i <= $count; $i++) {
|
||||
$cmd .= "echo -n \"parallel file $i content\" > $storagePath/parallel_$i.txt & ";
|
||||
}
|
||||
$cmd .= "wait";
|
||||
$body = [
|
||||
"command" => $cmd,
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
$this->waitForPath("$storagePath/parallel_$count.txt");
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator puts the content :content into the file :file in the POSIX storage folder of user :user
|
||||
*
|
||||
* @param string $content
|
||||
* @param string $file
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorChangesFileContent(string $content, string $file, string $user): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
$safeContent = escapeshellarg($content);
|
||||
$body = [
|
||||
"command" => "echo -n $safeContent >> $storagePath/$userUuid/$file",
|
||||
"raw" => true
|
||||
];
|
||||
sleep(1);
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator reads the content of the file :file in the POSIX storage folder of user :user
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorReadsTheFileContent(string $user, string $file): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
$body = [
|
||||
"command" => "cat $storagePath/$userUuid/$file",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator copies the file :file to the folder :folder for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $folder
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCopiesFileToFolder(string $user, string $file, string $folder): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
|
||||
$source = "$storagePath/$userUuid/$file";
|
||||
$destination = "$storagePath/$userUuid/$folder";
|
||||
|
||||
$body = [
|
||||
"command" => "cp $source $destination",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator renames the file :file to :newName for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $newName
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorRenamesFile(string $user, string $file, string $newName): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
|
||||
$source = "$storagePath/$userUuid/$file";
|
||||
$destination = "$storagePath/$userUuid/$newName";
|
||||
|
||||
$body = [
|
||||
"command" => "mv $source $destination",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator moves the file :file to the folder :folder for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $folder
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorMovesFileToFolder(string $user, string $file, string $folder): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
|
||||
$source = "$storagePath/$userUuid/$file";
|
||||
$destination = "$storagePath/$userUuid/$folder";
|
||||
|
||||
$body = [
|
||||
"command" => "mv $source $destination",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator deletes the file :file for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorDeletesFile(string $file, string $user): void {
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
|
||||
$body = [
|
||||
"command" => "rm $storagePath/$userUuid/$file",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator deletes the folder :folder for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $folder
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorDeletesFolder(string $folder, string $user): void {
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
|
||||
$body = [
|
||||
"command" => "rm -r $storagePath/$userUuid/$folder",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator copies the file :file to the space :space for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $space
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorCopiesFileToSpace(string $user, string $file, string $space): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$usersStoragePath = $this->getUsersStoragePath();
|
||||
$projectsStoragePath = $this->getProjectsStoragePath();
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($this->featureContext->getAdminUsername(), $space);
|
||||
$spaceId = explode('$', $spaceId)[1];
|
||||
|
||||
$source = "$usersStoragePath/$userUuid/$file";
|
||||
$destination = "$projectsStoragePath/$spaceId";
|
||||
|
||||
$body = [
|
||||
"command" => "cp $source $destination",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator deletes the project space :space on the POSIX filesystem
|
||||
*
|
||||
* @param string $space
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorDeletesSpace(string $space): void {
|
||||
$projectsStoragePath = $this->getProjectsStoragePath();
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($this->featureContext->getAdminUsername(), $space);
|
||||
$spaceId = explode('$', $spaceId)[1];
|
||||
|
||||
$body = [
|
||||
"command" => "rm -r $projectsStoragePath/$spaceId",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator checks the attribute :attribute of file :file for user :user on the POSIX filesystem
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param string $file
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdminChecksTheAttributeOfFileForUser(string $attribute, string $file, string $user): void {
|
||||
// this downloads the file using WebDAV and by that checks if it's still in
|
||||
// postprocessing. So its effectively a check for finished postprocessing
|
||||
$this->featureContext->userDownloadsFileUsingTheAPI($user, $file);
|
||||
|
||||
$userUuid = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$storagePath = $this->getUsersStoragePath();
|
||||
$body = [
|
||||
"command" => "getfattr -n " . escapeshellarg($attribute) . " --only-values $storagePath/$userUuid/$file",
|
||||
"raw" => true
|
||||
];
|
||||
$this->featureContext->setResponse(CliHelper::runCommand($body));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Amrita Shrestha <amrita@jankaritech.com>
|
||||
* @copyright Copyright (c) 2024 Amrita Shrestha <amrita@jankaritech.com>
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\CollaborationHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
/**
|
||||
* steps needed to re-configure КуСфера server
|
||||
*/
|
||||
class CollaborationContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
private SpacesContext $spacesContext;
|
||||
private string $lastAppOpenData;
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context from here
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
$this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setLastAppOpenData(string $data): void {
|
||||
$this->lastAppOpenData = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLastAppOpenData(): string {
|
||||
return $this->lastAppOpenData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user checks the information of file :file of space :space using office :app
|
||||
* @When user :user checks the information of file :file of space :space using office :app with view mode :view
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $space
|
||||
* @param string $app
|
||||
* @param string|null $viewMode
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userChecksTheInformationOfFileOfSpaceUsingOffice(
|
||||
string $user,
|
||||
string $file,
|
||||
string $space,
|
||||
string $app,
|
||||
string $viewMode = null
|
||||
): void {
|
||||
$fileId = $this->spacesContext->getFileId($user, $space, $file);
|
||||
$response = \json_decode(
|
||||
CollaborationHelper::sendPOSTRequestToAppOpen(
|
||||
$fileId,
|
||||
$app,
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$viewMode
|
||||
)->getBody()->getContents()
|
||||
);
|
||||
|
||||
$accessToken = $response->form_parameters->access_token;
|
||||
|
||||
// Extract the WOPISrc from the app_url
|
||||
$parsedUrl = parse_url($response->app_url);
|
||||
parse_str($parsedUrl['query'], $queryParams);
|
||||
$wopiSrc = $queryParams['WOPISrc'];
|
||||
|
||||
$this->featureContext->setResponse(
|
||||
HttpRequestHelper::get(
|
||||
$wopiSrc . "?access_token=$accessToken",
|
||||
$this->featureContext->getStepLineRef()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user creates a file :file inside folder :folder in space :space using wopi endpoint
|
||||
* @When user :user tries to create a file :file inside folder :folder in space :space using wopi endpoint
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $folder
|
||||
* @param string $space
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userCreatesFileInsideFolderInSpaceUsingWopiEndpoint(
|
||||
string $user,
|
||||
string $file,
|
||||
string $folder,
|
||||
string $space
|
||||
): void {
|
||||
$parentContainerId = $this->spacesContext->getResourceId($user, $space, $folder);
|
||||
$this->featureContext->setResponse(
|
||||
CollaborationHelper::createFile(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$parentContainerId,
|
||||
$file
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
* @param string $password
|
||||
* @param string $folder
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createFile(string $file, string $password, string $folder = ""): void {
|
||||
$token = $this->featureContext->shareNgGetLastCreatedLinkShareToken();
|
||||
$baseUrl = $this->featureContext->getBaseUrl();
|
||||
$davPath = WebDavHelper::getDavPath(WebDavHelper::DAV_VERSION_NEW, $token, "public-files");
|
||||
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
HttpRequestHelper::sendRequest(
|
||||
"$baseUrl/$davPath/$folder",
|
||||
$this->featureContext->getStepLineRef(),
|
||||
"PROPFIND",
|
||||
"public",
|
||||
$this->featureContext->getActualPassword($password)
|
||||
)
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("//d:prop/oc:fileid");
|
||||
$parentContainerId = (string) $xmlPart[0];
|
||||
|
||||
$headers = [
|
||||
"Public-Token" => $token
|
||||
];
|
||||
$this->featureContext->setResponse(
|
||||
CollaborationHelper::createFile(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
"public",
|
||||
$this->featureContext->getActualPassword($password),
|
||||
$parentContainerId,
|
||||
$file,
|
||||
$headers
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the public creates a file :file inside the last shared public link folder with password :password using wopi endpoint
|
||||
* @When the public tries to create a file :file inside the last shared public link folder with password :password using wopi endpoint
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $password
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function thePublicCreatesAFileInsideTheLastSharedPublicLinkFolderWithPasswordUsingWopiEndpoint(
|
||||
string $file,
|
||||
string $password
|
||||
): void {
|
||||
$this->createFile($file, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the public creates a file :file inside folder :folder in the last shared public link space with password :password using wopi endpoint
|
||||
* @When the public tries to create a file :file inside folder :folder in the last shared public link space with password :password using wopi endpoint
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $folder
|
||||
* @param string $password
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function thePublicCreatesAFileInsideFolderInTheLastSharedPublicLinkSpaceWithPasswordUsingWopiEndpoint(
|
||||
string $file,
|
||||
string $folder,
|
||||
string $password
|
||||
): void {
|
||||
$this->createFile($file, $password, $folder);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user tries to check the information of file :file of space :space using office :app with invalid file-id
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $space
|
||||
* @param string $app
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userTriesToCheckTheInformationOfFileOfSpaceUsingOfficeWithInvalidFileId(
|
||||
string $user,
|
||||
string $file,
|
||||
string $space,
|
||||
string $app
|
||||
): void {
|
||||
$response = \json_decode(
|
||||
CollaborationHelper::sendPOSTRequestToAppOpen(
|
||||
$this->spacesContext->getFileId($user, $space, $file),
|
||||
$app,
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef()
|
||||
)->getBody()->getContents()
|
||||
);
|
||||
$accessToken = $response->form_parameters->access_token;
|
||||
|
||||
// Extract the WOPISrc from the app_url
|
||||
$parsedUrl = parse_url($response->app_url);
|
||||
parse_str($parsedUrl['query'], $queryParams);
|
||||
$wopiSrc = $queryParams['WOPISrc'];
|
||||
$position = strpos($wopiSrc, '/files/') + \strlen('/files/');
|
||||
|
||||
// Extract the base URL up to and including '/files/'
|
||||
$fullUrl = substr($wopiSrc, 0, $position) . WebDavHelper::generateUUIDv4();
|
||||
$this->featureContext->setResponse(
|
||||
HttpRequestHelper::get(
|
||||
$fullUrl . "?access_token=$accessToken",
|
||||
$this->featureContext->getStepLineRef()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user tries to create a file :file inside deleted folder using wopi endpoint
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userTriesToCreateAFileInsideDeletedFolderUsingWopiEndpoint(string $user, string $file): void {
|
||||
$parentContainerId = $this->featureContext->getStoredFileID();
|
||||
$this->featureContext->setResponse(
|
||||
CollaborationHelper::createFile(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$parentContainerId,
|
||||
$file
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has sent the following app-open request:
|
||||
*
|
||||
* @param string $user
|
||||
* @param TableNode $properties
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasSentTheFollowingAppOpenRequest(string $user, TableNode $properties): void {
|
||||
$rows = $properties->getRowsHash();
|
||||
$appResponse = CollaborationHelper::sendPOSTRequestToAppOpen(
|
||||
$this->spacesContext->getFileId($user, $rows['space'], $rows['resource']),
|
||||
$rows['app'],
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef()
|
||||
);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $appResponse);
|
||||
$this->setLastAppOpenData($appResponse->getBody()->getContents());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user tries to get the information of the last opened file using wopi endpoint
|
||||
* @When user :user gets the information of the last opened file using wopi endpoint
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userGetsTheInformationOfTheLastOpenedFileUsingWopiEndpoint(string $user): void {
|
||||
$response = json_decode($this->getLastAppOpenData());
|
||||
$accessToken = $response->form_parameters->access_token;
|
||||
|
||||
// Extract the WOPISrc from the app_url
|
||||
$parsedUrl = parse_url($response->app_url);
|
||||
parse_str($parsedUrl['query'], $queryParams);
|
||||
$wopiSrc = $queryParams['WOPISrc'];
|
||||
|
||||
$this->featureContext->setResponse(
|
||||
HttpRequestHelper::get(
|
||||
$wopiSrc . "?access_token=$accessToken",
|
||||
$this->featureContext->getStepLineRef()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the response (should|should not) contain the following MIME types:$/
|
||||
*
|
||||
* @param string $shouldOrNot
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theFollowingMimeTypesShouldExistForUser(string $shouldOrNot, TableNode $table): void {
|
||||
$rows = $table->getRows();
|
||||
$responseArray = $this->featureContext->getJsonDecodedResponse(
|
||||
$this->featureContext->getResponse()
|
||||
)['mime-types'];
|
||||
$mimeTypes = \array_column($responseArray, 'mime_type');
|
||||
foreach ($rows as $row) {
|
||||
if ($shouldOrNot === "should not") {
|
||||
Assert::assertFalse(
|
||||
\in_array($row[0], $mimeTypes),
|
||||
"the response should not contain the mimetype $row[0].\nMime Types found in response:\n"
|
||||
. print_r($mimeTypes, true)
|
||||
);
|
||||
} else {
|
||||
Assert::assertTrue(
|
||||
\in_array($row[0], $mimeTypes),
|
||||
"the response does not contain the mimetype $row[0].\nMime Types found in response:\n"
|
||||
. print_r($mimeTypes, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the app list response should contain the following template information for office :app:
|
||||
*
|
||||
* @param string $app
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAppListResponseShouldContainTheFollowingTemplateInformationForOffice(
|
||||
string $app,
|
||||
TableNode $table
|
||||
): void {
|
||||
$responseArray = $this->featureContext->getJsonDecodedResponse($this->featureContext->getResponse());
|
||||
|
||||
Assert::assertArrayHasKey(
|
||||
"mime-types",
|
||||
$responseArray,
|
||||
"Expected 'mime-types' in the response but not found.\n" . print_r($responseArray, true)
|
||||
);
|
||||
|
||||
$mimeTypes = $responseArray['mime-types'];
|
||||
|
||||
$mimeTypeMap = [];
|
||||
foreach ($mimeTypes as $mimeType) {
|
||||
$mimeTypeMap[$mimeType['mime_type']] = $mimeType;
|
||||
}
|
||||
|
||||
foreach ($table->getColumnsHash() as $row) {
|
||||
Assert::assertArrayHasKey(
|
||||
$row['mime-type'],
|
||||
$mimeTypeMap,
|
||||
"Expected mime-type '{$row['mime-type']}' to exist in the response but it doesn't.\n"
|
||||
. print_r($mimeTypeMap, true)
|
||||
);
|
||||
|
||||
$mimeType = $mimeTypeMap[$row['mime-type']];
|
||||
$found = false;
|
||||
|
||||
foreach ($mimeType['app_providers'] as $provider) {
|
||||
if ($provider['name'] === $app && isset($row['target-extension'])) {
|
||||
Assert::assertSame(
|
||||
$row['target-extension'],
|
||||
$provider['target_ext'],
|
||||
"Expected 'target_ext' for $app to be '{$row['target-extension']}'"
|
||||
. " but found '{$provider['target_ext']}'"
|
||||
);
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
Assert::fail(
|
||||
"Expected response to contain app-provider '$app' with target-extension "
|
||||
. "'{$row['target-extension']}' for mime-type '{$row['mime-type']}',"
|
||||
. " but no matching provider was found.\n App Providers Found: "
|
||||
. print_r($mimeType['app_providers'], true)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user has created a file :file using wopi endpoint
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
*
|
||||
* @return string
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasCreatedAFileInSpaceUsingWopiEndpoint(string $user, string $file): string {
|
||||
$parentContainerId = $this->featureContext->getFileIdForPath($user, "/");
|
||||
$response = CollaborationHelper::createFile(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$parentContainerId,
|
||||
$file
|
||||
);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
|
||||
$decodedResponse = $this->featureContext->getJsonDecodedResponseBodyContent($response);
|
||||
return $decodedResponse->file_id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* context containing favorites related API steps
|
||||
*/
|
||||
class FavoritesContext implements Context {
|
||||
private WebDavPropertiesContext $webDavPropertiesContext;
|
||||
|
||||
/**
|
||||
* @Then /^as user "([^"]*)" (?:file|folder|entry) "([^"]*)" should be favorited$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
* @param integer $expectedValue 0|1
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function asUserFileOrFolderShouldBeFavorited(
|
||||
string $user,
|
||||
string $path,
|
||||
int $expectedValue = 1,
|
||||
?string $spaceId = null
|
||||
): void {
|
||||
$property = "oc:favorite";
|
||||
$this->webDavPropertiesContext->checkPropertyOfAFolder(
|
||||
$user,
|
||||
$path,
|
||||
$property,
|
||||
(string)$expectedValue,
|
||||
null,
|
||||
$spaceId,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^as user "([^"]*)" (?:file|folder|entry) "([^"]*)" should not be favorited$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function asUserFileShouldNotBeFavorited(string $user, string $path): void {
|
||||
$this->asUserFileOrFolderShouldBeFavorited($user, $path, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->webDavPropertiesContext = BehatHelper::getContext(
|
||||
$scope,
|
||||
$environment,
|
||||
'WebDavPropertiesContext'
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,551 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2018, ownCloud GmbH
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Steps that relate to files_versions app
|
||||
*/
|
||||
class FilesVersionsContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
private SpacesContext $spacesContext;
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
$this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user tries to get versions of file :file from :fileOwner
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $fileOwner
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userTriesToGetFileVersions(string $user, string $file, string $fileOwner): void {
|
||||
$this->featureContext->setResponse($this->getFileVersions($user, $file, $fileOwner));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user gets the number of versions of file :file
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userGetsFileVersions(string $user, string $file): void {
|
||||
$this->featureContext->setResponse($this->getFileVersions($user, $file));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the public tries to get the number of versions of file :file with password :password using file-id :endpoint
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $password
|
||||
* @param string $fileId
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function thePublicTriesToGetTheNumberOfVersionsOfFileWithPasswordUsingFileId(
|
||||
string $file,
|
||||
string $password,
|
||||
string $fileId
|
||||
): void {
|
||||
$password = $this->featureContext->getActualPassword($password);
|
||||
$this->featureContext->setResponse(
|
||||
$this->featureContext->makeDavRequest(
|
||||
"public",
|
||||
"PROPFIND",
|
||||
$fileId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"versions",
|
||||
$this->featureContext->getDavPathVersion(),
|
||||
false,
|
||||
$password
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string|null $fileOwner
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws JsonException
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function getFileVersions(
|
||||
string $user,
|
||||
string $file,
|
||||
?string $fileOwner = null,
|
||||
?string $spaceId = null,
|
||||
): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$fileOwner = $fileOwner ? $this->featureContext->getActualUsername($fileOwner) : $user;
|
||||
$fileId = $this->featureContext->getFileIdForPath($fileOwner, $file, $spaceId);
|
||||
Assert::assertNotNull(
|
||||
$fileId,
|
||||
__METHOD__
|
||||
. " fileid of file $file user $fileOwner not found (the file may not exist)"
|
||||
);
|
||||
return $this->featureContext->makeDavRequest(
|
||||
$user,
|
||||
"PROPFIND",
|
||||
$fileId,
|
||||
null,
|
||||
null,
|
||||
$spaceId,
|
||||
"versions"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user gets the number of versions of file :resource using file-id :fileId
|
||||
* @When user :user tries to get the number of versions of file :resource using file-id :fileId
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileId
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userGetsTheNumberOfVersionsOfFileOfTheSpace(string $user, string $fileId): void {
|
||||
$this->featureContext->setResponse(
|
||||
$this->featureContext->makeDavRequest(
|
||||
$user,
|
||||
"PROPFIND",
|
||||
$fileId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"versions"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getFileVersionMetadata(string $user, string $file): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$fileId = $this->featureContext->getFileIdForPath($user, $file);
|
||||
Assert::assertNotNull(
|
||||
$fileId,
|
||||
__METHOD__
|
||||
. " fileid of file $file user $user not found (the file may not exist)"
|
||||
);
|
||||
$body = '<?xml version="1.0"?>
|
||||
<d:propfind xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<d:prop>
|
||||
<oc:meta-version-edited-by />
|
||||
<oc:meta-version-edited-by-name />
|
||||
</d:prop>
|
||||
</d:propfind>';
|
||||
return $this->featureContext->makeDavRequest(
|
||||
$user,
|
||||
"PROPFIND",
|
||||
$fileId,
|
||||
null,
|
||||
$body,
|
||||
null,
|
||||
"versions",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param int $versionIndex
|
||||
* @param string $path
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function restoreVersionIndexOfFile(string $user, int $versionIndex, string $path): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$fileId = $this->featureContext->getFileIdForPath($user, $path);
|
||||
Assert::assertNotNull(
|
||||
$fileId,
|
||||
__METHOD__
|
||||
. " fileid of file $path user $user not found (the file may not exist)"
|
||||
);
|
||||
$response = $this->listVersionFolder($user, $fileId, 1);
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("//d:response/d:href");
|
||||
//restoring the version only works with DAV path v2
|
||||
$destinationUrl = $this->featureContext->getBaseUrl() . "/" .
|
||||
WebDavHelper::getDavPath(WebDavHelper::DAV_VERSION_NEW, $user) . \trim($path, "/");
|
||||
$fullUrl = $this->featureContext->getBaseUrlWithoutPath() .
|
||||
$xmlPart[$versionIndex];
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$fullUrl,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
'COPY',
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
['Destination' => $destinationUrl]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has restored version index :versionIndex of file :path
|
||||
*
|
||||
* @param string $user
|
||||
* @param int $versionIndex
|
||||
* @param string $path
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userHasRestoredVersionIndexOfFile(string $user, int $versionIndex, string $path): void {
|
||||
$response = $this->restoreVersionIndexOfFile($user, $versionIndex, $path);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user restores version index :versionIndex of file :path using the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param int $versionIndex
|
||||
* @param string $path
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRestoresVersionIndexOfFile(string $user, int $versionIndex, string $path): void {
|
||||
$response = $this->restoreVersionIndexOfFile($user, $versionIndex, $path);
|
||||
$this->featureContext->setResponse($response, $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* assert file versions count
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileId
|
||||
* @param int $expectedCount
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function assertFileVersionsCount(string $user, string $fileId, int $expectedCount): void {
|
||||
$response = $this->listVersionFolder($user, $fileId, 1);
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$actualCount = \count($responseXmlObject->xpath("//d:prop/d:getetag")) - 1;
|
||||
if ($actualCount === -1) {
|
||||
$actualCount = 0;
|
||||
}
|
||||
Assert::assertEquals(
|
||||
$expectedCount,
|
||||
$actualCount,
|
||||
"Expected $expectedCount versions but found $actualCount in \n" . $responseXmlObject->asXML()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the version folder of file :path for user :user should contain :count element(s)
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $user
|
||||
* @param int $count
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theVersionFolderOfFileShouldContainElements(
|
||||
string $path,
|
||||
string $user,
|
||||
int $count
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$fileId = $this->featureContext->getFileIdForPath($user, $path);
|
||||
Assert::assertNotNull(
|
||||
$fileId,
|
||||
__METHOD__
|
||||
. ". file '$path' for user '$user' not found (the file may not exist)"
|
||||
);
|
||||
$this->assertFileVersionsCount($user, $fileId, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the content length of file :path with version index :index for user :user in versions folder should be :length
|
||||
*
|
||||
* @param string $path
|
||||
* @param int $index
|
||||
* @param string $user
|
||||
* @param int $length
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theContentLengthOfFileForUserInVersionsFolderIs(
|
||||
string $path,
|
||||
int $index,
|
||||
string $user,
|
||||
int $length
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$fileId = $this->featureContext->getFileIdForPath($user, $path);
|
||||
Assert::assertNotNull(
|
||||
$fileId,
|
||||
__METHOD__
|
||||
. " fileid of file $path user $user not found (the file may not exist)"
|
||||
);
|
||||
$response = $this->listVersionFolder($user, $fileId, 1, ['d:getcontentlength']);
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml(
|
||||
$response,
|
||||
__METHOD__
|
||||
);
|
||||
$xmlPart = $responseXmlObject->xpath("//d:prop/d:getcontentlength");
|
||||
Assert::assertEquals(
|
||||
$length,
|
||||
(int) $xmlPart[$index],
|
||||
"The content length of file $path with version $index for user $user was
|
||||
expected to be $length but the actual content length is $xmlPart[$index]"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
* @param string $index
|
||||
* @param string|NullCpuCoreFinder $spaceId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function downloadVersion(
|
||||
string $user,
|
||||
string $path,
|
||||
string $index,
|
||||
?string $spaceId = null
|
||||
): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$fileId = $this->featureContext->getFileIdForPath($user, $path, $spaceId);
|
||||
Assert::assertNotNull(
|
||||
$fileId,
|
||||
__METHOD__
|
||||
. " fileid of file $path user $user not found (the file may not exist)"
|
||||
);
|
||||
$index = (int)$index;
|
||||
$response = $this->listVersionFolder($user, $fileId, 1);
|
||||
if ($response->getStatusCode() === 403) {
|
||||
return $response;
|
||||
}
|
||||
$responseXmlObject = new SimpleXMLElement($response->getBody()->getContents());
|
||||
$xmlPart = $responseXmlObject->xpath("//d:response/d:href");
|
||||
if (!isset($xmlPart[$index])) {
|
||||
Assert::fail(
|
||||
'could not find version of path "' . $path . '" with index "' . $index . '"'
|
||||
);
|
||||
}
|
||||
// the href already contains the path
|
||||
$url = WebDavHelper::sanitizeUrl(
|
||||
$this->featureContext->getBaseUrlWithoutPath() . $xmlPart[$index]
|
||||
);
|
||||
return HttpRequestHelper::get(
|
||||
$url,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user downloads the version of file :path with the index :index
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $path
|
||||
* @param string $index
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userDownloadsVersion(string $user, string $path, string $index): void {
|
||||
$this->featureContext->setResponse($this->downloadVersion($user, $path, $index), $user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the content of version index "([^"]*)" of file "([^"]*)" for user "([^"]*)" should be "([^"]*)"$/
|
||||
*
|
||||
* @param string $index
|
||||
* @param string $path
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theContentOfVersionIndexOfFileForUserShouldBe(
|
||||
string $index,
|
||||
string $path,
|
||||
string $user,
|
||||
string $content
|
||||
): void {
|
||||
$response = $this->downloadVersion($user, $path, $index);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe("200", '', $response);
|
||||
$this->featureContext->checkDownloadedContentMatches($content, '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" retrieves the meta information of (file|fileId) "([^"]*)" using the meta API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileOrFileId
|
||||
* @param string $path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userGetMetaInfo(string $user, string $fileOrFileId, string $path): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$baseUrl = $this->featureContext->getBaseUrl();
|
||||
$password = $this->featureContext->getPasswordForUser($user);
|
||||
|
||||
if ($fileOrFileId === "file") {
|
||||
$fileId = $this->featureContext->getFileIdForPath($user, $path);
|
||||
$metaPath = "/meta/$fileId/";
|
||||
} else {
|
||||
$metaPath = "/meta/$path/";
|
||||
}
|
||||
|
||||
$body = '<?xml version="1.0" encoding="utf-8"?>
|
||||
<a:propfind xmlns:a="DAV:" xmlns:oc="http://owncloud.org/ns">
|
||||
<a:prop>
|
||||
<oc:meta-path-for-user />
|
||||
</a:prop>
|
||||
</a:propfind>';
|
||||
|
||||
$response = WebDavHelper::makeDavRequest(
|
||||
$baseUrl,
|
||||
$user,
|
||||
$password,
|
||||
"PROPFIND",
|
||||
$metaPath,
|
||||
['Content-Type' => 'text/xml','Depth' => '0'],
|
||||
null,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$body,
|
||||
$this->featureContext->getDavPathVersion(),
|
||||
null
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the result parsed into a SimpleXMLElement
|
||||
* with a registered namespace with 'd' as prefix and 'DAV:' as namespace
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileId
|
||||
* @param int $folderDepth
|
||||
* @param string[]|null $properties
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function listVersionFolder(
|
||||
string $user,
|
||||
string $fileId,
|
||||
int $folderDepth,
|
||||
?array $properties = null
|
||||
): ResponseInterface {
|
||||
if (!$properties) {
|
||||
$properties = [
|
||||
'd:getetag'
|
||||
];
|
||||
}
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$password = $this->featureContext->getPasswordForUser($user);
|
||||
$response = WebDavHelper::propfind(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$password,
|
||||
$fileId,
|
||||
$properties,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
(string) $folderDepth,
|
||||
null,
|
||||
"versions"
|
||||
);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user gets the number of versions of file :file from space :space
|
||||
* @When user :user tries to get the number of versions of file :file from space :space
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $file
|
||||
* @param string $space
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userGetsTheNumberOfVersionsOfFileFromSpace(string $user, string $file, string $space): void {
|
||||
$fileId = $this->spacesContext->getFileId($user, $space, $file);
|
||||
$this->featureContext->setResponse(
|
||||
$this->featureContext->makeDavRequest(
|
||||
$user,
|
||||
"PROPFIND",
|
||||
$fileId,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
"versions"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,763 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Viktor Scharf <vscharf@owncloud.com>
|
||||
* @copyright Copyright (c) 2023 Viktor Scharf vscharf@owncloud.com
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\EmailHelper;
|
||||
use TestHelpers\OcsApiHelper;
|
||||
use TestHelpers\GraphHelper;
|
||||
use TestHelpers\SettingsHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Defines application features from the specific context.
|
||||
*/
|
||||
class NotificationContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
private SpacesContext $spacesContext;
|
||||
private string $notificationEndpointPath = '/apps/notifications/api/v1/notifications?format=json';
|
||||
private string $globalNotificationEndpointPath = '/apps/notifications/api/v1/notifications/global';
|
||||
|
||||
private array $notificationIds;
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function getNotificationIds(): array {
|
||||
return $this->notificationIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array[]
|
||||
*/
|
||||
public function getLastNotificationId(): array {
|
||||
return \end($this->notificationIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @AfterScenario
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteDeprovisioningNotification(): void {
|
||||
$payload["ids"] = ["deprovision"];
|
||||
|
||||
OcsApiHelper::sendRequest(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getAdminUsername(),
|
||||
$this->featureContext->getAdminPassword(),
|
||||
'DELETE',
|
||||
$this->globalNotificationEndpointPath,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
json_encode($payload)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $userRecipient;
|
||||
|
||||
/**
|
||||
* @param string $userRecipient
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setUserRecipient(string $userRecipient): void {
|
||||
$this->userRecipient = $userRecipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getUserRecipient(): string {
|
||||
return $this->userRecipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
$this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function listAllNotifications(string $user): ResponseInterface {
|
||||
$this->setUserRecipient($user);
|
||||
$language = SettingsHelper::getLanguageSettingValue(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getStepLineRef()
|
||||
);
|
||||
$headers = ["accept-language" => $language];
|
||||
return OcsApiHelper::sendRequest(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
'GET',
|
||||
$this->notificationEndpointPath,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
[],
|
||||
2,
|
||||
$headers
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" lists all notifications$/
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userListAllNotifications(string $user): void {
|
||||
$response = $this->listAllNotifications($user);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastHttpStatusCodesArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function deleteAllNotifications(string $user): ResponseInterface {
|
||||
$response = $this->listAllNotifications($user);
|
||||
if (isset($this->featureContext->getJsonDecodedResponseBodyContent($response)->ocs->data)) {
|
||||
$responseBody = $this->featureContext->getJsonDecodedResponseBodyContent($response)->ocs->data;
|
||||
foreach ($responseBody as $value) {
|
||||
// set notificationId
|
||||
$this->notificationIds[] = $value->notification_id;
|
||||
}
|
||||
}
|
||||
return $this->userDeletesNotification($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user deletes all notifications
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userDeletesAllNotifications(string $user): void {
|
||||
$response = $this->deleteAllNotifications($user);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has deleted all notifications
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userHasDeletedAllNotifications(string $user): void {
|
||||
$response = $this->deleteAllNotifications($user);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user deletes a notification related to resource :resource with subject :subject
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $resource
|
||||
* @param string $subject
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userDeletesNotificationOfResourceAndSubject(string $user, string $resource, string $subject): void {
|
||||
$response = $this->listAllNotifications($user);
|
||||
$this->filterNotificationsBySubjectAndResource($subject, $resource, $response);
|
||||
$this->featureContext->setResponse($this->userDeletesNotification($user));
|
||||
}
|
||||
|
||||
/**
|
||||
* deletes notification
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userDeletesNotification(string $user): ResponseInterface {
|
||||
$this->setUserRecipient($user);
|
||||
$payload["ids"] = $this->getNotificationIds();
|
||||
return OcsApiHelper::sendRequest(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
'DELETE',
|
||||
$this->notificationEndpointPath,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
\json_encode($payload),
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the notifications should be empty
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theNotificationsShouldBeEmpty(): void {
|
||||
$statusCode = $this->featureContext->getResponse()->getStatusCode();
|
||||
if ($statusCode !== 200) {
|
||||
$response = $this->featureContext->getResponse()->getBody()->getContents();
|
||||
throw new \Exception(
|
||||
__METHOD__
|
||||
. " Failed to get user notification list" . $response
|
||||
);
|
||||
}
|
||||
$notifications = $this->featureContext->getJsonDecodedResponseBodyContent()->ocs->data;
|
||||
Assert::assertNull($notifications, "response should not contain any notification");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then user :user should not have any notification
|
||||
*
|
||||
* @param $user
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userShouldNotHaveAnyNotification($user): void {
|
||||
$response = $this->listAllNotifications($user);
|
||||
$notifications = $this->featureContext->getJsonDecodedResponseBodyContent($response)->ocs->data;
|
||||
Assert::assertNull($notifications, "response should not contain any notification");
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^there should be "([^"]*)" notifications$/
|
||||
*
|
||||
* @param int $numberOfNotification
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userShouldHaveNotifications(int $numberOfNotification): void {
|
||||
if (!isset($this->featureContext->getJsonDecodedResponseBodyContent()->ocs->data)) {
|
||||
throw new Exception("Notification is empty");
|
||||
}
|
||||
$responseBody = $this->featureContext->getJsonDecodedResponseBodyContent()->ocs->data;
|
||||
$actualNumber = \count($responseBody);
|
||||
Assert::assertEquals(
|
||||
$numberOfNotification,
|
||||
$actualNumber,
|
||||
"Expected number of notifications was '$numberOfNotification', but got '$actualNumber'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the JSON response should contain a notification message with the subject "([^"]*)" and the message-details should match$/
|
||||
*
|
||||
* @param string $subject
|
||||
* @param PyStringNode $schemaString
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theJsonDataFromLastResponseShouldMatch(
|
||||
string $subject,
|
||||
PyStringNode $schemaString
|
||||
): void {
|
||||
$responseBody = $this->filterResponseAccordingToNotificationSubject($subject);
|
||||
// substitute the value here
|
||||
$schemaString = $schemaString->getRaw();
|
||||
$schemaString = $this->featureContext->substituteInLineCodes(
|
||||
$schemaString,
|
||||
$this->featureContext->getCurrentUser(),
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
$this->getUserRecipient(),
|
||||
);
|
||||
$this->featureContext->assertJsonDocumentMatchesSchema(
|
||||
$responseBody,
|
||||
$this->featureContext->getJSONSchema($schemaString)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* filter notification according to subject
|
||||
*
|
||||
* @param string $subject
|
||||
* @param ResponseInterface|null $response
|
||||
*
|
||||
* @return object
|
||||
*/
|
||||
public function filterResponseAccordingToNotificationSubject(
|
||||
string $subject,
|
||||
?ResponseInterface $response = null
|
||||
): object {
|
||||
$response = $response ?? $this->featureContext->getResponse();
|
||||
if (isset($this->featureContext->getJsonDecodedResponseBodyContent($response)->ocs->data)) {
|
||||
$responseBody = $this->featureContext->getJsonDecodedResponseBodyContent($response)->ocs->data;
|
||||
foreach ($responseBody as $value) {
|
||||
if (isset($value->subject) && $value->subject === $subject) {
|
||||
$responseBody = $value;
|
||||
// set notificationId
|
||||
$this->notificationIds[] = $value->notification_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$responseBody = $this->featureContext->getJsonDecodedResponseBodyContent($response);
|
||||
}
|
||||
return $responseBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* filter notification according to subject and resource
|
||||
*
|
||||
* @param string $subject
|
||||
* @param string $resource
|
||||
* @param ResponseInterface|null $response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filterNotificationsBySubjectAndResource(
|
||||
string $subject,
|
||||
string $resource,
|
||||
?ResponseInterface $response = null
|
||||
): array {
|
||||
$filteredNotifications = [];
|
||||
$response = $response ?? $this->featureContext->getResponse();
|
||||
$responseObject = $this->featureContext->getJsonDecodedResponseBodyContent($response);
|
||||
|
||||
if (!isset($responseObject->ocs->data)) {
|
||||
Assert::fail("Response doesn't contain notification: " . print_r($responseObject, true));
|
||||
}
|
||||
|
||||
$notifications = $responseObject->ocs->data;
|
||||
foreach ($notifications as $notification) {
|
||||
if (isset($notification->subject) && $notification->subject === $subject
|
||||
&& isset($notification->messageRichParameters->resource->name)
|
||||
&& $notification->messageRichParameters->resource->name === $resource
|
||||
) {
|
||||
$this->notificationIds[] = $notification->notification_id;
|
||||
$filteredNotifications[] = $notification;
|
||||
}
|
||||
}
|
||||
return $filteredNotifications;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^user "([^"]*)" should get a notification with subject "([^"]*)" and message:$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $subject
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userShouldGetANotificationWithMessage(string $user, string $subject, TableNode $table): void {
|
||||
$count = 0;
|
||||
// Sometimes the test might try to get the notifications before the server has created the notification.
|
||||
// To prevent the test from failing because of that, try to list the notifications again
|
||||
do {
|
||||
if ($count > 0) {
|
||||
\sleep(1);
|
||||
}
|
||||
$this->featureContext->setResponse(null);
|
||||
$response = $this->listAllNotifications($user);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
|
||||
++$count;
|
||||
} while (!isset($this->filterResponseAccordingToNotificationSubject($subject, $response)->message)
|
||||
&& $count <= 10
|
||||
);
|
||||
if (isset($this->filterResponseAccordingToNotificationSubject($subject, $response)->message)) {
|
||||
$actualMessage = str_replace(
|
||||
["\r", "\n"],
|
||||
" ",
|
||||
$this->filterResponseAccordingToNotificationSubject($subject, $response)->message
|
||||
);
|
||||
} else {
|
||||
throw new \Exception("Notification was not found even after retrying for 5 seconds.");
|
||||
}
|
||||
$expectedMessage = $table->getColumnsHash()[0]['message'];
|
||||
Assert::assertStringStartsWith(
|
||||
$expectedMessage,
|
||||
$actualMessage,
|
||||
__METHOD__ . "expected message to start with '$expectedMessage' but found'$actualMessage'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then user :user should get a notification for resource :resource with subject :subject and message:
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $resource
|
||||
* @param string $subject
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userShouldGetNotificationForResourceWithMessage(
|
||||
string $user,
|
||||
string $resource,
|
||||
string $subject,
|
||||
TableNode $table
|
||||
): void {
|
||||
$response = $this->listAllNotifications($user);
|
||||
$notification = $this->filterNotificationsBySubjectAndResource($subject, $resource, $response);
|
||||
|
||||
if (\count($notification) === 1) {
|
||||
$actualMessage = str_replace(["\r", "\r"], " ", $notification[0]->message);
|
||||
$expectedMessage = $table->getColumnsHash()[0]['message'];
|
||||
Assert::assertStringStartsWith(
|
||||
$expectedMessage,
|
||||
$actualMessage,
|
||||
__METHOD__ . "expected message to start with '$expectedMessage' but found'$actualMessage'"
|
||||
);
|
||||
$response = $this->userDeletesNotification($user);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
} elseif (\count($notification) === 0) {
|
||||
throw new \Exception(
|
||||
"Response doesn't contain any notification with resource '$resource' and subject '$subject'.\n"
|
||||
. print_r($notification, true)
|
||||
);
|
||||
} else {
|
||||
throw new \Exception(
|
||||
"Response contains more than one notification with resource '$resource' and subject '$subject'.\n"
|
||||
. print_r($notification, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then user :user should not get a notification related to resource :resource with subject :subject
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $resource
|
||||
* @param string $subject
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userShouldNotHaveANotificationRelatedToResourceWithSubject(
|
||||
string $user,
|
||||
string $resource,
|
||||
string $subject
|
||||
): void {
|
||||
$response = $this->listAllNotifications($user);
|
||||
$filteredResponse = $this->filterNotificationsBySubjectAndResource($subject, $resource, $response);
|
||||
Assert::assertCount(
|
||||
0,
|
||||
$filteredResponse,
|
||||
"Response should not contain notification related to resource '$resource' with subject '$subject' but found"
|
||||
. print_r($filteredResponse, true)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then user :user should have received the following email from user :sender about the share of project space :spaceName
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $sender
|
||||
* @param string $spaceName
|
||||
* @param PyStringNode $content
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userShouldHaveReceivedTheFollowingEmailFromUserAboutTheShareOfProjectSpace(
|
||||
string $user,
|
||||
string $sender,
|
||||
string $spaceName,
|
||||
PyStringNode $content
|
||||
): void {
|
||||
$rawExpectedEmailBodyContent = \str_replace("\r\n", "\n", $content->getRaw());
|
||||
$this->featureContext->setResponse(
|
||||
GraphHelper::getMySpaces(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
'',
|
||||
$this->featureContext->getStepLineRef()
|
||||
)
|
||||
);
|
||||
$expectedEmailBodyContent = $this->featureContext->substituteInLineCodes(
|
||||
$rawExpectedEmailBodyContent,
|
||||
$sender,
|
||||
[],
|
||||
[
|
||||
[
|
||||
"code" => "%space_id%",
|
||||
"function" =>
|
||||
[$this->spacesContext, "getSpaceIdByName"],
|
||||
"parameter" => [$sender, $spaceName]
|
||||
],
|
||||
]
|
||||
);
|
||||
$this->assertEmailContains($user, $expectedEmailBodyContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then user :user should have received the following email from user :sender
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $sender
|
||||
* @param PyStringNode $content
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userShouldHaveReceivedTheFollowingEmailFromUser(
|
||||
string $user,
|
||||
string $sender,
|
||||
PyStringNode $content
|
||||
): void {
|
||||
$rawExpectedEmailBodyContent = \str_replace("\r\n", "\n", $content->getRaw());
|
||||
$expectedEmailBodyContent = $this->featureContext->substituteInLineCodes(
|
||||
$rawExpectedEmailBodyContent,
|
||||
$sender
|
||||
);
|
||||
$this->assertEmailContains($user, $expectedEmailBodyContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then user :user should have received the following email from user :sender ignoring whitespaces
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $sender
|
||||
* @param PyStringNode $content
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userShouldHaveReceivedTheFollowingEmailFromUserIgnoringWhitespaces(
|
||||
string $user,
|
||||
string $sender,
|
||||
PyStringNode $content
|
||||
): void {
|
||||
$rawExpectedEmailBodyContent = \str_replace("\r\n", "\n", $content->getRaw());
|
||||
$expectedEmailBodyContent = $this->featureContext->substituteInLineCodes(
|
||||
$rawExpectedEmailBodyContent,
|
||||
$sender
|
||||
);
|
||||
$this->assertEmailContains($user, $expectedEmailBodyContent, true);
|
||||
}
|
||||
|
||||
/***
|
||||
* @param string $user
|
||||
* @param string $expectedEmailBodyContent
|
||||
* @param bool $ignoreWhiteSpace
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function assertEmailContains(
|
||||
string $user,
|
||||
string $expectedEmailBodyContent,
|
||||
$ignoreWhiteSpace = false
|
||||
): void {
|
||||
$address = $this->featureContext->getEmailAddressForUser($user);
|
||||
$this->featureContext->pushEmailRecipientAsMailBox($address);
|
||||
|
||||
// assert with retries as email delivery might be delayed
|
||||
$retried = 0;
|
||||
do {
|
||||
$actualEmailBodyContent = EmailHelper::getBodyOfLastEmail(
|
||||
$address,
|
||||
$this->featureContext->getStepLineRef()
|
||||
);
|
||||
if ($ignoreWhiteSpace) {
|
||||
$expectedEmailBodyContent = preg_replace('/\s+/', '', $expectedEmailBodyContent);
|
||||
$actualEmailBodyContent = preg_replace('/\s+/', '', $actualEmailBodyContent);
|
||||
}
|
||||
$tryAgain = !\str_contains($actualEmailBodyContent, $expectedEmailBodyContent)
|
||||
&& $retried <= STANDARD_RETRY_COUNT;
|
||||
$retried++;
|
||||
if ($tryAgain) {
|
||||
$mailBox = EmailHelper::getMailBoxFromEmail($address);
|
||||
echo "[INFO] Checking last email content for '$mailBox'. (Retry $retried)\n";
|
||||
// wait for 1 second before trying again
|
||||
sleep(1);
|
||||
}
|
||||
} while ($tryAgain);
|
||||
Assert::assertStringContainsString(
|
||||
$expectedEmailBodyContent,
|
||||
$actualEmailBodyContent,
|
||||
"The email address '$address' should have received an"
|
||||
. " email with the body containing '$expectedEmailBodyContent'"
|
||||
. " but the received email is '$actualEmailBodyContent'"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all emails from the mailboxes
|
||||
*
|
||||
* @AfterScenario @email
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearMailboxes(): void {
|
||||
$users = \array_keys($this->featureContext->getCreatedUsers());
|
||||
try {
|
||||
if (!empty($users)) {
|
||||
foreach ($users as $emailRecipient) {
|
||||
$retried = 0;
|
||||
do {
|
||||
$res = EmailHelper::deleteAllEmails(
|
||||
EmailHelper::getLocalEmailUrl(),
|
||||
$emailRecipient,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
);
|
||||
$deleteStatus = $res->getStatusCode();
|
||||
$mailBox = EmailHelper::getMailboxInformation($emailRecipient);
|
||||
$tryAgain = ($deleteStatus !== 200 || !empty($mailBox)) && $retried <= STANDARD_RETRY_COUNT;
|
||||
$retried++;
|
||||
if ($tryAgain) {
|
||||
echo "[INFO] Clearing mailbox '$emailRecipient'."
|
||||
. " Status: $deleteStatus. Emails: " . \count($mailBox) . "."
|
||||
. " (Retry $retried)\n";
|
||||
// wait for 1 second before trying again
|
||||
sleep(1);
|
||||
}
|
||||
} while ($tryAgain);
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
echo __METHOD__ .
|
||||
" could not delete inbucket messages, is inbucket set up?\n" .
|
||||
$e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string|null $user
|
||||
* @param string|null $deprovision_date
|
||||
* @param string|null $deprovision_date_format
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
*
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userCreatesDeprovisioningNotification(
|
||||
?string $user = null,
|
||||
?string $deprovision_date = "2043-07-04T11:23:12Z",
|
||||
?string $deprovision_date_format= "2006-01-02T15:04:05Z07:00"
|
||||
): ResponseInterface {
|
||||
$payload["type"] = "deprovision";
|
||||
$payload["data"] = [
|
||||
"deprovision_date" => $deprovision_date, "deprovision_date_format" => $deprovision_date_format];
|
||||
return OcsApiHelper::sendRequest(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user ? $this->featureContext->getActualUsername($user) : $this->featureContext->getAdminUsername(),
|
||||
$user ? $this->featureContext->getPasswordForUser($user) : $this->featureContext->getAdminPassword(),
|
||||
'POST',
|
||||
$this->globalNotificationEndpointPath,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
json_encode($payload)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates a deprovisioning notification
|
||||
* @When user :user tries to create a deprovisioning notification
|
||||
*
|
||||
* @param string|null $user
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function theAdministratorCreatesADeprovisioningNotification(?string $user = null) {
|
||||
$response = $this->userCreatesDeprovisioningNotification($user);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastHttpStatusCodesArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator creates a deprovisioning notification for date :deprovision_date of format :deprovision_date_format
|
||||
*
|
||||
* @param $deprovision_date
|
||||
* @param $deprovision_date_format
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function theAdministratorCreatesADeprovisioningNotificationUsingDateFormat(
|
||||
$deprovision_date,
|
||||
$deprovision_date_format
|
||||
) {
|
||||
$response = $this->userCreatesDeprovisioningNotification(null, $deprovision_date, $deprovision_date_format);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->pushToLastHttpStatusCodesArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the administrator has created a deprovisioning notification
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userHasCreatedDeprovisioningNotification(): void {
|
||||
$response = $this->userCreatesDeprovisioningNotification();
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the administrator deletes the deprovisioning notification
|
||||
* @When user :user tries to delete the deprovisioning notification
|
||||
*
|
||||
* @param string|null $user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userDeletesDeprovisioningNotification(?string $user = null): void {
|
||||
$payload["ids"] = ["deprovision"];
|
||||
|
||||
$response = OcsApiHelper::sendRequest(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user ? $this->featureContext->getActualUsername($user) : $this->featureContext->getAdminUsername(),
|
||||
$user ? $this->featureContext->getPasswordForUser($user) : $this->featureContext->getAdminPassword(),
|
||||
'DELETE',
|
||||
$this->globalNotificationEndpointPath,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
json_encode($payload)
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2019, ownCloud GmbH
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\OcsApiHelper;
|
||||
use TestHelpers\TranslationHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* steps needed to send requests to the OCS API
|
||||
*/
|
||||
class OCSContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
|
||||
/**
|
||||
* @When /^the user sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)"$/
|
||||
*
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theUserSendsToOcsApiEndpoint(string $verb, string $url): void {
|
||||
$response = $this->theUserSendsToOcsApiEndpointWithBody($verb, $url);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)"$/
|
||||
* @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" using password "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
* @param string|null $password
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userSendsToOcsApiEndpoint(string $user, string $verb, string $url, ?string $password = null): void {
|
||||
$response = $this->sendRequestToOcsEndpoint(
|
||||
$user,
|
||||
$verb,
|
||||
$url,
|
||||
null,
|
||||
$password
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
* @param TableNode|null $body
|
||||
* @param string|null $password
|
||||
* @param array|null $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function sendRequestToOcsEndpoint(
|
||||
string $user,
|
||||
string $verb,
|
||||
string $url,
|
||||
?TableNode $body = null,
|
||||
?string $password = null,
|
||||
?array $headers = null
|
||||
): ResponseInterface {
|
||||
/**
|
||||
* array of the data to be sent in the body.
|
||||
* contains $body data converted to an array
|
||||
*/
|
||||
$bodyArray = [];
|
||||
if ($body instanceof TableNode) {
|
||||
$bodyArray = $body->getRowsHash();
|
||||
}
|
||||
|
||||
if ($user !== 'UNAUTHORIZED_USER') {
|
||||
if ($password === null) {
|
||||
$password = $this->featureContext->getPasswordForUser($user);
|
||||
}
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
} else {
|
||||
$user = null;
|
||||
$password = null;
|
||||
}
|
||||
return OcsApiHelper::sendRequest(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$password,
|
||||
$verb,
|
||||
$url,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$bodyArray,
|
||||
$this->featureContext->getOcsApiVersion(),
|
||||
$headers
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
* @param TableNode|null $body
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function adminSendsHttpMethodToOcsApiEndpointWithBody(
|
||||
string $verb,
|
||||
string $url,
|
||||
?TableNode $body
|
||||
): ResponseInterface {
|
||||
$admin = $this->featureContext->getAdminUsername();
|
||||
return $this->sendRequestToOcsEndpoint(
|
||||
$admin,
|
||||
$verb,
|
||||
$url,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
* @param TableNode|null $body
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function theUserSendsToOcsApiEndpointWithBody(
|
||||
string $verb,
|
||||
string $url,
|
||||
?TableNode $body = null
|
||||
): ResponseInterface {
|
||||
return $this->sendRequestToOcsEndpoint(
|
||||
$this->featureContext->getCurrentUser(),
|
||||
$verb,
|
||||
$url,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" sends HTTP method "([^"]*)" to OCS API endpoint "([^"]*)" with headers$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $verb
|
||||
* @param string $url
|
||||
* @param TableNode $headersTable
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userSendsToOcsApiEndpointWithHeaders(
|
||||
string $user,
|
||||
string $verb,
|
||||
string $url,
|
||||
TableNode $headersTable
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$password = $this->featureContext->getPasswordForUser($user);
|
||||
$this->featureContext->setResponse(
|
||||
$this->sendRequestToOcsEndpoint(
|
||||
$user,
|
||||
$verb,
|
||||
$url,
|
||||
null,
|
||||
$password,
|
||||
$headersTable->getRowsHash()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the OCS status code should be "([^"]*)"$/
|
||||
*
|
||||
* @param string $statusCode
|
||||
* @param string $message
|
||||
* @param ResponseInterface|null $response
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theOCSStatusCodeShouldBe(
|
||||
string $statusCode,
|
||||
string $message = "",
|
||||
?ResponseInterface $response = null
|
||||
): void {
|
||||
$statusCodes = explode(",", $statusCode);
|
||||
$response = $response ?? $this->featureContext->getResponse();
|
||||
$responseStatusCode = $this->getOCSResponseStatusCode(
|
||||
$response
|
||||
);
|
||||
if (\is_array($statusCodes)) {
|
||||
if ($message === "") {
|
||||
$message = "OCS status code is not any of the expected values "
|
||||
. \implode(",", $statusCodes) . " got " . $responseStatusCode;
|
||||
}
|
||||
Assert::assertContainsEquals(
|
||||
$responseStatusCode,
|
||||
$statusCodes,
|
||||
$message
|
||||
);
|
||||
$this->featureContext->emptyLastOCSStatusCodesArray();
|
||||
} else {
|
||||
if ($message === "") {
|
||||
$message = "OCS status code is not the expected value " . $statusCodes . " got " . $responseStatusCode;
|
||||
}
|
||||
|
||||
Assert::assertEquals(
|
||||
$statusCodes,
|
||||
$responseStatusCode,
|
||||
$message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the OCS status code should be "([^"]*)" or "([^"]*)"$/
|
||||
*
|
||||
* @param string $statusCode1
|
||||
* @param string $statusCode2
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theOcsStatusCodeShouldBeOr(string $statusCode1, string $statusCode2): void {
|
||||
$statusCodes = [$statusCode1,$statusCode1];
|
||||
$response = $this->featureContext->getResponse();
|
||||
$responseStatusCode = $this->getOCSResponseStatusCode(
|
||||
$response
|
||||
);
|
||||
Assert::assertContainsEquals(
|
||||
$responseStatusCode,
|
||||
$statusCodes,
|
||||
"OCS status code is not any of the expected values "
|
||||
. \implode(",", $statusCodes) . " got " . $responseStatusCode
|
||||
);
|
||||
$this->featureContext->emptyLastOCSStatusCodesArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the text in an OCS status message
|
||||
*
|
||||
* @Then /^the OCS status message should be "([^"]*)"$/
|
||||
* @Then /^the OCS status message should be "([^"]*)" in language "([^"]*)"$/
|
||||
*
|
||||
* @param string $statusMessage
|
||||
* @param string|null $language
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theOCSStatusMessageShouldBe(string $statusMessage, ?string $language = null): void {
|
||||
$language = TranslationHelper::getLanguage($language);
|
||||
$statusMessage = $this->getActualStatusMessage($statusMessage, $language);
|
||||
|
||||
Assert::assertEquals(
|
||||
$statusMessage,
|
||||
$this->getOCSResponseStatusMessage(
|
||||
$this->featureContext->getResponse()
|
||||
),
|
||||
'Unexpected OCS status message :"' . $this->getOCSResponseStatusMessage(
|
||||
$this->featureContext->getResponse()
|
||||
) . '" in response'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the text in an OCS status message.
|
||||
* Use this step form if the expected text contains double quotes,
|
||||
* single quotes and other content that theOCSStatusMessageShouldBe()
|
||||
* cannot handle.
|
||||
*
|
||||
* After the step, write the expected text in PyString form like:
|
||||
*
|
||||
* """
|
||||
* File "abc.txt" can't be shared due to reason "xyz"
|
||||
* """
|
||||
*
|
||||
* @Then /^the OCS status message should be:$/
|
||||
*
|
||||
* @param PyStringNode $statusMessage
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theOCSStatusMessageShouldBePyString(
|
||||
PyStringNode $statusMessage
|
||||
): void {
|
||||
Assert::assertEquals(
|
||||
$statusMessage->getRaw(),
|
||||
$this->getOCSResponseStatusMessage(
|
||||
$this->featureContext->getResponse()
|
||||
),
|
||||
'Unexpected OCS status message: "' . $this->getOCSResponseStatusMessage(
|
||||
$this->featureContext->getResponse()
|
||||
) . '" in response'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the xml answer to get ocs response which doesn't match with
|
||||
* http one in v1 of the api.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getOCSResponseStatusCode(ResponseInterface $response): string {
|
||||
try {
|
||||
$jsonResponse = $this->featureContext->getJsonDecodedResponseBodyContent($response);
|
||||
} catch (JsonException $e) {
|
||||
$jsonResponse = null;
|
||||
}
|
||||
|
||||
if (\is_object($jsonResponse) && $jsonResponse->ocs->meta->statuscode) {
|
||||
return (string) $jsonResponse->ocs->meta->statuscode;
|
||||
}
|
||||
// go to xml response when json response is null (it means not formatted and get status code)
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml($response, __METHOD__);
|
||||
if (isset($responseXmlObject->meta[0], $responseXmlObject->meta[0]->statuscode)) {
|
||||
return (string) $responseXmlObject->meta[0]->statuscode;
|
||||
}
|
||||
Assert::fail("No OCS status code found in response");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the xml answer to return data items from ocs response
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return SimpleXMLElement
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getOCSResponseData(ResponseInterface $response): SimpleXMLElement {
|
||||
$responseXmlObject = HttpRequestHelper::getResponseXml($response, __METHOD__);
|
||||
if (isset($responseXmlObject->data)) {
|
||||
return $responseXmlObject->data;
|
||||
}
|
||||
Assert::fail("No OCS data items found in response");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the xml answer to get ocs response message which doesn't match with
|
||||
* http one in v1 of the api.
|
||||
*
|
||||
* @param ResponseInterface $response
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOCSResponseStatusMessage(ResponseInterface $response): string {
|
||||
return (string) HttpRequestHelper::getResponseXml($response, __METHOD__)->meta[0]->message;
|
||||
}
|
||||
|
||||
/**
|
||||
* convert status message in the desired language
|
||||
*
|
||||
* @param string $statusMessage
|
||||
* @param string|null $language
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getActualStatusMessage(string $statusMessage, ?string $language = null): string {
|
||||
if ($language !== null) {
|
||||
$multiLingualMessage = \json_decode(
|
||||
\file_get_contents(__DIR__ . "/../fixtures/multiLanguageErrors.json"),
|
||||
true
|
||||
);
|
||||
|
||||
if (isset($multiLingualMessage[$statusMessage][$language])) {
|
||||
$statusMessage = $multiLingualMessage[$statusMessage][$language];
|
||||
}
|
||||
}
|
||||
return $statusMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if the HTTP status code and the OCS status code indicate that the request was successful
|
||||
* this function is aware of the currently used OCS version
|
||||
*
|
||||
* @param string|null $message
|
||||
* @param ResponseInterface|null $response
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function assertOCSResponseIndicatesSuccess(
|
||||
?string $message = "",
|
||||
?ResponseInterface $response = null
|
||||
): void {
|
||||
$response = $response ?? $this->featureContext->getResponse();
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe('200', $message, $response);
|
||||
if ($this->featureContext->getOcsApiVersion() === 1) {
|
||||
$this->theOCSStatusCodeShouldBe('100', $message, $response);
|
||||
} else {
|
||||
$this->theOCSStatusCodeShouldBe('200', $message, $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Sajan Gurung <sajan@jankaritech.com>
|
||||
* @copyright Copyright (c) 2023 Sajan Gurung sajan@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use TestHelpers\OcConfigHelper;
|
||||
use TestHelpers\GraphHelper;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
/**
|
||||
* steps needed to re-configure КуСфера server
|
||||
*/
|
||||
class OcConfigContext implements Context {
|
||||
private array $enabledPermissionsRoles = [];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getEnabledPermissionsRoles(): array {
|
||||
return $this->enabledPermissionsRoles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $enabledPermissionsRoles
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setEnabledPermissionsRoles(array $enabledPermissionsRoles): void {
|
||||
$this->enabledPermissionsRoles = $enabledPermissionsRoles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given async upload has been enabled with post-processing delayed to :delayTime seconds
|
||||
*
|
||||
* @param string $delayTime
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function asyncUploadHasBeenEnabledWithDelayedPostProcessing(string $delayTime): void {
|
||||
$envs = [
|
||||
"OC_ASYNC_UPLOADS" => true,
|
||||
"OC_EVENTS_ENABLE_TLS" => false,
|
||||
"POSTPROCESSING_DELAY" => $delayTime . "s",
|
||||
];
|
||||
|
||||
$response = OcConfigHelper::reConfigureOc($envs);
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to set async upload with delayed post processing"
|
||||
);
|
||||
OcConfigHelper::setPostProcessingDelay($delayTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the config :configVariable has been set to :configValue
|
||||
*
|
||||
* @param string $configVariable
|
||||
* @param string $configValue
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function theConfigHasBeenSetTo(string $configVariable, string $configValue): void {
|
||||
$envs = [
|
||||
$configVariable => $configValue,
|
||||
];
|
||||
|
||||
$response = OcConfigHelper::reConfigureOc($envs);
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to set config $configVariable=$configValue"
|
||||
);
|
||||
if ($configVariable === "POSTPROCESSING_DELAY") {
|
||||
OcConfigHelper::setPostProcessingDelay($configValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the administrator has enabled the permissions role :role
|
||||
*
|
||||
* @param string $role
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorHasEnabledTheRole(string $role): void {
|
||||
$roleId = GraphHelper::getPermissionsRoleIdByName($role);
|
||||
$defaultRoles = array_values(GraphHelper::DEFAULT_PERMISSIONS_ROLES);
|
||||
|
||||
if (!\in_array($role, $defaultRoles)) {
|
||||
$defaultRoles[] = $roleId;
|
||||
}
|
||||
$envs = [
|
||||
"GRAPH_AVAILABLE_ROLES" => implode(',', $defaultRoles),
|
||||
];
|
||||
$response = OcConfigHelper::reConfigureOc($envs);
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to enable role $role"
|
||||
);
|
||||
$this->setEnabledPermissionsRoles($defaultRoles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the administrator has disabled the permissions role :role
|
||||
*
|
||||
* @param string $role
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theAdministratorHasDisabledThePermissionsRole(string $role): void {
|
||||
$roleId = GraphHelper::getPermissionsRoleIdByName($role);
|
||||
$availableRoles = $this->getEnabledPermissionsRoles();
|
||||
|
||||
if ($key = array_search($roleId, $availableRoles)) {
|
||||
unset($availableRoles[$key]);
|
||||
}
|
||||
$envs = [
|
||||
"GRAPH_AVAILABLE_ROLES" => implode(',', $availableRoles),
|
||||
];
|
||||
$response = OcConfigHelper::reConfigureOc($envs);
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to disable role $role"
|
||||
);
|
||||
$this->setEnabledPermissionsRoles($availableRoles);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the config :configVariable has been set to path :path
|
||||
*
|
||||
* @param string $configVariable
|
||||
* @param string $path
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function theConfigHasBeenSetPathTo(string $configVariable, string $path): void {
|
||||
if (\getenv('TEST_ROOT_PATH')) {
|
||||
$path = \getenv('TEST_ROOT_PATH') . "/" . $path;
|
||||
} else {
|
||||
$path = \realpath(\dirname(__FILE__) . "/../../" . $path);
|
||||
}
|
||||
|
||||
$response = OcConfigHelper::reConfigureOc(
|
||||
[
|
||||
$configVariable => $path
|
||||
]
|
||||
);
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to set config $configVariable=$path"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given the following configs have been set:
|
||||
*
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function theConfigHasBeenSetToValue(TableNode $table): void {
|
||||
$envs = [];
|
||||
foreach ($table->getHash() as $row) {
|
||||
$envs[$row['config']] = $row['value'];
|
||||
if ($row['config'] === "POSTPROCESSING_DELAY") {
|
||||
OcConfigHelper::setPostProcessingDelay($row['value']);
|
||||
}
|
||||
}
|
||||
|
||||
$response = OcConfigHelper::reConfigureOc($envs);
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to set config"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @AfterScenario @env-config
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rollbackOc(): void {
|
||||
OcConfigHelper::setPostProcessingDelay('0');
|
||||
$response = OcConfigHelper::rollbackOc();
|
||||
Assert::assertEquals(
|
||||
200,
|
||||
$response->getStatusCode(),
|
||||
"Failed to rollback КуСфера server. Check if КуСфера is started with ocwrapper."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Viktor Scharf <scharf.vi@gmail.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2024, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\OcHelper;
|
||||
use TestHelpers\OcmHelper;
|
||||
use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
/**
|
||||
* Acceptance test steps related to testing federation share(ocm) features
|
||||
*/
|
||||
class OcmContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
private string $invitationToken;
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context from here
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getLastFederatedInvitationToken(): string {
|
||||
if (empty($this->invitationToken)) {
|
||||
throw new Exception(__METHOD__ . " token not found");
|
||||
}
|
||||
return $this->invitationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $email
|
||||
* @param string $description
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function createInvitation(string $user, $email = null, $description = null): ResponseInterface {
|
||||
$response = OcmHelper::createInvitation(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$email,
|
||||
$description
|
||||
);
|
||||
$responseData = \json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR);
|
||||
if (isset($responseData["token"])) {
|
||||
$this->invitationToken = $responseData["token"];
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When :user creates the federation share invitation
|
||||
* @When :user creates the federation share invitation with email :email and description :description
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $email
|
||||
* @param string $description
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userCreatesTheFederationShareInvitation(string $user, $email = null, $description = null): void {
|
||||
$this->featureContext->setResponse($this->createInvitation($user, $email, $description));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given :user has created the federation share invitation
|
||||
* @Given :user has created the federation share invitation with email :email and description :description
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $email
|
||||
* @param string $description
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasCreatedTheFederationShareInvitation(string $user, $email = null, $description = null): void {
|
||||
$response = $this->createInvitation($user, $email, $description);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $token
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function acceptInvitation(string $user, ?string $token = null): ResponseInterface {
|
||||
$providerDomain = $this->featureContext->getLocalBaseUrlWithoutScheme();
|
||||
if ($this->featureContext->getCurrentServer() === "LOCAL") {
|
||||
$providerDomain = $this->featureContext->getRemoteBaseUrlWithoutScheme();
|
||||
}
|
||||
return OcmHelper::acceptInvitation(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$token ? $token : $this->getLastFederatedInvitationToken(),
|
||||
$providerDomain
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When :user accepts the last federation share invitation
|
||||
* @When :user tries to accept the last federation share invitation
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userAcceptsTheLastFederationShareInvitation(string $user): void {
|
||||
$this->featureContext->setResponse($this->acceptInvitation($user));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When :user tries to accept the invitation with invalid token
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userTriesToAcceptInvitationWithInvalidToken(string $user): void {
|
||||
$this->featureContext->setResponse($this->acceptInvitation($user, WebDavHelper::generateUUIDv4()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given :user has accepted invitation
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasAcceptedTheLastFederationShareInvitation(string $user): void {
|
||||
$response = $this->acceptInvitation($user);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(200, '', $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function findAcceptedUsers(string $user): ResponseInterface {
|
||||
return OcmHelper::findAcceptedUsers(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When :user searches for accepted users
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userFindsAcceptedUsers(string $user): void {
|
||||
$this->featureContext->setResponse($this->findAcceptedUsers($user));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $ocmUserName
|
||||
*
|
||||
* @return array
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function getAcceptedUserByName(string $user, string $ocmUserName): array {
|
||||
$users = ($this->featureContext->getJsonDecodedResponse($this->findAcceptedUsers($user)));
|
||||
foreach ($users as $user) {
|
||||
if (strpos($user["display_name"], $ocmUserName) !== false) {
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
throw new \Exception("Could not find user with name '{$ocmUserName}' in the accepted users list.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function listInvitations(string $user): ResponseInterface {
|
||||
return OcmHelper::listInvite(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When :user lists the created invitations
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userListsCreatedInvitations(string $user): void {
|
||||
$this->featureContext->setResponse($this->listInvitations($user));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When the user waits :number seconds for the invitation token to expire
|
||||
*
|
||||
* @param int $number
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function theUserWaitsForTokenToExpire(int $number): void {
|
||||
\sleep($number);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user deletes federated connection with user :ocmUser using the Graph API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $ocmUser
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userDeletesFederatedConnectionWithUserUsingTheGraphApi(string $user, string $ocmUser): void {
|
||||
$this->featureContext->setResponse($this->deleteConnection($user, $ocmUser));
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user has deleted federated connection with user :ocmUser
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $ocmUser
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasDeletedFederatedConnectionWithUser(string $user, string $ocmUser): void {
|
||||
$response = $this->deleteConnection($user, $ocmUser);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
200,
|
||||
"failed while deleting connection with user $ocmUser",
|
||||
$response
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $ocmUser
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function deleteConnection(string $user, string $ocmUser): ResponseInterface {
|
||||
$ocmUser = $this->getAcceptedUserByName($user, $ocmUser);
|
||||
return OcmHelper::deleteConnection(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$ocmUser['user_id'],
|
||||
$ocmUser['idp']
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,281 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
* @copyright Copyright (c) 2018 Artur Neumann artur@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* context containing search related API steps
|
||||
*/
|
||||
class SearchContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $pattern
|
||||
* @param string|null $limit
|
||||
* @param string|null $scopeType
|
||||
* @param string|null $scope
|
||||
* @param string|null $spaceName
|
||||
* @param TableNode|null $properties
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws GuzzleException|JsonException
|
||||
*/
|
||||
private function searchFiles(
|
||||
string $user,
|
||||
string $pattern,
|
||||
?string $limit = null,
|
||||
?string $scopeType = null,
|
||||
?string $scope = null,
|
||||
?string $spaceName = null,
|
||||
?TableNode $properties = null
|
||||
): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$baseUrl = $this->featureContext->getBaseUrl();
|
||||
$password = $this->featureContext->getPasswordForUser($user);
|
||||
if (str_contains($pattern, '$')) {
|
||||
$date = explode("$", $pattern);
|
||||
switch ($date[1]) {
|
||||
case "today":
|
||||
$pattern = $date[0] . date('Y-m-d', strtotime('today'));
|
||||
break;
|
||||
case "yesterday":
|
||||
$pattern = $date[0] . date('Y-m-d', strtotime('yesterday'));
|
||||
break;
|
||||
default:
|
||||
throw new Exception("cannot convert the date");
|
||||
}
|
||||
}
|
||||
$body
|
||||
= "<?xml version='1.0' encoding='utf-8' ?>\n" .
|
||||
" <oc:search-files xmlns:a='DAV:' xmlns:oc='http://owncloud.org/ns' >\n" .
|
||||
" <oc:search>\n";
|
||||
if ($scope !== null) {
|
||||
if ($scopeType === "space") {
|
||||
$spaceId = $this->featureContext->spacesContext->getSpaceIdByName($user, $scope);
|
||||
$pattern .= " scope:$spaceId";
|
||||
} else {
|
||||
$resourceID = $this->featureContext->spacesContext->getResourceId(
|
||||
$user,
|
||||
$spaceName ?? "Personal",
|
||||
$scope
|
||||
);
|
||||
$pattern .= " scope:$resourceID";
|
||||
}
|
||||
}
|
||||
$body .= "<oc:pattern>$pattern</oc:pattern>\n";
|
||||
if ($limit !== null) {
|
||||
$body .= " <oc:limit>$limit</oc:limit>\n";
|
||||
}
|
||||
|
||||
$body .= " </oc:search>\n";
|
||||
if ($properties !== null) {
|
||||
$propertiesRows = $properties->getRows();
|
||||
$body .= " <a:prop>";
|
||||
foreach ($propertiesRows as $property) {
|
||||
$body .= "<$property[0]/>";
|
||||
}
|
||||
$body .= " </a:prop>";
|
||||
}
|
||||
$body .= " </oc:search-files>";
|
||||
|
||||
$davPathVersionToUse = $this->featureContext->getDavPathVersion();
|
||||
// $davPath will be one of the followings:
|
||||
// - webdav
|
||||
// - dav/files
|
||||
// - dav/spaces
|
||||
$davPath = WebDavHelper::getDavPath($davPathVersionToUse);
|
||||
$fullUrl = WebDavHelper::sanitizeUrl("$baseUrl/$davPath");
|
||||
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$fullUrl,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
'REPORT',
|
||||
$user,
|
||||
$password,
|
||||
null,
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user searches for :pattern using the WebDAV API
|
||||
* @When user :user searches for :pattern and limits the results to :limit items using the WebDAV API
|
||||
* @When user :user searches for :pattern using the WebDAV API requesting these properties:
|
||||
* @When user :user searches for :pattern and limits the results to :limit items using the WebDAV API requesting these properties:
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $pattern
|
||||
* @param string|null $limit
|
||||
* @param TableNode|null $properties
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public function userSearchesUsingWebDavAPI(
|
||||
string $user,
|
||||
string $pattern,
|
||||
?string $limit = null,
|
||||
?TableNode $properties = null
|
||||
): void {
|
||||
// NOTE: because indexing of newly uploaded files or directories with КуСфера is decoupled and occurs asynchronously
|
||||
// short wait is necessary before searching
|
||||
sleep(10);
|
||||
$response = $this->searchFiles($user, $pattern, $limit, null, null, null, $properties);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then file/folder :path in the search result of user :user should contain these properties:
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $user
|
||||
* @param TableNode $properties
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function fileOrFolderInTheSearchResultShouldContainProperties(
|
||||
string $path,
|
||||
string $user,
|
||||
TableNode $properties
|
||||
): void {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$this->featureContext->verifyTableNodeColumns($properties, ['name', 'value']);
|
||||
$properties = $properties->getHash();
|
||||
$fileResult = $this->featureContext->findEntryFromSearchResponse(
|
||||
$path
|
||||
);
|
||||
Assert::assertNotFalse(
|
||||
$fileResult,
|
||||
"could not find file/folder '$path'"
|
||||
);
|
||||
foreach ($properties as $property) {
|
||||
$property['value'] = $this->featureContext->substituteInLineCodes(
|
||||
$property['value'],
|
||||
$user
|
||||
);
|
||||
if (\is_object($fileResult)) {
|
||||
$fileResultProperty = $fileResult->xpath("d:propstat//" . $property['name']);
|
||||
} else {
|
||||
throw new Exception("Expected fileResult to be an object, but found " . \gettype($fileResult));
|
||||
}
|
||||
if ($fileResultProperty) {
|
||||
Assert::assertMatchesRegularExpression(
|
||||
"/" . $property['value'] . "/",
|
||||
\trim((string)$fileResultProperty[0])
|
||||
);
|
||||
continue;
|
||||
}
|
||||
throw new Error("Could not find property '" . $property['name'] . "'");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the search result should contain these (?:files|entries) with highlight on keyword "([^"]*)"/
|
||||
*
|
||||
* @param TableNode $expectedFiles
|
||||
* @param string $expectedContent
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theSearchResultShouldContainEntriesWithHighlight(
|
||||
TableNode $expectedFiles,
|
||||
string $expectedContent
|
||||
): void {
|
||||
$this->featureContext->verifyTableNodeColumnsCount($expectedFiles, 1);
|
||||
$elementRows = $expectedFiles->getRows();
|
||||
$foundEntries = $this->featureContext->findEntryFromSearchResponse(
|
||||
null,
|
||||
true
|
||||
);
|
||||
foreach ($elementRows as $expectedFile) {
|
||||
$filename = $expectedFile[0];
|
||||
$content = $foundEntries[$filename];
|
||||
// Extract the content between the <mark> tags
|
||||
preg_match('/<mark>(.*?)<\/mark>/s', $content, $matches);
|
||||
$actualContent = $matches[1] ?? '';
|
||||
|
||||
// Remove any leading/trailing whitespace for comparison
|
||||
$actualContent = trim($actualContent);
|
||||
Assert::assertEquals(
|
||||
$expectedContent,
|
||||
$actualContent,
|
||||
"Expected text highlight to be '$expectedContent' but found '$actualContent'"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" searches for "([^"]*)" inside (folder|space) "([^"]*)" using the WebDAV API$/
|
||||
* @When /^user "([^"]*)" searches for "([^"]*)" inside (folder) "([^"]*)" in space "([^"]*)" using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $pattern
|
||||
* @param string $scopeType
|
||||
* @param string $scope
|
||||
* @param string|null $spaceName
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public function userSearchesInsideFolderOrSpaceUsingWebDavAPI(
|
||||
string $user,
|
||||
string $pattern,
|
||||
string $scopeType,
|
||||
string $scope,
|
||||
?string $spaceName = null,
|
||||
): void {
|
||||
// NOTE: since indexing of newly uploaded files or directories with КуСфера is decoupled and occurs asynchronously,
|
||||
// a short wait is necessary before searching
|
||||
sleep(5);
|
||||
$response = $this-> searchFiles($user, $pattern, null, $scopeType, $scope, $spaceName);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @author Viktor Scharf <v.scharf@owncloud.com>
|
||||
* @copyright Copyright (c) 2022 Viktor Scharf v.scharf@owncloud.com
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\SettingsHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Context for the TUS-specific steps using the Graph API
|
||||
*/
|
||||
class SettingsContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context from here
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getRoles(string $user): ResponseInterface {
|
||||
return SettingsHelper::getRolesList(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getStepLineRef()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" tries to get all existing roles using the settings API$/
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getAllExistingRoles(string $user): void {
|
||||
$response = $this->getRoles($user);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $userId
|
||||
* @param string $roleId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function assignRoleToUser(string $user, string $userId, string $roleId): ResponseInterface {
|
||||
return SettingsHelper::assignRoleToUser(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$userId,
|
||||
$roleId,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $userId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getAssignmentsList(string $user, string $userId): ResponseInterface {
|
||||
return SettingsHelper::getAssignmentsList(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$userId,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^the administrator has given "([^"]*)" the role "([^"]*)" using the settings api$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $role
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theAdministratorHasGivenUserTheRole(string $user, string $role): void {
|
||||
$admin = $this->featureContext->getAdminUserName();
|
||||
$roleId = $this->getRoleIdByRoleName($admin, $role);
|
||||
$userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id') ?: $user;
|
||||
$response = $this->assignRoleToUser($admin, $userId, $roleId);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
201,
|
||||
"Expected response status code should be 201",
|
||||
$response
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :assigner assigns the role :role to user :assignee using the settings API
|
||||
*
|
||||
* @param string $assigner
|
||||
* @param string $role
|
||||
* @param string $assignee
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userAssignsTheRoleToUserUsingTheSettingsApi(
|
||||
string $assigner,
|
||||
string $role,
|
||||
string $assignee
|
||||
): void {
|
||||
$response = $this->assignRoleToUser(
|
||||
$assigner,
|
||||
$this->featureContext->getAttributeOfCreatedUser($assignee, 'id'),
|
||||
$this->getRoleIdByRoleName($assigner, $role)
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $role
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getRoleIdByRoleName(string $user, string $role): string {
|
||||
// Sometimes the response body is not complete and results invalid json.
|
||||
// So we try again until we get a valid json.
|
||||
$retried = 0;
|
||||
do {
|
||||
$response = $this->getRoles($user);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
201,
|
||||
"Expected response status code should be 201",
|
||||
$response
|
||||
);
|
||||
|
||||
$rawBody = $response->getBody()->getContents();
|
||||
try {
|
||||
$decodedBody = \json_decode($rawBody, true, 512, JSON_THROW_ON_ERROR);
|
||||
$tryAgain = false;
|
||||
} catch (Exception $e) {
|
||||
$tryAgain = $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
|
||||
|
||||
if (!$tryAgain) {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tryAgain) {
|
||||
$retried += 1;
|
||||
echo "Invalid json body, retrying ($retried)...\n";
|
||||
// wait 500ms and try again
|
||||
\usleep(500 * 1000);
|
||||
}
|
||||
} while ($tryAgain);
|
||||
|
||||
Assert::assertArrayHasKey(
|
||||
'bundles',
|
||||
$decodedBody,
|
||||
__METHOD__ . " could not find bundles in body"
|
||||
);
|
||||
$bundles = $decodedBody["bundles"];
|
||||
|
||||
$roleToAssign = "";
|
||||
foreach ($bundles as $value) {
|
||||
// find the selected role
|
||||
if ($value["displayName"] === $role) {
|
||||
$roleToAssign = $value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert::assertNotEmpty($roleToAssign, "The selected role $role could not be found");
|
||||
return $roleToAssign["id"];
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" changes his own role to "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $role
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userChangeOwnRole(string $user, string $role): void {
|
||||
// we assume that the user knows uuid role.
|
||||
$roleId = $this->getRoleIdByRoleName($this->featureContext->getAdminUserName(), $role);
|
||||
$userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$response = $this->assignRoleToUser($user, $userId, $roleId);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" changes the role "([^"]*)" for user "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $role
|
||||
* @param string $assignedUser
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userChangeRoleAnotherUser(string $user, string $role, string $assignedUser): void {
|
||||
// we assume that the user knows uuid role.
|
||||
$roleId = $this->getRoleIdByRoleName($this->featureContext->getAdminUserName(), $role);
|
||||
$userId = $this->featureContext->getAttributeOfCreatedUser($assignedUser, 'id');
|
||||
$response = $this->assignRoleToUser($user, $userId, $roleId);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" tries to get list of assignment using the settings API$/
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userGetAssignmentsList(string $user): void {
|
||||
$userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$this->featureContext->setResponse($this->getAssignmentsList($user, $userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^user "([^"]*)" should have the role "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $role
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userShouldHaveRole(string $user, string $role): void {
|
||||
$userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$response = $this->getAssignmentsList($this->featureContext->getAdminUserName(), $userId);
|
||||
$assignmentResponse = $this->featureContext->getJsonDecodedResponseBodyContent($response);
|
||||
if (isset($assignmentResponse->assignments[0]->roleId)) {
|
||||
$actualRoleId = $assignmentResponse->assignments[0]->roleId;
|
||||
Assert::assertEquals(
|
||||
$this->getRoleIdByRoleName($this->featureContext->getAdminUserName(), $role),
|
||||
$actualRoleId,
|
||||
"user $user has no role $role"
|
||||
);
|
||||
} else {
|
||||
Assert::fail("Response should contain user role but not found.\n" . json_encode($assignmentResponse));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the setting API response should have the role "([^"]*)"$/
|
||||
*
|
||||
* @param string $role
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theSettingApiResponseShouldHaveTheRole(string $role): void {
|
||||
$assignmentRoleId = $this->featureContext->getJsonDecodedResponse(
|
||||
$this->featureContext->getResponse()
|
||||
)["assignments"][0]["roleId"];
|
||||
Assert::assertEquals(
|
||||
$this->getRoleIdByRoleName($this->featureContext->getAdminUserName(), $role),
|
||||
$assignmentRoleId,
|
||||
"user has no role $role"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function sendRequestGetBundlesList(string $user): ResponseInterface {
|
||||
return SettingsHelper::getBundlesList(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $bundleName
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getBundleByName(string $user, string $bundleName): array {
|
||||
return SettingsHelper::getBundleByName(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$bundleName,
|
||||
$this->featureContext->getStepLineRef()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param array $headers
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function sendRequestGetSettingsValuesList(string $user, array $headers = null): ResponseInterface {
|
||||
return SettingsHelper::getValuesList(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$headers
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" lists values-list with headers using the Settings API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param TableNode $headersTable
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theUserListsAllValuesListWithHeadersUsingSettingsApi(string $user, TableNode $headersTable): void {
|
||||
$this->featureContext->verifyTableNodeColumns(
|
||||
$headersTable,
|
||||
['header', 'value']
|
||||
);
|
||||
$headers = [];
|
||||
foreach ($headersTable as $row) {
|
||||
$headers[$row['header']] = $row ['value'];
|
||||
}
|
||||
$this->featureContext->setResponse($this->sendRequestGetSettingsValuesList($user, $headers));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $language
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function sendRequestToSwitchSystemLanguage(string $user, string $language): ResponseInterface {
|
||||
$profileBundlesList = $this->getBundleByName($user, "Profile");
|
||||
Assert::assertNotEmpty($profileBundlesList, "bundles list is empty");
|
||||
|
||||
$settingId = '';
|
||||
foreach ($profileBundlesList["settings"] as $value) {
|
||||
if ($value["name"] === "language") {
|
||||
$settingId = $value["id"];
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assert::assertNotEmpty($settingId, "settingId is empty");
|
||||
|
||||
$userId = $this->featureContext->getAttributeOfCreatedUser($user, 'id');
|
||||
$body = json_encode(
|
||||
[
|
||||
"value" => [
|
||||
"account_uuid" => "me",
|
||||
"bundleId" => $profileBundlesList["id"],
|
||||
"id" => $userId,
|
||||
"listValue" => [
|
||||
"values" => [
|
||||
[
|
||||
"stringValue" => $language
|
||||
]
|
||||
]
|
||||
],
|
||||
"resource" => [
|
||||
"type" => "TYPE_USER"
|
||||
],
|
||||
"settingId" => $settingId
|
||||
]
|
||||
],
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
return SettingsHelper::updateSettings(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$body,
|
||||
$this->featureContext->getStepLineRef()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^user "([^"]*)" has switched the system language to "([^"]*)" using the settings API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $language
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function theUserHasSwitchedSystemLanguage(string $user, string $language): void {
|
||||
$response = $this->sendRequestToSwitchSystemLanguage($user, $language);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
201,
|
||||
"Expected response status code should be 201",
|
||||
$response
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user switches the system language to :language using the settings API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $language
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userSwitchesTheSystemLanguageUsingTheSettingsApi(string $user, string $language): void {
|
||||
$response = $this->sendRequestToSwitchSystemLanguage($user, $language);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function sendRequestToDisableAutoAccepting(string $user): ResponseInterface {
|
||||
$body = json_encode(
|
||||
[
|
||||
"value" => [
|
||||
"account_uuid" => "me",
|
||||
"bundleId" => "2a506de7-99bd-4f0d-994e-c38e72c28fd9",
|
||||
"settingId" => "ec3ed4a3-3946-4efc-8f9f-76d38b12d3a9",
|
||||
"resource" => [
|
||||
"type" => "TYPE_USER"
|
||||
],
|
||||
"boolValue" => false
|
||||
]
|
||||
],
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
return SettingsHelper::updateSettings(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$body,
|
||||
$this->featureContext->getStepLineRef()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has disabled auto-accepting
|
||||
* @Given user :user has disabled the auto-sync share
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function theUserHasDisabledAutoAccepting(string $user): void {
|
||||
$response = $this->sendRequestToDisableAutoAccepting($user);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(
|
||||
201,
|
||||
"Expected response status code should be 201",
|
||||
$response
|
||||
);
|
||||
$this->featureContext->rememberUserAutoSyncSetting($user, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user disables the auto-sync share using the settings API
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userDisablesAutoAcceptingUsingSettingsApi(string $user): void {
|
||||
$response = $this->sendRequestToDisableAutoAccepting($user);
|
||||
$this->featureContext->setResponse($response);
|
||||
$this->featureContext->rememberUserAutoSyncSetting($user, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Joas Schilling <coding@schilljs.com>
|
||||
* @author Sergio Bertolin <sbertolin@owncloud.com>
|
||||
* @author Phillip Davis <phil@jankaritech.com>
|
||||
* @copyright Copyright (c) 2018, ownCloud GmbH
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use TestHelpers\BehatHelper;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Sharees context.
|
||||
*/
|
||||
class ShareesContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
private OCSContext $ocsContext;
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" gets the sharees using the sharing API with parameters$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param TableNode $body
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function userGetsTheShareesWithParameters(string $user, TableNode $body): void {
|
||||
$this->featureContext->setResponse(
|
||||
$this->getShareesWithParameters(
|
||||
$user,
|
||||
$body
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the "([^"]*)" sharees returned should be$/
|
||||
*
|
||||
* @param string $shareeType
|
||||
* @param TableNode $shareesList
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theShareesReturnedShouldBe(string $shareeType, TableNode $shareesList): void {
|
||||
$this->featureContext->verifyTableNodeColumnsCount($shareesList, 4);
|
||||
$sharees = $shareesList->getRows();
|
||||
$respondedArray = $this->getArrayOfShareesResponded(
|
||||
$this->featureContext->getResponse(),
|
||||
$shareeType
|
||||
);
|
||||
Assert::assertEquals(
|
||||
$sharees,
|
||||
$respondedArray,
|
||||
"Returned sharees do not match the expected ones. See the differences below."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the "([^"]*)" sharees returned should include$/
|
||||
*
|
||||
* @param string $shareeType
|
||||
* @param TableNode $shareesList
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theShareesReturnedShouldInclude(string $shareeType, TableNode $shareesList): void {
|
||||
$this->featureContext->verifyTableNodeColumnsCount($shareesList, 3);
|
||||
$sharees = $shareesList->getRows();
|
||||
$respondedArray = $this->getArrayOfShareesResponded(
|
||||
$this->featureContext->getResponse(),
|
||||
$shareeType
|
||||
);
|
||||
foreach ($sharees as $sharee) {
|
||||
Assert::assertContains(
|
||||
$sharee,
|
||||
$respondedArray,
|
||||
"Returned sharees do not match the expected ones. See the differences below."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the "([^"]*)" sharees returned should be empty$/
|
||||
*
|
||||
* @param string $shareeType
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function theShareesReturnedShouldBeEmpty(string $shareeType): void {
|
||||
$respondedArray = $this->getArrayOfShareesResponded(
|
||||
$this->featureContext->getResponse(),
|
||||
$shareeType
|
||||
);
|
||||
if (isset($respondedArray[0])) {
|
||||
// [0] is display name and [2] is user or group id
|
||||
$firstEntry = $respondedArray[0][0] . " (" . $respondedArray[0][2] . ")";
|
||||
} else {
|
||||
$firstEntry = "";
|
||||
}
|
||||
|
||||
Assert::assertEmpty(
|
||||
$respondedArray,
|
||||
"'$shareeType' array should be empty, but it starts with $firstEntry"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ResponseInterface $response
|
||||
* @param string $shareeType
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getArrayOfShareesResponded(
|
||||
ResponseInterface $response,
|
||||
string $shareeType
|
||||
): array {
|
||||
$elements = HttpRequestHelper::getResponseXml($response, __METHOD__)->data;
|
||||
$elements = \json_decode(\json_encode($elements), true);
|
||||
if (\strpos($shareeType, 'exact ') === 0) {
|
||||
$elements = $elements['exact'];
|
||||
$shareeType = \substr($shareeType, 6);
|
||||
}
|
||||
|
||||
Assert::assertArrayHasKey(
|
||||
$shareeType,
|
||||
$elements,
|
||||
__METHOD__ . " The sharees response does not have key '$shareeType'"
|
||||
);
|
||||
|
||||
$sharees = [];
|
||||
foreach ($elements[$shareeType] as $element) {
|
||||
if (\is_int(\key($element))) {
|
||||
// this is a list of elements instead of just one item,
|
||||
// so return the list
|
||||
foreach ($element as $innerItem) {
|
||||
$sharees[] = [
|
||||
$innerItem['label'],
|
||||
$innerItem['value']['shareType'],
|
||||
$innerItem['value']['shareWith'],
|
||||
$innerItem['value']['shareWithAdditionalInfo']
|
||||
];
|
||||
}
|
||||
} else {
|
||||
$sharees[] = [
|
||||
$element['label'],
|
||||
$element['value']['shareType'],
|
||||
$element['value']['shareWith'],
|
||||
$element['value']['shareWithAdditionalInfo']
|
||||
];
|
||||
}
|
||||
}
|
||||
return $sharees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param TableNode $body
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function getShareesWithParameters(string $user, TableNode $body): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$url = '/apps/files_sharing/api/v1/sharees';
|
||||
$this->featureContext->verifyTableNodeColumnsCount($body, 2);
|
||||
$parameters = [];
|
||||
foreach ($body->getRowsHash() as $key => $value) {
|
||||
$parameters[] = "$key=$value";
|
||||
}
|
||||
if (!empty($parameters)) {
|
||||
$url .= '?' . \implode('&', $parameters);
|
||||
}
|
||||
|
||||
return $this->ocsContext->sendRequestToOcsEndpoint(
|
||||
$user,
|
||||
'GET',
|
||||
$url
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
$this->ocsContext = BehatHelper::getContext($scope, $environment, 'OCSContext');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,460 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @author Viktor Scharf <v.scharf@owncloud.com>
|
||||
* @copyright Copyright (c) 2022 Viktor Scharf v.scharf@owncloud.com
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Context for the TUS-specific steps using the Graph API
|
||||
*/
|
||||
class SpacesTUSContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
private TUSContext $tusContext;
|
||||
private SpacesContext $spacesContext;
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context from here
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
$this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext');
|
||||
$this->tusContext = BehatHelper::getContext($scope, $environment, 'TUSContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^user "([^"]*)" has uploaded a file from "([^"]*)" to "([^"]*)" via TUS inside of the space "([^"]*)" using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasUploadedFileViaTusInSpace(
|
||||
string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
string $spaceName
|
||||
): void {
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
||||
$this->tusContext->uploadFileUsingTus($user, $source, $destination, $spaceId);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" uploads a file from "([^"]*)" to "([^"]*)" via TUS inside of the space "([^"]*)" using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userUploadsAFileViaTusInsideOfTheSpaceUsingTheWebdavApi(
|
||||
string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
string $spaceName
|
||||
): void {
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
||||
$this->tusContext->uploadFileUsingTus($user, $source, $destination, $spaceId);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has created a new TUS resource in the space :spaceName with the following headers:
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $spaceName
|
||||
* @param TableNode $headers
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasCreatedANewTusResourceForTheSpaceUsingTheWebdavApiWithTheseHeaders(
|
||||
string $user,
|
||||
string $spaceName,
|
||||
TableNode $headers
|
||||
): void {
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
||||
$response = $this->tusContext->createNewTUSResourceWithHeaders($user, $headers, '', $spaceId);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(201, "Expected response status code should be 201", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user creates a new TUS resource for the space :spaceName with content :content using the WebDAV API with these headers:
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $spaceName
|
||||
* @param string $content
|
||||
* @param TableNode $headers
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userCreatesANewTusResourceForTheSpaceUsingTheWebdavApiWithTheseHeaders(
|
||||
string $user,
|
||||
string $spaceName,
|
||||
string $content,
|
||||
TableNode $headers
|
||||
): void {
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
||||
$response = $this->tusContext->createNewTUSResourceWithHeaders($user, $headers, $content, $spaceId);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file with content to the specified space using the TUS protocol via the WebDAV API.
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param string $resource
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
private function uploadFileViaTus(string $user, string $content, string $resource, string $spaceName): void {
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
||||
$tmpFile = $this->tusContext->writeDataToTempFile($content);
|
||||
try {
|
||||
$this->tusContext->uploadFileUsingTus(
|
||||
$user,
|
||||
\basename($tmpFile),
|
||||
$resource,
|
||||
$spaceId
|
||||
);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
} catch (Exception $e) {
|
||||
Assert::assertStringContainsString('Unable to create resource', (string)$e);
|
||||
}
|
||||
\unlink($tmpFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" uploads a file with content "([^"]*)" to "([^"]*)" inside federated share "([^"]*)" via TUS using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param string $file
|
||||
* @param string $destination
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public function userUploadsAFileWithContentToInsideFederatedShareViaTusUsingTheWebdavApi(
|
||||
string $user,
|
||||
string $content,
|
||||
string $file,
|
||||
string $destination
|
||||
): void {
|
||||
$remoteItemId = $this->spacesContext->getSharesRemoteItemId($user, $destination);
|
||||
$remoteItemId = \rawurlencode($remoteItemId);
|
||||
$tmpFile = $this->tusContext->writeDataToTempFile($content);
|
||||
$this->tusContext->uploadFileUsingTus(
|
||||
$user,
|
||||
\basename($tmpFile),
|
||||
$file,
|
||||
$remoteItemId
|
||||
);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
\unlink($tmpFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" uploads a file with content "([^"]*)" to "([^"]*)" via TUS inside of the space "([^"]*)" using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param string $resource
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public function userUploadsAFileWithContentToViaTusInsideOfTheSpaceUsingTheWebdavApi(
|
||||
string $user,
|
||||
string $content,
|
||||
string $resource,
|
||||
string $spaceName
|
||||
): void {
|
||||
$this->uploadFileViaTus($user, $content, $resource, $spaceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^user "([^"]*)" has uploaded a file with content "([^"]*)" to "([^"]*)" via TUS inside of the space "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param string $resource
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public function userHasUploadedAFileWithContentToViaTusInsideOfTheSpace(
|
||||
string $user,
|
||||
string $content,
|
||||
string $resource,
|
||||
string $spaceName
|
||||
): void {
|
||||
$this->uploadFileViaTus($user, $content, $resource, $spaceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" uploads a file "([^"]*)" to "([^"]*)" with mtime "([^"]*)" via TUS inside of the space "([^"]*)" using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string $mtime Time in human-readable format is taken as input which is converted into milliseconds that is used by API
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userUploadsAFileToWithMtimeViaTusInsideOfTheSpaceUsingTheWebdavApi(
|
||||
string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
string $mtime,
|
||||
string $spaceName
|
||||
): void {
|
||||
switch ($mtime) {
|
||||
case "today":
|
||||
$mtime = date('Y-m-d', strtotime('today'));
|
||||
break;
|
||||
case "yesterday":
|
||||
$mtime = date('Y-m-d', strtotime('yesterday'));
|
||||
break;
|
||||
case "lastWeek":
|
||||
$mtime = date('Y-m-d', strtotime('-7 days'));
|
||||
break;
|
||||
case "lastMonth":
|
||||
$mtime = date('Y-m-d', strtotime('first day of previous month'));
|
||||
break;
|
||||
case "lastYear":
|
||||
$mtime = date('Y-m' . '-01', strtotime('-1 year'));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
||||
$mtime = new DateTime($mtime);
|
||||
$mtime = $mtime->format('U');
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$this->tusContext->uploadFileUsingTus(
|
||||
$user,
|
||||
$source,
|
||||
$destination,
|
||||
$spaceId,
|
||||
['mtime' => $mtime]
|
||||
);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^user "([^"]*)" has uploaded file with checksum "([^"]*)" to the last created TUS Location with offset "([^"]*)" and content "([^"]*)" via TUS inside of the space "([^"]*)" using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $checksum
|
||||
* @param string $offset
|
||||
* @param string $content
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
* @codingStandardsIgnoreStart
|
||||
*/
|
||||
public function userHasUploadedFileWithChecksumToTheLastCreatedTusLocationWithOffsetAndContentViaTusInsideOfTheSpaceUsingTheWebdavApi(
|
||||
// @codingStandardsIgnoreEnd
|
||||
string $user,
|
||||
string $checksum,
|
||||
string $offset,
|
||||
string $content,
|
||||
string $spaceName
|
||||
): void {
|
||||
$resourceLocation = $this->tusContext->getLastTusResourceLocation();
|
||||
$response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" uploads file with checksum "([^"]*)" to the last created TUS Location with offset "([^"]*)" and content "([^"]*)" via TUS inside of the space "([^"]*)" using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $checksum
|
||||
* @param string $offset
|
||||
* @param string $content
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
* @codingStandardsIgnoreStart
|
||||
*/
|
||||
public function userUploadsFileWithChecksumToTheLastCreatedTusLocationWithOffsetAndContentViaTusInsideOfTheSpaceUsingTheWebdavApi(
|
||||
// @codingStandardsIgnoreEnd
|
||||
string $user,
|
||||
string $checksum,
|
||||
string $offset,
|
||||
string $content,
|
||||
string $spaceName
|
||||
): void {
|
||||
$resourceLocation = $this->tusContext->getLastTusResourceLocation();
|
||||
$response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" sends a chunk to the last created TUS Location with offset "([^"]*)" and data "([^"]*)" with checksum "([^"]*)" via TUS inside of the space "([^"]*)" using the WebDAV API$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $offset
|
||||
* @param string $data
|
||||
* @param string $checksum
|
||||
* @param string $spaceName
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
* @codingStandardsIgnoreStart
|
||||
*/
|
||||
public function userSendsAChunkToTheLastCreatedTusLocationWithOffsetAndDataWithChecksumViaTusInsideOfTheSpaceUsingTheWebdavApi(
|
||||
// @codingStandardsIgnoreEnd
|
||||
string $user,
|
||||
string $offset,
|
||||
string $data,
|
||||
string $checksum,
|
||||
string $spaceName
|
||||
): void {
|
||||
$resourceLocation = $this->tusContext->getLastTusResourceLocation();
|
||||
$response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" sends a chunk to the last created TUS Location with data "([^"]*)" with the following headers:$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $data
|
||||
* @param TableNode $headers
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public function userSendsAChunkToTheLastCreatedTusLocationWithDataInsideOfTheSpaceWithHeaders(
|
||||
string $user,
|
||||
string $data,
|
||||
TableNode $headers
|
||||
): void {
|
||||
$rows = $headers->getRowsHash();
|
||||
$resourceLocation = $this->tusContext->getLastTusResourceLocation();
|
||||
$response = $this->tusContext->uploadChunkToTUSLocation(
|
||||
$user,
|
||||
$resourceLocation,
|
||||
$rows['Upload-Offset'],
|
||||
$data,
|
||||
$rows['Upload-Checksum'],
|
||||
['Origin' => $rows['Origin']]
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" overwrites recently shared file with offset "([^"]*)" and data "([^"]*)" with checksum "([^"]*)" via TUS inside of the space "([^"]*)" using the WebDAV API with these headers:$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $offset
|
||||
* @param string $data
|
||||
* @param string $checksum
|
||||
* @param string $spaceName
|
||||
* @param TableNode $headers
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @codingStandardsIgnoreStart
|
||||
*/
|
||||
public function userOverwritesRecentlySharedFileWithOffsetAndDataWithChecksumViaTusInsideOfTheSpaceUsingTheWebdavApiWithTheseHeaders(
|
||||
// @codingStandardsIgnoreEnd
|
||||
string $user,
|
||||
string $offset,
|
||||
string $data,
|
||||
string $checksum,
|
||||
string $spaceName,
|
||||
TableNode $headers
|
||||
): void {
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
||||
$createResponse = $this->tusContext->createNewTUSResource($user, $headers, $spaceId);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(201, "", $createResponse);
|
||||
$resourceLocation = $this->tusContext->getLastTusResourceLocation();
|
||||
$response = $this->tusContext->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^as "([^"]*)" the mtime of the file "([^"]*)" in space "([^"]*)" should be "([^"]*)"$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $resource
|
||||
* @param string $spaceName
|
||||
* @param string $mtime
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception|GuzzleException
|
||||
*/
|
||||
public function theMtimeOfTheFileInSpaceShouldBe(
|
||||
string $user,
|
||||
string $resource,
|
||||
string $spaceName,
|
||||
string $mtime
|
||||
): void {
|
||||
$spaceId = $this->spacesContext->getSpaceIdByName($user, $spaceName);
|
||||
$mtime = new DateTime($mtime);
|
||||
Assert::assertEquals(
|
||||
$mtime->format('U'),
|
||||
WebDavHelper::getMtimeOfResource(
|
||||
$this->featureContext->getActualUsername($user),
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$resource,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$this->featureContext->getDavPathVersion(),
|
||||
$spaceId,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,746 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @author Artur Neumann <artur@jankaritech.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2020, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Exception\GuzzleException;
|
||||
use TusPhp\Exception\ConnectionException;
|
||||
use TusPhp\Exception\TusException;
|
||||
use TusPhp\Tus\Client;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\HttpRequestHelper;
|
||||
use TestHelpers\WebDavHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
use TestHelpers\UploadHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* TUS related test steps
|
||||
*/
|
||||
class TUSContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
|
||||
private array $tusResourceLocations = [];
|
||||
|
||||
/**
|
||||
* @param string $filenameHash
|
||||
* @param string $location
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function saveTusResourceLocation(string $filenameHash, string $location): void {
|
||||
$this->tusResourceLocations[$filenameHash][] = $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filenameHash
|
||||
* @param int|null $index
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTusResourceLocation(string $filenameHash, ?int $index = null): string {
|
||||
if ($index === null) {
|
||||
// get the last one
|
||||
$index = \count($this->tusResourceLocations[$filenameHash]) - 1;
|
||||
}
|
||||
return $this->tusResourceLocations[$filenameHash][$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLastTusResourceLocation(): string {
|
||||
$lastKey = \array_key_last($this->tusResourceLocations);
|
||||
$index = \count($this->tusResourceLocations[$lastKey]) - 1;
|
||||
return $this->tusResourceLocations[$lastKey][$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uploadMetadata
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function parseFilenameHash(string $uploadMetadata): string {
|
||||
$filenameHash = \explode("filename ", $uploadMetadata)[1] ?? '';
|
||||
return \explode(" ", $filenameHash, 2)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param TableNode $headersTable
|
||||
* @param string $content
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function createNewTUSResourceWithHeaders(
|
||||
string $user,
|
||||
TableNode $headersTable,
|
||||
string $content = '',
|
||||
?string $spaceId = null
|
||||
): ResponseInterface {
|
||||
$this->featureContext->verifyTableNodeColumnsCount($headersTable, 2);
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$password = $this->featureContext->getUserPassword($user);
|
||||
|
||||
$headers = $headersTable->getRowsHash();
|
||||
$response = $this->featureContext->makeDavRequest(
|
||||
$user,
|
||||
"POST",
|
||||
null,
|
||||
$headers,
|
||||
$content,
|
||||
$spaceId,
|
||||
"files",
|
||||
null,
|
||||
false,
|
||||
$password
|
||||
);
|
||||
$locationHeader = $response->getHeader('Location');
|
||||
if (\sizeof($locationHeader) > 0) {
|
||||
$filenameHash = $this->parseFilenameHash($headers['Upload-Metadata']);
|
||||
$this->saveTusResourceLocation($filenameHash, $locationHeader[0]);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user creates a new TUS resource on the WebDAV API with these headers:
|
||||
*
|
||||
* @param string $user
|
||||
* @param TableNode $headers
|
||||
* @param string $content
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userCreateNewTUSResourceWithHeaders(string $user, TableNode $headers, string $content = ''): void {
|
||||
$response = $this->createNewTUSResourceWithHeaders($user, $headers, $content);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has created a new TUS resource on the WebDAV API with these headers:
|
||||
*
|
||||
* @param string $user
|
||||
* @param TableNode $headers Tus-Resumable: 1.0.0 header is added automatically
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasCreatedNewTUSResourceWithHeaders(string $user, TableNode $headers): void {
|
||||
$response = $this->createNewTUSResource($user, $headers);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(201, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param TableNode $headers
|
||||
* @param string|null $spaceId
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*/
|
||||
public function createNewTUSResource(string $user, TableNode $headers, ?string $spaceId = null): ResponseInterface {
|
||||
$rows = $headers->getRows();
|
||||
$rows[] = ['Tus-Resumable', '1.0.0'];
|
||||
return $this->createNewTUSResourceWithHeaders($user, new TableNode($rows), '', $spaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $resourceLocation
|
||||
* @param string $offset
|
||||
* @param string $data
|
||||
* @param string $checksum
|
||||
* @param array|null $extraHeaders
|
||||
*
|
||||
* @return ResponseInterface
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function uploadChunkToTUSLocation(
|
||||
string $user,
|
||||
string $resourceLocation,
|
||||
string $offset,
|
||||
string $data,
|
||||
string $checksum = '',
|
||||
?array $extraHeaders = null
|
||||
): ResponseInterface {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$password = $this->featureContext->getUserPassword($user);
|
||||
$headers = [
|
||||
'Content-Type' => 'application/offset+octet-stream',
|
||||
'Tus-Resumable' => '1.0.0',
|
||||
'Upload-Checksum' => $checksum,
|
||||
'Upload-Offset' => $offset
|
||||
];
|
||||
$headers = empty($extraHeaders) ? $headers : array_merge($headers, $extraHeaders);
|
||||
|
||||
return HttpRequestHelper::sendRequest(
|
||||
$resourceLocation,
|
||||
$this->featureContext->getStepLineRef(),
|
||||
'PATCH',
|
||||
$user,
|
||||
$password,
|
||||
$headers,
|
||||
$data
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data with retry on offset mismatch using the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $offset
|
||||
* @param string $data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userSendsAChunkToTUSLocationWithOffsetAndDataWithRetryOnOffsetMismatch(
|
||||
string $user,
|
||||
string $offset,
|
||||
string $data,
|
||||
): void {
|
||||
$resourceLocation = $this->getLastTusResourceLocation();
|
||||
|
||||
$retried = 0;
|
||||
do {
|
||||
$tryAgain = false;
|
||||
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data);
|
||||
// retry on 409 Conflict (Offset mismatch during TUS upload)
|
||||
if ($response->getStatusCode() === 409) {
|
||||
$tryAgain = true;
|
||||
}
|
||||
$tryAgain = $tryAgain && $retried < HttpRequestHelper::numRetriesOnHttpTooEarly();
|
||||
if ($tryAgain) {
|
||||
$retried += 1;
|
||||
echo "Offset mismatch during TUS upload, retrying ($retried)...\n";
|
||||
// wait 1s and try again
|
||||
\sleep(1);
|
||||
}
|
||||
} while ($tryAgain);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data using the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $offset
|
||||
* @param string $data
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function userSendsAChunkToTUSLocationWithOffsetAndData(string $user, string $offset, string $data): void {
|
||||
$resourceLocation = $this->getLastTusResourceLocation();
|
||||
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user uploads file :source to :destination using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string|null $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param array $uploadMetadata array of metadata to be placed in the
|
||||
* `Upload-Metadata` header.
|
||||
* see https://tus.io/protocols/resumable-upload.html#upload-metadata
|
||||
* Don't Base64 encode the value.
|
||||
* @param int $noOfChunks
|
||||
* @param int|null $bytes
|
||||
* @param string $checksum
|
||||
*
|
||||
* @return void
|
||||
* @throws ConnectionException
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
* @throws ReflectionException
|
||||
* @throws TusException
|
||||
*/
|
||||
public function userUploadsUsingTusAFileTo(
|
||||
?string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
array $uploadMetadata = [],
|
||||
int $noOfChunks = 1,
|
||||
?int $bytes = null,
|
||||
string $checksum = ''
|
||||
): void {
|
||||
$this->uploadFileUsingTus($user, $source, $destination, null, $uploadMetadata, $noOfChunks, $bytes, $checksum);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string|null $spaceId
|
||||
* @param array $uploadMetadata
|
||||
* @param integer $noOfChunks
|
||||
* @param integer $bytes
|
||||
* @param string $checksum
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function uploadFileUsingTus(
|
||||
?string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
?string $spaceId = null,
|
||||
array $uploadMetadata = [],
|
||||
int $noOfChunks = 1,
|
||||
?int $bytes = null,
|
||||
string $checksum = ''
|
||||
) {
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$password = $this->featureContext->getUserPassword($user);
|
||||
$headers = [
|
||||
'Authorization' => 'Basic ' . \base64_encode($user . ':' . $password)
|
||||
];
|
||||
if ($bytes !== null) {
|
||||
$creationWithUploadHeader = [
|
||||
'Content-Type' => 'application/offset+octet-stream',
|
||||
'Tus-Resumable' => '1.0.0'
|
||||
];
|
||||
$headers = \array_merge($headers, $creationWithUploadHeader);
|
||||
}
|
||||
if ($checksum != '') {
|
||||
$checksumHeader = [
|
||||
'Upload-Checksum' => $checksum
|
||||
];
|
||||
$headers = \array_merge($headers, $checksumHeader);
|
||||
}
|
||||
|
||||
$client = new Client(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
[
|
||||
'verify' => false,
|
||||
'headers' => $headers
|
||||
]
|
||||
);
|
||||
|
||||
$davPathVersion = $this->featureContext->getDavPathVersion();
|
||||
$suffixPath = $user;
|
||||
if ($davPathVersion === WebDavHelper::DAV_VERSION_SPACES) {
|
||||
$suffixPath = $spaceId ?: $this->featureContext->getPersonalSpaceIdForUser($user);
|
||||
}
|
||||
|
||||
$client->setChecksumAlgorithm('sha1');
|
||||
$client->setApiPath(WebDavHelper::getDavPath($davPathVersion, $suffixPath));
|
||||
$client->setMetadata($uploadMetadata);
|
||||
$sourceFile = UploadHelper::getAcceptanceTestsDir() . $source;
|
||||
$client->setKey((string)rand())->file($sourceFile, $destination);
|
||||
$this->featureContext->pauseUploadDelete();
|
||||
|
||||
if ($bytes !== null) {
|
||||
$client->file($sourceFile, $destination)->createWithUpload($client->getKey(), $bytes);
|
||||
} elseif (\filesize($sourceFile) === 0) {
|
||||
$client->file($sourceFile, $destination)->createWithUpload($client->getKey(), 0);
|
||||
} elseif ($noOfChunks === 1) {
|
||||
$client->file($sourceFile, $destination)->upload();
|
||||
} else {
|
||||
$bytesPerChunk = (int)\ceil(\filesize($sourceFile) / $noOfChunks);
|
||||
for ($i = 0; $i < $noOfChunks; $i++) {
|
||||
$client->upload($bytesPerChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user uploads file with content :content to :destination using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param string $destination
|
||||
*
|
||||
* @return void
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userUploadsAFileWithContentToUsingTus(
|
||||
string $user,
|
||||
string $content,
|
||||
string $destination
|
||||
): void {
|
||||
$temporaryFileName = $this->writeDataToTempFile($content);
|
||||
try {
|
||||
$this->uploadFileUsingTus(
|
||||
$user,
|
||||
\basename($temporaryFileName),
|
||||
$destination
|
||||
);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
} catch (Exception $e) {
|
||||
Assert::assertStringContainsString('TusPhp\Exception\FileException: Unable to create resource', (string)$e);
|
||||
}
|
||||
\unlink($temporaryFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user uploads file with content :content in :noOfChunks chunks to :destination using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string|null $user
|
||||
* @param string $content
|
||||
* @param int|null $noOfChunks
|
||||
* @param string $destination
|
||||
*
|
||||
* @return void
|
||||
* @throws ConnectionException
|
||||
* @throws GuzzleException
|
||||
* @throws JsonException
|
||||
* @throws ReflectionException
|
||||
* @throws TusException
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userUploadsAFileWithContentInChunksUsingTus(
|
||||
?string $user,
|
||||
string $content,
|
||||
?int $noOfChunks,
|
||||
string $destination
|
||||
): void {
|
||||
$temporaryFileName = $this->writeDataToTempFile($content);
|
||||
$this->uploadFileUsingTus(
|
||||
$user,
|
||||
\basename($temporaryFileName),
|
||||
$destination,
|
||||
null,
|
||||
[],
|
||||
$noOfChunks
|
||||
);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
\unlink($temporaryFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has uploaded file :source to :destination with mtime :mtime using the TUS protocol
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string $mtime Time in human-readable format is taken as input which is converted into milliseconds that is used by API
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userHasUploadedFileWithMtimeUsingTUS(
|
||||
string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
string $mtime
|
||||
): void {
|
||||
$mtime = new DateTime($mtime);
|
||||
$mtime = $mtime->format('U');
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$this->uploadFileUsingTus(
|
||||
$user,
|
||||
$source,
|
||||
$destination,
|
||||
null,
|
||||
['mtime' => $mtime]
|
||||
);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user uploads file :source to :destination with mtime :mtime using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $destination
|
||||
* @param string $mtime Time in human-readable format is taken as input which is converted into milliseconds that is used by API
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userUploadsFileWithContentToWithMtimeUsingTUS(
|
||||
string $user,
|
||||
string $source,
|
||||
string $destination,
|
||||
string $mtime
|
||||
): void {
|
||||
$mtime = new DateTime($mtime);
|
||||
$mtime = $mtime->format('U');
|
||||
$user = $this->featureContext->getActualUsername($user);
|
||||
$this->uploadFileUsingTus(
|
||||
$user,
|
||||
$source,
|
||||
$destination,
|
||||
null,
|
||||
['mtime' => $mtime]
|
||||
);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
*
|
||||
* @return string the file name
|
||||
* @throws Exception
|
||||
*/
|
||||
public function writeDataToTempFile(string $content): string {
|
||||
$temporaryFileName = \tempnam(
|
||||
UploadHelper::getAcceptanceTestsDir(),
|
||||
"tus-upload-test-"
|
||||
);
|
||||
if ($temporaryFileName === false) {
|
||||
throw new \Exception("could not create a temporary filename");
|
||||
}
|
||||
$temporaryFile = \fopen($temporaryFileName, "w");
|
||||
if ($temporaryFile === false) {
|
||||
throw new \Exception("could not open " . $temporaryFileName . " for write");
|
||||
}
|
||||
\fwrite($temporaryFile, $content);
|
||||
\fclose($temporaryFile);
|
||||
return $temporaryFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
// clear TUS locations cache
|
||||
$this->tusResourceLocations = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user creates a new TUS resource with content :content on the WebDAV API with these headers:
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param TableNode $headers
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
* @throws GuzzleException
|
||||
*/
|
||||
public function userCreatesWithUpload(
|
||||
string $user,
|
||||
string $content,
|
||||
TableNode $headers
|
||||
): void {
|
||||
$response = $this->createNewTUSResourceWithHeaders($user, $headers, $content);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user creates file :source and uploads content :content in the same request using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $source
|
||||
* @param string $content
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userUploadsWithCreatesWithUpload(
|
||||
string $user,
|
||||
string $source,
|
||||
string $content
|
||||
): void {
|
||||
$temporaryFileName = $this->writeDataToTempFile($content);
|
||||
$this->uploadFileUsingTus(
|
||||
$user,
|
||||
\basename($temporaryFileName),
|
||||
$source,
|
||||
null,
|
||||
[],
|
||||
1,
|
||||
-1
|
||||
);
|
||||
$this->featureContext->setLastUploadDeleteTime(\time());
|
||||
\unlink($temporaryFileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user uploads file with checksum :checksum to the last created TUS Location with offset :offset and content :content using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $checksum
|
||||
* @param string $offset
|
||||
* @param string $content
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userUploadsFileWithChecksum(
|
||||
string $user,
|
||||
string $checksum,
|
||||
string $offset,
|
||||
string $content
|
||||
): void {
|
||||
$resourceLocation = $this->getLastTusResourceLocation();
|
||||
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user uploads content :content with checksum :checksum and offset :offset to the index :locationIndex location of file :filename using the TUS protocol
|
||||
* @When user :user tries to upload content :content with checksum :checksum and offset :offset to the index :locationIndex location of file :filename using the TUS protocol
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $content
|
||||
* @param string $checksum
|
||||
* @param string $offset
|
||||
* @param string $locationIndex
|
||||
* @param string $filename
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userUploadsContentWithChecksumAndOffsetToIndexLocationUsingTUSProtocol(
|
||||
string $user,
|
||||
string $content,
|
||||
string $checksum,
|
||||
string $offset,
|
||||
string $locationIndex,
|
||||
string $filename
|
||||
): void {
|
||||
$filenameHash = \base64_encode($filename);
|
||||
$resourceLocation = $this->getTusResourceLocation($filenameHash, (int)$locationIndex);
|
||||
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has uploaded file with checksum :checksum to the last created TUS Location with offset :offset and content :content using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $checksum
|
||||
* @param string $offset
|
||||
* @param string $content
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userHasUploadedFileWithChecksum(
|
||||
string $user,
|
||||
string $checksum,
|
||||
string $offset,
|
||||
string $content
|
||||
): void {
|
||||
$resourceLocation = $this->getLastTusResourceLocation();
|
||||
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $content, $checksum);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user sends a chunk to the last created TUS Location with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $offset
|
||||
* @param string $data
|
||||
* @param string $checksum
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userUploadsChunkFileWithChecksum(
|
||||
string $user,
|
||||
string $offset,
|
||||
string $data,
|
||||
string $checksum
|
||||
): void {
|
||||
$resourceLocation = $this->getLastTusResourceLocation();
|
||||
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given user :user has uploaded a chunk to the last created TUS Location with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $offset
|
||||
* @param string $data
|
||||
* @param string $checksum
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userHasUploadedChunkFileWithChecksum(
|
||||
string $user,
|
||||
string $offset,
|
||||
string $data,
|
||||
string $checksum
|
||||
): void {
|
||||
$resourceLocation = $this->getLastTusResourceLocation();
|
||||
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(204, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user overwrites recently shared file with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API with these headers:
|
||||
* @When user :user overwrites existing file with offset :offset and data :data with checksum :checksum using the TUS protocol on the WebDAV API with these headers:
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $offset
|
||||
* @param string $data
|
||||
* @param string $checksum
|
||||
* @param TableNode $headers Tus-Resumable: 1.0.0 header is added automatically
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws GuzzleException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userOverwritesFileWithChecksum(
|
||||
string $user,
|
||||
string $offset,
|
||||
string $data,
|
||||
string $checksum,
|
||||
TableNode $headers
|
||||
): void {
|
||||
$createResponse = $this->createNewTUSResource($user, $headers);
|
||||
$this->featureContext->theHTTPStatusCodeShouldBe(201, "", $createResponse);
|
||||
$resourceLocation = $this->getLastTusResourceLocation();
|
||||
$response = $this->uploadChunkToTUSLocation($user, $resourceLocation, $offset, $data, $checksum);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* @author Viktor Scharf <scharf.vi@gmail.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2022, ownCloud GmbH
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* 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, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use TestHelpers\GraphHelper;
|
||||
use TestHelpers\BehatHelper;
|
||||
|
||||
require_once 'bootstrap.php';
|
||||
|
||||
/**
|
||||
* Acceptance test steps related to testing tags features
|
||||
*/
|
||||
class TagContext implements Context {
|
||||
private FeatureContext $featureContext;
|
||||
private SpacesContext $spacesContext;
|
||||
|
||||
/**
|
||||
* This will run before EVERY scenario.
|
||||
* It will set the properties for this object.
|
||||
*
|
||||
* @BeforeScenario
|
||||
*
|
||||
* @param BeforeScenarioScope $scope
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function before(BeforeScenarioScope $scope): void {
|
||||
// Get the environment
|
||||
$environment = $scope->getEnvironment();
|
||||
// Get all the contexts you need in this context from here
|
||||
$this->featureContext = BehatHelper::getContext($scope, $environment, 'FeatureContext');
|
||||
$this->spacesContext = BehatHelper::getContext($scope, $environment, 'SpacesContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $fileOrFolder (file|folder)
|
||||
* @param string $resource
|
||||
* @param string $space
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createTags(
|
||||
string $user,
|
||||
string $fileOrFolder,
|
||||
string $resource,
|
||||
string $space,
|
||||
TableNode $table
|
||||
): ResponseInterface {
|
||||
$tagNameArray = [];
|
||||
foreach ($table->getRows() as $value) {
|
||||
$tagNameArray[] = $value[0];
|
||||
}
|
||||
if ($fileOrFolder === 'folder' || $fileOrFolder === 'folders') {
|
||||
$resourceId = $this->spacesContext->getResourceId($user, $space, $resource);
|
||||
} else {
|
||||
$resourceId = $this->spacesContext->getFileId($user, $space, $resource);
|
||||
}
|
||||
|
||||
return GraphHelper::createTags(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$resourceId,
|
||||
$tagNameArray
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" creates the following tags for (folder|file) "([^"]*)" of space "([^"]*)":$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileOrFolder (file|folder)
|
||||
* @param string $resource
|
||||
* @param string $space
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theUserCreatesFollowingTags(
|
||||
string $user,
|
||||
string $fileOrFolder,
|
||||
string $resource,
|
||||
string $space,
|
||||
TableNode $table
|
||||
): void {
|
||||
$response = $this->createTags($user, $fileOrFolder, $resource, $space, $table);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^user "([^"]*)" has created the following tags for (folder|file)\s?"([^"]*)" of the space "([^"]*)":$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileOrFolder (file|folder)
|
||||
* @param string $resource
|
||||
* @param string $space
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theUserHasCreatedFollowingTags(
|
||||
string $user,
|
||||
string $fileOrFolder,
|
||||
string $resource,
|
||||
string $space,
|
||||
TableNode $table
|
||||
): void {
|
||||
$response = $this->createTags($user, $fileOrFolder, $resource, $space, $table);
|
||||
$this->featureContext->theHttpStatusCodeShouldBe(200, "", $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^user "([^"]*)" has tagged the following (folders|files) of the space "([^"]*)":$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $filesOrFolders (files|folders)
|
||||
* @param string $space
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userHasCreatedTheFollowingTagsForFilesOfTheSpace(
|
||||
string $user,
|
||||
string $filesOrFolders,
|
||||
string $space,
|
||||
TableNode $table
|
||||
): void {
|
||||
$this->featureContext->verifyTableNodeColumns($table, ["path", "tagName"]);
|
||||
$rows = $table->getHash();
|
||||
foreach ($rows as $row) {
|
||||
$tags = explode(',', $row['tagName']);
|
||||
$response = $this->createTags($user, $filesOrFolders, $row['path'], $space, new TableNode([$tags]));
|
||||
$this->featureContext->theHttpStatusCodeShouldBe(200, "", $response);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @When user :user lists all available tag(s) via the Graph API
|
||||
*
|
||||
* @param string $user
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theUserGetsAllAvailableTags(string $user): void {
|
||||
// Note: after creating or deleting tags, in some cases tags do not appear or disappear immediately,
|
||||
// So wait is necessary before listing tags
|
||||
sleep(5);
|
||||
$this->featureContext->setResponse(
|
||||
GraphHelper::getTags(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$this->featureContext->getStepLineRef()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the response should (not|)\s?contain following tag(s):$/
|
||||
*
|
||||
* @param string $shouldOrNot (not|)
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function theFollowingTagsShouldExistForUser(string $shouldOrNot, TableNode $table): void {
|
||||
$rows = $table->getRows();
|
||||
foreach ($rows as $row) {
|
||||
$responseArray = $this->featureContext->getJsonDecodedResponse(
|
||||
$this->featureContext->getResponse()
|
||||
)['value'];
|
||||
if ($shouldOrNot === "not") {
|
||||
Assert::assertFalse(
|
||||
\in_array($row[0], $responseArray),
|
||||
"the response should not contain the tag $row[0].\nResponse\n"
|
||||
. print_r($responseArray, true)
|
||||
);
|
||||
} else {
|
||||
Assert::assertTrue(
|
||||
\in_array($row[0], $responseArray),
|
||||
"the response does not contain the tag $row[0].\nResponse\n"
|
||||
. print_r($responseArray, true)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param string $fileOrFolder (file|folder)
|
||||
* @param string $resource
|
||||
* @param string $space
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return ResponseInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function removeTagsFromResourceOfTheSpace(
|
||||
string $user,
|
||||
string $fileOrFolder,
|
||||
string $resource,
|
||||
string $space,
|
||||
TableNode $table
|
||||
): ResponseInterface {
|
||||
$tagNameArray = [];
|
||||
foreach ($table->getRows() as $value) {
|
||||
$tagNameArray[] = $value[0];
|
||||
}
|
||||
|
||||
if ($fileOrFolder === 'folder') {
|
||||
$resourceId = $this->spacesContext->getResourceId($user, $space, $resource);
|
||||
} else {
|
||||
$resourceId = $this->spacesContext->getFileId($user, $space, $resource);
|
||||
}
|
||||
|
||||
return GraphHelper::deleteTags(
|
||||
$this->featureContext->getBaseUrl(),
|
||||
$this->featureContext->getStepLineRef(),
|
||||
$user,
|
||||
$this->featureContext->getPasswordForUser($user),
|
||||
$resourceId,
|
||||
$tagNameArray
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^user "([^"]*)" removes the following tags for (folder|file) "([^"]*)" of space "([^"]*)":$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileOrFolder (file|folder)
|
||||
* @param string $resource
|
||||
* @param string $space
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userRemovesTagsFromResourceOfTheSpace(
|
||||
string $user,
|
||||
string $fileOrFolder,
|
||||
string $resource,
|
||||
string $space,
|
||||
TableNode $table
|
||||
): void {
|
||||
$response = $this->removeTagsFromResourceOfTheSpace(
|
||||
$user,
|
||||
$fileOrFolder,
|
||||
$resource,
|
||||
$space,
|
||||
$table
|
||||
);
|
||||
$this->featureContext->setResponse($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^user "([^"]*)" has removed the following tags for (folder|file) "([^"]*)" of space "([^"]*)":$/
|
||||
*
|
||||
* @param string $user
|
||||
* @param string $fileOrFolder (file|folder)
|
||||
* @param string $resource
|
||||
* @param string $space
|
||||
* @param TableNode $table
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public function userHAsRemovedTheFollowingTagsForFileOfSpace(
|
||||
string $user,
|
||||
string $fileOrFolder,
|
||||
string $resource,
|
||||
string $space,
|
||||
TableNode $table
|
||||
): void {
|
||||
$response = $this->removeTagsFromResourceOfTheSpace($user, $fileOrFolder, $resource, $space, $table);
|
||||
$this->featureContext->theHttpStatusCodeShouldBe(200, "", $response);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @author Phil Davis <phil@jankaritech.com>
|
||||
* @copyright Copyright (c) 2020 Phil Davis phil@jankaritech.com
|
||||
*
|
||||
* This code 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 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/>
|
||||
*
|
||||
*/
|
||||
|
||||
use Composer\Autoload\ClassLoader;
|
||||
|
||||
$classLoader = new ClassLoader();
|
||||
|
||||
$classLoader->addPsr4("TestHelpers\\", __DIR__ . "/../TestHelpers", true);
|
||||
|
||||
$classLoader->register();
|
||||
|
||||
// Default number of times to retry where retries are useful
|
||||
if (!\defined('STANDARD_RETRY_COUNT')) {
|
||||
\define('STANDARD_RETRY_COUNT', 10);
|
||||
}
|
||||
// Minimum number of times to retry where retries are useful
|
||||
if (!\defined('MINIMUM_RETRY_COUNT')) {
|
||||
\define('MINIMUM_RETRY_COUNT', 2);
|
||||
}
|
||||
|
||||
// Minimum number of times to retry where retries are useful
|
||||
if (!\defined('HTTP_REQUEST_TIMEOUT')) {
|
||||
\define('HTTP_REQUEST_TIMEOUT', 60);
|
||||
}
|
||||
|
||||
// The remote server-under-test might or might not happen to have this directory.
|
||||
// If it does not exist, then the tests may end up creating it.
|
||||
if (!\defined('ACCEPTANCE_TEST_DIR_ON_REMOTE_SERVER')) {
|
||||
\define('ACCEPTANCE_TEST_DIR_ON_REMOTE_SERVER', 'tests/acceptance');
|
||||
}
|
||||
|
||||
// The following directory should NOT already exist on the remote server-under-test.
|
||||
// Acceptance tests are free to do anything needed in this directory, and to
|
||||
// delete it during or at the end of testing.
|
||||
if (!\defined('TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER')) {
|
||||
\define('TEMPORARY_STORAGE_DIR_ON_REMOTE_SERVER', ACCEPTANCE_TEST_DIR_ON_REMOTE_SERVER . '/server_tmp');
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# when deleting the tests suites from /features there might be the tests scenarios that might be in the expected to failure file
|
||||
# this script checks if there are such scenarios in the expected to failure file which needs to be deleted
|
||||
|
||||
# helper functions
|
||||
log_error() {
|
||||
echo -e "\e[31m$1\e[0m"
|
||||
}
|
||||
|
||||
log_info() {
|
||||
echo -e "\e[37m$1\e[0m"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "\e[32m$1\e[0m"
|
||||
}
|
||||
|
||||
SCRIPT_PATH=$(dirname "$0")
|
||||
PATH_TO_SUITES="${SCRIPT_PATH}/features"
|
||||
EXPECTED_FAILURE_FILES=("expected-failures-decomposed-storage.md" "expected-failures-posix-storage.md" "expected-failures-without-remotephp.md")
|
||||
# contains all the suites names inside tests/acceptance/features
|
||||
AVAILABLE_SUITES=($(ls -l "$PATH_TO_SUITES" | grep '^d' | awk '{print $NF}'))
|
||||
|
||||
# regex to match [someSuites/someFeatureFile.feature:lineNumber]
|
||||
SCENARIO_REGEX="\[([a-zA-Z0-9]+/[a-zA-Z0-9]+\.feature:[0-9]+)]"
|
||||
|
||||
# contains all those suites available in the expected to failure files in pattern [someSuites/someFeatureFile.feature:lineNumber]
|
||||
EXPECTED_FAILURE_SCENARIOS=()
|
||||
EXIT_CODE=0
|
||||
for expected_failure_file in "${EXPECTED_FAILURE_FILES[@]}"; do
|
||||
PATH_TO_EXPECTED_FAILURE_FILE="${SCRIPT_PATH}/${expected_failure_file}"
|
||||
EXPECTED_FAILURE_SCENARIOS=($(grep -Eo ${SCENARIO_REGEX} ${PATH_TO_EXPECTED_FAILURE_FILE}))
|
||||
# get and store only the suites names from EXPECTED_FAILURE_SCENARIOS
|
||||
EXPECTED_FAILURE_SUITES=()
|
||||
for scenario in "${EXPECTED_FAILURE_SCENARIOS[@]}"; do
|
||||
if [[ $scenario =~ \[([a-zA-Z0-9]+) ]]; then
|
||||
suite="${BASH_REMATCH[1]}"
|
||||
EXPECTED_FAILURE_SUITES+=("$suite")
|
||||
fi
|
||||
done
|
||||
# also filter the duplicated suites name
|
||||
EXPECTED_FAILURE_SUITES=($(echo "${EXPECTED_FAILURE_SUITES[@]}" | tr ' ' '\n' | sort | uniq))
|
||||
# Check the existence of the suite
|
||||
NON_EXISTING_SCENARIOS=()
|
||||
FEATURE_PATTERN="[a-zA-Z0-9]+\\.feature:[0-9]+"
|
||||
for suite in "${EXPECTED_FAILURE_SUITES[@]}"; do
|
||||
pattern="(\\b${suite}/${FEATURE_PATTERN}\\b)"
|
||||
if [[ " ${AVAILABLE_SUITES[*]} " != *" $suite "* ]]; then
|
||||
NON_EXISTING_SCENARIOS+=($(grep -Eo ${pattern} ${PATH_TO_EXPECTED_FAILURE_FILE}))
|
||||
else
|
||||
SCENARIOS=($(grep -Eo ${pattern} ${PATH_TO_EXPECTED_FAILURE_FILE} | grep -Eo "${FEATURE_PATTERN}"))
|
||||
for scenario in "${SCENARIOS[@]}"; do
|
||||
FEATURE_FILE=$(echo "$scenario" | cut -d':' -f1)
|
||||
if [[ ! -f "$PATH_TO_SUITES/$suite/$FEATURE_FILE" ]]; then
|
||||
NON_EXISTING_SCENARIOS+=("$suite/$scenario")
|
||||
fi
|
||||
done
|
||||
fi
|
||||
done
|
||||
|
||||
count="${#NON_EXISTING_SCENARIOS[@]}"
|
||||
if [ "$count" -gt 0 ]; then
|
||||
EXIT_CODE=1
|
||||
log_info "The following test scenarios do not exist anymore:"
|
||||
log_info "They can be deleted from the '${expected_failure_file}'"
|
||||
for scenario_path in "${NON_EXISTING_SCENARIOS[@]}"; do
|
||||
log_error "$scenario_path"
|
||||
done
|
||||
else
|
||||
log_success "All the suites in the expected failure file '${expected_failure_file}' exist!"
|
||||
fi
|
||||
log_info "\n"
|
||||
done
|
||||
|
||||
exit "$EXIT_CODE"
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"rules": {
|
||||
"indentation": "error",
|
||||
"no_repetitive_step_keyword": "error",
|
||||
"require_step": "error",
|
||||
"no_trailing_whitespace": "error",
|
||||
"require_scenario": "error",
|
||||
"no_then_as_first_step": "error",
|
||||
"only_given_step_in_background": "error",
|
||||
"newline_before_scenario": ["error", 2, true],
|
||||
"lowercase_title": "off",
|
||||
"no_but_in_given_when": "error"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,776 @@
|
||||
default:
|
||||
autoload:
|
||||
"": "%paths.base%/../bootstrap"
|
||||
|
||||
suites:
|
||||
apiSpaces:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSpaces"
|
||||
context: &common_ldap_suite_context
|
||||
parameters:
|
||||
ldapAdminPassword: admin
|
||||
ldapUsersOU: TestUsers
|
||||
ldapGroupsOU: TestGroups
|
||||
ldapInitialUserFilePath: /../config/ldap-users.ldif
|
||||
contexts:
|
||||
- FeatureContext: &common_feature_context_params
|
||||
adminUsername: admin
|
||||
adminPassword: admin
|
||||
regularUserPassword: 123456
|
||||
- SettingsContext:
|
||||
- SpacesContext:
|
||||
- CapabilitiesContext:
|
||||
- FilesVersionsContext:
|
||||
- NotificationContext:
|
||||
- OCSContext:
|
||||
- PublicWebDavContext:
|
||||
- TagContext:
|
||||
- TUSContext:
|
||||
- SpacesTUSContext:
|
||||
- GraphContext:
|
||||
- OcConfigContext:
|
||||
- WebDavLockingContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiSpacesShares:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSpacesShares"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SettingsContext:
|
||||
- SpacesContext:
|
||||
- CapabilitiesContext:
|
||||
- ChecksumContext:
|
||||
- FavoritesContext:
|
||||
- FilesVersionsContext:
|
||||
- OCSContext:
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- TUSContext:
|
||||
- SpacesTUSContext:
|
||||
- ArchiverContext:
|
||||
- GraphContext:
|
||||
- OcConfigContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiContract:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiContract"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SettingsContext:
|
||||
- SpacesContext:
|
||||
- CapabilitiesContext:
|
||||
- FilesVersionsContext:
|
||||
- OCSContext:
|
||||
- PublicWebDavContext:
|
||||
- SearchContext:
|
||||
- TUSContext:
|
||||
- SpacesTUSContext:
|
||||
- GraphContext:
|
||||
- SharingNgContext:
|
||||
- OcConfigContext:
|
||||
|
||||
apiArchiver:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiArchiver"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SettingsContext:
|
||||
- ArchiverContext:
|
||||
- SpacesContext:
|
||||
- CapabilitiesContext:
|
||||
- FilesVersionsContext:
|
||||
- OCSContext:
|
||||
- PublicWebDavContext:
|
||||
- GraphContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiGraph:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiGraph"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SettingsContext:
|
||||
- GraphContext:
|
||||
- SpacesContext:
|
||||
- CapabilitiesContext:
|
||||
- FilesVersionsContext:
|
||||
- OCSContext:
|
||||
- PublicWebDavContext:
|
||||
- TUSContext:
|
||||
- SpacesTUSContext:
|
||||
- OcConfigContext:
|
||||
- TrashbinContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiGraphUserGroup:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiGraphUserGroup"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SettingsContext:
|
||||
- GraphContext:
|
||||
- SpacesContext:
|
||||
- CapabilitiesContext:
|
||||
- FilesVersionsContext:
|
||||
- OCSContext:
|
||||
- PublicWebDavContext:
|
||||
- TUSContext:
|
||||
- SpacesTUSContext:
|
||||
- OcConfigContext:
|
||||
- TrashbinContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiCors:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiCors"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- FilesVersionsContext:
|
||||
- OCSContext:
|
||||
- GraphContext:
|
||||
- OcConfigContext:
|
||||
- SettingsContext:
|
||||
- TUSContext:
|
||||
- SpacesTUSContext:
|
||||
|
||||
apiDepthInfinity:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiDepthInfinity"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- OCSContext:
|
||||
- GraphContext:
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- OcConfigContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiAsyncUpload:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiAsyncUpload"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- WebDavPropertiesContext:
|
||||
- FilesVersionsContext:
|
||||
- OCSContext:
|
||||
- GraphContext:
|
||||
- OcConfigContext:
|
||||
|
||||
apiNotification:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiNotification"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- NotificationContext:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- OCSContext:
|
||||
- GraphContext:
|
||||
- FilesVersionsContext:
|
||||
- SettingsContext:
|
||||
- OcConfigContext:
|
||||
- SharingNgContext:
|
||||
- OcmContext:
|
||||
|
||||
apiAntivirus:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiAntivirus"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- NotificationContext:
|
||||
- SpacesContext:
|
||||
- OCSContext:
|
||||
- GraphContext:
|
||||
- FilesVersionsContext:
|
||||
- SettingsContext:
|
||||
- OcConfigContext:
|
||||
- PublicWebDavContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiDownloads:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiDownloads"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- NotificationContext:
|
||||
- SpacesContext:
|
||||
- WebDavPropertiesContext:
|
||||
- OCSContext:
|
||||
- GraphContext:
|
||||
- TrashbinContext:
|
||||
- FavoritesContext:
|
||||
- ChecksumContext:
|
||||
- FilesVersionsContext:
|
||||
- SettingsContext:
|
||||
- OcConfigContext:
|
||||
- PublicWebDavContext:
|
||||
- ArchiverContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiSearch1:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSearch1"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SettingsContext:
|
||||
- GraphContext:
|
||||
- SpacesContext:
|
||||
- PublicWebDavContext:
|
||||
- SearchContext:
|
||||
- OCSContext:
|
||||
- SpacesTUSContext:
|
||||
- TagContext:
|
||||
- TUSContext:
|
||||
- WebDavPropertiesContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiSearch2:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSearch2"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SettingsContext:
|
||||
- GraphContext:
|
||||
- SpacesContext:
|
||||
- FilesVersionsContext:
|
||||
- SearchContext:
|
||||
- OCSContext:
|
||||
- TrashbinContext:
|
||||
- SpacesTUSContext:
|
||||
- TagContext:
|
||||
- TUSContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiSearchContent:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSearchContent"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SettingsContext:
|
||||
- GraphContext:
|
||||
- SpacesContext:
|
||||
- PublicWebDavContext:
|
||||
- SearchContext:
|
||||
- CapabilitiesContext:
|
||||
- FilesVersionsContext:
|
||||
- OCSContext:
|
||||
- TrashbinContext:
|
||||
- TagContext:
|
||||
- SpacesTUSContext:
|
||||
- TUSContext:
|
||||
- OcConfigContext:
|
||||
- SharingNgContext:
|
||||
- WebDavPropertiesContext:
|
||||
|
||||
apiReshare:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiReshare"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- OcConfigContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiSpacesDavOperation:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSpacesDavOperation"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- WebDavPropertiesContext:
|
||||
- FilesVersionsContext:
|
||||
- SharingNgContext:
|
||||
- SettingsContext:
|
||||
- OcConfigContext:
|
||||
|
||||
apiLocks:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiLocks"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- WebDavLockingContext:
|
||||
- PublicWebDavContext:
|
||||
- SharingNgContext:
|
||||
- OcConfigContext:
|
||||
|
||||
apiSharingNg1:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSharingNg1"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- SharingNgContext:
|
||||
- OcConfigContext:
|
||||
- SettingsContext:
|
||||
|
||||
apiSharingNg2:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSharingNg2"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- SharingNgContext:
|
||||
- OcConfigContext:
|
||||
- SettingsContext:
|
||||
|
||||
apiSettings:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSettings"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- GraphContext:
|
||||
- SettingsContext:
|
||||
|
||||
apiSharingNgShareInvitation:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSharingNgShareInvitation"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- SharingNgContext:
|
||||
|
||||
apiSharingNgLinkSharePermission:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSharingNgLinkSharePermission"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- SharingNgContext:
|
||||
- PublicWebDavContext:
|
||||
- OcConfigContext:
|
||||
|
||||
apiSharingNgLinkShareRoot:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiSharingNgLinkShareRoot"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- SharingNgContext:
|
||||
- PublicWebDavContext:
|
||||
- OcConfigContext:
|
||||
|
||||
apiOcm:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiOcm"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- OcmContext:
|
||||
- SharingNgContext:
|
||||
- SpacesContext:
|
||||
- OcConfigContext:
|
||||
- NotificationContext:
|
||||
- SettingsContext:
|
||||
- SpacesTUSContext:
|
||||
|
||||
apiActivities:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiActivities"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SharingNgContext:
|
||||
- GraphContext:
|
||||
- PublicWebDavContext:
|
||||
|
||||
apiCollaboration:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiCollaboration"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SharingNgContext:
|
||||
- CollaborationContext:
|
||||
- TrashbinContext:
|
||||
|
||||
apiServiceAvailability:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiServiceAvailability"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- OcConfigContext:
|
||||
|
||||
apiAuthApp:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiAuthApp"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- AuthAppContext:
|
||||
- CliContext:
|
||||
- OcConfigContext:
|
||||
|
||||
apiTenancy:
|
||||
paths:
|
||||
- "%paths.base%/../features/apiTenancy"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
|
||||
cliCommands:
|
||||
paths:
|
||||
- "%paths.base%/../features/cliCommands"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- CliContext:
|
||||
- OcConfigContext:
|
||||
- FilesVersionsContext:
|
||||
- SharingNgContext:
|
||||
- SearchContext:
|
||||
- TagContext:
|
||||
- TrashbinContext:
|
||||
- SpacesTUSContext:
|
||||
|
||||
collaborativePosix:
|
||||
paths:
|
||||
- "%paths.base%/../features/collaborativePosix"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- CliContext:
|
||||
- OcConfigContext:
|
||||
- SharingNgContext:
|
||||
- PublicWebDavContext:
|
||||
|
||||
coreApiMain:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiMain"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- ChecksumContext:
|
||||
- FilesVersionsContext:
|
||||
- TrashbinContext:
|
||||
- CapabilitiesContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiAuth:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiAuth"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SearchContext:
|
||||
- PublicWebDavContext:
|
||||
- WebDavPropertiesContext:
|
||||
- AuthContext:
|
||||
|
||||
coreApiCapabilities:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiCapabilities"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- CapabilitiesContext:
|
||||
|
||||
coreApiShareCreateSpecialToShares1:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiShareCreateSpecialToShares1"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- SettingsContext:
|
||||
|
||||
coreApiShareCreateSpecialToShares2:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiShareCreateSpecialToShares2"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- SettingsContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiSharees:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiSharees"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- ShareesContext:
|
||||
- OcConfigContext:
|
||||
|
||||
coreApiShareManagementToShares:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiShareManagementToShares"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SpacesContext:
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- FilesVersionsContext:
|
||||
- SettingsContext:
|
||||
- SharingNgContext:
|
||||
- OcConfigContext:
|
||||
|
||||
coreApiShareManagementBasicToShares:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiShareManagementBasicToShares"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- AuthContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiShareOperationsToShares1:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiShareOperationsToShares1"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- SettingsContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiShareOperationsToShares2:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiShareOperationsToShares2"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiSharePublicLink1:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiSharePublicLink1"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- SharingNgContext:
|
||||
- WebDavPropertiesContext:
|
||||
- OcConfigContext:
|
||||
|
||||
coreApiSharePublicLink2:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiSharePublicLink2"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- OcConfigContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiShareUpdateToShares:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiShareUpdateToShares"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiTrashbin:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiTrashbin"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiTrashbinRestore:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiTrashbinRestore"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- TrashbinContext:
|
||||
- WebDavPropertiesContext:
|
||||
|
||||
coreApiVersions:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiVersions"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- ChecksumContext:
|
||||
- FilesVersionsContext:
|
||||
- WebDavPropertiesContext:
|
||||
- TrashbinContext:
|
||||
- SharingNgContext:
|
||||
- OcConfigContext:
|
||||
|
||||
coreApiWebdavDelete:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavDelete"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SearchContext:
|
||||
- PublicWebDavContext:
|
||||
- WebDavPropertiesContext:
|
||||
- TrashbinContext:
|
||||
|
||||
coreApiWebdavLocks:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavLocks"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- WebDavLockingContext:
|
||||
- WebDavPropertiesContext:
|
||||
|
||||
coreApiWebdavLocks2:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavLocks2"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- WebDavLockingContext:
|
||||
- WebDavPropertiesContext:
|
||||
|
||||
coreApiWebdavMove:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavMove"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SharingNgContext:
|
||||
- WebDavPropertiesContext:
|
||||
- FilesVersionsContext:
|
||||
- TrashbinContext:
|
||||
|
||||
coreApiWebdavOperations:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavOperations"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SearchContext:
|
||||
- PublicWebDavContext:
|
||||
- SharingNgContext:
|
||||
- WebDavPropertiesContext:
|
||||
- TrashbinContext:
|
||||
- OcConfigContext:
|
||||
|
||||
coreApiWebdavPreviews:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavPreviews"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SharingNgContext:
|
||||
- WebDavPropertiesContext:
|
||||
- OcConfigContext:
|
||||
|
||||
coreApiWebdavProperties:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavProperties"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SharingNgContext:
|
||||
- WebDavPropertiesContext:
|
||||
- OcConfigContext:
|
||||
|
||||
coreApiWebdavUpload:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavUpload"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- WebDavPropertiesContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiWebdavUploadTUS:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavUploadTUS"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- PublicWebDavContext:
|
||||
- TUSContext:
|
||||
- FilesVersionsContext:
|
||||
- ChecksumContext:
|
||||
- SharingNgContext:
|
||||
|
||||
coreApiWebdavEtagPropagation1:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavEtagPropagation1"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- TrashbinContext:
|
||||
- PublicWebDavContext:
|
||||
- FilesVersionsContext:
|
||||
- SharingNgContext:
|
||||
- WebDavPropertiesContext:
|
||||
|
||||
coreApiWebdavEtagPropagation2:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavEtagPropagation2"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- TrashbinContext:
|
||||
- PublicWebDavContext:
|
||||
- FilesVersionsContext:
|
||||
- SharingNgContext:
|
||||
- WebDavPropertiesContext:
|
||||
- OcConfigContext:
|
||||
|
||||
coreApiWebdavCopyCreate:
|
||||
paths:
|
||||
- "%paths.base%/../features/coreApiWebdavCopyCreate"
|
||||
context: *common_ldap_suite_context
|
||||
contexts:
|
||||
- FeatureContext: *common_feature_context_params
|
||||
- SharingNgContext:
|
||||
- OcConfigContext:
|
||||
|
||||
extensions:
|
||||
rdx\behatvars\BehatVariablesExtension: ~
|
||||
|
||||
Cjm\Behat\StepThroughExtension: ~
|
||||
@@ -0,0 +1,9 @@
|
||||
dn: ou=TestUsers,dc=example,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: TestUsers
|
||||
|
||||
dn: ou=TestGroups,dc=example,dc=org
|
||||
objectclass: top
|
||||
objectclass: organizationalUnit
|
||||
ou: TestGroups
|
||||
@@ -0,0 +1,165 @@
|
||||
.ONESHELL:
|
||||
SHELL := bash
|
||||
|
||||
# define standard colors
|
||||
BLACK := $(shell tput -Txterm setaf 0)
|
||||
RED := $(shell tput -Txterm setaf 1)
|
||||
GREEN := $(shell tput -Txterm setaf 2)
|
||||
YELLOW := $(shell tput -Txterm setaf 3)
|
||||
BLUE := $(shell tput -Txterm setaf 4)
|
||||
PURPLE := $(shell tput -Txterm setaf 5)
|
||||
CYAN := $(shell tput -Txterm setaf 6)
|
||||
WHITE := $(shell tput -Txterm setaf 7)
|
||||
|
||||
RESET := $(shell tput -Txterm sgr0)
|
||||
|
||||
COMPOSE_FILE := src/qsfera-base.yml
|
||||
|
||||
# run tests with ocwrapper by default
|
||||
WITH_WRAPPER ?= true
|
||||
OC_WRAPPER := ../../ocwrapper/bin/ocwrapper
|
||||
|
||||
# enable tika for full text extraction
|
||||
ifeq ($(START_TIKA),true)
|
||||
COMPOSE_FILE := src/tika.yml:$(COMPOSE_FILE)
|
||||
export SEARCH_EXTRACTOR_TYPE := tika
|
||||
else
|
||||
export SEARCH_EXTRACTOR_TYPE := basic
|
||||
endif
|
||||
|
||||
# enable email server
|
||||
ifeq ($(START_EMAIL),true)
|
||||
COMPOSE_FILE := src/email.yml:$(COMPOSE_FILE)
|
||||
export OC_ADD_RUN_SERVICES := notifications
|
||||
endif
|
||||
|
||||
# enable antivirus
|
||||
ifeq ($(START_ANTIVIRUS),true)
|
||||
COMPOSE_FILE := src/antivirus.yml:$(COMPOSE_FILE)
|
||||
export OC_ADD_RUN_SERVICES := $(OC_ADD_RUN_SERVICES) antivirus
|
||||
export POSTPROCESSING_STEPS := virusscan
|
||||
endif
|
||||
|
||||
# enable wopi services
|
||||
ifeq ($(ENABLE_WOPI),true)
|
||||
COMPOSE_FILE := $(COMPOSE_FILE):src/wopi.yml
|
||||
endif
|
||||
|
||||
# default to posix
|
||||
STORAGE_DRIVER ?= posix
|
||||
ifeq ($(STORAGE_DRIVER),posix)
|
||||
# posix requires a additional driver config
|
||||
COMPOSE_FILE := $(COMPOSE_FILE):src/posix.yml
|
||||
else ifeq ($(STORAGE_DRIVER),decomposeds3)
|
||||
COMPOSE_FILE := src/ceph.yml:$(COMPOSE_FILE)
|
||||
endif
|
||||
|
||||
# use latest as default tag if OC_IMAGE is provided but no tag is set
|
||||
ifneq ($(strip $(OC_IMAGE)),)
|
||||
ifeq ($(strip $(OC_IMAGE_TAG)),)
|
||||
OC_IMAGE_TAG := latest
|
||||
endif
|
||||
endif
|
||||
|
||||
COMPOSE_PROJECT_NAME := qsfera-acceptance-tests
|
||||
|
||||
# Export variables for sub-make calls
|
||||
export COMPOSE_PROJECT_NAME
|
||||
export COMPOSE_FILE
|
||||
|
||||
export OC_IMAGE
|
||||
export OC_IMAGE_TAG
|
||||
|
||||
# test configurations
|
||||
export STORAGE_DRIVER
|
||||
export WITH_WRAPPER
|
||||
export BEHAT_SUITE
|
||||
export BEHAT_FEATURE
|
||||
export TEST_SERVER_URL
|
||||
export USE_BEARER_TOKEN
|
||||
|
||||
## make definition
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo -e "Test suites: ${CYAN}https://github.com/qsfera/server/tree/main/tests/acceptance/features${RESET}"
|
||||
@echo -e "Testing docs: ${CYAN}https://github.com/qsfera/server/tree/main/tests/README.md${RESET}"
|
||||
@echo
|
||||
@echo "Available commands (targets):"
|
||||
@echo -e " ${GREEN}run-api-tests\t\t${RESET}Build dev image, start services and run the tests."
|
||||
@echo -e " ${GREEN}start-services\t${RESET}Start service containers."
|
||||
@echo -e " ${GREEN}run-test-only\t\t${RESET}Run the tests only."
|
||||
@echo -e " ${GREEN}show-test-logs\t${RESET}Show the test logs."
|
||||
@echo -e " ${GREEN}ps\t\t\t${RESET}Show the running containers."
|
||||
@echo
|
||||
@echo -e " ${YELLOW}clean-dev-image\t${RESET}Delete the docker image built during acceptance tests."
|
||||
@echo -e " ${YELLOW}clean-containers\t${RESET}Delete the docker containers and volumes."
|
||||
@echo -e " ${YELLOW}clean-tests\t\t${RESET}Delete test dependencies and results."
|
||||
@echo -e " ${YELLOW}clean-all\t\t${RESET}Clean all resources: images, containers, volumes, test files."
|
||||
@echo
|
||||
@echo "Available environment variables:"
|
||||
@echo -e " ${PURPLE}OC_IMAGE\t\t${RESET}${CYAN}[image_repo]${RESET} КуСфера image to use. If provided, the dev image build is skipped."
|
||||
@echo -e " ${PURPLE}OC_IMAGE_TAG\t\t${RESET}${CYAN}[image_tag]${RESET} КуСфера image tag to use. If provided, the dev image build is skipped."
|
||||
@echo -e " ${PURPLE}WITH_WRAPPER\t\t${RESET}${CYAN}[true|false]${RESET} Start КуСфера server using ocwrapper. Default: ${YELLOW}true${RESET}"
|
||||
@echo -e " ${PURPLE}STORAGE_DRIVER\t${RESET}${CYAN}[posix|decomposed|decomposeds3]${RESET} Storage driver to use. Default: ${YELLOW}posix${RESET}"
|
||||
@echo -e " ${PURPLE}BEHAT_FEATURE\t\t${RESET}${RESET}${CYAN}[path]${RESET} Path to a feature file. Example: ${YELLOW}tests/acceptance/features/apiGraph/changeRole.feature${RESET}"
|
||||
@echo -e " ${PURPLE}BEHAT_SUITE\t\t${RESET}${RESET}${CYAN}[suite_name]${RESET} Test suite to run. Example: ${YELLOW}apiGraph${RESET}"
|
||||
@echo -e " ${PURPLE}TEST_SERVER_URL\t${RESET}${CYAN}[url]${RESET} URL of the КуСфера server to test against."
|
||||
@echo -e " ${PURPLE}USE_BEARER_TOKEN\t${RESET}${CYAN}[true|false]${RESET} Use a bearer token for authentication. Default: ${YELLOW}false${RESET}"
|
||||
@echo
|
||||
@echo -e "Example usage:"
|
||||
@echo -e " ${PURPLE}WITH_WRAPPER${RESET}=${YELLOW}false${RESET} \\"
|
||||
@echo -e " ${PURPLE}STORAGE_DRIVER${RESET}=${YELLOW}posix${RESET} \\"
|
||||
@echo -e " ${PURPLE}BEHAT_FEATURE${RESET}=${YELLOW}tests/acceptance/features/apiGraph/changeRole.feature${RESET} \\"
|
||||
@echo -e " make ${GREEN}run-api-tests${RESET}"
|
||||
|
||||
.PHONY: run-api-tests
|
||||
run-api-tests: $(OC_WRAPPER) build-dev-image clean-containers
|
||||
@echo "${BLUE}[INFO]${RESET} Compose project: ${YELLOW}$(COMPOSE_PROJECT_NAME)${RESET}"
|
||||
@echo "${BLUE}[INFO]${RESET} Compose file: ${YELLOW}$(COMPOSE_FILE)${RESET}"
|
||||
@echo "${BLUE}[INFO]${RESET} Using storage driver: ${YELLOW}$(STORAGE_DRIVER)${RESET}"
|
||||
# force use local server when using this command
|
||||
export TEST_SERVER_URL=https://qsfera-server:9200
|
||||
$(MAKE) --no-print-directory start-services
|
||||
$(MAKE) --no-print-directory run-test-only
|
||||
|
||||
.PHONY: start-services
|
||||
start-services: $(OC_WRAPPER) ## start services
|
||||
docker compose up -d --build --force-recreate
|
||||
|
||||
.PHONY: run-test-only
|
||||
run-test-only:
|
||||
docker compose -f src/acceptance.yml up
|
||||
|
||||
.PHONY: show-test-logs
|
||||
show-test-logs: ## show test logs
|
||||
docker compose logs --no-log-prefix -f acceptance-tests | less
|
||||
|
||||
.PHONY: ps
|
||||
ps: ## show running containers
|
||||
docker compose ps
|
||||
|
||||
$(OC_WRAPPER):
|
||||
@if [ "$(WITH_WRAPPER)" == "true" ]; then \
|
||||
$(MAKE) --no-print-directory -C ../../ocwrapper build \
|
||||
; fi;
|
||||
|
||||
.PHONY: build-dev-image
|
||||
build-dev-image:
|
||||
@if [ -z "$(OC_IMAGE)" ] && [ -z "$(OC_IMAGE_TAG)" ]; then \
|
||||
$(MAKE) --no-print-directory -C ../../../qsfera dev-docker \
|
||||
; fi;
|
||||
|
||||
.PHONY: clean-dev-image
|
||||
clean-dev-image: ## clean docker image built during acceptance tests
|
||||
@docker image rm qsfera/qsfera:dev || true
|
||||
|
||||
.PHONY: clean-containers
|
||||
clean-containers: ## clean docker containers created during acceptance tests
|
||||
docker compose down --remove-orphans -v
|
||||
|
||||
.PHONY: clean-tests
|
||||
clean-tests:
|
||||
@$(MAKE) --no-print-directory -C ../../../. clean-tests
|
||||
|
||||
.PHONY: clean
|
||||
clean-all: clean-containers clean-dev-image clean-tests ## clean all
|
||||
@@ -0,0 +1,22 @@
|
||||
services:
|
||||
acceptance-tests:
|
||||
image: opencloudeu/php-alpine-ci:8.4
|
||||
working_dir: /woodpecker/src/github.com/qsfera/server
|
||||
command: /bin/bash /test/run-tests.sh
|
||||
environment:
|
||||
OC_ROOT: /woodpecker/src/github.com/qsfera/server
|
||||
TEST_SERVER_URL: ${TEST_SERVER_URL:-https://qsfera-server:9200}
|
||||
OC_WRAPPER_URL: http://qsfera-server:5200
|
||||
STORAGE_DRIVER: ${STORAGE_DRIVER:-posix}
|
||||
BEHAT_SUITE: ${BEHAT_SUITE:-}
|
||||
BEHAT_FEATURE: ${BEHAT_FEATURE:-}
|
||||
USE_BEARER_TOKEN: ${USE_BEARER_TOKEN:-false}
|
||||
# email
|
||||
EMAIL_HOST: email
|
||||
EMAIL_PORT: 9000
|
||||
env_file:
|
||||
- ../../../../.woodpecker.env
|
||||
volumes:
|
||||
- ./run-tests.sh:/test/run-tests.sh
|
||||
- ../../../../:/woodpecker/src/github.com/qsfera/server
|
||||
- ../../../../vendor-bin/behat/composer.json:/tmp/vendor-bin/behat/composer.json
|
||||
@@ -0,0 +1,9 @@
|
||||
services:
|
||||
start_antivirus:
|
||||
image: dadarek/wait-for-dependencies
|
||||
depends_on:
|
||||
- clamav
|
||||
command: clamav:3310
|
||||
clamav:
|
||||
image: opencloudeu/clamav-ci:latest
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
start_ceph:
|
||||
image: dadarek/wait-for-dependencies
|
||||
depends_on:
|
||||
- ceph
|
||||
command: ceph:5000
|
||||
ceph:
|
||||
image: ceph/daemon
|
||||
command: demo
|
||||
environment:
|
||||
MON_IP: 127.0.0.1
|
||||
CEPH_PUBLIC_NETWORK: 0.0.0.0/0
|
||||
CEPH_DEMO_UID: test-user
|
||||
CEPH_DEMO_ACCESS_KEY: test
|
||||
CEPH_DEMO_SECRET_KEY: test
|
||||
CEPH_DEMO_BUCKET: test
|
||||
RGW_NAME: ceph
|
||||
@@ -0,0 +1,10 @@
|
||||
services:
|
||||
start_email:
|
||||
image: dadarek/wait-for-dependencies
|
||||
depends_on:
|
||||
- email
|
||||
command: email:9000
|
||||
email:
|
||||
image: inbucket/inbucket
|
||||
ports:
|
||||
- '9000:9000'
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
mkdir -p /var/www/onlyoffice/Data/certs
|
||||
cd /var/www/onlyoffice/Data/certs
|
||||
openssl req -x509 -newkey rsa:4096 -keyout onlyoffice.key -out onlyoffice.crt -sha256 -days 365 -batch -nodes
|
||||
chmod 400 /var/www/onlyoffice/Data/certs/onlyoffice.key
|
||||
|
||||
/app/ds/run-document-server.sh
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
services:
|
||||
qsfera-server:
|
||||
environment:
|
||||
# posix requires a shared cache store
|
||||
STORAGE_USERS_ID_CACHE_STORE: "nats-js-kv"
|
||||
STORAGE_USERS_POSIX_WATCH_FS: "true"
|
||||
@@ -0,0 +1,58 @@
|
||||
services:
|
||||
qsfera-server:
|
||||
image: ${OC_IMAGE:-qsfera/qsfera}:${OC_IMAGE_TAG:-dev}
|
||||
entrypoint: ["/bin/sh", "/usr/bin/serve-qsfera.sh"]
|
||||
user: root
|
||||
environment:
|
||||
WITH_WRAPPER: ${WITH_WRAPPER:-true}
|
||||
OC_URL: "https://qsfera-server:9200"
|
||||
STORAGE_USERS_DRIVER: ${STORAGE_DRIVER:-posix}
|
||||
STORAGE_USERS_POSIX_WATCH_FS: "true"
|
||||
STORAGE_USERS_DRIVER_LOCAL_ROOT: /srv/app/tmp/qsfera/local/root
|
||||
STORAGE_USERS_DRIVER_OC_ROOT: /srv/app/tmp/qsfera/storage/users
|
||||
STORAGE_SYSTEM_DRIVER_OC_ROOT: /srv/app/tmp/qsfera/storage/metadata
|
||||
SHARING_USER_JSON_FILE: /srv/app/tmp/qsfera/shares.json
|
||||
PROXY_ENABLE_BASIC_AUTH: "true"
|
||||
WEB_UI_CONFIG_FILE: /woodpecker/src/github.com/qsfera/server/tests/config/woodpecker/qsfera-config.json
|
||||
OC_INSECURE: "true"
|
||||
IDM_CREATE_DEMO_USERS: "true"
|
||||
IDM_ADMIN_PASSWORD: "admin"
|
||||
FRONTEND_SEARCH_MIN_LENGTH: "2"
|
||||
OC_ADD_RUN_SERVICES: ${OC_ADD_RUN_SERVICES:-}
|
||||
PROXY_HTTP_ADDR: "0.0.0.0:9200"
|
||||
|
||||
# decomposeds3 specific settings
|
||||
STORAGE_USERS_DECOMPOSEDS3_ENDPOINT: http://ceph:8080
|
||||
STORAGE_USERS_DECOMPOSEDS3_REGION: default
|
||||
STORAGE_USERS_DECOMPOSEDS3_ACCESS_KEY: test
|
||||
STORAGE_USERS_DECOMPOSEDS3_SECRET_KEY: test
|
||||
STORAGE_USERS_DECOMPOSEDS3_BUCKET: test
|
||||
# email
|
||||
NOTIFICATIONS_SMTP_HOST: email
|
||||
NOTIFICATIONS_SMTP_PORT: 2500
|
||||
NOTIFICATIONS_SMTP_INSECURE: "true"
|
||||
NOTIFICATIONS_SMTP_SENDER: "КуСфера <noreply@example.com>"
|
||||
NOTIFICATIONS_DEBUG_ADDR: 0.0.0.0:9174
|
||||
|
||||
# antivirus
|
||||
ANTIVIRUS_SCANNER_TYPE: "clamav"
|
||||
ANTIVIRUS_CLAMAV_SOCKET: tcp://clamav:3310
|
||||
|
||||
# postprocessing step
|
||||
POSTPROCESSING_STEPS: ${POSTPROCESSING_STEPS:-}
|
||||
|
||||
# tika
|
||||
SEARCH_EXTRACTOR_TYPE: ${SEARCH_EXTRACTOR_TYPE:-basic}
|
||||
SEARCH_EXTRACTOR_TIKA_TIKA_URL: "http://tika:9998"
|
||||
SEARCH_EXTRACTOR_CS3SOURCE_INSECURE: "true"
|
||||
|
||||
# fonts map for txt thumbnails (including unicode support)
|
||||
THUMBNAILS_TXT_FONTMAP_FILE: "/woodpecker/src/github.com/qsfera/server/tests/config/drone/fontsMap.json"
|
||||
ports:
|
||||
- "9200:9200"
|
||||
- "5200:5200" ## ocwrapper
|
||||
- "9174:9174" ## notifications debug
|
||||
volumes:
|
||||
- ../../../config:/woodpecker/src/github.com/qsfera/server/tests/config
|
||||
- ../../../ocwrapper/bin/ocwrapper:/usr/bin/ocwrapper
|
||||
- ./serve-qsfera.sh:/usr/bin/serve-qsfera.sh
|
||||
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir -p "${OC_ROOT}/vendor-bin/behat"
|
||||
if [ ! -f "${OC_ROOT}/vendor-bin/behat/composer.json" ]; then
|
||||
cp /tmp/vendor-bin/behat/composer.json "${OC_ROOT}/vendor-bin/behat/composer.json"
|
||||
fi
|
||||
|
||||
git config --global advice.detachedHead false
|
||||
|
||||
## CONFIGURE TEST
|
||||
BEHAT_FILTER_TAGS='~@skip'
|
||||
EXPECTED_FAILURES_FILE=''
|
||||
|
||||
if [ "$STORAGE_DRIVER" = "posix" ]; then
|
||||
BEHAT_FILTER_TAGS+='&&~@skipOnQsfera-posix-Storage'
|
||||
EXPECTED_FAILURES_FILE="${OC_ROOT}/tests/acceptance/expected-failures-posix-storage.md"
|
||||
elif [ "$STORAGE_DRIVER" = "decomposed" ]; then
|
||||
BEHAT_FILTER_TAGS+='&&~@skipOnQsfera-decomposed-Storage'
|
||||
EXPECTED_FAILURES_FILE="${OC_ROOT}/tests/acceptance/expected-failures-decomposed-storage.md"
|
||||
fi
|
||||
|
||||
export BEHAT_FILTER_TAGS
|
||||
export EXPECTED_FAILURES_FILE
|
||||
|
||||
if [ -n "$BEHAT_FEATURE" ]; then
|
||||
export BEHAT_FEATURE
|
||||
echo "[INFO] Running feature: $BEHAT_FEATURE"
|
||||
# allow running without filters if its a feature
|
||||
unset BEHAT_FILTER_TAGS
|
||||
unset BEHAT_SUITE
|
||||
unset EXPECTED_FAILURES_FILE
|
||||
elif [ -n "$BEHAT_SUITE" ]; then
|
||||
export BEHAT_SUITE
|
||||
echo "[INFO] Running suite: $BEHAT_SUITE"
|
||||
unset BEHAT_FEATURE
|
||||
fi
|
||||
|
||||
## RUN TEST
|
||||
sleep 10
|
||||
make -C "$OC_ROOT" test-acceptance-api
|
||||
|
||||
chmod -R 777 "${OC_ROOT}/vendor-bin/"*"/vendor" "${OC_ROOT}/vendor-bin/"*"/composer.lock" "${OC_ROOT}/tests/acceptance/output" 2>/dev/null || true
|
||||
@@ -0,0 +1,45 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -xe
|
||||
|
||||
if [ -z "$TEST_GROUP" ]; then
|
||||
echo "TEST_GROUP not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Waiting for collaboration WOPI endpoint..."
|
||||
|
||||
until curl -s http://collaboration:9304 >/dev/null; do
|
||||
echo "Waiting for collaboration WOPI endpoint..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Collaboration is up"
|
||||
|
||||
if [ -z "$OC_URL" ]; then
|
||||
OC_URL="https://qsfera-server:9200"
|
||||
fi
|
||||
|
||||
curl -vk -X DELETE "$OC_URL/remote.php/webdav/test.wopitest" -u admin:admin
|
||||
curl -vk -X PUT "$OC_URL/remote.php/webdav/test.wopitest" -u admin:admin -D headers.txt
|
||||
cat headers.txt
|
||||
FILE_ID="$(cat headers.txt | sed -n -e 's/^.*oc-fileid: //Ip')"
|
||||
export FILE_ID
|
||||
URL="$OC_URL/app/open?app_name=FakeOffice&file_id=$FILE_ID"
|
||||
URL="$(echo "$URL" | tr -d '[:cntrl:]')"
|
||||
export URL
|
||||
curl -vk -X POST "$URL" -u admin:admin > open.json
|
||||
cat open.json
|
||||
cat open.json | jq .form_parameters.access_token | tr -d '"' > accesstoken
|
||||
cat open.json | jq .form_parameters.access_token_ttl | tr -d '"' > accesstokenttl
|
||||
WOPI_FILE_ID="$(cat open.json | jq .app_url | sed -n -e 's/^.*files%2F//p' | tr -d '"')"
|
||||
echo "http://collaboration:9300/wopi/files/$WOPI_FILE_ID" > wopisrc
|
||||
|
||||
WOPI_TOKEN=$(cat accesstoken)
|
||||
export WOPI_TOKEN
|
||||
WOPI_TTL=$(cat accesstokenttl)
|
||||
export WOPI_TTL
|
||||
WOPI_SRC=$(cat wopisrc)
|
||||
export WOPI_SRC
|
||||
|
||||
/app/Microsoft.Office.WopiValidator -s -t "$WOPI_TOKEN" -w "$WOPI_SRC" -l "$WOPI_TTL" --testgroup $TEST_GROUP
|
||||
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
# init КуСфера
|
||||
qsfera init
|
||||
|
||||
if [ "$WITH_WRAPPER" = "true" ]; then
|
||||
ocwrapper serve --bin=qsfera
|
||||
else
|
||||
qsfera server
|
||||
fi
|
||||
@@ -0,0 +1,8 @@
|
||||
services:
|
||||
tika-service:
|
||||
image: dadarek/wait-for-dependencies
|
||||
depends_on:
|
||||
- tika
|
||||
command: tika:9998
|
||||
tika:
|
||||
image: apache/tika:3.2.3.0-full
|
||||
@@ -0,0 +1,86 @@
|
||||
services:
|
||||
fakeoffice:
|
||||
image: owncloudci/alpine:latest
|
||||
entrypoint: /bin/sh
|
||||
command:
|
||||
[
|
||||
"-c",
|
||||
"while true; do echo -e \"HTTP/1.1 200 OK\n\n$(cat /hosting-discovery.xml)\" | nc -l -k -p 8080; done",
|
||||
]
|
||||
ports:
|
||||
- 8080:8080
|
||||
extra_hosts:
|
||||
- qsfera.local:${DOCKER_HOST:-host-gateway}
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080"]
|
||||
volumes:
|
||||
- ./../../../config/woodpecker/hosting-discovery.xml:/hosting-discovery.xml
|
||||
|
||||
qsfera:
|
||||
image: qsfera/qsfera:dev
|
||||
container_name: qsfera-server
|
||||
ports:
|
||||
- 9200:9200
|
||||
entrypoint: /bin/sh
|
||||
command: ["-c", "qsfera init || true; sleep 10; qsfera server"]
|
||||
environment:
|
||||
OC_URL: https://qsfera-server:9200
|
||||
OC_CONFIG_DIR: /etc/qsfera
|
||||
STORAGE_USERS_DRIVER: posix
|
||||
PROXY_ENABLE_BASIC_AUTH: true
|
||||
OC_LOG_LEVEL: error
|
||||
OC_LOG_COLOR: false
|
||||
OC_INSECURE: true
|
||||
IDM_ADMIN_PASSWORD: admin
|
||||
GATEWAY_GRPC_ADDR: 0.0.0.0:9142
|
||||
NATS_NATS_HOST: 0.0.0.0
|
||||
NATS_NATS_PORT: 9233
|
||||
volumes:
|
||||
- config:/etc/qsfera
|
||||
depends_on:
|
||||
fakeoffice:
|
||||
condition: service_healthy
|
||||
|
||||
collaboration:
|
||||
image: qsfera/qsfera:dev
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 9300:9300
|
||||
entrypoint:
|
||||
- /bin/sh
|
||||
command: ["-c", "qsfera collaboration server"]
|
||||
environment:
|
||||
OC_CONFIG_DIR: /etc/qsfera
|
||||
MICRO_REGISTRY: nats-js-kv
|
||||
MICRO_REGISTRY_ADDRESS: qsfera:9233
|
||||
COLLABORATION_LOG_LEVEL: info
|
||||
COLLABORATION_GRPC_ADDR: 0.0.0.0:9301
|
||||
COLLABORATION_HTTP_ADDR: 0.0.0.0:9300
|
||||
COLLABORATION_DEBUG_ADDR: 0.0.0.0:9304
|
||||
COLLABORATION_APP_PROOF_DISABLE: true
|
||||
COLLABORATION_APP_INSECURE: true
|
||||
COLLABORATION_CS3API_DATAGATEWAY_INSECURE: true
|
||||
COLLABORATION_WOPI_SECRET: some-wopi-secret
|
||||
COLLABORATION_SERVICE_NAME: collaboration-fakeoffice
|
||||
COLLABORATION_APP_NAME: FakeOffice
|
||||
COLLABORATION_APP_PRODUCT: Microsoft
|
||||
COLLABORATION_APP_ADDR: http://fakeoffice:8080
|
||||
COLLABORATION_WOPI_SRC: http://collaboration:9300
|
||||
volumes:
|
||||
- config:/etc/qsfera
|
||||
depends_on:
|
||||
- qsfera
|
||||
|
||||
wopi-validator:
|
||||
image: ${WOPI_VALIDATOR_IMAGE:-opencloudeu/wopi-validator-ci}
|
||||
volumes:
|
||||
- ./run-wopi-validator.sh:/app/run-wopi-validator.sh
|
||||
environment:
|
||||
TEST_GROUP: ${TEST_GROUP:-PutRelativeFile}
|
||||
entrypoint: /app/run-wopi-validator.sh
|
||||
depends_on:
|
||||
- collaboration
|
||||
restart: "on-failure"
|
||||
|
||||
volumes:
|
||||
config:
|
||||
@@ -0,0 +1,105 @@
|
||||
x-common_config: &common_config
|
||||
image: qsfera/qsfera:dev
|
||||
restart: unless-stopped
|
||||
entrypoint: /bin/sh
|
||||
command: ["-c", "qsfera collaboration server"]
|
||||
user: root
|
||||
|
||||
x-common_env: &common_env
|
||||
OC_CONFIG_DIR: /etc/qsfera
|
||||
MICRO_REGISTRY: nats-js-kv
|
||||
MICRO_REGISTRY_ADDRESS: qsfera-server:9233
|
||||
COLLABORATION_LOG_LEVEL: info
|
||||
COLLABORATION_GRPC_ADDR: 0.0.0.0:9301
|
||||
COLLABORATION_HTTP_ADDR: 0.0.0.0:9300
|
||||
COLLABORATION_DEBUG_ADDR: 0.0.0.0:9304
|
||||
COLLABORATION_APP_PROOF_DISABLE: true
|
||||
COLLABORATION_APP_INSECURE: true
|
||||
COLLABORATION_CS3API_DATAGATEWAY_INSECURE: true
|
||||
COLLABORATION_WOPI_SECRET: some-wopi-secret
|
||||
|
||||
x-config_volume: &config_volume
|
||||
- config:/etc/qsfera
|
||||
|
||||
x-depends_on: &depends_on
|
||||
- qsfera-server
|
||||
|
||||
services:
|
||||
qsfera-server:
|
||||
environment:
|
||||
OC_CONFIG_DIR: /etc/qsfera
|
||||
GATEWAY_GRPC_ADDR: 0.0.0.0:9142
|
||||
NATS_NATS_HOST: 0.0.0.0
|
||||
NATS_NATS_PORT: 9233
|
||||
volumes: *config_volume
|
||||
|
||||
fakeoffice:
|
||||
image: alpine:latest
|
||||
entrypoint: /bin/sh
|
||||
command:
|
||||
[
|
||||
"-c",
|
||||
"while true; do echo -e \"HTTP/1.1 200 OK\n\n$(cat /fakeoffice-discovery.xml)\" | nc -l -k -p 8080; done",
|
||||
]
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://fakeoffice:8080"]
|
||||
volumes:
|
||||
- ./../../../config/woodpecker/hosting-discovery.xml:/fakeoffice-discovery.xml
|
||||
|
||||
collabora:
|
||||
image: collabora/code:24.04.5.1.1
|
||||
environment:
|
||||
DONT_GEN_SSL_CERT: set
|
||||
extra_params: --o:ssl.enable=true --o:ssl.termination=true --o:welcome.enable=false --o:net.frame_ancestors=https://qsfera-server:9200
|
||||
entrypoint: /bin/sh
|
||||
command: ["-c", "coolconfig generate-proof-key; /start-collabora-online.sh"]
|
||||
|
||||
onlyoffice:
|
||||
image: onlyoffice/documentserver:7.5.1
|
||||
environment:
|
||||
WOPI_ENABLED: true
|
||||
USE_UNAUTHORIZED_STORAGE: true
|
||||
entrypoint: bash /entrypoint.sh
|
||||
volumes:
|
||||
- ./onlyoffice-entrypoint.sh:/entrypoint.sh
|
||||
|
||||
collaboration-fakeoffice:
|
||||
<<: *common_config
|
||||
environment:
|
||||
<<: *common_env
|
||||
COLLABORATION_SERVICE_NAME: collaboration-fakeoffice
|
||||
COLLABORATION_APP_NAME: FakeOffice
|
||||
COLLABORATION_APP_PRODUCT: Microsoft
|
||||
COLLABORATION_APP_ADDR: http://fakeoffice:8080
|
||||
COLLABORATION_WOPI_SRC: http://collaboration-fakeoffice:9300
|
||||
volumes: *config_volume
|
||||
depends_on: *depends_on
|
||||
|
||||
collaboration-collabora:
|
||||
<<: *common_config
|
||||
environment:
|
||||
<<: *common_env
|
||||
COLLABORATION_SERVICE_NAME: collaboration-collabora
|
||||
COLLABORATION_APP_NAME: Collabora
|
||||
COLLABORATION_APP_PRODUCT: Collabora
|
||||
COLLABORATION_APP_ADDR: https://collabora:9980
|
||||
COLLABORATION_APP_ICON: https://collabora:9980/favicon.ico
|
||||
COLLABORATION_WOPI_SRC: http://collaboration-collabora:9300
|
||||
volumes: *config_volume
|
||||
depends_on: *depends_on
|
||||
|
||||
collaboration-onlyoffice:
|
||||
<<: *common_config
|
||||
environment:
|
||||
<<: *common_env
|
||||
COLLABORATION_SERVICE_NAME: collaboration-onlyoffice
|
||||
COLLABORATION_APP_NAME: OnlyOffice
|
||||
COLLABORATION_APP_PRODUCT: OnlyOffice
|
||||
COLLABORATION_APP_ADDR: https://onlyoffice
|
||||
COLLABORATION_APP_ICON: https://onlyoffice/web-apps/apps/documenteditor/main/resources/img/favicon.ico
|
||||
COLLABORATION_WOPI_SRC: http://collaboration-onlyoffice:9300
|
||||
volumes: *config_volume
|
||||
depends_on: *depends_on
|
||||
|
||||
volumes:
|
||||
config:
|
||||
@@ -0,0 +1,357 @@
|
||||
## Scenarios from КуСфера API tests that are expected to fail with decomposed storage
|
||||
|
||||
#### [Downloading the archive of the resource (files | folder) using resource path is not possible](https://github.com/owncloud/ocis/issues/4637)
|
||||
|
||||
- [apiArchiver/downloadByPath.feature:25](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L25)
|
||||
- [apiArchiver/downloadByPath.feature:26](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L26)
|
||||
- [apiArchiver/downloadByPath.feature:43](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L43)
|
||||
- [apiArchiver/downloadByPath.feature:44](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L44)
|
||||
- [apiArchiver/downloadByPath.feature:47](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L47)
|
||||
- [apiArchiver/downloadByPath.feature:73](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L73)
|
||||
- [apiArchiver/downloadByPath.feature:171](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L171)
|
||||
- [apiArchiver/downloadByPath.feature:172](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L172)
|
||||
|
||||
#### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755)
|
||||
|
||||
- [apiSpacesShares/shareUploadTUS.feature:283](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareUploadTUS.feature#L283)
|
||||
- [apiSpacesShares/shareUploadTUS.feature:303](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareUploadTUS.feature#L303)
|
||||
- [apiSpacesShares/shareUploadTUS.feature:384](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareUploadTUS.feature#L384)
|
||||
|
||||
#### [Settings service user can list other peoples assignments](https://github.com/owncloud/ocis/issues/5032)
|
||||
|
||||
- [apiGraph/getAssignedRole.feature:31](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraph/getAssignedRole.feature#L31)
|
||||
- [apiGraph/getAssignedRole.feature:32](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraph/getAssignedRole.feature#L32)
|
||||
- [apiGraph/getAssignedRole.feature:33](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraph/getAssignedRole.feature#L33)
|
||||
|
||||
#### [A User can get information of another user with Graph API](https://github.com/owncloud/ocis/issues/5125)
|
||||
|
||||
- [apiGraphUserGroup/getUser.feature:84](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L84)
|
||||
- [apiGraphUserGroup/getUser.feature:85](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L85)
|
||||
- [apiGraphUserGroup/getUser.feature:86](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L86)
|
||||
- [apiGraphUserGroup/getUser.feature:628](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L628)
|
||||
- [apiGraphUserGroup/getUser.feature:629](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L629)
|
||||
- [apiGraphUserGroup/getUser.feature:630](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L630)
|
||||
- [apiGraphUserGroup/getUser.feature:645](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L645)
|
||||
- [apiGraphUserGroup/getUser.feature:646](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L646)
|
||||
- [apiGraphUserGroup/getUser.feature:647](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L647)
|
||||
|
||||
#### [Normal user can get expanded members information of a group](https://github.com/owncloud/ocis/issues/5604)
|
||||
|
||||
- [apiGraphUserGroup/getGroup.feature:399](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L399)
|
||||
- [apiGraphUserGroup/getGroup.feature:400](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L400)
|
||||
- [apiGraphUserGroup/getGroup.feature:401](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L401)
|
||||
- [apiGraphUserGroup/getGroup.feature:460](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L460)
|
||||
- [apiGraphUserGroup/getGroup.feature:461](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L461)
|
||||
- [apiGraphUserGroup/getGroup.feature:462](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L462)
|
||||
- [apiGraphUserGroup/getGroup.feature:508](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L508)
|
||||
- [apiGraphUserGroup/getGroup.feature:509](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L509)
|
||||
- [apiGraphUserGroup/getGroup.feature:510](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L510)
|
||||
|
||||
#### [Same users can be added in a group multiple time](https://github.com/owncloud/ocis/issues/5702)
|
||||
|
||||
- [apiGraphUserGroup/addUserToGroup.feature:295](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/addUserToGroup.feature#L295)
|
||||
|
||||
#### [Users are added in a group with wrong host in host-part of user](https://github.com/owncloud/ocis/issues/5871)
|
||||
|
||||
- [apiGraphUserGroup/addUserToGroup.feature:379](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/addUserToGroup.feature#L379)
|
||||
- [apiGraphUserGroup/addUserToGroup.feature:393](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/addUserToGroup.feature#L393)
|
||||
|
||||
#### [Adding the same user as multiple members in a single request results in listing the same user twice in the group](https://github.com/owncloud/ocis/issues/5855)
|
||||
|
||||
- [apiGraphUserGroup/addUserToGroup.feature:430](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/addUserToGroup.feature#L430)
|
||||
|
||||
#### [Shared file locking is not possible using different path](https://github.com/owncloud/ocis/issues/7599)
|
||||
|
||||
- [apiLocks/lockFiles.feature:185](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L185)
|
||||
- [apiLocks/lockFiles.feature:186](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L186)
|
||||
- [apiLocks/lockFiles.feature:187](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L187)
|
||||
- [apiLocks/lockFiles.feature:309](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L309)
|
||||
- [apiLocks/lockFiles.feature:310](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L310)
|
||||
- [apiLocks/lockFiles.feature:311](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L311)
|
||||
- [apiLocks/lockFiles.feature:364](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L364)
|
||||
- [apiLocks/lockFiles.feature:365](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L365)
|
||||
- [apiLocks/lockFiles.feature:366](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L366)
|
||||
- [apiLocks/lockFiles.feature:367](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L367)
|
||||
- [apiLocks/lockFiles.feature:368](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L368)
|
||||
- [apiLocks/lockFiles.feature:369](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L369)
|
||||
- [apiLocks/lockFiles.feature:399](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L399)
|
||||
- [apiLocks/lockFiles.feature:400](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L400)
|
||||
- [apiLocks/lockFiles.feature:401](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L401)
|
||||
- [apiLocks/lockFiles.feature:402](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L402)
|
||||
- [apiLocks/lockFiles.feature:403](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L403)
|
||||
- [apiLocks/lockFiles.feature:404](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L404)
|
||||
- [apiLocks/unlockFiles.feature:62](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L62)
|
||||
- [apiLocks/unlockFiles.feature:63](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L63)
|
||||
- [apiLocks/unlockFiles.feature:64](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L64)
|
||||
- [apiLocks/unlockFiles.feature:171](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L171)
|
||||
- [apiLocks/unlockFiles.feature:172](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L172)
|
||||
- [apiLocks/unlockFiles.feature:173](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L173)
|
||||
- [apiLocks/unlockFiles.feature:174](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L174)
|
||||
- [apiLocks/unlockFiles.feature:175](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L175)
|
||||
- [apiLocks/unlockFiles.feature:176](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L176)
|
||||
- [apiLocks/unlockFiles.feature:199](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L199)
|
||||
- [apiLocks/unlockFiles.feature:200](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L200)
|
||||
- [apiLocks/unlockFiles.feature:201](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L201)
|
||||
- [apiLocks/unlockFiles.feature:202](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L202)
|
||||
- [apiLocks/unlockFiles.feature:203](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L203)
|
||||
- [apiLocks/unlockFiles.feature:204](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L204)
|
||||
- [apiLocks/unlockFiles.feature:227](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L227)
|
||||
- [apiLocks/unlockFiles.feature:228](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L228)
|
||||
- [apiLocks/unlockFiles.feature:229](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L229)
|
||||
- [apiLocks/unlockFiles.feature:230](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L230)
|
||||
- [apiLocks/unlockFiles.feature:231](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L231)
|
||||
- [apiLocks/unlockFiles.feature:232](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L232)
|
||||
|
||||
#### [Folders can be locked and locking works partially](https://github.com/owncloud/ocis/issues/7641)
|
||||
|
||||
- [apiLocks/lockFiles.feature:443](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L443)
|
||||
- [apiLocks/lockFiles.feature:444](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L444)
|
||||
- [apiLocks/lockFiles.feature:445](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L445)
|
||||
- [apiLocks/lockFiles.feature:446](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L446)
|
||||
- [apiLocks/lockFiles.feature:447](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L447)
|
||||
- [apiLocks/lockFiles.feature:448](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L448)
|
||||
- [apiLocks/lockFiles.feature:417](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L417)
|
||||
- [apiLocks/lockFiles.feature:418](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L418)
|
||||
- [apiLocks/lockFiles.feature:419](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L419)
|
||||
- [apiLocks/lockFiles.feature:420](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L420)
|
||||
- [apiLocks/lockFiles.feature:421](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L421)
|
||||
- [apiLocks/lockFiles.feature:422](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L422)
|
||||
|
||||
#### [Anonymous users can unlock a file shared to them through a public link if they get the lock token](https://github.com/owncloud/ocis/issues/7761)
|
||||
|
||||
- [apiLocks/unlockFiles.feature:42](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L42)
|
||||
- [apiLocks/unlockFiles.feature:43](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L43)
|
||||
- [apiLocks/unlockFiles.feature:44](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L44)
|
||||
- [apiLocks/unlockFiles.feature:45](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L45)
|
||||
- [apiLocks/unlockFiles.feature:46](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L46)
|
||||
- [apiLocks/unlockFiles.feature:47](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L47)
|
||||
|
||||
#### [Trying to unlock a shared file with sharer's lock token gives 500](https://github.com/owncloud/ocis/issues/7767)
|
||||
|
||||
- [apiLocks/unlockFiles.feature:115](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L115)
|
||||
- [apiLocks/unlockFiles.feature:116](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L116)
|
||||
- [apiLocks/unlockFiles.feature:117](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L117)
|
||||
- [apiLocks/unlockFiles.feature:118](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L118)
|
||||
- [apiLocks/unlockFiles.feature:119](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L119)
|
||||
- [apiLocks/unlockFiles.feature:120](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L120)
|
||||
- [apiLocks/unlockFiles.feature:143](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L143)
|
||||
- [apiLocks/unlockFiles.feature:144](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L144)
|
||||
- [apiLocks/unlockFiles.feature:145](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L145)
|
||||
- [apiLocks/unlockFiles.feature:146](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L146)
|
||||
- [apiLocks/unlockFiles.feature:147](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L147)
|
||||
- [apiLocks/unlockFiles.feature:148](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L148)
|
||||
|
||||
#### [Anonymous user trying lock a file shared to them through a public link gives 405](https://github.com/owncloud/ocis/issues/7790)
|
||||
|
||||
- [apiLocks/lockFiles.feature:532](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L532)
|
||||
- [apiLocks/lockFiles.feature:533](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L533)
|
||||
- [apiLocks/lockFiles.feature:534](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L534)
|
||||
- [apiLocks/lockFiles.feature:535](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L535)
|
||||
- [apiLocks/lockFiles.feature:554](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L554)
|
||||
- [apiLocks/lockFiles.feature:555](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L555)
|
||||
- [apiLocks/lockFiles.feature:556](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L556)
|
||||
- [apiLocks/lockFiles.feature:557](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L557)
|
||||
|
||||
#### [sharee (editor role) MOVE a file by file-id into shared sub-folder returns 502](https://github.com/owncloud/ocis/issues/7617)
|
||||
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:368](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L368)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:591](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L591)
|
||||
|
||||
#### [MOVE a file into same folder with same name returns 404 instead of 403](https://github.com/owncloud/ocis/issues/1976)
|
||||
|
||||
- [apiSpacesShares/moveSpaces.feature:69](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/moveSpaces.feature#L69)
|
||||
- [apiSpacesShares/moveSpaces.feature:70](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/moveSpaces.feature#L70)
|
||||
- [apiSpacesShares/moveSpaces.feature:416](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/moveSpaces.feature#L416)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:61](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L61)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:174](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L174)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:175](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L175)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:176](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L176)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:393](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L393)
|
||||
|
||||
#### [OCM. admin cannot get federated users if he hasn't connection with them ](https://github.com/owncloud/ocis/issues/9829)
|
||||
|
||||
- [apiOcm/searchFederationUsers.feature:429](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/searchFederationUsers.feature#L429)
|
||||
- [apiOcm/searchFederationUsers.feature:601](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/searchFederationUsers.feature#L601)
|
||||
|
||||
#### [OCM. federated connection is not dropped when one of the users deletes the connection](https://github.com/owncloud/ocis/issues/10216)
|
||||
|
||||
- [apiOcm/deleteFederatedConnections.feature:21](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/deleteFederatedConnections.feature#L21)
|
||||
- [apiOcm/deleteFederatedConnections.feature:67](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/deleteFederatedConnections.feature#L67)
|
||||
|
||||
#### [OCM. server crash after deleting share for ocm user](https://github.com/owncloud/ocis/issues/10213)
|
||||
|
||||
- [apiOcm/deleteFederatedConnections.feature:102](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/deleteFederatedConnections.feature#L102)
|
||||
|
||||
#### [Shares Jail PROPFIND returns different File IDs for the same item](https://github.com/owncloud/ocis/issues/9933)
|
||||
|
||||
- [apiSharingNg1/propfindShares.feature:149](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNg1/propfindShares.feature#L149)
|
||||
|
||||
#### [Readiness check for some services returns 500 status code](https://github.com/owncloud/ocis/issues/10661)
|
||||
|
||||
- [apiServiceAvailability/serviceAvailabilityCheck.feature:123](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L123)
|
||||
|
||||
#### [Skip tests for different languages](https://github.com/qsfera/server/issues/183)
|
||||
|
||||
- [apiActivities/activities.feature:2598](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiActivities/activities.feature#L2598)
|
||||
|
||||
#### [Missing properties in REPORT response](https://github.com/owncloud/ocis/issues/9780), [d:getetag property has empty value in REPORT response](https://github.com/owncloud/ocis/issues/9783)
|
||||
|
||||
- [apiSearch1/search.feature:437](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L437)
|
||||
- [apiSearch1/search.feature:438](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L438)
|
||||
- [apiSearch1/search.feature:439](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L439)
|
||||
- [apiSearch1/search.feature:465](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L465)
|
||||
- [apiSearch1/search.feature:466](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L466)
|
||||
- [apiSearch1/search.feature:467](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L467)
|
||||
|
||||
## Scenarios from core API tests that are expected to fail with decomposed storage
|
||||
|
||||
### File
|
||||
|
||||
Basic file management like up and download, move, copy, properties, trash, versions and chunking.
|
||||
|
||||
#### [Custom dav properties with namespaces are rendered incorrectly](https://github.com/owncloud/ocis/issues/2140)
|
||||
|
||||
_ocdav: double-check the webdav property parsing when custom namespaces are used_
|
||||
|
||||
- [coreApiWebdavProperties/setFileProperties.feature:128](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L128)
|
||||
- [coreApiWebdavProperties/setFileProperties.feature:129](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L129)
|
||||
- [coreApiWebdavProperties/setFileProperties.feature:130](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L130)
|
||||
|
||||
### Sync
|
||||
|
||||
Synchronization features like etag propagation, setting mtime and locking files
|
||||
|
||||
#### [Uploading an old method chunked file with checksum should fail using new DAV path](https://github.com/owncloud/ocis/issues/2323)
|
||||
|
||||
- [coreApiMain/checksums.feature:233](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiMain/checksums.feature#L233)
|
||||
- [coreApiMain/checksums.feature:234](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiMain/checksums.feature#L234)
|
||||
- [coreApiMain/checksums.feature:235](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiMain/checksums.feature#L235)
|
||||
|
||||
### Share
|
||||
|
||||
#### [d:quota-available-bytes in dprop of PROPFIND give wrong response value](https://github.com/owncloud/ocis/issues/8197)
|
||||
|
||||
- [coreApiWebdavProperties/getQuota.feature:57](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L57)
|
||||
- [coreApiWebdavProperties/getQuota.feature:58](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L58)
|
||||
- [coreApiWebdavProperties/getQuota.feature:59](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L59)
|
||||
- [coreApiWebdavProperties/getQuota.feature:73](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L73)
|
||||
- [coreApiWebdavProperties/getQuota.feature:74](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L74)
|
||||
- [coreApiWebdavProperties/getQuota.feature:75](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L75)
|
||||
|
||||
#### [deleting a file inside a received shared folder is moved to the trash-bin of the sharer not the receiver](https://github.com/owncloud/ocis/issues/1124)
|
||||
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:54](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L54)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:55](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L55)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:56](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L56)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:83](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L83)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:84](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L84)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:85](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L85)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:142](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L142)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:143](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L143)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:144](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L144)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:202](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L202)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:203](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L203)
|
||||
|
||||
### Other
|
||||
|
||||
API, search, favorites, config, capabilities, not existing endpoints, CORS and others
|
||||
|
||||
#### [sending MKCOL requests to another or non-existing user's webDav endpoints as normal user should return 404](https://github.com/owncloud/ocis/issues/5049)
|
||||
|
||||
_ocdav: api compatibility, return correct status code_
|
||||
|
||||
- [coreApiAuth/webDavMKCOLAuth.feature:42](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavMKCOLAuth.feature#L42)
|
||||
- [coreApiAuth/webDavMKCOLAuth.feature:53](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavMKCOLAuth.feature#L53)
|
||||
|
||||
#### [trying to lock file of another user gives http 500](https://github.com/owncloud/ocis/issues/2176)
|
||||
|
||||
- [coreApiAuth/webDavLOCKAuth.feature:46](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavLOCKAuth.feature#L46)
|
||||
- [coreApiAuth/webDavLOCKAuth.feature:58](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavLOCKAuth.feature#L58)
|
||||
|
||||
#### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285)
|
||||
|
||||
- [coreApiWebdavOperations/refuseAccess.feature:21](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L21)
|
||||
- [coreApiWebdavOperations/refuseAccess.feature:22](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L22)
|
||||
|
||||
#### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755)
|
||||
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:74](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L74)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:75](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L75)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:76](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L76)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:77](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L77)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:79](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L79)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:78](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L78)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:147](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L147)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:148](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L148)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:149](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L149)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:192](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L192)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:193](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L193)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:194](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L194)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:195](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L195)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:196](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L196)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:197](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L197)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:240](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L240)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:241](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L241)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:242](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L242)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:243](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L243)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:244](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L244)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:245](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L245)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:255](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L255)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:256](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L256)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:279](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L279)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:280](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L280)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:376](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L376)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:377](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L377)
|
||||
|
||||
#### [Trying to delete other user's trashbin item returns 409 for spaces path instead of 404](https://github.com/owncloud/ocis/issues/9791)
|
||||
|
||||
- [coreApiTrashbin/trashbinDelete.feature:92](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L92)
|
||||
|
||||
#### [MOVE a file into same folder with same name returns 404 instead of 403](https://github.com/owncloud/ocis/issues/1976)
|
||||
|
||||
- [coreApiWebdavMove/moveFile.feature:100](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L100)
|
||||
- [coreApiWebdavMove/moveFile.feature:101](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L101)
|
||||
- [coreApiWebdavMove/moveFile.feature:102](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L102)
|
||||
- [coreApiWebdavMove/moveFolder.feature:217](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L217)
|
||||
- [coreApiWebdavMove/moveFolder.feature:218](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L218)
|
||||
- [coreApiWebdavMove/moveFolder.feature:219](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L219)
|
||||
- [coreApiWebdavMove/moveShareOnQsfera.feature:334](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnQsfera.feature#L334)
|
||||
- [coreApiWebdavMove/moveShareOnQsfera.feature:337](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnQsfera.feature#L337)
|
||||
- [coreApiWebdavMove/moveShareOnQsfera.feature:340](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnQsfera.feature#L340)
|
||||
|
||||
#### [COPY file/folder to same name is possible (but 500 code error for folder with spaces path)](https://github.com/owncloud/ocis/issues/8711)
|
||||
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:198](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L198)
|
||||
- [coreApiWebdavCopyCreate/copyFile.feature:1094](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/copyFile.feature#L1094)
|
||||
- [coreApiWebdavCopyCreate/copyFile.feature:1095](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/copyFile.feature#L1095)
|
||||
- [coreApiWebdavCopyCreate/copyFile.feature:1096](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/copyFile.feature#L1096)
|
||||
|
||||
#### [Trying to restore personal file to file of share received folder returns 403 but the share file is deleted (new dav path)](https://github.com/owncloud/ocis/issues/10356)
|
||||
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:277](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L277)
|
||||
|
||||
#### [Preview. UTF characters do not display on prievew](https://github.com/qsfera/server/issues/1451)
|
||||
|
||||
- [coreApiWebdavPreviews/previews.feature:249](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L249)
|
||||
- [coreApiWebdavPreviews/previews.feature:250](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L250)
|
||||
- [coreApiWebdavPreviews/previews.feature:251](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L251)
|
||||
|
||||
#### [Preview of text file truncated](https://github.com/qsfera/server/issues/1452)
|
||||
|
||||
- [coreApiWebdavPreviews/previews.feature:263](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L263)
|
||||
- [coreApiWebdavPreviews/previews.feature:264](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L264)
|
||||
- [coreApiWebdavPreviews/previews.feature:265](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L265)
|
||||
|
||||
|
||||
#### [500 when user tries to delete group from space](https://github.com/qsfera/server/issues/2799)
|
||||
|
||||
- [apiSharingNg1/removeAccessToDrive.feature:145](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNg1/removeAccessToDrive.feature#L145)
|
||||
|
||||
### Won't fix
|
||||
|
||||
Not everything needs to be implemented for qsfera.
|
||||
|
||||
- _Blacklisted ignored files are no longer required because qsfera can handle `.htaccess` files without security implications introduced by serving user provided files with apache._
|
||||
|
||||
Note: always have an empty line at the end of this file.
|
||||
The bash script that processes this file requires that the last line has a newline on the end.
|
||||
@@ -0,0 +1,360 @@
|
||||
## Scenarios from КуСфера API tests that are expected to fail with posix storage
|
||||
|
||||
#### [Downloading the archive of the resource (files | folder) using resource path is not possible](https://github.com/owncloud/ocis/issues/4637)
|
||||
|
||||
- [apiArchiver/downloadByPath.feature:25](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L25)
|
||||
- [apiArchiver/downloadByPath.feature:26](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L26)
|
||||
- [apiArchiver/downloadByPath.feature:43](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L43)
|
||||
- [apiArchiver/downloadByPath.feature:44](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L44)
|
||||
- [apiArchiver/downloadByPath.feature:47](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L47)
|
||||
- [apiArchiver/downloadByPath.feature:73](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L73)
|
||||
- [apiArchiver/downloadByPath.feature:171](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L171)
|
||||
- [apiArchiver/downloadByPath.feature:172](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiArchiver/downloadByPath.feature#L172)
|
||||
|
||||
#### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755)
|
||||
|
||||
- [apiSpacesShares/shareUploadTUS.feature:283](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareUploadTUS.feature#L283)
|
||||
- [apiSpacesShares/shareUploadTUS.feature:303](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareUploadTUS.feature#L303)
|
||||
- [apiSpacesShares/shareUploadTUS.feature:384](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareUploadTUS.feature#L384)
|
||||
|
||||
#### [Settings service user can list other peoples assignments](https://github.com/owncloud/ocis/issues/5032)
|
||||
|
||||
- [apiGraph/getAssignedRole.feature:31](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraph/getAssignedRole.feature#L31)
|
||||
- [apiGraph/getAssignedRole.feature:32](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraph/getAssignedRole.feature#L32)
|
||||
- [apiGraph/getAssignedRole.feature:33](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraph/getAssignedRole.feature#L33)
|
||||
|
||||
#### [A User can get information of another user with Graph API](https://github.com/owncloud/ocis/issues/5125)
|
||||
|
||||
- [apiGraphUserGroup/getUser.feature:84](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L84)
|
||||
- [apiGraphUserGroup/getUser.feature:85](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L85)
|
||||
- [apiGraphUserGroup/getUser.feature:86](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L86)
|
||||
- [apiGraphUserGroup/getUser.feature:628](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L628)
|
||||
- [apiGraphUserGroup/getUser.feature:629](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L629)
|
||||
- [apiGraphUserGroup/getUser.feature:630](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L630)
|
||||
- [apiGraphUserGroup/getUser.feature:645](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L645)
|
||||
- [apiGraphUserGroup/getUser.feature:646](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L646)
|
||||
- [apiGraphUserGroup/getUser.feature:647](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getUser.feature#L647)
|
||||
|
||||
#### [Normal user can get expanded members information of a group](https://github.com/owncloud/ocis/issues/5604)
|
||||
|
||||
- [apiGraphUserGroup/getGroup.feature:399](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L399)
|
||||
- [apiGraphUserGroup/getGroup.feature:400](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L400)
|
||||
- [apiGraphUserGroup/getGroup.feature:401](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L401)
|
||||
- [apiGraphUserGroup/getGroup.feature:460](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L460)
|
||||
- [apiGraphUserGroup/getGroup.feature:461](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L461)
|
||||
- [apiGraphUserGroup/getGroup.feature:462](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L462)
|
||||
- [apiGraphUserGroup/getGroup.feature:508](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L508)
|
||||
- [apiGraphUserGroup/getGroup.feature:509](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L509)
|
||||
- [apiGraphUserGroup/getGroup.feature:510](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/getGroup.feature#L510)
|
||||
|
||||
#### [Same users can be added in a group multiple time](https://github.com/owncloud/ocis/issues/5702)
|
||||
|
||||
- [apiGraphUserGroup/addUserToGroup.feature:295](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/addUserToGroup.feature#L295)
|
||||
|
||||
#### [Users are added in a group with wrong host in host-part of user](https://github.com/owncloud/ocis/issues/5871)
|
||||
|
||||
- [apiGraphUserGroup/addUserToGroup.feature:379](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/addUserToGroup.feature#L379)
|
||||
- [apiGraphUserGroup/addUserToGroup.feature:393](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/addUserToGroup.feature#L393)
|
||||
|
||||
#### [Adding the same user as multiple members in a single request results in listing the same user twice in the group](https://github.com/owncloud/ocis/issues/5855)
|
||||
|
||||
- [apiGraphUserGroup/addUserToGroup.feature:430](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiGraphUserGroup/addUserToGroup.feature#L430)
|
||||
|
||||
#### [Shared file locking is not possible using different path](https://github.com/owncloud/ocis/issues/7599)
|
||||
|
||||
- [apiLocks/lockFiles.feature:185](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L185)
|
||||
- [apiLocks/lockFiles.feature:186](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L186)
|
||||
- [apiLocks/lockFiles.feature:187](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L187)
|
||||
- [apiLocks/lockFiles.feature:309](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L309)
|
||||
- [apiLocks/lockFiles.feature:310](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L310)
|
||||
- [apiLocks/lockFiles.feature:311](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L311)
|
||||
- [apiLocks/lockFiles.feature:364](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L364)
|
||||
- [apiLocks/lockFiles.feature:365](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L365)
|
||||
- [apiLocks/lockFiles.feature:366](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L366)
|
||||
- [apiLocks/lockFiles.feature:367](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L367)
|
||||
- [apiLocks/lockFiles.feature:368](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L368)
|
||||
- [apiLocks/lockFiles.feature:369](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L369)
|
||||
- [apiLocks/lockFiles.feature:399](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L399)
|
||||
- [apiLocks/lockFiles.feature:400](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L400)
|
||||
- [apiLocks/lockFiles.feature:401](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L401)
|
||||
- [apiLocks/lockFiles.feature:402](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L402)
|
||||
- [apiLocks/lockFiles.feature:403](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L403)
|
||||
- [apiLocks/lockFiles.feature:404](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L404)
|
||||
- [apiLocks/unlockFiles.feature:62](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L62)
|
||||
- [apiLocks/unlockFiles.feature:63](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L63)
|
||||
- [apiLocks/unlockFiles.feature:64](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L64)
|
||||
- [apiLocks/unlockFiles.feature:171](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L171)
|
||||
- [apiLocks/unlockFiles.feature:172](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L172)
|
||||
- [apiLocks/unlockFiles.feature:173](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L173)
|
||||
- [apiLocks/unlockFiles.feature:174](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L174)
|
||||
- [apiLocks/unlockFiles.feature:175](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L175)
|
||||
- [apiLocks/unlockFiles.feature:176](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L176)
|
||||
- [apiLocks/unlockFiles.feature:199](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L199)
|
||||
- [apiLocks/unlockFiles.feature:200](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L200)
|
||||
- [apiLocks/unlockFiles.feature:201](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L201)
|
||||
- [apiLocks/unlockFiles.feature:202](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L202)
|
||||
- [apiLocks/unlockFiles.feature:203](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L203)
|
||||
- [apiLocks/unlockFiles.feature:204](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L204)
|
||||
- [apiLocks/unlockFiles.feature:227](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L227)
|
||||
- [apiLocks/unlockFiles.feature:228](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L228)
|
||||
- [apiLocks/unlockFiles.feature:229](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L229)
|
||||
- [apiLocks/unlockFiles.feature:230](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L230)
|
||||
- [apiLocks/unlockFiles.feature:231](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L231)
|
||||
- [apiLocks/unlockFiles.feature:232](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L232)
|
||||
|
||||
#### [Folders can be locked and locking works partially](https://github.com/owncloud/ocis/issues/7641)
|
||||
|
||||
- [apiLocks/lockFiles.feature:443](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L443)
|
||||
- [apiLocks/lockFiles.feature:444](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L444)
|
||||
- [apiLocks/lockFiles.feature:445](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L445)
|
||||
- [apiLocks/lockFiles.feature:446](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L446)
|
||||
- [apiLocks/lockFiles.feature:447](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L447)
|
||||
- [apiLocks/lockFiles.feature:448](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L448)
|
||||
- [apiLocks/lockFiles.feature:417](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L417)
|
||||
- [apiLocks/lockFiles.feature:418](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L418)
|
||||
- [apiLocks/lockFiles.feature:419](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L419)
|
||||
- [apiLocks/lockFiles.feature:420](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L420)
|
||||
- [apiLocks/lockFiles.feature:421](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L421)
|
||||
- [apiLocks/lockFiles.feature:422](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L422)
|
||||
|
||||
#### [Anonymous users can unlock a file shared to them through a public link if they get the lock token](https://github.com/owncloud/ocis/issues/7761)
|
||||
|
||||
- [apiLocks/unlockFiles.feature:42](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L42)
|
||||
- [apiLocks/unlockFiles.feature:43](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L43)
|
||||
- [apiLocks/unlockFiles.feature:44](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L44)
|
||||
- [apiLocks/unlockFiles.feature:45](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L45)
|
||||
- [apiLocks/unlockFiles.feature:46](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L46)
|
||||
- [apiLocks/unlockFiles.feature:47](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L47)
|
||||
|
||||
#### [Trying to unlock a shared file with sharer's lock token gives 500](https://github.com/owncloud/ocis/issues/7767)
|
||||
|
||||
- [apiLocks/unlockFiles.feature:115](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L115)
|
||||
- [apiLocks/unlockFiles.feature:116](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L116)
|
||||
- [apiLocks/unlockFiles.feature:117](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L117)
|
||||
- [apiLocks/unlockFiles.feature:118](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L118)
|
||||
- [apiLocks/unlockFiles.feature:119](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L119)
|
||||
- [apiLocks/unlockFiles.feature:120](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L120)
|
||||
- [apiLocks/unlockFiles.feature:143](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L143)
|
||||
- [apiLocks/unlockFiles.feature:144](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L144)
|
||||
- [apiLocks/unlockFiles.feature:145](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L145)
|
||||
- [apiLocks/unlockFiles.feature:146](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L146)
|
||||
- [apiLocks/unlockFiles.feature:147](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L147)
|
||||
- [apiLocks/unlockFiles.feature:148](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L148)
|
||||
|
||||
#### [Anonymous user trying lock a file shared to them through a public link gives 405](https://github.com/owncloud/ocis/issues/7790)
|
||||
|
||||
- [apiLocks/lockFiles.feature:532](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L532)
|
||||
- [apiLocks/lockFiles.feature:533](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L533)
|
||||
- [apiLocks/lockFiles.feature:534](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L534)
|
||||
- [apiLocks/lockFiles.feature:535](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L535)
|
||||
- [apiLocks/lockFiles.feature:554](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L554)
|
||||
- [apiLocks/lockFiles.feature:555](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L555)
|
||||
- [apiLocks/lockFiles.feature:556](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L556)
|
||||
- [apiLocks/lockFiles.feature:557](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L557)
|
||||
|
||||
#### [sharee (editor role) MOVE a file by file-id into shared sub-folder returns 502](https://github.com/owncloud/ocis/issues/7617)
|
||||
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:368](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L368)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:591](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L591)
|
||||
|
||||
#### [MOVE a file into same folder with same name returns 404 instead of 403](https://github.com/owncloud/ocis/issues/1976)
|
||||
|
||||
- [apiSpacesShares/moveSpaces.feature:69](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/moveSpaces.feature#L69)
|
||||
- [apiSpacesShares/moveSpaces.feature:70](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/moveSpaces.feature#L70)
|
||||
- [apiSpacesShares/moveSpaces.feature:416](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/moveSpaces.feature#L416)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:61](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L61)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:174](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L174)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:175](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L175)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:176](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L176)
|
||||
- [apiSpacesDavOperation/moveByFileId.feature:393](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesDavOperation/moveByFileId.feature#L393)
|
||||
|
||||
#### [OCM. admin cannot get federated users if he hasn't connection with them ](https://github.com/owncloud/ocis/issues/9829)
|
||||
|
||||
- [apiOcm/searchFederationUsers.feature:429](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/searchFederationUsers.feature#L429)
|
||||
- [apiOcm/searchFederationUsers.feature:601](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/searchFederationUsers.feature#L601)
|
||||
|
||||
#### [OCM. federated connection is not dropped when one of the users deletes the connection](https://github.com/owncloud/ocis/issues/10216)
|
||||
|
||||
- [apiOcm/deleteFederatedConnections.feature:21](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/deleteFederatedConnections.feature#L21)
|
||||
- [apiOcm/deleteFederatedConnections.feature:67](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/deleteFederatedConnections.feature#L67)
|
||||
|
||||
#### [OCM. server crash after deleting share for ocm user](https://github.com/owncloud/ocis/issues/10213)
|
||||
|
||||
- [apiOcm/deleteFederatedConnections.feature:102](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiOcm/deleteFederatedConnections.feature#L102)
|
||||
|
||||
#### [Shares Jail PROPFIND returns different File IDs for the same item](https://github.com/owncloud/ocis/issues/9933)
|
||||
|
||||
- [apiSharingNg1/propfindShares.feature:149](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNg1/propfindShares.feature#L149)
|
||||
|
||||
#### [Readiness check for some services returns 500 status code](https://github.com/owncloud/ocis/issues/10661)
|
||||
|
||||
- [apiServiceAvailability/serviceAvailabilityCheck.feature:123](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiServiceAvailability/serviceAvailabilityCheck.feature#L123)
|
||||
|
||||
#### [Skip tests for different languages](https://github.com/qsfera/server/issues/183)
|
||||
|
||||
- [apiActivities/activities.feature:2598](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiActivities/activities.feature#L2598)
|
||||
|
||||
#### [Missing properties in REPORT response](https://github.com/owncloud/ocis/issues/9780), [d:getetag property has empty value in REPORT response](https://github.com/owncloud/ocis/issues/9783)
|
||||
|
||||
- [apiSearch1/search.feature:437](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L437)
|
||||
- [apiSearch1/search.feature:438](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L438)
|
||||
- [apiSearch1/search.feature:439](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L439)
|
||||
- [apiSearch1/search.feature:465](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L465)
|
||||
- [apiSearch1/search.feature:466](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L466)
|
||||
- [apiSearch1/search.feature:467](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L467)
|
||||
|
||||
## Scenarios from core API tests that are expected to fail with posix storage
|
||||
|
||||
### File
|
||||
|
||||
Basic file management like up and download, move, copy, properties, trash, versions and chunking.
|
||||
|
||||
#### [Custom dav properties with namespaces are rendered incorrectly](https://github.com/owncloud/ocis/issues/2140)
|
||||
|
||||
_ocdav: double-check the webdav property parsing when custom namespaces are used_
|
||||
|
||||
- [coreApiWebdavProperties/setFileProperties.feature:128](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L128)
|
||||
- [coreApiWebdavProperties/setFileProperties.feature:129](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L129)
|
||||
- [coreApiWebdavProperties/setFileProperties.feature:130](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/setFileProperties.feature#L130)
|
||||
|
||||
### Sync
|
||||
|
||||
Synchronization features like etag propagation, setting mtime and locking files
|
||||
|
||||
#### [Uploading an old method chunked file with checksum should fail using new DAV path](https://github.com/owncloud/ocis/issues/2323)
|
||||
|
||||
- [coreApiMain/checksums.feature:233](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiMain/checksums.feature#L233)
|
||||
- [coreApiMain/checksums.feature:234](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiMain/checksums.feature#L234)
|
||||
- [coreApiMain/checksums.feature:235](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiMain/checksums.feature#L235)
|
||||
|
||||
### Share
|
||||
|
||||
#### [d:quota-available-bytes in dprop of PROPFIND give wrong response value](https://github.com/owncloud/ocis/issues/8197)
|
||||
|
||||
- [coreApiWebdavProperties/getQuota.feature:57](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L57)
|
||||
- [coreApiWebdavProperties/getQuota.feature:58](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L58)
|
||||
- [coreApiWebdavProperties/getQuota.feature:59](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L59)
|
||||
- [coreApiWebdavProperties/getQuota.feature:73](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L73)
|
||||
- [coreApiWebdavProperties/getQuota.feature:74](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L74)
|
||||
- [coreApiWebdavProperties/getQuota.feature:75](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavProperties/getQuota.feature#L75)
|
||||
|
||||
#### [deleting a file inside a received shared folder is moved to the trash-bin of the sharer not the receiver](https://github.com/owncloud/ocis/issues/1124)
|
||||
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:54](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L54)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:55](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L55)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:56](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L56)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:83](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L83)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:84](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L84)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:85](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L85)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:142](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L142)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:143](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L143)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:144](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L144)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:202](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L202)
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:203](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L203)
|
||||
|
||||
### Other
|
||||
|
||||
API, search, favorites, config, capabilities, not existing endpoints, CORS and others
|
||||
|
||||
#### [sending MKCOL requests to another or non-existing user's webDav endpoints as normal user should return 404](https://github.com/owncloud/ocis/issues/5049)
|
||||
|
||||
_ocdav: api compatibility, return correct status code_
|
||||
|
||||
- [coreApiAuth/webDavMKCOLAuth.feature:42](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavMKCOLAuth.feature#L42)
|
||||
- [coreApiAuth/webDavMKCOLAuth.feature:53](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavMKCOLAuth.feature#L53)
|
||||
|
||||
#### [trying to lock file of another user gives http 500](https://github.com/owncloud/ocis/issues/2176)
|
||||
|
||||
- [coreApiAuth/webDavLOCKAuth.feature:46](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavLOCKAuth.feature#L46)
|
||||
- [coreApiAuth/webDavLOCKAuth.feature:58](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavLOCKAuth.feature#L58)
|
||||
|
||||
|
||||
|
||||
#### [WWW-Authenticate header for unauthenticated requests is not clear](https://github.com/owncloud/ocis/issues/2285)
|
||||
|
||||
- [coreApiWebdavOperations/refuseAccess.feature:21](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L21)
|
||||
- [coreApiWebdavOperations/refuseAccess.feature:22](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/refuseAccess.feature#L22)
|
||||
|
||||
#### [PATCH request for TUS upload with wrong checksum gives incorrect response](https://github.com/owncloud/ocis/issues/1755)
|
||||
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:74](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L74)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:75](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L75)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:76](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L76)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:77](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L77)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:79](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L79)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:78](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L78)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:147](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L147)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:148](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L148)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:149](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L149)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:192](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L192)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:193](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L193)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:194](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L194)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:195](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L195)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:196](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L196)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:197](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L197)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:240](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L240)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:241](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L241)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:242](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L242)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:243](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L243)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:244](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L244)
|
||||
- [coreApiWebdavUploadTUS/checksums.feature:245](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/checksums.feature#L245)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:255](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L255)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:256](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L256)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:279](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L279)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:280](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L280)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:376](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L376)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:377](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L377)
|
||||
|
||||
|
||||
#### [Trying to delete other user's trashbin item returns 409 for spaces path instead of 404](https://github.com/owncloud/ocis/issues/9791)
|
||||
|
||||
- [coreApiTrashbin/trashbinDelete.feature:92](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinDelete.feature#L92)
|
||||
|
||||
#### [MOVE a file into same folder with same name returns 404 instead of 403](https://github.com/owncloud/ocis/issues/1976)
|
||||
|
||||
- [coreApiWebdavMove/moveFile.feature:100](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L100)
|
||||
- [coreApiWebdavMove/moveFile.feature:101](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L101)
|
||||
- [coreApiWebdavMove/moveFile.feature:102](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFile.feature#L102)
|
||||
- [coreApiWebdavMove/moveFolder.feature:217](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L217)
|
||||
- [coreApiWebdavMove/moveFolder.feature:218](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L218)
|
||||
- [coreApiWebdavMove/moveFolder.feature:219](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveFolder.feature#L219)
|
||||
- [coreApiWebdavMove/moveShareOnQsfera.feature:334](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnQsfera.feature#L334)
|
||||
- [coreApiWebdavMove/moveShareOnQsfera.feature:337](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnQsfera.feature#L337)
|
||||
- [coreApiWebdavMove/moveShareOnQsfera.feature:340](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavMove/moveShareOnQsfera.feature#L340)
|
||||
|
||||
#### [COPY file/folder to same name is possible (but 500 code error for folder with spaces path)](https://github.com/owncloud/ocis/issues/8711)
|
||||
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:198](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L198)
|
||||
- [coreApiWebdavCopyCreate/copyFile.feature:1094](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/copyFile.feature#L1094)
|
||||
- [coreApiWebdavCopyCreate/copyFile.feature:1095](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/copyFile.feature#L1095)
|
||||
- [coreApiWebdavCopyCreate/copyFile.feature:1096](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/copyFile.feature#L1096)
|
||||
|
||||
#### [Trying to restore personal file to file of share received folder returns 403 but the share file is deleted (new dav path)](https://github.com/owncloud/ocis/issues/10356)
|
||||
|
||||
- [coreApiTrashbin/trashbinSharingToShares.feature:277](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiTrashbin/trashbinSharingToShares.feature#L277)
|
||||
|
||||
#### [Preview. UTF characters do not display on prievew](https://github.com/qsfera/server/issues/1451)
|
||||
|
||||
- [coreApiWebdavPreviews/previews.feature:249](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L249)
|
||||
- [coreApiWebdavPreviews/previews.feature:250](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L250)
|
||||
- [coreApiWebdavPreviews/previews.feature:251](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L251)
|
||||
|
||||
#### [Preview of text file truncated](https://github.com/qsfera/server/issues/1452)
|
||||
|
||||
- [coreApiWebdavPreviews/previews.feature:263](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L263)
|
||||
- [coreApiWebdavPreviews/previews.feature:264](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L264)
|
||||
- [coreApiWebdavPreviews/previews.feature:265](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/previews.feature#L265)
|
||||
|
||||
### TODO: adjust test
|
||||
|
||||
- [apiSharingNg1/removeAccessToDrive.feature:145](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNg1/removeAccessToDrive.feature#L145)
|
||||
|
||||
|
||||
### Won't fix
|
||||
|
||||
Not everything needs to be implemented for qsfera.
|
||||
|
||||
- _Blacklisted ignored files are no longer required because qsfera can handle `.htaccess` files without security implications introduced by serving user provided files with apache._
|
||||
|
||||
Note: always have an empty line at the end of this file.
|
||||
The bash script that processes this file requires that the last line has a newline on the end.
|
||||
@@ -0,0 +1,366 @@
|
||||
## Scenarios that are expected to fail when remote.php is not used
|
||||
|
||||
#### [REPORT request without remote.php returns empty result (only with dav/spaces path)](https://github.com/owncloud/ocis/issues/10329)
|
||||
|
||||
- [apiContract/sharesReport.feature:43](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L43)
|
||||
- [apiContract/sharesReport.feature:84](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L84)
|
||||
- [apiContract/sharesReport.feature:150](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L150)
|
||||
- [apiContract/sharesReport.feature:180](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/sharesReport.feature#L180)
|
||||
- [apiContract/spacesReport.feature:16](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L16)
|
||||
- [apiContract/spacesReport.feature:35](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L35)
|
||||
- [apiContract/spacesReport.feature:55](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L55)
|
||||
- [apiContract/spacesReport.feature:74](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/spacesReport.feature#L74)
|
||||
- [apiContract/spacesSharesReport.feature:46](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/spacesSharesReport.feature#L46)
|
||||
- [apiContract/spacesSharesReport.feature:77](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiContract/spacesSharesReport.feature#L77)
|
||||
- [apiSearch1/dateSearch.feature:19](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L19)
|
||||
- [apiSearch1/dateSearch.feature:39](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L39)
|
||||
- [apiSearch1/dateSearch.feature:40](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L40)
|
||||
- [apiSearch1/dateSearch.feature:41](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L41)
|
||||
- [apiSearch1/dateSearch.feature:42](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L42)
|
||||
- [apiSearch1/dateSearch.feature:43](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L43)
|
||||
- [apiSearch1/dateSearch.feature:44](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L44)
|
||||
- [apiSearch1/dateSearch.feature:45](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L45)
|
||||
- [apiSearch1/dateSearch.feature:46](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L46)
|
||||
- [apiSearch1/dateSearch.feature:47](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L47)
|
||||
- [apiSearch1/dateSearch.feature:48](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L48)
|
||||
- [apiSearch1/dateSearch.feature:50](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L50)
|
||||
- [apiSearch1/dateSearch.feature:53](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/dateSearch.feature#L53)
|
||||
- [apiSearch1/search.feature:113](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L113)
|
||||
- [apiSearch1/search.feature:176](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L176)
|
||||
- [apiSearch1/search.feature:280](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L280)
|
||||
- [apiSearch1/search.feature:304](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L304)
|
||||
- [apiSearch1/search.feature:317](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L317)
|
||||
- [apiSearch1/search.feature:318](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L318)
|
||||
- [apiSearch1/search.feature:319](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L319)
|
||||
- [apiSearch1/search.feature:320](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L320)
|
||||
- [apiSearch1/search.feature:321](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L321)
|
||||
- [apiSearch1/search.feature:324](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L324)
|
||||
- [apiSearch1/search.feature:356](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L356)
|
||||
- [apiSearch1/search.feature:369](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L369)
|
||||
- [apiSearch1/search.feature:396](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L396)
|
||||
- [apiSearch1/search.feature:410](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch1/search.feature#L410)
|
||||
- [apiSearch2/mediaTypeSearch.feature:31](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L31)
|
||||
- [apiSearch2/mediaTypeSearch.feature:32](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L32)
|
||||
- [apiSearch2/mediaTypeSearch.feature:33](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L33)
|
||||
- [apiSearch2/mediaTypeSearch.feature:34](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L34)
|
||||
- [apiSearch2/mediaTypeSearch.feature:35](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L35)
|
||||
- [apiSearch2/mediaTypeSearch.feature:36](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L36)
|
||||
- [apiSearch2/mediaTypeSearch.feature:37](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L37)
|
||||
- [apiSearch2/mediaTypeSearch.feature:38](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L38)
|
||||
- [apiSearch2/mediaTypeSearch.feature:39](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L39)
|
||||
- [apiSearch2/mediaTypeSearch.feature:60](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L60)
|
||||
- [apiSearch2/mediaTypeSearch.feature:61](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L61)
|
||||
- [apiSearch2/mediaTypeSearch.feature:62](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L62)
|
||||
- [apiSearch2/mediaTypeSearch.feature:63](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L63)
|
||||
- [apiSearch2/mediaTypeSearch.feature:64](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L64)
|
||||
- [apiSearch2/mediaTypeSearch.feature:65](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L65)
|
||||
- [apiSearch2/mediaTypeSearch.feature:66](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L66)
|
||||
- [apiSearch2/mediaTypeSearch.feature:67](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L67)
|
||||
- [apiSearch2/mediaTypeSearch.feature:68](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L68)
|
||||
- [apiSearch2/mediaTypeSearch.feature:90](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L90)
|
||||
- [apiSearch2/mediaTypeSearch.feature:91](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L91)
|
||||
- [apiSearch2/mediaTypeSearch.feature:92](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L92)
|
||||
- [apiSearch2/mediaTypeSearch.feature:93](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L93)
|
||||
- [apiSearch2/mediaTypeSearch.feature:94](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L94)
|
||||
- [apiSearch2/mediaTypeSearch.feature:95](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L95)
|
||||
- [apiSearch2/mediaTypeSearch.feature:96](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L96)
|
||||
- [apiSearch2/mediaTypeSearch.feature:97](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L97)
|
||||
- [apiSearch2/mediaTypeSearch.feature:98](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L98)
|
||||
- [apiSearch2/mediaTypeSearch.feature:126](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L126)
|
||||
- [apiSearch2/mediaTypeSearch.feature:127](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L127)
|
||||
- [apiSearch2/mediaTypeSearch.feature:128](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L128)
|
||||
- [apiSearch2/mediaTypeSearch.feature:129](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L129)
|
||||
- [apiSearch2/mediaTypeSearch.feature:130](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L130)
|
||||
- [apiSearch2/mediaTypeSearch.feature:131](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L131)
|
||||
- [apiSearch2/mediaTypeSearch.feature:132](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L132)
|
||||
- [apiSearch2/mediaTypeSearch.feature:133](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L133)
|
||||
- [apiSearch2/mediaTypeSearch.feature:134](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L134)
|
||||
- [apiSearch2/mediaTypeSearch.feature:161](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L161)
|
||||
- [apiSearch2/mediaTypeSearch.feature:162](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L162)
|
||||
- [apiSearch2/mediaTypeSearch.feature:163](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L163)
|
||||
- [apiSearch2/mediaTypeSearch.feature:164](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L164)
|
||||
- [apiSearch2/mediaTypeSearch.feature:165](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L165)
|
||||
- [apiSearch2/mediaTypeSearch.feature:166](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L166)
|
||||
- [apiSearch2/mediaTypeSearch.feature:167](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L167)
|
||||
- [apiSearch2/mediaTypeSearch.feature:168](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L168)
|
||||
- [apiSearch2/mediaTypeSearch.feature:169](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L169)
|
||||
- [apiSearch2/mediaTypeSearch.feature:172](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/mediaTypeSearch.feature#L172)
|
||||
- [apiSearch2/tagSearch.feature:36](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L36)
|
||||
- [apiSearch2/tagSearch.feature:64](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L64)
|
||||
- [apiSearch2/tagSearch.feature:85](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L85)
|
||||
- [apiSearch2/tagSearch.feature:108](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L108)
|
||||
- [apiSearch2/tagSearch.feature:138](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L138)
|
||||
- [apiSearch2/tagSearch.feature:172](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L172)
|
||||
- [apiSearch2/tagSearch.feature:207](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L207)
|
||||
- [apiSearch2/tagSearch.feature:226](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L226)
|
||||
- [apiSearch2/tagSearch.feature:254](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L254)
|
||||
- [apiSearch2/tagSearch.feature:255](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L255)
|
||||
- [apiSearch2/tagSearch.feature:256](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L256)
|
||||
- [apiSearch2/tagSearch.feature:257](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L257)
|
||||
- [apiSearch2/tagSearch.feature:258](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L258)
|
||||
- [apiSearch2/tagSearch.feature:259](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L259)
|
||||
- [apiSearch2/tagSearch.feature:260](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L260)
|
||||
- [apiSearch2/tagSearch.feature:261](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L261)
|
||||
- [apiSearch2/tagSearch.feature:262](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L262)
|
||||
- [apiSearch2/tagSearch.feature:263](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L263)
|
||||
- [apiSearch2/tagSearch.feature:265](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearch2/tagSearch.feature#L265)
|
||||
- [apiSearchContent/contentSearch.feature:27](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L27)
|
||||
- [apiSearchContent/contentSearch.feature:51](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L51)
|
||||
- [apiSearchContent/contentSearch.feature:84](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L84)
|
||||
- [apiSearchContent/contentSearch.feature:112](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L112)
|
||||
- [apiSearchContent/contentSearch.feature:131](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L131)
|
||||
- [apiSearchContent/contentSearch.feature:147](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L147)
|
||||
- [apiSearchContent/contentSearch.feature:163](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L163)
|
||||
- [apiSearchContent/contentSearch.feature:186](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L186)
|
||||
- [apiSearchContent/contentSearch.feature:215](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L215)
|
||||
- [apiSearchContent/contentSearch.feature:233](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L233)
|
||||
- [apiSearchContent/contentSearch.feature:234](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L234)
|
||||
- [apiSearchContent/contentSearch.feature:235](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L235)
|
||||
- [apiSearchContent/contentSearch.feature:236](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L236)
|
||||
- [apiSearchContent/contentSearch.feature:237](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L237)
|
||||
- [apiSearchContent/contentSearch.feature:238](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L238)
|
||||
- [apiSearchContent/contentSearch.feature:267](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSearchContent/contentSearch.feature#L267)
|
||||
- [cliCommands/searchReIndex.feature:15](https://github.com/qsfera/server/blob/main/tests/acceptance/features/cliCommands/searchReIndex.feature#L15)
|
||||
|
||||
#### [Downloading public files without remote.php returns 401 unauthorized error](https://github.com/owncloud/ocis/issues/9724)
|
||||
|
||||
- [apiSpaces/editPublicLinkOfSpace.feature:48](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/editPublicLinkOfSpace.feature#L48)
|
||||
- [apiSpaces/editPublicLinkOfSpace.feature:49](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/editPublicLinkOfSpace.feature#L49)
|
||||
- [apiSpacesShares/publicLinkDownload.feature:16](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/publicLinkDownload.feature#L16)
|
||||
- [apiSpacesShares/publicLinkDownload.feature:32](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/publicLinkDownload.feature#L32)
|
||||
- [apiSpacesShares/shareSpacesViaLink.feature:42](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature#L42)
|
||||
- [apiSpacesShares/shareSpacesViaLink.feature:43](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature#L43)
|
||||
- [apiSpacesShares/shareSpacesViaLink.feature:44](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature#L44)
|
||||
- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:146](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L146)
|
||||
- [apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature:147](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareSubItemOfSpaceViaPublicLink.feature#L147)
|
||||
- [apiSharingNgLinkSharePermission/createLinkShare.feature:473](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNgLinkSharePermission/createLinkShare.feature#L473)
|
||||
- [apiSharingNgLinkSharePermission/createLinkShare.feature:1208](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNgLinkSharePermission/createLinkShare.feature#L1208)
|
||||
- [apiSharingNgLinkSharePermission/updateLinkShare.feature:204](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNgLinkSharePermission/updateLinkShare.feature#L204)
|
||||
- [apiSharingNgLinkSharePermission/updateLinkShare.feature:282](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNgLinkSharePermission/updateLinkShare.feature#L282)
|
||||
- [apiSharingNgLinkShareRoot/updateLinkShare.feature:10](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNgLinkShareRoot/updateLinkShare.feature#L10)
|
||||
- [apiSharingNgLinkShareRoot/updateLinkShare.feature:42](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSharingNgLinkShareRoot/updateLinkShare.feature#L42)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:220](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L220)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:221](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L221)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:59](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L59)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:60](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L60)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:88](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L88)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:89](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L89)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:122](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L122)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:123](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L123)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:209](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L209)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:210](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L210)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:229](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L229)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:230](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L230)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:257](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L257)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:258](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L258)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:273](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L273)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:274](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L274)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:277](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L277)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:291](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L291)
|
||||
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:111](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L111)
|
||||
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:112](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L112)
|
||||
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:249](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L249)
|
||||
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:250](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L250)
|
||||
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:276](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L276)
|
||||
- [coreApiSharePublicLink2/updatePublicLinkShare.feature:277](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/updatePublicLinkShare.feature#L277)
|
||||
|
||||
#### [Most (if not all) requests to public link share without remote.php returns 401 Unauthorized error](https://github.com/owncloud/ocis/issues/10331)
|
||||
|
||||
- [apiSpaces/publicLink.feature:18](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L18)
|
||||
- [apiSpaces/publicLink.feature:23](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L23)
|
||||
- [apiSpaces/publicLink.feature:28](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L28)
|
||||
- [apiSpaces/publicLink.feature:34](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L34)
|
||||
- [apiSpaces/publicLink.feature:40](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/publicLink.feature#L40)
|
||||
- [apiSpaces/uploadSpaces.feature:95](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/uploadSpaces.feature#L95)
|
||||
- [apiSpaces/uploadSpaces.feature:112](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/uploadSpaces.feature#L112)
|
||||
- [apiSpaces/uploadSpaces.feature:129](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/uploadSpaces.feature#L129)
|
||||
- [apiSpacesShares/shareSpacesViaLink.feature:58](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpacesShares/shareSpacesViaLink.feature#L58)
|
||||
- [apiDepthInfinity/propfind.feature:74](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiDepthInfinity/propfind.feature#L74)
|
||||
- [apiDepthInfinity/propfind.feature:124](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiDepthInfinity/propfind.feature#L124)
|
||||
- [apiLocks/lockFiles.feature:490](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L490)
|
||||
- [apiLocks/lockFiles.feature:487](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L487)
|
||||
- [apiLocks/lockFiles.feature:488](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L488)
|
||||
- [apiLocks/lockFiles.feature:489](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L489)
|
||||
- [apiLocks/lockFiles.feature:513](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L513)
|
||||
- [apiLocks/lockFiles.feature:510](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L510)
|
||||
- [apiLocks/lockFiles.feature:511](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L511)
|
||||
- [apiLocks/lockFiles.feature:512](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/lockFiles.feature#L512)
|
||||
- [apiLocks/unlockFiles.feature:320](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L320)
|
||||
- [apiLocks/unlockFiles.feature:321](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L321)
|
||||
- [apiLocks/unlockFiles.feature:322](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L322)
|
||||
- [apiLocks/unlockFiles.feature:323](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiLocks/unlockFiles.feature#L323)
|
||||
- [apiAntivirus/antivirus.feature:114](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L114)
|
||||
- [apiAntivirus/antivirus.feature:115](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L115)
|
||||
- [apiAntivirus/antivirus.feature:116](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L116)
|
||||
- [apiAntivirus/antivirus.feature:117](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L117)
|
||||
- [apiAntivirus/antivirus.feature:118](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L118)
|
||||
- [apiAntivirus/antivirus.feature:119](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L119)
|
||||
- [apiAntivirus/antivirus.feature:140](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L140)
|
||||
- [apiAntivirus/antivirus.feature:141](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L141)
|
||||
- [apiAntivirus/antivirus.feature:142](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L142)
|
||||
- [apiAntivirus/antivirus.feature:143](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L143)
|
||||
- [apiAntivirus/antivirus.feature:144](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L144)
|
||||
- [apiAntivirus/antivirus.feature:145](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L145)
|
||||
- [apiAntivirus/antivirus.feature:357](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L357)
|
||||
- [apiAntivirus/antivirus.feature:358](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L358)
|
||||
- [apiAntivirus/antivirus.feature:359](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiAntivirus/antivirus.feature#L359)
|
||||
- [apiCollaboration/wopi.feature:956](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L956)
|
||||
- [apiCollaboration/wopi.feature:957](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L957)
|
||||
- [apiCollaboration/wopi.feature:958](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L958)
|
||||
- [apiCollaboration/wopi.feature:961](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L961)
|
||||
- [apiCollaboration/wopi.feature:1047](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L1047)
|
||||
- [apiCollaboration/wopi.feature:1048](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L1048)
|
||||
- [apiCollaboration/wopi.feature:1049](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L1049)
|
||||
- [apiCollaboration/wopi.feature:1052](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiCollaboration/wopi.feature#L1052)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:27](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L27)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:28](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L28)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:29](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L29)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:30](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L30)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:33](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L33)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:46](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L46)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:70](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L70)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:95](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L95)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:120](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L120)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:132](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L132)
|
||||
- [coreApiSharePublicLink1/changingPublicLinkShare.feature:145](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/changingPublicLinkShare.feature#L145)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:189](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L189)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:190](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L190)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:304](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L304)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:328](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L328)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:329](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L329)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:344](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L344)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:345](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L345)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:348](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L348)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:363](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L363)
|
||||
- [coreApiSharePublicLink1/createPublicLinkShare.feature:377](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink1/createPublicLinkShare.feature#L377)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:13](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L13)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:28](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L28)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:44](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L44)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:60](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L60)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:75](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L75)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:92](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L92)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:105](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L105)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:137](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L137)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:138](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L138)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:139](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L139)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:140](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L140)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:158](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L158)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:159](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L159)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:160](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L160)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:161](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L161)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:180](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L180)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:181](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L181)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:182](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L182)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:183](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L183)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:186](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L186)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:212](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L212)
|
||||
- [coreApiSharePublicLink2/copyFromPublicLink.feature:225](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/copyFromPublicLink.feature#L225)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:28](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L28)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:29](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L29)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:30](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L30)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:33](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L33)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:44](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L44)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:58](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L58)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:70](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L70)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:82](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L82)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:94](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L94)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:106](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L106)
|
||||
- [coreApiSharePublicLink2/uploadToPublicLinkShare.feature:118](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiSharePublicLink2/uploadToPublicLinkShare.feature#L118)
|
||||
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:238](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L238)
|
||||
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:239](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L239)
|
||||
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:240](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L240)
|
||||
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:262](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L262)
|
||||
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:263](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L263)
|
||||
- [coreApiWebdavEtagPropagation1/deleteFileFolder.feature:264](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/deleteFileFolder.feature#L264)
|
||||
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:308](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L308)
|
||||
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:309](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L309)
|
||||
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:310](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L310)
|
||||
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:333](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L333)
|
||||
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:334](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L334)
|
||||
- [coreApiWebdavEtagPropagation1/moveFileFolder.feature:335](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation1/moveFileFolder.feature#L335)
|
||||
- [coreApiWebdavEtagPropagation2/copyFileFolder.feature:135](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature#L135)
|
||||
- [coreApiWebdavEtagPropagation2/copyFileFolder.feature:136](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature#L136)
|
||||
- [coreApiWebdavEtagPropagation2/copyFileFolder.feature:137](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/copyFileFolder.feature#L137)
|
||||
- [coreApiWebdavEtagPropagation2/createFolder.feature:111](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/createFolder.feature#L111)
|
||||
- [coreApiWebdavEtagPropagation2/upload.feature:188](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature#L188)
|
||||
- [coreApiWebdavEtagPropagation2/upload.feature:189](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature#L189)
|
||||
- [coreApiWebdavEtagPropagation2/upload.feature:190](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavEtagPropagation2/upload.feature#L190)
|
||||
- [coreApiWebdavOperations/listFiles.feature:112](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L112)
|
||||
- [coreApiWebdavOperations/listFiles.feature:140](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/listFiles.feature#L140)
|
||||
- [coreApiWebdavOperations/propfind.feature:40](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L40)
|
||||
- [coreApiWebdavOperations/propfind.feature:55](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L55)
|
||||
- [coreApiWebdavOperations/propfind.feature:69](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L69)
|
||||
- [coreApiWebdavUpload/uploadFile.feature:376](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUpload/uploadFile.feature#L376)
|
||||
- [apiActivities/shareActivities.feature:1956](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiActivities/shareActivities.feature#L1956)
|
||||
- [apiActivities/shareActivities.feature:2095](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiActivities/shareActivities.feature#L2095)
|
||||
|
||||
#### [Cannot create new TUS upload resource using /webdav without remote.php - returns html instead](https://github.com/owncloud/ocis/issues/10346)
|
||||
|
||||
- [apiSpaces/tusUpload.feature:60](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/tusUpload.feature#L60)
|
||||
- [apiSpaces/tusUpload.feature:104](https://github.com/qsfera/server/blob/main/tests/acceptance/features/apiSpaces/tusUpload.feature#L104)
|
||||
- [coreApiWebdavUploadTUS/creationWithUploadExtension.feature:38](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/creationWithUploadExtension.feature#L38)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:16](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L16)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:17](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L17)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:18](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L18)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:19](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L19)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:20](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L20)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:21](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L21)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:22](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L22)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:46](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L46)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:47](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L47)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:48](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L48)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:49](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L49)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:50](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L50)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:51](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L51)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:52](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L52)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:75](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L75)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:86](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L86)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:98](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L98)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:109](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L109)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:122](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L122)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:133](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L133)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:146](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L146)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:187](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L187)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:199](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L199)
|
||||
- [coreApiWebdavUploadTUS/uploadFile.feature:212](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFile.feature#L212)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtime.feature:16](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature#L16)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtime.feature:27](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature#L27)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtime.feature:39](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature#L39)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtime.feature:51](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature#L51)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtime.feature:65](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature#L65)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtime.feature:79](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtime.feature#L79)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtimeShares.feature:29](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtimeShares.feature#L29)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtimeShares.feature:48](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtimeShares.feature#L48)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtimeShares.feature:69](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtimeShares.feature#L69)
|
||||
- [coreApiWebdavUploadTUS/uploadFileMtimeShares.feature:89](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadFileMtimeShares.feature#L89)
|
||||
- [coreApiWebdavUploadTUS/uploadToMoveFolder.feature:20](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToMoveFolder.feature#L20)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:29](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L29)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:48](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L48)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:69](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L69)
|
||||
- [coreApiWebdavUploadTUS/uploadToShare.feature:89](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavUploadTUS/uploadToShare.feature#L89)
|
||||
|
||||
#### [PROPFIND to /webdav root (old dav path) without remote.php returns html instead of xml](https://github.com/owncloud/ocis/issues/10334)
|
||||
|
||||
- [coreApiAuth/webDavAuth.feature:15](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavAuth.feature#L15)
|
||||
- [coreApiAuth/webDavAuth.feature:25](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiAuth/webDavAuth.feature#L25)
|
||||
- [coreApiShareManagementToShares/moveReceivedShare.feature:49](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature#L49)
|
||||
- [coreApiShareManagementToShares/moveReceivedShare.feature:130](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature#L130)
|
||||
- [coreApiShareManagementToShares/moveReceivedShare.feature:129](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiShareManagementToShares/moveReceivedShare.feature#L129)
|
||||
- [coreApiWebdavOperations/propfind.feature:25](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L25)
|
||||
- [coreApiWebdavOperations/propfind.feature:26](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L26)
|
||||
- [coreApiWebdavOperations/propfind.feature:36](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavOperations/propfind.feature#L36)
|
||||
|
||||
#### [Public cannot download file preview of unprotected (without password) link share without remote.php](https://github.com/owncloud/ocis/issues/10341)
|
||||
|
||||
- [coreApiWebdavPreviews/linkSharePreviews.feature:25](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/linkSharePreviews.feature#L25)
|
||||
- [coreApiWebdavPreviews/linkSharePreviews.feature:54](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavPreviews/linkSharePreviews.feature#L54)
|
||||
|
||||
#### [Trying to create .. resource with /webdav root (old dav path) without remote.php returns html](https://github.com/owncloud/ocis/issues/10339)
|
||||
|
||||
- [coreApiWebdavCopyCreate/createFileFolder.feature:176](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/createFileFolder.feature#L176)
|
||||
- [coreApiWebdavCopyCreate/createFileFolder.feature:177](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/createFileFolder.feature#L177)
|
||||
- [coreApiWebdavCopyCreate/createFileFolder.feature:196](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/createFileFolder.feature#L196)
|
||||
- [coreApiWebdavCopyCreate/createFileFolder.feature:197](https://github.com/qsfera/server/blob/main/tests/acceptance/features/coreApiWebdavCopyCreate/createFileFolder.feature#L197)
|
||||
|
||||
Note: always have an empty line at the end of this file.
|
||||
The bash script that processes this file requires that the last line has a newline on the end.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,484 @@
|
||||
@antivirus @skipOnReva
|
||||
Feature: antivirus
|
||||
As a system administrator and user
|
||||
I want to protect myself and others from known viruses
|
||||
So that I can prevent files with viruses from being uploaded
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
|
||||
|
||||
Scenario Outline: upload a normal file without virus
|
||||
Given using <dav-path-version> DAV path
|
||||
When user "Alice" uploads file "filesForUpload/textfile.txt" to "/normalfile.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And as "Alice" file "/normalfile.txt" should exist
|
||||
And the content of file "/normalfile.txt" for user "Alice" should be:
|
||||
"""
|
||||
This is a testfile.
|
||||
|
||||
Cheers.
|
||||
"""
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus
|
||||
Given using <dav-path-version> DAV path
|
||||
When user "Alice" uploads file "filesForUpload/filesWithVirus/<file-name>" to "<new-file-name>" using the WebDAV API
|
||||
# antivirus service can scan files during post-processing. on demand scanning is currently not available
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And as "Alice" file "<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus and a file without virus
|
||||
Given using <dav-path-version> DAV path
|
||||
When user "Alice" uploads file "filesForUpload/filesWithVirus/<file-name>" to "<new-file-name>" using the WebDAV API
|
||||
# antivirus service can scan files during post-processing. on demand scanning is currently not available
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" uploads file "filesForUpload/textfile.txt" to "/normalfile.txt" using the WebDAV API
|
||||
And the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And as "Alice" file "<new-file-name>" should not exist
|
||||
But as "Alice" file "/normalfile.txt" should exist
|
||||
And the content of file "/normalfile.txt" for user "Alice" should be:
|
||||
"""
|
||||
This is a testfile.
|
||||
|
||||
Cheers.
|
||||
"""
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus in chunks
|
||||
Given using <dav-path-version> DAV path
|
||||
When user "Alice" uploads the following chunks to "/myChunkedFile.txt" with old chunking and using the WebDAV API
|
||||
| number | content |
|
||||
| 1 | X5O!P%@AP[4\PZX54(P^)7C |
|
||||
| 2 | C)7}$EICAR-STANDARD-ANT |
|
||||
| 3 | IVIRUS-TEST-FILE!$H+H* |
|
||||
# antivirus service can scan files during post-processing. on demand scanning is currently not available
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in myChunkedFile.txt. Upload not possible. Virus: |
|
||||
And as "Alice" file "/myChunkedFile.txt" should not exist
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@issue-10331 @env-config
|
||||
Scenario Outline: public uploads a file with the virus to a public share
|
||||
Given using <dav-path-version> DAV path
|
||||
And the config "OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD" has been set to "false"
|
||||
And using SharingNG
|
||||
And user "Alice" has created folder "/uploadFolder"
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | uploadFolder |
|
||||
| space | Personal |
|
||||
| permissionsRole | edit |
|
||||
| expirationDateTime | 2040-01-01T23:59:59.000Z |
|
||||
When the public uploads file "filesForUpload/filesWithVirus/<file-name>" to "<new-file-name>" inside last link shared folder using the public WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
|
||||
@issue-10331
|
||||
Scenario Outline: public uploads a file with the virus to a password-protected public share
|
||||
Given using <dav-path-version> DAV path
|
||||
And using SharingNG
|
||||
And user "Alice" has created folder "/uploadFolder"
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | uploadFolder |
|
||||
| space | Personal |
|
||||
| permissionsRole | edit |
|
||||
| password | %public% |
|
||||
| expirationDateTime | 2040-01-01T23:59:59.000Z |
|
||||
When the public uploads file "filesForUpload/filesWithVirus/<file-name>" to "<new-file-name>" inside last link shared folder with password "%public%" using the public WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus to a user share
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Brian" has been created with default attributes
|
||||
And user "Alice" has created folder "uploadFolder"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | uploadFolder |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Editor |
|
||||
And user "Brian" has a share "uploadFolder" synced
|
||||
When user "Brian" uploads file "filesForUpload/filesWithVirus/<file-name>" to "/Shares/uploadFolder/<new-file-name>" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And as "Brian" file "/Shares/uploadFolder/<new-file-name>" should not exist
|
||||
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus to a group share
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Brian" has been created with default attributes
|
||||
And group "group1" has been created
|
||||
And user "Brian" has been added to group "group1"
|
||||
And user "Alice" has created folder "uploadFolder"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | uploadFolder |
|
||||
| space | Personal |
|
||||
| sharee | group1 |
|
||||
| shareType | group |
|
||||
| permissionsRole | Editor |
|
||||
And user "Brian" has a share "uploadFolder" synced
|
||||
When user "Brian" uploads file "filesForUpload/filesWithVirus/<file-name>" to "/Shares/uploadFolder/<new-file-name>" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
When the user waits for "10" seconds for postprocessing to finish
|
||||
And as "Brian" file "/Shares/uploadFolder/<new-file-name>" should not exist
|
||||
And as "Alice" file "/uploadFolder/<new-file-name>" should not exist
|
||||
Examples:
|
||||
| dav-path-version | file-name | new-file-name | message |
|
||||
| old | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| old | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| new | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| new | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
| spaces | eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| spaces | eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus to a project space
|
||||
Given using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has created a folder "uploadFolder" in space "new-space"
|
||||
When user "Alice" uploads a file "filesForUpload/filesWithVirus/<file-name>" to "/uploadFolder/<new-file-name>" in space "new-space" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification for resource "<new-file-name>" with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And for user "Alice" folder "uploadFolder" of the space "new-space" should not contain these entries:
|
||||
| <new-file-name> |
|
||||
When user "Alice" uploads a file "filesForUpload/filesWithVirus/<file-name>" to "/<new-file-name>" in space "new-space" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification for resource "<new-file-name>" with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And for user "Alice" the space "new-space" should not contain these entries:
|
||||
| /<new-file-name> |
|
||||
Examples:
|
||||
| file-name | new-file-name | message |
|
||||
| eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
|
||||
|
||||
Scenario Outline: upload a file with virus to a shared project space
|
||||
Given using spaces DAV path
|
||||
And user "Brian" has been created with default attributes
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Editor |
|
||||
When user "Brian" uploads a file "/filesForUpload/filesWithVirus/<file-name>" to "/<new-file-name>" in space "new-space" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And for user "Brian" the space "new-space" should not contain these entries:
|
||||
| /<new-file-name> |
|
||||
And for user "Alice" the space "new-space" should not contain these entries:
|
||||
| /<new-file-name> |
|
||||
Examples:
|
||||
| file-name | new-file-name | message |
|
||||
| eicar.com | virusFile1.txt | Virus found in virusFile1.txt. Upload not possible. Virus: |
|
||||
| eicar_com.zip | virusFile2.zip | Virus found in virusFile2.zip. Upload not possible. Virus: |
|
||||
|
||||
@env-config @issue-6494
|
||||
Scenario Outline: upload a file with virus by setting antivirus infected file handling config to continue
|
||||
Given the config "ANTIVIRUS_INFECTED_FILE_HANDLING" has been set to "continue"
|
||||
And using <dav-path-version> DAV path
|
||||
When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And as "Alice" file "/aFileWithVirus.txt" should exist
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@env-config
|
||||
Scenario Outline: upload a file with virus smaller than the upload threshold
|
||||
Given the config "ANTIVIRUS_MAX_SCAN_SIZE" has been set to "100"
|
||||
And using <dav-path-version> DAV path
|
||||
When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in aFileWithVirus.txt. Upload not possible. Virus: |
|
||||
And as "Alice" file "/aFileWithVirus.txt" should not exist
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@env-config
|
||||
Scenario Outline: upload a file with virus larger than the upload threshold
|
||||
Given the config "ANTIVIRUS_MAX_SCAN_SIZE" has been set to "100"
|
||||
And using <dav-path-version> DAV path
|
||||
When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar_com.zip" to "/aFileWithVirus.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And as "Alice" file "/aFileWithVirus.txt" should exist
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@issue-enterprise-5706 @issue-183 @issue-369
|
||||
Scenario Outline: upload a file with virus and get notification in different languages
|
||||
Given user "Alice" has switched the system language to "<language>" using the Graph API
|
||||
And using <dav-path-version> DAV path
|
||||
When user "Alice" uploads file "filesForUpload/filesWithVirus/eicar.com" to "/aFileWithVirus.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Alice" should get a notification with subject "<subject>" and message:
|
||||
| message |
|
||||
| <message> |
|
||||
And as "Alice" file "/aFileWithVirus.txt" should not exist
|
||||
Examples:
|
||||
| dav-path-version | language | subject | message |
|
||||
| old | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: |
|
||||
| new | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: |
|
||||
| spaces | es | Virus encontrado | Virus encontrado en aFileWithVirus.txt. La subida no ha sido posible. Virus: |
|
||||
| old | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: |
|
||||
| new | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: |
|
||||
| spaces | de | Virus gefunden | In aFileWithVirus.txt wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen. Grund: |
|
||||
|
||||
@issue-enterprise-5709
|
||||
Scenario Outline: try to create a version of file by uploading virus content
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Alice" has uploaded file with content "hello world" to "test.txt"
|
||||
And user "Alice" has uploaded file with content "hello nepal" to "test.txt"
|
||||
When user "Alice" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "test.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in test.txt. Upload not possible. Virus: |
|
||||
And as "Alice" file "/test.txt" should exist
|
||||
And the version folder of file "/test.txt" for user "Alice" should contain "1" element
|
||||
And the content of file "/test.txt" for user "Alice" should be "hello nepal"
|
||||
When user "Alice" restores version index "1" of file "/test.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And the content of file "/test.txt" for user "Alice" should be "hello world"
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@env-config @issue-10331
|
||||
Scenario Outline: try to overwrite a file with the virus content in a public link share
|
||||
Given the config "OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD" has been set to "false"
|
||||
And using <dav-path-version> DAV path
|
||||
And using SharingNG
|
||||
And user "Alice" has uploaded file with content "hello" to "test.txt"
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | test.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | edit |
|
||||
When the public overwrites file "test.txt" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" using the public WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in test.txt. Upload not possible. Virus: |
|
||||
And the content of file "/test.txt" for user "Alice" should be "hello"
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario Outline: try to overwrite a file with the virus content in group share
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Brian" has been created with default attributes
|
||||
And group "group1" has been created
|
||||
And user "Brian" has been added to group "group1"
|
||||
And user "Alice" has been added to group "group1"
|
||||
And user "Alice" has uploaded file with content "hello" to "/test.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | test.txt |
|
||||
| space | Personal |
|
||||
| sharee | group1 |
|
||||
| shareType | group |
|
||||
| permissionsRole | File Editor |
|
||||
And user "Brian" has a share "test.txt" synced
|
||||
When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "Shares/test.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in test.txt. Upload not possible. Virus: |
|
||||
And the content of file "/test.txt" for user "Alice" should be "hello"
|
||||
And the content of file "Shares/test.txt" for user "Brian" should be "hello"
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario Outline: try to overwrite a file with the virus content in user share
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Brian" has been created with default attributes
|
||||
And user "Alice" has created folder "uploadFolder"
|
||||
And user "Alice" has uploaded file with content "this is a test file." to "uploadFolder/test.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | uploadFolder |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Editor |
|
||||
And user "Brian" has a share "uploadFolder" synced
|
||||
And user "Alice" has uploaded file with content "this is a test file." to "/test.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | test.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | File Editor |
|
||||
And user "Brian" has a share "test.txt" synced
|
||||
When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "Shares/uploadFolder/test.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And user "Brian" should get a notification for resource "test.txt" with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in test.txt. Upload not possible. Virus: |
|
||||
And the content of file "Shares/uploadFolder/test.txt" for user "Brian" should be "this is a test file."
|
||||
And the content of file "uploadFolder/test.txt" for user "Alice" should be "this is a test file."
|
||||
When user "Brian" uploads file with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to "Shares/test.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And user "Brian" should get a notification for resource "test.txt" with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in test.txt. Upload not possible. Virus: |
|
||||
And the content of file "Shares/test.txt" for user "Brian" should be "this is a test file."
|
||||
And the content of file "/test.txt" for user "Alice" should be "this is a test file."
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario: try to overwrite the .space/readme.md file
|
||||
Given using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has created a folder ".space" in space "new-space"
|
||||
And user "Alice" has uploaded a file inside space "new-space" with content "Here you can add a description for this Space." to ".space/readme.md"
|
||||
When user "Alice" uploads a file inside space "new-space" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to ".space/readme.md" using the WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And user "Alice" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in readme.md. Upload not possible. Virus: |
|
||||
And for user "Alice" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space."
|
||||
|
||||
|
||||
Scenario: try to overwrite the .space/readme.md file in space share
|
||||
Given using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Brian" has been created with default attributes
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Editor |
|
||||
And user "Alice" has created a folder ".space" in space "new-space"
|
||||
And user "Alice" has uploaded a file inside space "new-space" with content "Here you can add a description for this Space." to ".space/readme.md"
|
||||
And user "Alice" has set the file ".space/readme.md" as a description in a special section of the "new-space" space
|
||||
When user "Brian" uploads a file inside space "new-space" with content "X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*" to ".space/readme.md" using the WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in readme.md. Upload not possible. Virus: |
|
||||
And for user "Brian" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space."
|
||||
And for user "Alice" the content of the file ".space/readme.md" of the space "new-space" should be "Here you can add a description for this Space."
|
||||
|
||||
|
||||
Scenario: member of a project space tries to overwrite a file with virus content
|
||||
Given using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Brian" has been created with default attributes
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Editor |
|
||||
And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "text.txt"
|
||||
When user "Brian" uploads a file "filesForUpload/filesWithVirus/eicar.com" to "text.txt" in space "new-space" using the WebDAV API
|
||||
Then the HTTP status code should be "204"
|
||||
And user "Brian" should get a notification with subject "Virus found" and message:
|
||||
| message |
|
||||
| Virus found in text.txt. Upload not possible. Virus: |
|
||||
And for user "Brian" the content of the file "/text.txt" of the space "new-space" should be "hello world"
|
||||
And for user "Alice" the content of the file "/text.txt" of the space "new-space" should be "hello world"
|
||||
@@ -0,0 +1,15 @@
|
||||
Feature: service health check
|
||||
|
||||
|
||||
Scenario: check service health
|
||||
When a user requests these URLs with "GET" and no authentication
|
||||
| endpoint | service |
|
||||
| http://%base_url_hostname%:9297/healthz | antivirus |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
|
||||
|
||||
Scenario: check service readiness
|
||||
When a user requests these URLs with "GET" and no authentication
|
||||
| endpoint | service |
|
||||
| http://%base_url_hostname%:9297/readyz | antivirus |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
@@ -0,0 +1,174 @@
|
||||
Feature: download multiple resources bundled into an archive
|
||||
As a user
|
||||
I want to be able to download multiple items at once
|
||||
So that I don't have to execute repetitive tasks
|
||||
|
||||
As a developer
|
||||
I want to be able to use the resource ID to download multiple items at once
|
||||
So that I don't have to know the full path of the resource
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
|
||||
|
||||
Scenario Outline: download a single file
|
||||
Given user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
When user "Alice" downloads the <archive-type> archive of "/textfile0.txt" using the resource id and setting these headers:
|
||||
| header | value |
|
||||
| User-Agent | <user-agent> |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded <archive-type> archive should contain these files:
|
||||
| name | content |
|
||||
| textfile0.txt | some data |
|
||||
Examples:
|
||||
| user-agent | archive-type |
|
||||
| Linux | tar |
|
||||
| Windows NT | zip |
|
||||
|
||||
|
||||
Scenario Outline: download a single folder
|
||||
Given user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "some data" to "/my_data/textfile0.txt"
|
||||
And user "Alice" has uploaded file with content "more data" to "/my_data/an_other_file.txt"
|
||||
When user "Alice" downloads the <archive-type> archive of "/my_data" using the resource id and setting these headers:
|
||||
| header | value |
|
||||
| User-Agent | <user-agent> |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded <archive-type> archive should contain these files:
|
||||
| name | content |
|
||||
| my_data/textfile0.txt | some data |
|
||||
| my_data/an_other_file.txt | more data |
|
||||
Examples:
|
||||
| user-agent | archive-type |
|
||||
| Linux | zip |
|
||||
| Windows NT | tar |
|
||||
|
||||
|
||||
Scenario: download multiple files and folders
|
||||
Given user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
And user "Alice" has uploaded file with content "other data" to "/textfile1.txt"
|
||||
And user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt"
|
||||
And user "Alice" has created folder "more_data"
|
||||
And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt"
|
||||
When user "Alice" downloads the archive of these items using the resource ids
|
||||
| textfile0.txt |
|
||||
| textfile1.txt |
|
||||
| my_data |
|
||||
| more_data |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded zip archive should contain these files:
|
||||
| name | content |
|
||||
| textfile0.txt | some data |
|
||||
| textfile1.txt | other data |
|
||||
| my_data/textfile2.txt | some data |
|
||||
| more_data/an_other_file.txt | more data |
|
||||
|
||||
|
||||
Scenario: download a single file as different user
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
When user "Brian" downloads the archive of "/textfile0.txt" of user "Alice" using the resource id
|
||||
Then the HTTP status code should be "404"
|
||||
|
||||
|
||||
Scenario: download multiple shared items as share receiver
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
And user "Alice" has uploaded file with content "other data" to "/textfile1.txt"
|
||||
And user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt"
|
||||
And user "Alice" has created folder "more_data"
|
||||
And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile0.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile0.txt" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile1.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile1.txt" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | my_data |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "my_data" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | more_data |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "more_data" synced
|
||||
When user "Brian" downloads the archive of these items using the resource ids
|
||||
| /Shares/textfile0.txt |
|
||||
| /Shares/textfile1.txt |
|
||||
| /Shares/my_data |
|
||||
| /Shares/more_data |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded zip archive should contain these files:
|
||||
| name | content |
|
||||
| textfile0.txt | some data |
|
||||
| textfile1.txt | other data |
|
||||
| my_data/textfile2.txt | some data |
|
||||
| more_data/an_other_file.txt | more data |
|
||||
|
||||
@issue-4636
|
||||
Scenario Outline: download the Shares folder as share receiver
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
And user "Alice" has uploaded file with content "other data" to "/textfile1.txt"
|
||||
And user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt"
|
||||
And user "Alice" has created folder "more_data"
|
||||
And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile0.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile0.txt" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile1.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile1.txt" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | my_data |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "my_data" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | more_data |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "more_data" synced
|
||||
When user "Brian" downloads the <archive-type> archive of "/Shares" using the resource id and setting these headers:
|
||||
| header | value |
|
||||
| User-Agent | <user-agent> |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded <archive-type> archive should contain these files:
|
||||
| name | content |
|
||||
| textfile0.txt | some data |
|
||||
| textfile1.txt | other data |
|
||||
| my_data/textfile2.txt | some data |
|
||||
| more_data/an_other_file.txt | more data |
|
||||
Examples:
|
||||
| user-agent | archive-type |
|
||||
| Linux | tar |
|
||||
| Windows NT | zip |
|
||||
@@ -0,0 +1,172 @@
|
||||
Feature: download multiple resources bundled into an archive
|
||||
As a user
|
||||
I want to be able to download multiple items at once
|
||||
So that I don't have to execute repetitive tasks
|
||||
|
||||
As a developer
|
||||
I want to be able to use the full path of the resource to download multiple items at once
|
||||
So that I don't have to know the ID of the resource
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
|
||||
@issue-4637
|
||||
Scenario Outline: download a single file
|
||||
Given user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
When user "Alice" downloads the <archive-type> archive of "/home/textfile0.txt" using the resource path and setting these headers:
|
||||
| header | value |
|
||||
| User-Agent | <user-agent> |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded <archive-type> archive should contain these files:
|
||||
| name | content |
|
||||
| textfile0.txt | some data |
|
||||
Examples:
|
||||
| user-agent | archive-type |
|
||||
| Linux | tar |
|
||||
| Windows NT | zip |
|
||||
|
||||
@issue-4637
|
||||
Scenario Outline: download a single folder
|
||||
Given user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "some data" to "/my_data/textfile0.txt"
|
||||
And user "Alice" has uploaded file with content "more data" to "/my_data/an_other_file.txt"
|
||||
When user "Alice" downloads the <archive-type> archive of "/home/my_data" using the resource path and setting these headers:
|
||||
| header | value |
|
||||
| User-Agent | <user-agent> |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded <archive-type> archive should contain these files:
|
||||
| name | content |
|
||||
| my_data/textfile0.txt | some data |
|
||||
| my_data/an_other_file.txt | more data |
|
||||
Examples:
|
||||
| user-agent | archive-type |
|
||||
| Linux | tar |
|
||||
| Windows NT | zip |
|
||||
|
||||
@issue-4637
|
||||
Scenario: download multiple files and folders
|
||||
Given user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
And user "Alice" has uploaded file with content "other data" to "/textfile1.txt"
|
||||
And user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt"
|
||||
And user "Alice" has created folder "more_data"
|
||||
And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt"
|
||||
When user "Alice" downloads the archive of these items using the resource paths
|
||||
| /home/textfile0.txt |
|
||||
| /home/textfile1.txt |
|
||||
| /home/my_data |
|
||||
| /home/more_data |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded tar archive should contain these files:
|
||||
| name | content |
|
||||
| textfile0.txt | some data |
|
||||
| textfile1.txt | other data |
|
||||
| my_data/textfile2.txt | some data |
|
||||
| more_data/an_other_file.txt | more data |
|
||||
|
||||
|
||||
Scenario: download a not existing single file
|
||||
When user "Alice" downloads the archive of "/doesnotexist.txt" of user "Alice" using the resource path
|
||||
Then the HTTP status code should be "404"
|
||||
|
||||
@issue-4637
|
||||
Scenario: download multiple shared items as share receiver
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
And user "Alice" has uploaded file with content "other data" to "/textfile1.txt"
|
||||
And user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt"
|
||||
And user "Alice" has created folder "more_data"
|
||||
And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile0.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile0.txt" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile1.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile1.txt" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | my_data |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "my_data" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | more_data |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "more_data" synced
|
||||
When user "Brian" downloads the archive of these items using the resource path
|
||||
| /home/Shares/textfile0.txt |
|
||||
| /home/Shares/textfile1.txt |
|
||||
| /home/Shares/my_data |
|
||||
| /home/Shares/more_data |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded tar archive should contain these files:
|
||||
| name | content |
|
||||
| textfile0.txt | some data |
|
||||
| textfile1.txt | other data |
|
||||
| my_data/textfile2.txt | some data |
|
||||
| more_data/an_other_file.txt | more data |
|
||||
|
||||
@issue-4637
|
||||
Scenario Outline: download the Shares folder as share receiver
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has uploaded file with content "some data" to "/textfile0.txt"
|
||||
And user "Alice" has uploaded file with content "other data" to "/textfile1.txt"
|
||||
And user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "some data" to "/my_data/textfile2.txt"
|
||||
And user "Alice" has created folder "more_data"
|
||||
And user "Alice" has uploaded file with content "more data" to "/more_data/an_other_file.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile0.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile0.txt" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile1.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile1.txt" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | my_data |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "my_data" synced
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | more_data |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "more_data" synced
|
||||
When user "Brian" downloads the <archive-type> archive of "/home/Shares" using the resource path and setting these headers:
|
||||
| header | value |
|
||||
| User-Agent | <user-agent> |
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded <archive-type> archive should contain these files:
|
||||
| name | content |
|
||||
| Shares/textfile0.txt | some data |
|
||||
| Shares/textfile1.txt | other data |
|
||||
| Shares/my_data/textfile2.txt | some data |
|
||||
| Shares/more_data/an_other_file.txt | more data |
|
||||
Examples:
|
||||
| user-agent | archive-type |
|
||||
| Linux | tar |
|
||||
| Windows NT | zip |
|
||||
@@ -0,0 +1,46 @@
|
||||
@env-config
|
||||
Feature: delay post-processing of uploaded files
|
||||
As a user
|
||||
I want to delay the post-processing of uploaded files
|
||||
So that I can check the early request
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
And async upload has been enabled with post-processing delayed to "30" seconds
|
||||
|
||||
@issue-5326
|
||||
Scenario Outline: user sends GET request to the file while it's still being processed
|
||||
Given user "Alice" has uploaded file with content "uploaded content" to "/file.txt"
|
||||
When user "Alice" requests "<dav-path>" with "GET" without retrying
|
||||
Then the HTTP status code should be "425"
|
||||
Examples:
|
||||
| dav-path |
|
||||
| /webdav/file.txt |
|
||||
| /dav/files/%username%/file.txt |
|
||||
| /dav/spaces/%spaceid%/file.txt |
|
||||
|
||||
|
||||
Scenario Outline: user sends PROPFIND request to the file while it's still being processed
|
||||
Given user "Alice" has uploaded file with content "uploaded content" to "/file.txt"
|
||||
When user "Alice" requests "<dav-path>" with "PROPFIND" without retrying
|
||||
Then the HTTP status code should be "207"
|
||||
And the value of the item "//d:response/d:propstat/d:status" in the response should be "HTTP/1.1 425 TOO EARLY"
|
||||
Examples:
|
||||
| dav-path |
|
||||
| /webdav/file.txt |
|
||||
| /dav/files/%username%/file.txt |
|
||||
| /dav/spaces/%spaceid%/file.txt |
|
||||
|
||||
|
||||
Scenario Outline: user sends PROPFIND request to the folder while files in the folder are still being processed
|
||||
Given user "Alice" has created folder "my_data"
|
||||
And user "Alice" has uploaded file with content "uploaded content" to "/my_data/file.txt"
|
||||
When user "Alice" requests "<dav-path>" with "PROPFIND" without retrying
|
||||
Then the HTTP status code should be "207"
|
||||
And as user "Alice" the value of the item "//d:status" of path "<dav-path>/" in the response should be "HTTP/1.1 200 OK"
|
||||
And as user "Alice" the value of the item "//d:status" of path "<dav-path>/file.txt" in the response should be "HTTP/1.1 425 TOO EARLY"
|
||||
Examples:
|
||||
| dav-path |
|
||||
| /webdav/my_data |
|
||||
| /dav/files/%username%/my_data |
|
||||
| /dav/spaces/%spaceid%/my_data |
|
||||
@@ -0,0 +1,131 @@
|
||||
Feature: create auth-app token
|
||||
As a user
|
||||
I want to create auth-app Tokens
|
||||
So that I can use 3rd party apps
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
|
||||
|
||||
Scenario: user creates auth-app token
|
||||
When user "Alice" creates app token with expiration time "72h" using the auth-app API
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"token",
|
||||
"expiration_date",
|
||||
"created_date",
|
||||
"label"
|
||||
],
|
||||
"properties": {
|
||||
"token": {
|
||||
"pattern": "^([a-zA-Z]+ ){5}[a-zA-Z]+$"
|
||||
},
|
||||
"label": {
|
||||
"const": "Generated via API"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@env-config
|
||||
Scenario: user lists auth-app tokens generated by different auth-app api
|
||||
Given the config "AUTH_APP_ENABLE_IMPERSONATION" has been set to "true"
|
||||
And user "Alice" has created app token with expiration time "72h" using the auth-app API
|
||||
And the administrator has created app token for user "Alice" with expiration time "72h" using the auth-app API
|
||||
And user "Alice" has created app token with expiration time "72h" using the auth-app CLI
|
||||
When user "Alice" lists all created tokens using the auth-app API
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "array",
|
||||
"minItems": 3,
|
||||
"maxItems": 3,
|
||||
"uniqueItems": true,
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"token",
|
||||
"expiration_date",
|
||||
"created_date",
|
||||
"label"
|
||||
],
|
||||
"properties": {
|
||||
"token": {
|
||||
"pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$"
|
||||
},
|
||||
"label": {
|
||||
"const": "Generated via API"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"token",
|
||||
"expiration_date",
|
||||
"created_date",
|
||||
"label"
|
||||
],
|
||||
"properties": {
|
||||
"token": {
|
||||
"pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$"
|
||||
},
|
||||
"label": {
|
||||
"const": "Generated via CLI"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"token",
|
||||
"expiration_date",
|
||||
"created_date",
|
||||
"label"
|
||||
],
|
||||
"properties": {
|
||||
"token": {
|
||||
"pattern": "^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-4[0-9A-Fa-f]{3}-[89ABab][0-9A-Fa-f]{3}-[0-9A-Fa-f]{12}$"
|
||||
},
|
||||
"label": {
|
||||
"const": "Generated via API (Impersonation)"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@env-config
|
||||
Scenario: admin creates auth-app token for other user
|
||||
Given the config "AUTH_APP_ENABLE_IMPERSONATION" has been set to "true"
|
||||
When the administrator creates app token for user "Alice" with expiration time "72h" using the auth-app API
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"token",
|
||||
"expiration_date",
|
||||
"created_date",
|
||||
"label"
|
||||
],
|
||||
"properties": {
|
||||
"token": {
|
||||
"pattern": "^([a-zA-Z]+ ){5}[a-zA-Z]+$"
|
||||
},
|
||||
"label": {
|
||||
"const": "Generated via API (Impersonation)"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
Feature: service health check
|
||||
|
||||
|
||||
Scenario: check service health
|
||||
When a user requests these URLs with "GET" and no authentication
|
||||
| endpoint | service |
|
||||
| http://%collaboration_hostname%:9304/healthz | collaboration |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
|
||||
|
||||
Scenario: check service readiness
|
||||
When a user requests these URLs with "GET" and no authentication
|
||||
| endpoint | service |
|
||||
| http://%collaboration_hostname%:9304/readyz | collaboration |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
Feature: Copy test
|
||||
As a user
|
||||
I want to check the PROPFIND response
|
||||
So that I can make sure that the response contains all the relevant values
|
||||
|
||||
|
||||
Background:
|
||||
Given these users have been created with default attributes:
|
||||
| username |
|
||||
| Alice |
|
||||
And using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
|
||||
|
||||
Scenario: check the COPY response headers
|
||||
Given user "Alice" has uploaded a file inside space "new-space" with content "some content" to "testfile.txt"
|
||||
And user "Alice" has created a folder "new" in space "new-space"
|
||||
When user "Alice" copies file "testfile.txt" from space "new-space" to "/new/testfile.txt" inside space "new-space" with following headers using the WebDAV API
|
||||
| header | value |
|
||||
| Origin | %base_url% |
|
||||
Then the HTTP status code should be "201"
|
||||
And the following headers should match these regular expressions
|
||||
| Oc-Fileid | /^[a-f0-9!\$\-]{110}$/ |
|
||||
| Access-Control-Allow-Origin | /^%base_url%$/ |
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
Feature: Propfind test
|
||||
As a user
|
||||
I want to check the PROPFIND response
|
||||
So that I can make sure that the response contains all the relevant values
|
||||
|
||||
Background:
|
||||
Given these users have been created with default attributes:
|
||||
| username |
|
||||
| Alice |
|
||||
| Brian |
|
||||
And using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
|
||||
|
||||
Scenario: space-admin checks the PROPFIND request of a space
|
||||
Given user "Alice" has uploaded a file inside space "new-space" with content "some content" to "testfile.txt"
|
||||
When user "Alice" sends PROPFIND request to space "new-space" with depth "0" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Alice" the PROPFIND response should contain a space "new-space" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:name | new-space |
|
||||
| oc:permissions | RDNVCKZP |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 12 |
|
||||
|
||||
|
||||
Scenario Outline: space member with a different role checks the PROPFIND request of a space
|
||||
Given user "Alice" has uploaded a file inside space "new-space" with content "some content" to "testfile.txt"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | <space-role> |
|
||||
When user "Brian" sends PROPFIND request to space "new-space" with depth "0" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the PROPFIND response should contain a space "new-space" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:name | new-space |
|
||||
| oc:permissions | <oc-permission> |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 12 |
|
||||
Examples:
|
||||
| space-role | oc-permission |
|
||||
| Manager | RDNVCKZP |
|
||||
| Space Editor | DNVCK |
|
||||
| Space Viewer | |
|
||||
|
||||
|
||||
Scenario Outline: space member with a different role checks the PROPFIND request of the folder in the space
|
||||
Given user "Alice" has created a folder "folderMain" in space "new-space"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | <space-role> |
|
||||
When user "Brian" sends PROPFIND request from the space "new-space" to the resource "folderMain" with depth "0" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And as user "Brian" the PROPFIND response should contain a resource "folderMain" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | folderMain |
|
||||
| oc:permissions | <oc-permission> |
|
||||
| oc:size | 0 |
|
||||
Examples:
|
||||
| space-role | oc-permission |
|
||||
| Manager | RDNVCKZP |
|
||||
| Space Editor | DNVCK |
|
||||
| Space Viewer | |
|
||||
|
||||
|
||||
Scenario Outline: space member with a different role checks the PROPFIND request of the sub-folder in the space
|
||||
Given user "Alice" has created a folder "folderMain/subFolder1/subFolder2" in space "new-space"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | <space-role> |
|
||||
When user "Brian" sends PROPFIND request from the space "new-space" to the resource "folderMain/subFolder1/subFolder2" with depth "0" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And as user "Brian" the PROPFIND response should contain a resource "subFolder2" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | subFolder2 |
|
||||
| oc:permissions | <oc-permission> |
|
||||
| oc:size | 0 |
|
||||
Examples:
|
||||
| space-role | oc-permission |
|
||||
| Manager | RDNVCKZP |
|
||||
| Space Editor | DNVCK |
|
||||
| Space Viewer | |
|
||||
|
||||
|
||||
Scenario Outline: space member with a different role checks the PROPFIND request of the file in the space
|
||||
Given user "Alice" has uploaded a file inside space "new-space" with content "some content" to "testfile.txt"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | <space-role> |
|
||||
When user "Brian" sends PROPFIND request from the space "new-space" to the resource "testfile.txt" with depth "0" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And as user "Brian" the PROPFIND response should contain a resource "testfile.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | testfile.txt |
|
||||
| oc:permissions | <oc-permission> |
|
||||
| oc:size | 12 |
|
||||
Examples:
|
||||
| space-role | oc-permission |
|
||||
| Manager | RDNVWZP |
|
||||
| Space Editor | DNVW |
|
||||
| Space Viewer | |
|
||||
|
||||
@issue-1523
|
||||
Scenario: propfind response contains a restored folder with correct name
|
||||
Given user "Alice" has created a folder "folderMain" in space "Personal"
|
||||
And user "Alice" has marked folder "folderMain" as favorite from space "Personal"
|
||||
And user "Alice" has deleted folder "folderMain"
|
||||
And user "Alice" has created a folder "folderMain" in space "Personal"
|
||||
When user "Alice" restores the folder with original path "/folderMain" to "/folderMain (1)" using the trashbin API
|
||||
And user "Alice" sends PROPFIND request to space "Personal" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And as user "Alice" the PROPFIND response should contain a resource "folderMain" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | folderMain |
|
||||
| oc:permissions | RDNVCKZP |
|
||||
| oc:size | 0 |
|
||||
| oc:favorite | 0 |
|
||||
And as user "Alice" the PROPFIND response should contain a resource "folderMain (1)" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | folderMain (1) |
|
||||
| oc:permissions | RDNVCKZP |
|
||||
| oc:size | 0 |
|
||||
| oc:favorite | 1 |
|
||||
@@ -0,0 +1,181 @@
|
||||
Feature: REPORT request to Shares space
|
||||
As a user
|
||||
I want to check the share REPORT response
|
||||
So that I can make sure that the response contains all the relevant details for shares
|
||||
|
||||
Background:
|
||||
Given these users have been created with default attributes:
|
||||
| username |
|
||||
| Alice |
|
||||
| Brian |
|
||||
And user "Alice" has created folder "/folderMain"
|
||||
And user "Alice" has created folder "/folderMain/SubFolder1"
|
||||
And user "Alice" has created folder "/folderMain/SubFolder1/subFOLDER2"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | /folderMain |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "folderMain" synced
|
||||
|
||||
|
||||
Scenario Outline: check the REPORT response of the found folder
|
||||
Given using <dav-path-version> DAV path
|
||||
When user "Brian" searches for "SubFolder1" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "SubFolder1" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | SubFolder1 |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | S |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario Outline: check the REPORT response of the found file
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Alice" has uploaded file with content "Not all those who wander are lost." to "/folderMain/SubFolder1/subFOLDER2/frodo.txt"
|
||||
And user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "/folderMain/SubFolder1/subFOLDER2/testavatar.jpg"
|
||||
When user "Brian" searches for "frodo.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "frodo.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | frodo.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | S |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 34 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
|
||||
When user "Brian" searches for "testavatar.jpg" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "testavatar.jpg" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | testavatar.jpg |
|
||||
| d:getcontenttype | image/jpeg |
|
||||
| oc:permissions | S |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:has-preview | 1 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario Outline: search for the shared folder when share is not accepted
|
||||
Given user "Brian" has disabled auto-accepting
|
||||
And using <dav-path-version> DAV path
|
||||
And user "Alice" has created folder "/folderToBrian"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | /folderToBrian |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
When user "Brian" searches for "folderToBrian" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And the search result should contain "0" entries
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@issue-9607 @issue-10329 @env-config
|
||||
Scenario Outline: check the REPORT response of a folder shared with secure viewer role
|
||||
Given using <dav-path-version> DAV path
|
||||
And the administrator has enabled the permissions role "Secure Viewer"
|
||||
And user "Alice" has created folder "/secureFolder"
|
||||
And user "Alice" has uploaded file with content "secure content" to "/secureFolder/secure.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | secureFolder |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Secure Viewer |
|
||||
And user "Brian" has a share "secureFolder" synced
|
||||
When user "Brian" searches for "secureFolder" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "secureFolder" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:shareroot | /secureFolder |
|
||||
| oc:name | secureFolder |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | SMX |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 14 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
When user "Brian" searches for "secure.txt" using the WebDAV API
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
Then the HTTP status code should be "207"
|
||||
And as user "Brian" the REPORT response should contain a resource "secure.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:shareroot | /secureFolder |
|
||||
| oc:name | secure.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SX |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 14 |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@issue-9607 @issue-10329 @env-config
|
||||
Scenario Outline: check the REPORT response of a file shared with secure viewer role
|
||||
Given using <dav-path-version> DAV path
|
||||
And the administrator has enabled the permissions role "Secure Viewer"
|
||||
And user "Alice" has uploaded file with content "secure content" to "/secure.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | secure.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Secure Viewer |
|
||||
And user "Brian" has a share "secure.txt" synced
|
||||
When user "Brian" searches for "secure.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "secure.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:shareroot | /secure.txt |
|
||||
| oc:name | secure.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SMX |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 14 |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
@@ -0,0 +1,95 @@
|
||||
Feature: REPORT request to project space
|
||||
As a user
|
||||
I want to check the REPORT response of project spaces
|
||||
So that I can make sure that the response contains all the relevant details
|
||||
|
||||
Background:
|
||||
Given these users have been created with default attributes:
|
||||
| username |
|
||||
| Alice |
|
||||
| Brian |
|
||||
And using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "findData" with the default quota using the Graph API
|
||||
|
||||
@issue-10329
|
||||
Scenario: check the response of the searched file
|
||||
Given user "Alice" has uploaded a file inside space "findData" with content "some content" to "testFile.txt"
|
||||
And user "Alice" has marked file "testFile.txt" as favorite from space "findData"
|
||||
When user "Alice" searches for "testFile.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the search result of user "Alice" should contain only these entries:
|
||||
| testFile.txt |
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Alice" the REPORT response should contain a resource "testFile.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | testFile.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | RDNVW |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 12 |
|
||||
| oc:favorite | 1 |
|
||||
|
||||
@issue-10329
|
||||
Scenario: check the response of the searched sub-file
|
||||
Given user "Alice" has created a folder "folderMain/SubFolder1/subFOLDER2" in space "findData"
|
||||
And user "Alice" has uploaded a file inside space "findData" with content "some content" to "folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt"
|
||||
When user "Alice" searches for "insideTheFolder.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the search result of user "Alice" should contain only these entries:
|
||||
| insideTheFolder.txt |
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Alice" the REPORT response should contain a resource "insideTheFolder.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | insideTheFolder.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | RDNVW |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 12 |
|
||||
|
||||
@issue-10329
|
||||
Scenario: check the response of the searched folder
|
||||
Given user "Alice" has created a folder "folderMain" in space "findData"
|
||||
And user "Alice" has marked folder "folderMain" as favorite from space "findData"
|
||||
When user "Alice" searches for "folderMain" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the search result of user "Alice" should contain only these entries:
|
||||
| folderMain |
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Alice" the REPORT response should contain a resource "folderMain" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | folderMain |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | RDNVCK |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 0 |
|
||||
| oc:favorite | 1 |
|
||||
|
||||
@issue-10329
|
||||
Scenario: check the response of the searched sub-folder
|
||||
Given user "Alice" has created a folder "folderMain/sub-folder" in space "findData"
|
||||
When user "Alice" searches for "*sub*" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the search result of user "Alice" should contain only these entries:
|
||||
| sub-folder |
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And the HTTP status code should be "207"
|
||||
And as user "Alice" the REPORT response should contain a resource "sub-folder" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:name | sub-folder |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | RDNVCK |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 0 |
|
||||
@@ -0,0 +1,98 @@
|
||||
Feature: Report test
|
||||
As a user
|
||||
I want to check the share REPORT response
|
||||
So that I can make sure that the response contains all the relevant details for shares
|
||||
|
||||
Background:
|
||||
Given these users have been created with default attributes:
|
||||
| username |
|
||||
| Alice |
|
||||
| Brian |
|
||||
And using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "find data" with the default quota using the Graph API
|
||||
And user "Alice" has created a folder "folderMain/SubFolder1/subFOLDER2" in space "find data"
|
||||
And user "Alice" has uploaded a file inside space "find data" with content "some content" to "folderMain/SubFolder1/subFOLDER2/insideTheFolder.txt"
|
||||
|
||||
|
||||
Scenario Outline: check the response of the found folder
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | folderMain |
|
||||
| space | find data |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "folderMain" synced
|
||||
When user "Brian" searches for "SubFolder1" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "SubFolder1" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | SubFolder1 |
|
||||
| d:getcontenttype | httpd/unix-directory |
|
||||
| oc:permissions | S |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| oc:size | 12 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario Outline: check the response of the found file
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | folderMain |
|
||||
| space | find data |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Editor |
|
||||
And user "Brian" has a share "folderMain" synced
|
||||
When user "Brian" searches for "insideTheFolder.txt" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And as user "Brian" the REPORT response should contain a resource "insideTheFolder.txt" with these key and value pairs:
|
||||
| key | value |
|
||||
| oc:fileid | %file_id_pattern% |
|
||||
| oc:file-parent | %file_id_pattern% |
|
||||
| oc:shareroot | /folderMain |
|
||||
| oc:name | insideTheFolder.txt |
|
||||
| d:getcontenttype | text/plain |
|
||||
| oc:permissions | SD |
|
||||
| oc:privatelink | %base_url%/f/[0-9a-z-$%]+ |
|
||||
| d:getcontentlength | 12 |
|
||||
| oc:remote-item-id | %file_id_pattern% |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario Outline: search for the shared folder when the share is not accepted
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Brian" has disabled auto-accepting
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | folderMain |
|
||||
| space | find data |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
When user "Brian" searches for "folderMain" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should match these regular expressions
|
||||
| X-Request-Id | %request_id_pattern% |
|
||||
And the search result should contain "0" entries
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
@@ -0,0 +1,168 @@
|
||||
# NOTE: set env OC_CORS_ALLOW_ORIGINS=https://aphno.badal while running КуСфера server
|
||||
@env-config
|
||||
Feature: CORS headers
|
||||
As a user
|
||||
I want to send a cross-origin request
|
||||
So that I can check if the correct headers are set
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
And the config "OC_CORS_ALLOW_ORIGINS" has been set to "https://aphno.badal"
|
||||
|
||||
@issue-5195
|
||||
Scenario Outline: CORS headers should be returned when setting CORS domain sending origin header
|
||||
Given using OCS API version "<ocs-api-version>"
|
||||
When user "Alice" sends HTTP method "GET" to OCS API endpoint "<endpoint>" with headers
|
||||
| header | value |
|
||||
| Origin | https://aphno.badal |
|
||||
Then the OCS status code should be "<ocs-status-code>"
|
||||
And the HTTP status code should be "<http-status-code>"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Access-Control-Expose-Headers | Location |
|
||||
| Access-Control-Allow-Origin | https://aphno.badal |
|
||||
Examples:
|
||||
| ocs-api-version | endpoint | ocs-status-code | http-status-code |
|
||||
| 1 | /config | 100 | 200 |
|
||||
| 2 | /config | 200 | 200 |
|
||||
| 1 | /apps/files_sharing/api/v1/shares | 100 | 200 |
|
||||
| 2 | /apps/files_sharing/api/v1/shares | 200 | 200 |
|
||||
|
||||
|
||||
Scenario Outline: CORS headers should not be returned when CORS domain does not match origin header
|
||||
Given using OCS API version "<ocs-api-version>"
|
||||
When user "Alice" sends HTTP method "GET" to OCS API endpoint "<endpoint>" with headers
|
||||
| header | value |
|
||||
| Origin | https://mero.badal |
|
||||
Then the OCS status code should be "<ocs-status-code>"
|
||||
And the HTTP status code should be "<http-status-code>"
|
||||
And the following headers should not be set
|
||||
| header |
|
||||
| Access-Control-Allow-Headers |
|
||||
| Access-Control-Expose-Headers |
|
||||
| Access-Control-Allow-Origin |
|
||||
| Access-Control-Allow-Methods |
|
||||
Examples:
|
||||
| ocs-api-version | endpoint | ocs-status-code | http-status-code |
|
||||
| 1 | /config | 100 | 200 |
|
||||
| 2 | /config | 200 | 200 |
|
||||
| 1 | /apps/files_sharing/api/v1/shares | 100 | 200 |
|
||||
| 2 | /apps/files_sharing/api/v1/shares | 200 | 200 |
|
||||
|
||||
@issue-5194
|
||||
# The Access-Control-Request-Headers need to be in lower-case and alphabetically order to comply with the rs/cors
|
||||
# package see: https://github.com/rs/cors/commit/4c32059b2756926619f6bf70281b91be7b5dddb2#diff-bf80d8fbedf172fab9ba2604da7f7be972e48b2f78a8d0cd21619d5f93665895R367
|
||||
Scenario Outline: CORS headers should be returned when an preflight request is sent
|
||||
Given using OCS API version "<ocs-api-version>"
|
||||
When user "Alice" sends HTTP method "OPTIONS" to OCS API endpoint "<endpoint>" with headers
|
||||
| header | value |
|
||||
| Origin | https://aphno.badal |
|
||||
| Access-Control-Request-Headers | accept,authorization,cache-control,content-type,depth,destination,if-match,if-none-match,ocs-apirequest,origin,overwrite,tus-checksum-algorithm,tus-resumable,upload-checksum,upload-concat,upload-defer-length,upload-expires,upload-length,upload-metadata,upload-offset,x-http-method-override,x-request-id,x-requested-with |
|
||||
| Access-Control-Request-Method | <request-method> |
|
||||
And the HTTP status code should be "204"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Access-Control-Allow-Headers | accept,authorization,cache-control,content-type,depth,destination,if-match,if-none-match,ocs-apirequest,origin,overwrite,tus-checksum-algorithm,tus-resumable,upload-checksum,upload-concat,upload-defer-length,upload-expires,upload-length,upload-metadata,upload-offset,x-http-method-override,x-request-id,x-requested-with |
|
||||
| Access-Control-Allow-Origin | https://aphno.badal |
|
||||
| Access-Control-Allow-Methods | <request-method> |
|
||||
Examples:
|
||||
| ocs-api-version | endpoint | request-method |
|
||||
| 1 | /apps/files_sharing/api/v1/shares | GET |
|
||||
| 2 | /apps/files_sharing/api/v1/shares | PUT |
|
||||
| 1 | /apps/files_sharing/api/v1/shares | DELETE |
|
||||
| 2 | /apps/files_sharing/api/v1/shares | POST |
|
||||
|
||||
|
||||
Scenario: CORS headers should be returned when setting CORS domain sending origin header in the Graph api
|
||||
When user "Alice" lists all available spaces with headers using the Graph API
|
||||
| header | value |
|
||||
| Origin | https://aphno.badal |
|
||||
Then the HTTP status code should be "200"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Access-Control-Allow-Origin | https://aphno.badal |
|
||||
|
||||
@issue-8231
|
||||
Scenario Outline: CORS headers should be returned when setting CORS domain sending origin header in the Webdav api
|
||||
Given using <dav-path-version> DAV path
|
||||
When user "Alice" sends PROPFIND request to space "Personal" with headers using the WebDAV API
|
||||
| header | value |
|
||||
| Origin | https://aphno.badal |
|
||||
Then the HTTP status code should be "207"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Access-Control-Allow-Origin | https://aphno.badal |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario: CORS headers should be returned when setting CORS domain sending origin header in the settings api
|
||||
When user "Alice" lists values-list with headers using the Settings API
|
||||
| header | value |
|
||||
| Origin | https://aphno.badal |
|
||||
Then the HTTP status code should be "201"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Access-Control-Allow-Origin | https://aphno.badal |
|
||||
|
||||
@issue-8380
|
||||
Scenario: CORS headers should be returned when uploading file using Tus and when CORS domain sending origin header in the Webdav api
|
||||
Given user "Alice" has created a new TUS resource in the space "Personal" with the following headers:
|
||||
| Upload-Length | 5 |
|
||||
# dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt
|
||||
| Upload-Metadata | filename dGV4dEZpbGUudHh0 |
|
||||
| Tus-Resumable | 1.0.0 |
|
||||
| Origin | https://aphno.badal |
|
||||
When user "Alice" sends a chunk to the last created TUS Location with data "01234" with the following headers:
|
||||
| Origin | https://aphno.badal |
|
||||
| Upload-Checksum | MD5 4100c4d44da9177247e44a5fc1546778 |
|
||||
| Upload-Offset | 0 |
|
||||
Then the HTTP status code should be "204"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Access-Control-Allow-Origin | https://aphno.badal |
|
||||
And for user "Alice" the content of the file "/textFile.txt" of the space "Personal" should be "01234"
|
||||
|
||||
@issue-8380
|
||||
Scenario: uploading file using Tus using different CORS headers
|
||||
Given user "Alice" has created a new TUS resource in the space "Personal" with the following headers:
|
||||
| Upload-Length | 5 |
|
||||
# dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt
|
||||
| Upload-Metadata | filename dGV4dEZpbGUudHh0 |
|
||||
| Tus-Resumable | 1.0.0 |
|
||||
| Origin | https://something.else |
|
||||
When user "Alice" sends a chunk to the last created TUS Location with data "01234" with the following headers:
|
||||
| Origin | https://something.else |
|
||||
| Upload-Checksum | MD5 4100c4d44da9177247e44a5fc1546778 |
|
||||
| Upload-Offset | 0 |
|
||||
Then the HTTP status code should be "403"
|
||||
|
||||
@issue-8380
|
||||
# The Access-Control-Request-Headers need to be in lower-case and alphabetically order to comply with the rs/cors
|
||||
# package see: https://github.com/rs/cors/commit/4c32059b2756926619f6bf70281b91be7b5dddb2#diff-bf80d8fbedf172fab9ba2604da7f7be972e48b2f78a8d0cd21619d5f93665895R367
|
||||
Scenario Outline: CORS headers should be returned when an preflight request is sent to Tus upload
|
||||
Given user "Alice" has created a new TUS resource in the space "Personal" with the following headers:
|
||||
| Upload-Length | 5 |
|
||||
# dGV4dEZpbGUudHh0 is the base64 encode of textFile.txt
|
||||
| Upload-Metadata | filename dGV4dEZpbGUudHh0 |
|
||||
| Tus-Resumable | 1.0.0 |
|
||||
When user "Alice" sends HTTP method "OPTIONS" to URL "<endpoint>" with headers
|
||||
| header | value |
|
||||
| Origin | https://aphno.badal |
|
||||
| Access-Control-Request-Headers | accept,authorization,cache-control,content-type,depth,destination,if-match,if-none-match,ocs-apirequest,origin,overwrite,tus-checksum-algorithm,tus-resumable,upload-checksum,upload-concat,upload-defer-length,upload-expires,upload-length,upload-metadata,upload-offset,x-http-method-override,x-request-id,x-requested-with |
|
||||
| Access-Control-Request-Method | <request-method> |
|
||||
And the HTTP status code should be "204"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Access-Control-Allow-Headers | accept,authorization,cache-control,content-type,depth,destination,if-match,if-none-match,ocs-apirequest,origin,overwrite,tus-checksum-algorithm,tus-resumable,upload-checksum,upload-concat,upload-defer-length,upload-expires,upload-length,upload-metadata,upload-offset,x-http-method-override,x-request-id,x-requested-with |
|
||||
| Access-Control-Allow-Origin | https://aphno.badal |
|
||||
| Access-Control-Allow-Methods | <request-method> |
|
||||
Examples:
|
||||
| endpoint | request-method |
|
||||
| /%tus_upload_location% | PUT |
|
||||
| /%tus_upload_location% | POST |
|
||||
| /%tus_upload_location% | HEAD |
|
||||
| /%tus_upload_location% | PATCH |
|
||||
@@ -0,0 +1,151 @@
|
||||
@env-config
|
||||
Feature: PROPFIND with depth:infinity
|
||||
As a user
|
||||
I want to retrieve all properties of a resource
|
||||
So that I can get the information about a resource
|
||||
|
||||
Background:
|
||||
Given the config "OCDAV_ALLOW_PROPFIND_DEPTH_INFINITY" has been set to "true"
|
||||
And user "Alice" has been created with default attributes
|
||||
And user "Alice" has created the following folders
|
||||
| path |
|
||||
| simple-folder |
|
||||
| simple-folder/simple-folder1 |
|
||||
| simple-folder/simple-empty-folder |
|
||||
| simple-folder/simple-folder1/simple-folder2 |
|
||||
And user "Alice" has uploaded the following files with content "simple-test-content"
|
||||
| path |
|
||||
| textfile0.txt |
|
||||
| welcome.txt |
|
||||
| simple-folder/textfile0.txt |
|
||||
| simple-folder/welcome.txt |
|
||||
| simple-folder/simple-folder1/textfile0.txt |
|
||||
| simple-folder/simple-folder1/welcome.txt |
|
||||
| simple-folder/simple-folder1/simple-folder2/textfile0.txt |
|
||||
| simple-folder/simple-folder1/simple-folder2/welcome.txt |
|
||||
|
||||
|
||||
Scenario Outline: get the list of resources with depth infinity
|
||||
Given using <dav-path-version> DAV path
|
||||
When user "Alice" lists the resources in "/" with depth "infinity" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the last DAV response for user "Alice" should contain these nodes
|
||||
| name |
|
||||
| textfile0.txt |
|
||||
| welcome.txt |
|
||||
| simple-folder/ |
|
||||
| simple-folder/textfile0.txt |
|
||||
| simple-folder/welcome.txt |
|
||||
| simple-folder/simple-empty-folder/ |
|
||||
| simple-folder/simple-folder1/ |
|
||||
| simple-folder/simple-folder1/simple-folder2 |
|
||||
| simple-folder/simple-folder1/textfile0.txt |
|
||||
| simple-folder/simple-folder1/welcome.txt |
|
||||
| simple-folder/simple-folder1/simple-folder2/textfile0.txt |
|
||||
| simple-folder/simple-folder1/simple-folder2/welcome.txt |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
|
||||
Scenario Outline: get the list of resources of a folder with depth infinity
|
||||
Given using <dav-path-version> DAV path
|
||||
When user "Alice" lists the resources in "simple-folder" with depth "infinity" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the last DAV response for user "Alice" should contain these nodes
|
||||
| name |
|
||||
| simple-folder/textfile0.txt |
|
||||
| simple-folder/welcome.txt |
|
||||
| simple-folder/simple-folder1/ |
|
||||
| simple-folder/simple-folder1/simple-folder2 |
|
||||
| simple-folder/simple-folder1/textfile0.txt |
|
||||
| simple-folder/simple-folder1/welcome.txt |
|
||||
| simple-folder/simple-folder1/simple-folder2/textfile0.txt |
|
||||
| simple-folder/simple-folder1/simple-folder2/welcome.txt |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| old |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@issue-10331
|
||||
Scenario: get the list of resources in a folder shared through public link with depth infinity
|
||||
Given using SharingNG
|
||||
And the following configs have been set:
|
||||
| config | value |
|
||||
| OCDAV_ALLOW_PROPFIND_DEPTH_INFINITY | true |
|
||||
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | simple-folder |
|
||||
| space | Personal |
|
||||
| permissionsRole | view |
|
||||
When the public lists the resources in the last created public link with depth "infinity" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the last public link DAV response should contain these nodes
|
||||
| name |
|
||||
| textfile0.txt |
|
||||
| welcome.txt |
|
||||
| simple-folder1/ |
|
||||
| simple-folder1/welcome.txt |
|
||||
| simple-folder1/simple-folder2 |
|
||||
| simple-folder1/textfile0.txt |
|
||||
| simple-folder1/simple-folder2/textfile0.txt |
|
||||
| simple-folder1/simple-folder2/welcome.txt |
|
||||
|
||||
|
||||
Scenario Outline: get the list of files in the trashbin with depth infinity
|
||||
Given using <dav-path-version> DAV path
|
||||
And user "Alice" has deleted the following resources
|
||||
| path |
|
||||
| textfile0.txt |
|
||||
| welcome.txt |
|
||||
| simple-folder/ |
|
||||
When user "Alice" lists the resources in the trashbin with depth "infinity" using the WebDAV API
|
||||
Then the HTTP status code should be "207"
|
||||
And the trashbin DAV response should contain these nodes
|
||||
| name |
|
||||
| textfile0.txt |
|
||||
| welcome.txt |
|
||||
| simple-folder/ |
|
||||
| simple-folder/textfile0.txt |
|
||||
| simple-folder/welcome.txt |
|
||||
| simple-folder/simple-folder1/textfile0.txt |
|
||||
| simple-folder/simple-folder1/welcome.txt |
|
||||
| simple-folder/simple-folder1/simple-folder2/textfile0.txt |
|
||||
| simple-folder/simple-folder1/simple-folder2/welcome.txt |
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| new |
|
||||
| spaces |
|
||||
|
||||
@issue-10331
|
||||
Scenario: get the list of resources in a folder shared through public link with depth infinity when depth infinity is not allowed
|
||||
Given the following configs have been set:
|
||||
| config | value |
|
||||
| OCDAV_ALLOW_PROPFIND_DEPTH_INFINITY | false |
|
||||
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||
And using SharingNG
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | simple-folder |
|
||||
| space | Personal |
|
||||
| permissionsRole | view |
|
||||
When the public lists the resources in the last created public link with depth "infinity" using the WebDAV API
|
||||
Then the HTTP status code should be "400"
|
||||
|
||||
|
||||
Scenario Outline: get the list of files in the trashbin with depth infinity when depth infinity is not allowed
|
||||
Given the config "OCDAV_ALLOW_PROPFIND_DEPTH_INFINITY" has been set to "false"
|
||||
And using <dav-path-version> DAV path
|
||||
And user "Alice" has deleted the following resources
|
||||
| path |
|
||||
| textfile0.txt |
|
||||
| welcome.txt |
|
||||
| simple-folder |
|
||||
When user "Alice" lists the resources in the trashbin with depth "infinity" using the WebDAV API
|
||||
Then the HTTP status code should be "400"
|
||||
Examples:
|
||||
| dav-path-version |
|
||||
| new |
|
||||
| spaces |
|
||||
@@ -0,0 +1,66 @@
|
||||
Feature: Download file in project space
|
||||
As a user with different role
|
||||
I want to be able to download files
|
||||
So that I can have it in my local storage
|
||||
|
||||
Background:
|
||||
Given these users have been created with default attributes:
|
||||
| username |
|
||||
| Alice |
|
||||
| Brian |
|
||||
| Bob |
|
||||
And using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "download file" with the default quota using the Graph API
|
||||
And user "Alice" has uploaded a file inside space "download file" with content "some content" to "file.txt"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | download file |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Editor |
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | download file |
|
||||
| sharee | Bob |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Viewer |
|
||||
|
||||
|
||||
Scenario Outline: user downloads a file in the project space
|
||||
When user "<user>" downloads the file "file.txt" of the space "download file" using the WebDAV API
|
||||
Then the HTTP status code should be "200"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Content-Length | 12 |
|
||||
Examples:
|
||||
| user |
|
||||
| Alice |
|
||||
| Brian |
|
||||
| Bob |
|
||||
|
||||
|
||||
Scenario Outline: users with role manager and editor can download an old version of the file in the project space
|
||||
Given user "Alice" has uploaded a file inside space "download file" with content "new content" to "file.txt"
|
||||
And user "Alice" has uploaded a file inside space "download file" with content "newest content" to "file.txt"
|
||||
When user "<user>" downloads version of the file "file.txt" with the index "1" of the space "download file" using the WebDAV API
|
||||
Then the HTTP status code should be "200"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Content-Length | 11 |
|
||||
When user "<user>" downloads version of the file "file.txt" with the index "2" of the space "download file" using the WebDAV API
|
||||
Then the HTTP status code should be "200"
|
||||
And the following headers should be set
|
||||
| header | value |
|
||||
| Content-Length | 12 |
|
||||
Examples:
|
||||
| user |
|
||||
| Alice |
|
||||
| Brian |
|
||||
|
||||
|
||||
Scenario: user with role viewer cannot get versions of a file in the project space
|
||||
Given user "Alice" has uploaded a file inside space "download file" with content "new content" to "file.txt"
|
||||
And user "Alice" has uploaded a file inside space "download file" with content "newest content" to "file.txt"
|
||||
When user "Bob" tries to get versions of the file "file.txt" from the space "download file" using the WebDAV API
|
||||
Then the HTTP status code should be "403"
|
||||
When user "Bob" tries to download version of the file "file.txt" with the index "1" of the space "download file" using the WebDAV API
|
||||
Then the HTTP status code should be "403"
|
||||
@@ -0,0 +1,63 @@
|
||||
Feature: Download space
|
||||
As a user
|
||||
I want to download space
|
||||
So that I can store it locally
|
||||
|
||||
|
||||
Background:
|
||||
Given these users have been created with default attributes:
|
||||
| username |
|
||||
| Alice |
|
||||
| Brian |
|
||||
And using spaces DAV path
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And user "Alice" has created a space "Project-space" with the default quota using the Graph API
|
||||
And user "Alice" has uploaded a file inside space "Project-space" with content "some data" to "file1.txt"
|
||||
And user "Alice" has created a folder ".space" in space "Project-space"
|
||||
And user "Alice" has uploaded a file inside space "Project-space" with content "space description" to ".space/readme.md"
|
||||
|
||||
|
||||
Scenario: user downloads a space
|
||||
Given user "Alice" has uploaded a file inside space "Project-space" with content "other data" to "file2.txt"
|
||||
When user "Alice" downloads the space "Project-space" using the WebDAV API
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded "zip" archive should contain these files:
|
||||
| name | content |
|
||||
| file1.txt | some data |
|
||||
| file2.txt | other data |
|
||||
| .space/readme.md | space description |
|
||||
|
||||
|
||||
Scenario Outline: user downloads a shared space (shared by others)
|
||||
Given user "Alice" has sent the following space share invitation:
|
||||
| space | Project-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | <space-role> |
|
||||
When user "Brian" downloads the space "Project-space" using the WebDAV API
|
||||
Then the HTTP status code should be "200"
|
||||
And the downloaded "zip" archive should contain these files:
|
||||
| name | content |
|
||||
| file1.txt | some data |
|
||||
| .space/readme.md | space description |
|
||||
Examples:
|
||||
| space-role |
|
||||
| Manager |
|
||||
| Space Editor |
|
||||
| Space Viewer |
|
||||
|
||||
|
||||
Scenario Outline: admin/space-admin tries to download a space that they do not have access to
|
||||
Given the administrator has assigned the role "<user-role>" to user "Brian" using the Graph API
|
||||
When user "Brian" tries to download the space "Project-space" owned by user "Alice" using the WebDAV API
|
||||
Then the HTTP status code should be "404"
|
||||
Examples:
|
||||
| user-role |
|
||||
| Admin |
|
||||
| Space Admin |
|
||||
|
||||
|
||||
Scenario: user tries to download disabled space
|
||||
Given user "Alice" has disabled a space "Project-space"
|
||||
When user "Alice" tries to download the space "Project-space" using the WebDAV API
|
||||
Then the HTTP status code should be "404"
|
||||
@@ -0,0 +1,72 @@
|
||||
Feature: change role
|
||||
As an admin
|
||||
I want to change the role of user
|
||||
So that I can manage the role of user
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
|
||||
|
||||
Scenario Outline: admin user changes the role of another user with different roles
|
||||
Given user "Brian" has been created with default attributes
|
||||
And the administrator has assigned the role "Admin" to user "Alice" using the Graph API
|
||||
And the administrator has assigned the role "<user-role>" to user "Brian" using the Graph API
|
||||
When user "Alice" changes the role of user "Brian" to role "<new-user-role>" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And user "Brian" should have the role "<new-user-role>"
|
||||
Examples:
|
||||
| user-role | new-user-role |
|
||||
| Admin | Admin |
|
||||
| Admin | Space Admin |
|
||||
| Admin | User |
|
||||
| Admin | User Light |
|
||||
| Space Admin | Admin |
|
||||
| Space Admin | Space Admin |
|
||||
| Space Admin | User |
|
||||
| Space Admin | User Light |
|
||||
| User | Admin |
|
||||
| User | Space Admin |
|
||||
| User | User |
|
||||
| User | User Light |
|
||||
| User Light | Admin |
|
||||
| User Light | Space Admin |
|
||||
| User Light | User |
|
||||
| User Light | User Light |
|
||||
|
||||
|
||||
Scenario Outline: admin user tries to change his/her own role
|
||||
Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API
|
||||
When user "Alice" tries to change the role of user "Alice" to role "<new-user-role>" using the Graph API
|
||||
Then the HTTP status code should be "403"
|
||||
And user "Alice" should have the role "Admin"
|
||||
Examples:
|
||||
| new-user-role |
|
||||
| Space Admin |
|
||||
| User |
|
||||
| User Light |
|
||||
| Admin |
|
||||
|
||||
|
||||
Scenario Outline: non-admin cannot change the user role
|
||||
Given the administrator has assigned the role "<user-role>" to user "Alice" using the Graph API
|
||||
And user "Brian" has been created with default attributes
|
||||
When user "Alice" tries to change the role of user "Alice" to role "Admin" using the Graph API
|
||||
Then the HTTP status code should be "403"
|
||||
And user "Brian" should have the role "User"
|
||||
Examples:
|
||||
| user-role |
|
||||
| Space Admin |
|
||||
| User |
|
||||
| User Light |
|
||||
|
||||
|
||||
Scenario Outline: non-admin user tries to change the role of nonexistent user
|
||||
Given the administrator has assigned the role "<user-role>" to user "Alice" using the Graph API
|
||||
And user "Brian" has been created with default attributes
|
||||
When user "Alice" tries to change the role of user "nonexistent" to role "Admin" using the Graph API
|
||||
Then the HTTP status code should be "403"
|
||||
Examples:
|
||||
| user-role |
|
||||
| Space Admin |
|
||||
| User |
|
||||
| User Light |
|
||||
@@ -0,0 +1,405 @@
|
||||
@env-config
|
||||
Feature: enforce password on public link
|
||||
As a user
|
||||
I want to enforce passwords on public links shared with upload, edit, or contribute permission
|
||||
So that the password is required to access the contents of the link
|
||||
|
||||
Password requirements. set by default:
|
||||
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | true |
|
||||
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 8 |
|
||||
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 1 |
|
||||
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 1 |
|
||||
| OC_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
||||
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 1 |
|
||||
|
||||
Background:
|
||||
And user "Alice" has been created with default attributes
|
||||
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
|
||||
|
||||
|
||||
Scenario Outline: create a public link without a password when enforce-password for writable share is enabled
|
||||
Given the following configs have been set:
|
||||
| config | value |
|
||||
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||
When user "Alice" creates the following resource link share using the Graph API:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | <permissions-role> |
|
||||
Then the HTTP status code should be "<status-code>"
|
||||
Examples:
|
||||
| permissions-role | status-code |
|
||||
| view | 200 |
|
||||
| edit | 400 |
|
||||
|
||||
|
||||
Scenario: try to update a public link to edit permission without a password
|
||||
Given the following configs have been set:
|
||||
| config | value |
|
||||
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | view |
|
||||
When user "Alice" tries to update the last public link share using the permissions endpoint of the Graph API:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | edit |
|
||||
Then the HTTP status code should be "400"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"error"
|
||||
],
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"code",
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"pattern": "invalidRequest"
|
||||
},
|
||||
"message": {
|
||||
"const": "password protection is enforced"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@issue-2048
|
||||
Scenario: update a public link to edit permission. Need set pasword first
|
||||
Given the following configs have been set:
|
||||
| config | value |
|
||||
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | view |
|
||||
When user "Alice" sets the following password for the last link share using the Graph API:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| password | %public% |
|
||||
Then the HTTP status code should be "200"
|
||||
And user "Alice" updates the last public link share using the permissions endpoint of the Graph API:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | edit |
|
||||
And the HTTP status code should be "200"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"hasPassword",
|
||||
"link"
|
||||
],
|
||||
"properties": {
|
||||
"hasPassword": { "const": true },
|
||||
"link": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": { "const": "edit" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@issue-9724 @issue-10331
|
||||
Scenario: create a public link with a password in accordance with the password policy
|
||||
Given the following configs have been set:
|
||||
| config | value |
|
||||
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
||||
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
||||
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
||||
| OC_PASSWORD_POLICY_MIN_DIGITS | 2 |
|
||||
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
||||
When user "Alice" creates the following resource link share using the Graph API:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | edit |
|
||||
| password | 3s:5WW9uE5h=A |
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"hasPassword",
|
||||
"id",
|
||||
"link"
|
||||
],
|
||||
"properties": {
|
||||
"hasPassword": { "const": true },
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z]{15}$"
|
||||
},
|
||||
"link": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": { "const": "edit" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario: try to create a public link with a password that does not comply with the password policy
|
||||
Given the following configs have been set:
|
||||
| config | value |
|
||||
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
||||
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
||||
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
||||
| OC_PASSWORD_POLICY_MIN_DIGITS | 2 |
|
||||
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
||||
When user "Alice" tries to create the following resource link share using the Graph API:
|
||||
| space | Personal |
|
||||
| resource | testfile.txt |
|
||||
| permissionsRole | edit |
|
||||
| password | Pas1 |
|
||||
Then the HTTP status code should be "400"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["error"],
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"code",
|
||||
"innererror",
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"code": { "const": "invalidRequest" },
|
||||
"innererror": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"date",
|
||||
"request-id"
|
||||
]
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"pattern": "at least 13 characters are required\\s+at least 3 lowercase letters are required\\s+at least 2 uppercase letters are required\\s+at least 2 numbers are required\\s+at least 2 special characters are required\\s+[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~]+"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@issue-9724 @issue-10331
|
||||
Scenario: update a public link with a password in accordance with the password policy
|
||||
Given the following configs have been set:
|
||||
| config | value |
|
||||
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
||||
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
||||
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
||||
| OC_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
||||
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | view |
|
||||
When user "Alice" sets the following password for the last link share using the Graph API:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| password | 6a0Q;A3 +i^m[ |
|
||||
Then the HTTP status code should be "200"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [ "hasPassword" ],
|
||||
"properties": {
|
||||
"hasPassword": { "const": true }
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario Outline: try to update a public link with a password that does not comply with the password policy
|
||||
Given the following configs have been set:
|
||||
| config | value |
|
||||
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
|
||||
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
|
||||
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
|
||||
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
|
||||
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
|
||||
| OC_PASSWORD_POLICY_MIN_DIGITS | 1 |
|
||||
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
|
||||
And using OCS API version "<ocs-api-version>"
|
||||
And using SharingNG
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | view |
|
||||
When user "Alice" updates the last public link share using the sharing API with
|
||||
| permissions | 3 |
|
||||
| password | Pws^ |
|
||||
And user "Alice" tries to set the following password for the last link share using the Graph API:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| password | Pws^ |
|
||||
Then the HTTP status code should be "400"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [ "error" ],
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "object",
|
||||
"required": [ "message" ],
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"pattern": "at least 13 characters are required\\s+at least 3 lowercase letters are required\\s+at least 2 uppercase letters are required\\s+at least 1 numbers are required\\s+at least 2 special characters are required\\s+[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~]+"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
@issue-9724 @issue-10331
|
||||
Scenario Outline: create a public link with a password in accordance with the password policy (valid cases)
|
||||
Given the config "<config>" has been set to "<config-value>"
|
||||
When user "Alice" creates the following resource link share using the Graph API:
|
||||
| space | Personal |
|
||||
| resource | testfile.txt |
|
||||
| permissionsRole | view |
|
||||
| password | <password> |
|
||||
Then the HTTP status code should be "200"
|
||||
Examples:
|
||||
| config | config-value | password |
|
||||
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 4 | Ps-1 |
|
||||
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 14 | Ps1:with space |
|
||||
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 4 | PS1:test |
|
||||
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 3 | PS1:TeƒsT |
|
||||
| OC_PASSWORD_POLICY_MIN_DIGITS | 2 | PS1:test2 |
|
||||
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 | PS1:test pass |
|
||||
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 33 | pS1! #$%&'()*+,-./:;<=>?@[\]^_`{ }~ |
|
||||
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 5 | 1sameCharacterShouldWork!!!!! |
|
||||
|
||||
|
||||
Scenario Outline: try to create a public link with a password that does not comply with the password policy (invalid cases)
|
||||
When user "Alice" tries to create the following resource link share using the Graph API:
|
||||
| space | Personal |
|
||||
| resource | testfile.txt |
|
||||
| permissionsRole | view |
|
||||
| password | <password> |
|
||||
Then the HTTP status code should be "400"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [ "error" ],
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "object",
|
||||
"required": [ "message" ],
|
||||
"properties": {
|
||||
"message": {
|
||||
"const": "<message>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Examples:
|
||||
| password | message |
|
||||
| 1Pw: | at least 8 characters are required |
|
||||
| 1P:12345 | at least 1 lowercase letters are required |
|
||||
| test-123 | at least 1 uppercase letters are required |
|
||||
| Test-psw | at least 1 numbers are required |
|
||||
|
||||
|
||||
Scenario Outline: update a public link with a password that is listed in the Banned-Password-List
|
||||
Given the config "OC_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/woodpecker/banned-password-list.txt"
|
||||
And user "Alice" has created the following resource link share:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| permissionsRole | view |
|
||||
| password | %public% |
|
||||
And user "Alice" tries to set the following password for the last link share using the Graph API:
|
||||
| resource | testfile.txt |
|
||||
| space | Personal |
|
||||
| password | <password> |
|
||||
Then the HTTP status code should be "400"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [ "error" ],
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "object",
|
||||
"required": [ "message" ],
|
||||
"properties": {
|
||||
"message": { "const": "<message>" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Examples:
|
||||
| password | message |
|
||||
| 123 | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||
| password | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||
| КуСфера | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||
|
||||
|
||||
Scenario Outline: create a public link with a password that is listed in the Banned-Password-List
|
||||
Given the config "OC_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/woodpecker/banned-password-list.txt"
|
||||
When user "Alice" tries to create the following resource link share using the Graph API:
|
||||
| space | Personal |
|
||||
| resource | testfile.txt |
|
||||
| permissionsRole | view |
|
||||
| password | <password> |
|
||||
Then the HTTP status code should be "400"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [ "error" ],
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "object",
|
||||
"required": [ "message" ],
|
||||
"properties": {
|
||||
"message": { "const": "<message>" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
Examples:
|
||||
| password | message |
|
||||
| 123 | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||
| password | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||
| КуСфера | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
|
||||
@@ -0,0 +1,618 @@
|
||||
Feature: favorites
|
||||
As a user
|
||||
I want to check that I can mark and unmark files and folders as favorites using the Graph API
|
||||
|
||||
Background:
|
||||
Given user "Alice" has been created with default attributes
|
||||
|
||||
|
||||
Scenario Outline: add a file to favorites in the personal space
|
||||
Given user "Alice" has created folder "parent"
|
||||
And user "Alice" has uploaded file "filesForUpload/<file>" to "/parent/<file>"
|
||||
When user "Alice" marks file "parent/<file>" as favorite from space "Personal" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"eTag",
|
||||
"file",
|
||||
"id",
|
||||
"lastModifiedDateTime",
|
||||
"name",
|
||||
"parentReference",
|
||||
"size"
|
||||
],
|
||||
"properties": {
|
||||
"eTag": {
|
||||
"type": "string",
|
||||
"pattern": "%etag_pattern%"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"required": ["mimeType"],
|
||||
"properties": {
|
||||
"mimeType": {
|
||||
"const": "<mimeType>"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"const": "<file>"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"driveId",
|
||||
"driveType",
|
||||
"id",
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string",
|
||||
"pattern": "^%space_id_pattern%$"
|
||||
},
|
||||
"driveType": {
|
||||
"const": "personal"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"name": {
|
||||
"const": "parent"
|
||||
},
|
||||
"path": {
|
||||
"const": "/parent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"const": <size>
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
And as user "Alice" file "parent/<file>" should be favorited
|
||||
Examples:
|
||||
| file | size | mimeType |
|
||||
| simple.odt | 10119 | application/vnd.oasis.opendocument.text |
|
||||
| testavatar.jpg | 45343 | image/jpeg |
|
||||
| simple.pdf | 17684 | application/pdf |
|
||||
|
||||
|
||||
Scenario: add a folder to favorites in the personal space
|
||||
Given user "Alice" has created folder "parent"
|
||||
And user "Alice" has uploaded file with content "first" to "/parent/first.txt"
|
||||
And user "Alice" has uploaded file with content "second" to "/parent/second.txt"
|
||||
When user "Alice" marks folder "parent" as favorite from space "Personal" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"eTag",
|
||||
"folder",
|
||||
"id",
|
||||
"lastModifiedDateTime",
|
||||
"name",
|
||||
"parentReference",
|
||||
"size"
|
||||
],
|
||||
"properties": {
|
||||
"eTag": {
|
||||
"type": "string",
|
||||
"pattern": "%etag_pattern%"
|
||||
},
|
||||
"folder": {
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"const": "parent"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"driveId",
|
||||
"driveType",
|
||||
"id",
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string",
|
||||
"pattern": "^%space_id_pattern%$"
|
||||
},
|
||||
"driveType": {
|
||||
"const": "personal"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"name": {
|
||||
"const": "/"
|
||||
},
|
||||
"path": {
|
||||
"const": "/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"const": 11
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
And as user "Alice" folder "parent" should be favorited
|
||||
|
||||
|
||||
Scenario: add a shared file to favorites
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has uploaded file with content "КуСфера test text file" to "textfile.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | textfile.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile.txt" synced
|
||||
When user "Brian" marks file "textfile.txt" as favorite from space "Shares" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"eTag",
|
||||
"file",
|
||||
"id",
|
||||
"lastModifiedDateTime",
|
||||
"name",
|
||||
"parentReference",
|
||||
"size"
|
||||
],
|
||||
"properties": {
|
||||
"eTag": {
|
||||
"type": "string",
|
||||
"pattern": "%etag_pattern%"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"required": ["mimeType"],
|
||||
"properties": {
|
||||
"mimeType": {
|
||||
"const": "text/plain"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"const": "textfile.txt"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"driveId",
|
||||
"driveType",
|
||||
"id",
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string",
|
||||
"pattern": "^%space_id_pattern%$"
|
||||
},
|
||||
"driveType": {
|
||||
"const": "personal"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"name": {
|
||||
"const": "/"
|
||||
},
|
||||
"path": {
|
||||
"const": "/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"const": 24
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
And as user "Brian" file "Shares/textfile.txt" should be favorited
|
||||
But as user "Alice" file "textfile.txt" should not be favorited
|
||||
|
||||
|
||||
Scenario: add a shared folder to favorites
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has created folder "parent"
|
||||
And user "Alice" has created folder "parent/sub"
|
||||
And user "Alice" has uploaded file with content "КуСфера test text file" to "parent/textfile.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | parent |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "parent" synced
|
||||
When user "Brian" marks folder "parent/sub" as favorite from space "Shares" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"eTag",
|
||||
"folder",
|
||||
"id",
|
||||
"lastModifiedDateTime",
|
||||
"name",
|
||||
"parentReference",
|
||||
"size"
|
||||
],
|
||||
"properties": {
|
||||
"eTag": {
|
||||
"type": "string",
|
||||
"pattern": "%etag_pattern%"
|
||||
},
|
||||
"folder": {
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"const": "sub"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"driveId",
|
||||
"driveType",
|
||||
"id",
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string",
|
||||
"pattern": "^%space_id_pattern%$"
|
||||
},
|
||||
"driveType": {
|
||||
"const": "personal"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"name": {
|
||||
"const": "parent"
|
||||
},
|
||||
"path": {
|
||||
"const": "/parent"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"const": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
And as user "Brian" folder "Shares/parent/sub" should be favorited
|
||||
But as user "Alice" folder "parent/sub" should not be favorited
|
||||
|
||||
|
||||
Scenario: add a file of the project space to favorites
|
||||
Given user "Brian" has been created with default attributes
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And using spaces DAV path
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "text.txt"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Viewer |
|
||||
When user "Brian" marks file "text.txt" as favorite from space "new-space" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"eTag",
|
||||
"file",
|
||||
"id",
|
||||
"lastModifiedDateTime",
|
||||
"name",
|
||||
"parentReference",
|
||||
"size"
|
||||
],
|
||||
"properties": {
|
||||
"eTag": {
|
||||
"type": "string",
|
||||
"pattern": "%etag_pattern%"
|
||||
},
|
||||
"file": {
|
||||
"type": "object",
|
||||
"required": ["mimeType"],
|
||||
"properties": {
|
||||
"mimeType": {
|
||||
"const": "text/plain"
|
||||
}
|
||||
}
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"const": "text.txt"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"driveId",
|
||||
"driveType",
|
||||
"id",
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string",
|
||||
"pattern": "^%space_id_pattern%$"
|
||||
},
|
||||
"driveType": {
|
||||
"const": "project"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"name": {
|
||||
"const": "/"
|
||||
},
|
||||
"path": {
|
||||
"const": "/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"const": 11
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario: add a folder of the project space to favorites
|
||||
Given user "Brian" has been created with default attributes
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And using spaces DAV path
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has created a folder "space-folder" in space "new-space"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Viewer |
|
||||
When user "Brian" marks folder "space-folder" as favorite from space "new-space" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And the JSON data of the response should match
|
||||
"""
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"eTag",
|
||||
"folder",
|
||||
"id",
|
||||
"lastModifiedDateTime",
|
||||
"name",
|
||||
"parentReference",
|
||||
"size"
|
||||
],
|
||||
"properties": {
|
||||
"eTag": {
|
||||
"type": "string",
|
||||
"pattern": "%etag_pattern%"
|
||||
},
|
||||
"folder": {
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"lastModifiedDateTime": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"name": {
|
||||
"const": "space-folder"
|
||||
},
|
||||
"parentReference": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"driveId",
|
||||
"driveType",
|
||||
"id",
|
||||
"name",
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"driveId": {
|
||||
"type": "string",
|
||||
"pattern": "^%space_id_pattern%$"
|
||||
},
|
||||
"driveType": {
|
||||
"const": "project"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^%file_id_pattern%$"
|
||||
},
|
||||
"name": {
|
||||
"const": "/"
|
||||
},
|
||||
"path": {
|
||||
"const": "/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"size": {
|
||||
"type": "integer",
|
||||
"const": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
Scenario: remove file from favorites from the personal space
|
||||
Given user "Alice" has created folder "parent"
|
||||
And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/parent/textfile.txt"
|
||||
And user "Alice" has marked file "parent/textfile.txt" as favorite from space "Personal"
|
||||
When user "Alice" unmarks file "parent/textfile.txt" as favorite from space "Personal" using the Graph API
|
||||
Then the HTTP status code should be "204"
|
||||
And as user "Alice" file "parent/textfile.txt" should not be favorited
|
||||
|
||||
|
||||
Scenario: remove folder from favorites from the personal space
|
||||
Given user "Alice" has created folder "parent"
|
||||
And user "Alice" has marked folder "parent" as favorite from space "Personal"
|
||||
When user "Alice" unmarks folder "parent" as favorite from space "Personal" using the Graph API
|
||||
Then the HTTP status code should be "204"
|
||||
And as user "Alice" folder "parent" should not be favorited
|
||||
|
||||
|
||||
Scenario: remove file from favorites from the shares
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has created folder "parent"
|
||||
And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/parent/textfile.txt"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | parent/textfile.txt |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "textfile.txt" synced
|
||||
And user "Brian" has marked file "textfile.txt" as favorite from space "Shares"
|
||||
When user "Brian" unmarks file "textfile.txt" as favorite from space "Shares" using the Graph API
|
||||
Then the HTTP status code should be "204"
|
||||
And as user "Brian" file "Shares/textfile.txt" should not be favorited
|
||||
|
||||
|
||||
Scenario: remove folder from favorites from the shares
|
||||
Given user "Brian" has been created with default attributes
|
||||
And user "Alice" has created folder "parent"
|
||||
And user "Alice" has created folder "parent/sub"
|
||||
And user "Alice" has sent the following resource share invitation:
|
||||
| resource | parent |
|
||||
| space | Personal |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Viewer |
|
||||
And user "Brian" has a share "parent" synced
|
||||
And user "Brian" has marked folder "parent/sub" as favorite from space "Shares"
|
||||
When user "Brian" unmarks folder "parent/sub" as favorite from space "Shares" using the Graph API
|
||||
Then the HTTP status code should be "204"
|
||||
And as user "Brian" folder "Shares/parent/sub" should not be favorited
|
||||
|
||||
|
||||
Scenario: remove file from favorites from the project space
|
||||
Given user "Brian" has been created with default attributes
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And using spaces DAV path
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "text.txt"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Viewer |
|
||||
And user "Brian" has marked file "text.txt" as favorite from space "new-space"
|
||||
When user "Brian" unmarks file "text.txt" as favorite from space "new-space" using the Graph API
|
||||
Then the HTTP status code should be "204"
|
||||
|
||||
|
||||
Scenario: remove folder from favorites from the project space
|
||||
Given user "Brian" has been created with default attributes
|
||||
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
|
||||
And using spaces DAV path
|
||||
And user "Alice" has created a space "new-space" with the default quota using the Graph API
|
||||
And user "Alice" has created a folder "space-folder" in space "new-space"
|
||||
And user "Alice" has sent the following space share invitation:
|
||||
| space | new-space |
|
||||
| sharee | Brian |
|
||||
| shareType | user |
|
||||
| permissionsRole | Space Viewer |
|
||||
And user "Brian" has marked folder "space-folder" as favorite from space "new-space"
|
||||
When user "Brian" unmarks folder "space-folder" as favorite from space "new-space" using the Graph API
|
||||
Then the HTTP status code should be "204"
|
||||
|
||||
|
||||
Scenario: add a file to favorites after unmarking it as favorite in the personal space
|
||||
Given user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "/testavatar.jpg"
|
||||
And user "Alice" has marked file "testavatar.jpg" as favorite from space "Personal"
|
||||
And user "Alice" has unmarked file "testavatar.jpg" as favorite from space "Personal"
|
||||
When user "Alice" marks file "/testavatar.jpg" as favorite from space "Personal" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And as user "Alice" file "testavatar.jpg" should be favorited
|
||||
|
||||
|
||||
Scenario: add a file to favorites twice
|
||||
Given user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "/testavatar.jpg"
|
||||
And user "Alice" has marked file "testavatar.jpg" as favorite from space "Personal"
|
||||
When user "Alice" marks file "/testavatar.jpg" as favorite from space "Personal" using the Graph API
|
||||
Then the HTTP status code should be "201"
|
||||
And as user "Alice" file "testavatar.jpg" should be favorited
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user