diff --git a/.github/scripts/build-and-release-app-for-upgrade-testing.sh b/.github/scripts/build-and-release-app-for-upgrade-testing.sh
new file mode 100644
index 000000000..7dae1ba3d
--- /dev/null
+++ b/.github/scripts/build-and-release-app-for-upgrade-testing.sh
@@ -0,0 +1,204 @@
+# SPDX-FileCopyrightText: 2023-2024 Jankari Tech Pvt. Ltd.
+# SPDX-FileCopyrightText: 2023 Bundesministerium des Innern und für Heimat, PG ZenDiS "Projektgruppe für Aufbau ZenDiS"
+# SPDX-FileCopyrightText: 2023 Nextcloud GmbH
+# SPDX-License-Identifier: AGPL-3.0-only
+#!/usr/bin/env bash
+
+# This bash script is to register and publish the apps in self-hosted appstore.
+# To run this script the self-hosted appstore instances must be up and running
+
+set -e
+
+# 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"
+}
+
+# env required
+# NEXCLOUD_PATH=/home/nabin/www/stable29 # path to nextcloud
+# $TAG=2.11.3 # tag that we want to publish
+# WORKING_DIRECTORY=/home/nabin/www/fork-integrationOpenproject # current working directory simply done by pwd command
+
+if [[ -z "$TAG" ]] || [[ -z "$NEXCLOUD_PATH" ]] || [[ -z "$WORKING_DIRECTORY" ]]; then
+ log_error "Environment variables TAG, NEXCLOUD_PATH, or WORKING_DIRECTORY are missing."
+ exit 1
+fi
+
+if [[ ! -d "$WORKING_DIRECTORY/integration_openproject" ]]; then
+ log_error "integration_openproject directory does not exist."
+ exit 1
+fi
+
+# build the apps
+cd $WORKING_DIRECTORY
+make -C integration_openproject
+
+mkdir -p publish
+
+# remove unnecessary app files
+log_info "Copying necessary app files to publish directory..."
+rsync -a \
+--exclude=server \
+--exclude=dev \
+--exclude=.git \
+--exclude=appinfo/signature.json \
+--exclude='*.swp' \
+--exclude=build \
+--exclude=.gitignore \
+--exclude=.travis.yml \
+--exclude=.scrutinizer.yml \
+--exclude=CONTRIBUTING.md \
+--exclude=composer.phar \
+--exclude=js/node_modules \
+--exclude=node_modules \
+--exclude=src \
+--exclude=translationfiles \
+--exclude='webpack.*' \
+--exclude=stylelint.config.js \
+--exclude=.eslintrc.js \
+--exclude=.github \
+--exclude=.gitlab-ci.yml \
+--exclude=crowdin.yml \
+--exclude=tools \
+--exclude=.tx \
+--exclude=.l10nignore \
+--exclude=l10n/.tx \
+--exclude=l10n/l10n.pl \
+--exclude=l10n/templates \
+--exclude='l10n/*.sh' \
+--exclude='l10n/[a-z][a-z]' \
+--exclude='l10n/[a-z][a-z]_[A-Z][A-Z]' \
+--exclude=l10n/no-php \
+--exclude=makefile \
+--exclude=screenshots \
+--exclude='phpunit*xml' \
+--exclude=tests \
+--exclude=ci \
+--exclude=vendor/bin \
+integration_openproject publish/
+
+cd publish
+
+# update version in info.xml
+sed -i "s|.*|$TAG|" "integration_openproject/appinfo/info.xml"
+
+# https://nextcloudappstore.readthedocs.io/en/latest/developer.html#obtaining-a-certificate
+log_info "Generating app.key and app.crt..."
+sudo openssl req -x509 -newkey rsa:4096 -sha256 -nodes \
+ -keyout app.key \
+ -out app.crt \
+ -days 3650 \
+ -subj "/CN=$APP_ID" \
+ -addext "basicConstraints=CA:FALSE" \
+ -addext "keyUsage=digitalSignature" \
+ -addext "extendedKeyUsage=codeSigning"
+
+if [[ ! -s app.key || ! -s app.crt ]]; then
+ log_error "Failed to generate app signing certificate and key. app.key or app.crt not found."
+ exit 1
+fi
+
+log_info "Adding the generated certificate to nextcloud's root.crt..."
+nextcloud_root_crt="${NEXCLOUD_PATH}/resources/codesigning/root.crt"
+if [[ -f ${nextcloud_root_crt} ]]; then
+ echo "" >> ${nextcloud_root_crt}
+ cat app.crt >> ${nextcloud_root_crt}
+else
+ log_error "Nextcloud's root.crt not found at ${nextcloud_root_crt}."
+ exit 1
+fi
+
+# fix permisions for signing
+sudo chown $USER: app.key
+sudo chown -R $USER: $APP_ID
+
+# Sign the app
+# need full path for signing
+log_info "Signing the app using occ integrity:sign-app command..."
+php ${NEXCLOUD_PATH}/occ integrity:sign-app \
+ --privateKey=${WORKING_DIRECTORY}/publish/app.key \
+ --certificate=${WORKING_DIRECTORY}/publish/app.crt \
+ --path=${WORKING_DIRECTORY}/publish/$APP_ID || { log_error "Failed to sign app."; exit 1; }
+
+# php /home/runner/html/nextcloud/occ integrity:sign-app \
+# --privateKey=/home/runner/work/integration_openproject/integration_openproject/publish/app.key \
+# --certificate=/home/runner/work/integration_openproject/integration_openproject/publish/app.crt \
+# --path=/home/runner/work/integration_openproject/integration_openproject/publish/integration_openproject
+
+# Archive the app
+tar -czf $APP_ID-$TAG.tar.gz $APP_ID
+if [[ ! -f $APP_ID-$TAG.tar.gz ]]; then
+ log_error "Failed to archive the app. Archive file $APP_ID-$TAG.tar.gz not found."
+ exit 1
+fi
+log_success "Archived the app into $APP_ID-$TAG.tar.gz."
+
+# Sign the archive
+sudo openssl dgst -sha512 -sign app.key $APP_ID-$TAG.tar.gz | openssl base64 | tee ${WORKING_DIRECTORY}/publish/sign.txt || { log_error "Failed to sign the archive."; exit 1; }
+if [[ ! -s ${WORKING_DIRECTORY}/publish/sign.txt ]]; then
+ log_error "Failed to sign the archive. Signature file sign.txt is empty or not found."
+ exit 1
+else
+ log_success "Signed the app archive successfully."
+fi
+
+log_success "App build and release process has been completed successfully."
+
+## copy archieve in nextcloud directory to download
+cp $APP_ID-$TAG.tar.gz ${NEXCLOUD_PATH}/$APP_ID-$TAG.tar.gz
+if [[ -f ${NEXCLOUD_PATH}/$APP_ID-$TAG.tar.gz ]]; then
+ log_success "App archive has been copied successfully."
+else
+ log_error "Failed to copy app archive to ${NEXCLOUD_PATH}."
+ exit 1
+fi
+
+## Prepare apps.json file
+if [[ ! -f ${WORKING_DIRECTORY}/publish/integration_openproject/appinfo/signature.json ]]; then
+ log_error "Signature file not found at ${WORKING_DIRECTORY}/publish/integration_openproject/appinfo/signature.json."
+ exit 1
+fi
+
+# Convert sign.txt content to one line by removing newlines
+signature=$(tr -d '\n' < "${WORKING_DIRECTORY}/publish/sign.txt")
+certificate=$(jq '.certificate' ${WORKING_DIRECTORY}/publish/integration_openproject/appinfo/signature.json)
+
+cat > apps.json <> $GITHUB_OUTPUT
+ outputs:
+ matrix: ${{ steps.create-matrix.outputs.matrix }}
+
+ upgrade-test:
+ name: Upgrade Testing
+ needs: create-matrix
+ if: ${{ success() }}
+ strategy:
+ matrix: ${{ fromJson(needs.create-matrix.outputs.matrix) }}
+ runs-on: ubuntu-latest
+ services:
+ database-mysql:
+ image: ghcr.io/nextcloud/continuous-integration-mariadb-10.5:latest
+ env:
+ MYSQL_ROOT_PASSWORD: 'nextcloud'
+ MYSQL_PASSWORD: 'nextcloud'
+ MYSQL_USER: 'nextcloud'
+ MYSQL_DATABASE: 'nextcloud'
+ ports:
+ - 3306:3306
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
+ with:
+ path: integration_openproject
+
+ - name: Checkout
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
+ with:
+ repository: nextcloud/activity
+ ref: ${{ matrix.nextcloudVersion }}
+ path: activity
+
+ - name: Setup NodeJS
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238
+ with:
+ node-version: 20
+
+ - name: Setup npm
+ run: npm i -g npm
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@a4e22b60bbb9c1021113f2860347b0759f66fe5d
+ with:
+ php-version: ${{ format('{0}.{1}', matrix.phpVersionMajor,matrix.phpVersionMinor) }}
+ extensions: mbstring, intl, mysql, gd
+ ini-values: post_max_size=256M, max_execution_time=180
+
+ - name: Build nextcloud project
+ run: |
+ export DEBIAN_FRONTEND=noninteractive
+ echo "###### installing nextcloud"
+ mkdir ~/html
+ git clone https://github.com/nextcloud/server.git --recursive --depth 1 -b ${{ matrix.nextcloudVersion }} ~/html/nextcloud
+ cd ~/html/nextcloud
+ git submodule update --init
+ mkdir -p data
+ php occ maintenance:install \
+ --database mysql \
+ --database-name nextcloud \
+ --database-host 127.0.0.1 \
+ --database-user nextcloud \
+ --database-pass nextcloud \
+ --admin-user admin \
+ --admin-pass admin
+ php occ maintenance:mode --off
+ sudo php -S localhost:80 -t ~/html/nextcloud &
+
+ - name: Wait for Nextcloud server to be ready
+ run: |
+ if ! timeout 5m bash -c '
+ until curl -s -f http://localhost/status.php | grep '"'"'"installed":true'"'"'; do
+ echo "[INFO] Waiting for server to be ready..."
+ sleep 10
+ done
+ '; then
+ echo "[ERROR] Server not ready within 5 minutes."
+ exit 1
+ fi
+
+ - name: Enable other apps from official app store
+ run: |
+ cd ~/html/nextcloud
+ php occ app:enable oidc user_oidc groupfolders integration_openproject
+
+ # activity app cannot be installed using occ command
+ - name: Setup and enable dependent apps
+ run: |
+ cp -R activity ~/html/nextcloud/apps
+ cd ~/html/nextcloud
+ php occ app:enable activity
+
+ - name: Build integration_openproject app for upgrade testing
+ env:
+ APP_ID: integration_openproject
+ NEXCLOUD_PATH: /home/runner/html/nextcloud
+ WORKING_DIRECTORY: ${{ github.workspace }}
+ run: |
+ cd integration_openproject
+ bash .github/scripts/build-and-release-app-for-upgrade-testing.sh
+
+ - name: Update integration_openproject app
+ run: |
+ # latest data didn't get fetched properly, so we need to clear the appstore cache
+ echo "" > ~/html/nextcloud/data/appdata_*/appstore/apps.json
+ cd ~/html/nextcloud
+ php occ config:system:set ratelimit.protection.enabled --value false --type bool
+ php occ config:system:set appstoreurl --value "http://localhost"
+ php occ config:system:set allow_local_remote_servers --value true
+ php occ app:update --allow-unstable integration_openproject
+ if php occ app:list | grep -q "integration_openproject.*$TAG"; then
+ echo "App updated successfully to version $TAG"
+ else
+ echo "App not updated to version $TAG"
+ exit 1
+ fi
+
+ - name: API Tests
+ env:
+ NEXTCLOUD_BASE_URL: http://localhost
+ run: |
+ cd integration_openproject
+ # Run API tests
+ make api-test
\ No newline at end of file
diff --git a/tests/acceptance/features/bootstrap/FeatureContext.php b/tests/acceptance/features/bootstrap/FeatureContext.php
index 1c7f59258..103708279 100644
--- a/tests/acceptance/features/bootstrap/FeatureContext.php
+++ b/tests/acceptance/features/bootstrap/FeatureContext.php
@@ -270,6 +270,12 @@ private function deleteUserDataFromDocker(string $user): void {
echo "'docker' command not found. Skipping user data deletion.\n";
return;
}
+
+ // Skip if Nextcloud Docker container does not exist
+ exec("docker ps --format \"{{.Names}}\"", $output);
+ if(!in_array('nextcloud', $output)) {
+ return;
+ }
$firstChar = substr($user, 0, 1);
$restChars = substr($user, 1);