diff --git a/.github/workflows/beta-release.yaml b/.github/workflows/beta-release.yaml new file mode 100644 index 00000000..87655a46 --- /dev/null +++ b/.github/workflows/beta-release.yaml @@ -0,0 +1,290 @@ +name: Beta Release + +on: + push: + branches: + - beta + # - code-refactor/ObjectService + +jobs: + release-management: + runs-on: ubuntu-latest + steps: + + # Stap 1: Code ophalen + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ssh-key: ${{ secrets.DEPLOY_KEY }} + + # Stap 2: Stel de appnaam in (gebruik de repo-naam) + - name: Set app env + run: | + echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV + + # Stap 3: Haal huidige versie uit info.xml, verhoog de patch en voeg beta-suffix toe + - name: Get current version and append beta suffix + id: increment_version + run: | + # Get version from main branch + git fetch origin main + main_version=$(git show origin/main:appinfo/info.xml | grep -oP '(?<=)[^<]+' || echo "") + + # Get current version from development branch + current_version=$(grep -oP '(?<=)[^<]+' appinfo/info.xml || echo "") + + # Split main version into parts + IFS='.' read -ra main_version_parts <<< "$main_version" + + # Increment patch version by 1 from main + next_patch=$((main_version_parts[2] + 1)) + + # Extract beta counter from current version if it exists + beta_counter=1 + if [[ $current_version =~ -beta\.([0-9]+)$ ]]; then + # If current patch version is still ahead of main, increment counter + current_patch=$(echo $current_version | grep -oP '^[0-9]+\.[0-9]+\.(\d+)' | cut -d. -f3) + if [ "$current_patch" -eq "$next_patch" ]; then + beta_counter=$((BASH_REMATCH[1] + 1)) + fi + fi + + beta_version="${main_version_parts[0]}.${main_version_parts[1]}.${next_patch}-beta.${beta_counter}" + + echo "NEW_VERSION=$beta_version" >> $GITHUB_ENV + echo "new_version=$beta_version" >> $GITHUB_OUTPUT + echo "Main version: $main_version" + echo "Current version: $current_version" + echo "Using beta version: $beta_version" + + # Stap 4: Update de versie in info.xml + - name: Update version in info.xml + run: | + sed -i "s|.*|${{ env.NEW_VERSION }}|" appinfo/info.xml + + # Stap 5: Commit de nieuwe versie (indien er wijzigingen zijn) + - name: Commit version update + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git commit -am "Bump beta version to ${{ env.NEW_VERSION }} [skip ci]" + git push + + # Stap 6: Bereid de signing certificaten voor + - name: Prepare Signing Certificate and Key + run: | + echo "${{ secrets.NEXTCLOUD_SIGNING_CERT }}" > signing-cert.crt + echo "${{ secrets.NEXTCLOUD_SIGNING_KEY }}" > signing-key.key + + # Stap 7: Installeer npm dependencies + - name: Install npm dependencies + uses: actions/setup-node@v3 + with: + node-version: '18.x' + + # Stap 8: Stel PHP in en installeer benodigde extensies + - name: Set up PHP and install extensions + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: zip, gd + + # Stap 9: Voer npm install, build en composer install uit + - run: npm ci + - run: npm run build + - run: composer install --no-dev --optimize-autoloader --classmap-authoritative + + # Stap 9a: Verify vendor dependencies are installed + - name: Verify vendor dependencies + run: | + echo "Checking critical dependencies..." + + # Check that vendor directory exists and has content + if [ ! -d "vendor" ] || [ -z "$(ls -A vendor 2>/dev/null)" ]; then + echo "ERROR: vendor directory is missing or empty" + exit 1 + fi + + # Check specific critical dependencies + missing_deps=0 + + if [ ! -d "vendor/openai-php/client/src" ]; then + echo "ERROR: openai-php/client source files not found" + missing_deps=1 + fi + + if [ ! -d "vendor/theodo-group/llphant/src" ]; then + echo "ERROR: theodo-group/llphant source files not found" + missing_deps=1 + fi + + if [ $missing_deps -eq 1 ]; then + echo "HINT: Check composer.json dependencies and composer install output" + exit 1 + fi + + echo "✓ All critical dependencies verified with source files" + + # Stap 10: Kopieer de bestanden naar de package directory + - name: Copy the package files into the package + run: | + mkdir -p package/${{ github.event.repository.name }} + rsync -av --progress \ + --exclude='/package' \ + --exclude='/.git' \ + --exclude='/.github' \ + --exclude='/.cursor' \ + --exclude='/.vscode' \ + --exclude='/.nextcloud' \ + --exclude='/docker' \ + --exclude='/docker-compose.yml' \ + --exclude='/docs' \ + --exclude='/website' \ + --exclude='/node_modules' \ + --exclude='/src' \ + --exclude='/phpcs-custom-sniffs' \ + --exclude='/resources' \ + --exclude='/tests' \ + --exclude='/path' \ + --exclude='/package.json' \ + --exclude='/package-lock.json' \ + --exclude='/composer.json' \ + --exclude='/composer.lock' \ + --exclude='/composer-setup.php' \ + --exclude='/phpcs.xml' \ + --exclude='/phpmd.xml' \ + --exclude='/psalm.xml' \ + --exclude='/phpunit.xml' \ + --exclude='/.phpunit.cache' \ + --exclude='.phpunit.result.cache' \ + --exclude='/jest.config.js' \ + --exclude='/webpack.config.js' \ + --exclude='/tsconfig.json' \ + --exclude='/.babelrc' \ + --exclude='/.eslintrc.js' \ + --exclude='/.prettierrc' \ + --exclude='/stylelint.config.js' \ + --exclude='/.spectral.yml' \ + --exclude='/.gitignore' \ + --exclude='/.gitattributes' \ + --exclude='/.php-cs-fixer.dist.php' \ + --exclude='/.nvmrc' \ + --exclude='/changelog-ci-config.json' \ + --exclude='/coverage.txt' \ + --exclude='/signing-key.key' \ + --exclude='/signing-cert.crt' \ + --exclude='/openapi.json' \ + --exclude='/*_ANALYSIS.md' \ + --exclude='/*_FIX.md' \ + --exclude='/*_SUMMARY.md' \ + --exclude='/*_GUIDE.md' \ + ./ package/${{ github.event.repository.name }}/ + + # Stap 11: Verify package contents before creating tarball + - name: Verify package vendor directory + run: | + echo "Verifying package contains complete vendor dependencies..." + + # Check vendor directory was copied + if [ ! -d "package/${{ github.event.repository.name }}/vendor" ]; then + echo "ERROR: vendor directory not found in package" + exit 1 + fi + + # Verify vendor packages have source files (not just LICENSE) + if [ ! -d "package/${{ github.event.repository.name }}/vendor/openai-php/client/src" ]; then + echo "ERROR: openai-php/client/src not found in package" + echo "HINT: Check rsync exclusion patterns - they may be too broad" + ls -la package/${{ github.event.repository.name }}/vendor/openai-php/client/ || true + exit 1 + fi + + # Quick sanity check: count vendor subdirectories + vendor_count=$(find package/${{ github.event.repository.name }}/vendor -maxdepth 1 -type d | wc -l) + if [ $vendor_count -lt 10 ]; then + echo "WARNING: Only $vendor_count vendor directories found (expected 20+)" + echo "Listing vendor contents:" + ls -la package/${{ github.event.repository.name }}/vendor/ + fi + + echo "✓ Package vendor directory verified with source files" + + # Stap 12: Maak het TAR.GZ archief + - name: Create Tarball + run: | + cd package && tar -czf ../nextcloud-release.tar.gz ${{ github.event.repository.name }} + + # Stap 13: Sign het TAR.GZ bestand met OpenSSL + - name: Sign the TAR.GZ file with OpenSSL + run: | + openssl dgst -sha512 -sign signing-key.key nextcloud-release.tar.gz | openssl base64 -out nextcloud-release.signature + + # Stap 13a: Upload tarball as workflow artifact for easy inspection + - name: Upload tarball as artifact + uses: actions/upload-artifact@v4 + with: + name: nextcloud-release-${{ env.NEW_VERSION }} + path: | + nextcloud-release.tar.gz + nextcloud-release.signature + retention-days: 30 + + # Stap 14: Genereer Git versie informatie (optioneel, voor logging) + - name: Git Version + id: version + uses: codacy/git-version@2.7.1 + with: + release-branch: beta + + # Stap 15: Extraheer repository description (optioneel) + - name: Extract repository description + id: repo-description + run: | + description=$(jq -r '.description' <(curl -s https://api.github.com/repos/${{ github.repository }})) + echo "REPO_DESCRIPTION=$description" >> $GITHUB_ENV + + # Stap 16: Output de versie (voor logging) + - name: Use the version + run: | + echo "Git Version info: ${{ steps.version.outputs.version }}" + + # Stap 17: Maak een nieuwe GitHub release (als prerelease) + - name: Upload Beta Release + uses: ncipollo/release-action@v1.12.0 + with: + tag: v${{ env.NEW_VERSION }} + name: Beta Release ${{ env.NEW_VERSION }} + draft: false + prerelease: true + + # Stap 18: Voeg het tarball toe als asset aan de GitHub release + - name: Attach tarball to GitHub release + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: nextcloud-release.tar.gz + asset_name: ${{ env.APP_NAME }}-${{ env.NEW_VERSION }}.tar.gz + tag: v${{ env.NEW_VERSION }} + overwrite: true + + # Stap 19: Upload de app naar de Nextcloud App Store + - name: Upload app to Nextcloud appstore + uses: nextcloud-releases/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1 + with: + app_name: ${{ env.APP_NAME }} + appstore_token: ${{ secrets.NEXTCLOUD_APPSTORE_TOKEN }} + download_url: https://github.com/${{ github.repository }}/releases/download/v${{ env.NEW_VERSION }}/${{ env.APP_NAME }}-${{ env.NEW_VERSION }}.tar.gz + app_private_key: ${{ secrets.NEXTCLOUD_SIGNING_KEY }} + nightly: false + + # Stap 20: Verifieer de release + - name: Verify version and contents + run: | + echo "App version: ${{ env.NEW_VERSION }}" + echo "Tarball contents:" + tar -tvf nextcloud-release.tar.gz | head -100 + echo "Verify vendor directory in tarball:" + tar -tvf nextcloud-release.tar.gz | grep "vendor/openai-php/client" | head -5 || echo "WARNING: openai-php/client not found in tarball!" + echo "info.xml contents:" + tar -xOf nextcloud-release.tar.gz ${{ env.APP_NAME }}/appinfo/info.xml \ No newline at end of file diff --git a/.github/workflows/release-workflow.yaml b/.github/workflows/release-workflow.yaml new file mode 100644 index 00000000..2cb3e6ed --- /dev/null +++ b/.github/workflows/release-workflow.yaml @@ -0,0 +1,301 @@ +name: Release Workflow + +on: + push: + branches: + - main + - master + workflow_dispatch: + inputs: + version: + description: 'Version to release (leave empty to use info.xml version)' + required: false + default: '' + +jobs: + release-management: + runs-on: ubuntu-latest + steps: + + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + ssh-key: ${{ secrets.DEPLOY_KEY }} + + - name: Set app env + run: | + echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV + + - name: Get current version and increment + id: increment_version + run: | + current_version=$(grep -oP '(?<=)[^<]+' appinfo/info.xml) + IFS='.' read -ra version_parts <<< "$current_version" + ((version_parts[2]++)) + new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" + echo "NEW_VERSION=$new_version" >> $GITHUB_ENV + echo "new_version=$new_version" >> $GITHUB_OUTPUT + + - name: Update version in info.xml + run: | + sed -i "s|.*|${{ env.NEW_VERSION }}|" appinfo/info.xml + + - name: Commit version update + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git commit -am "Bump version to ${{ env.NEW_VERSION }}" -m "[skip ci]" + git push + + # Step 1: Prepare the signing certificate and key + - name: Prepare Signing Certificate and Key + run: | + echo "${{ secrets.NEXTCLOUD_SIGNING_CERT }}" > signing-cert.crt + echo "${{ secrets.NEXTCLOUD_SIGNING_KEY }}" > signing-key.key + + # Step 3: Install Node.js dependencies using npm + - name: Install npm dependencies + uses: actions/setup-node@v3 + with: + node-version: '18.x' # Specify Node.js version + + # Step 4: Install PHP extensions + - name: Set up PHP and install extensions + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + extensions: zip, gd + + # Step 5: Build the node dependencies + - run: npm ci + + # Step 6: Build the node dependencies + - run: npm run build + + # Step 7: Build composer dependencies + - run: composer install --no-dev --optimize-autoloader --classmap-authoritative + + # Step 7a: Verify vendor dependencies are installed + - name: Verify vendor dependencies + run: | + echo "Checking critical dependencies..." + + # Check that vendor directory exists and has content + if [ ! -d "vendor" ] || [ -z "$(ls -A vendor 2>/dev/null)" ]; then + echo "ERROR: vendor directory is missing or empty" + exit 1 + fi + + # Check specific critical dependencies + missing_deps=0 + + if [ ! -d "vendor/openai-php/client/src" ]; then + echo "ERROR: openai-php/client source files not found" + missing_deps=1 + fi + + if [ ! -d "vendor/theodo-group/llphant/src" ]; then + echo "ERROR: theodo-group/llphant source files not found" + missing_deps=1 + fi + + if [ $missing_deps -eq 1 ]; then + echo "HINT: Check composer.json dependencies and composer install output" + exit 1 + fi + + echo "✓ All critical dependencies verified with source files" + + # Step 8: Copy the files into the package directory + - name: Copy the package files into the package + run: | + mkdir -p package/${{ github.event.repository.name }} + rsync -av --progress \ + --exclude='/package' \ + --exclude='/.git' \ + --exclude='/.github' \ + --exclude='/.cursor' \ + --exclude='/.vscode' \ + --exclude='/.nextcloud' \ + --exclude='/docker' \ + --exclude='/docker-compose.yml' \ + --exclude='/docs' \ + --exclude='/website' \ + --exclude='/node_modules' \ + --exclude='/src' \ + --exclude='/phpcs-custom-sniffs' \ + --exclude='/resources' \ + --exclude='/tests' \ + --exclude='/path' \ + --exclude='/package.json' \ + --exclude='/package-lock.json' \ + --exclude='/composer.json' \ + --exclude='/composer.lock' \ + --exclude='/composer-setup.php' \ + --exclude='/phpcs.xml' \ + --exclude='/phpmd.xml' \ + --exclude='/psalm.xml' \ + --exclude='/phpunit.xml' \ + --exclude='/.phpunit.cache' \ + --exclude='.phpunit.result.cache' \ + --exclude='/jest.config.js' \ + --exclude='/webpack.config.js' \ + --exclude='/tsconfig.json' \ + --exclude='/.babelrc' \ + --exclude='/.eslintrc.js' \ + --exclude='/.prettierrc' \ + --exclude='/stylelint.config.js' \ + --exclude='/.spectral.yml' \ + --exclude='/.gitignore' \ + --exclude='/.gitattributes' \ + --exclude='/.php-cs-fixer.dist.php' \ + --exclude='/.nvmrc' \ + --exclude='/changelog-ci-config.json' \ + --exclude='/coverage.txt' \ + --exclude='/signing-key.key' \ + --exclude='/signing-cert.crt' \ + --exclude='/openapi.json' \ + --exclude='/*_ANALYSIS.md' \ + --exclude='/*_FIX.md' \ + --exclude='/*_SUMMARY.md' \ + --exclude='/*_GUIDE.md' \ + ./ package/${{ github.event.repository.name }}/ + + # Step 8a: Verify package contents before creating tarball + - name: Verify package vendor directory + run: | + echo "Verifying package contains complete vendor dependencies..." + + # Check vendor directory was copied + if [ ! -d "package/${{ github.event.repository.name }}/vendor" ]; then + echo "ERROR: vendor directory not found in package" + exit 1 + fi + + # Verify vendor packages have source files (not just LICENSE) + if [ ! -d "package/${{ github.event.repository.name }}/vendor/openai-php/client/src" ]; then + echo "ERROR: openai-php/client/src not found in package" + echo "HINT: Check rsync exclusion patterns - they may be too broad" + ls -la package/${{ github.event.repository.name }}/vendor/openai-php/client/ || true + exit 1 + fi + + # Quick sanity check: count vendor subdirectories + vendor_count=$(find package/${{ github.event.repository.name }}/vendor -maxdepth 1 -type d | wc -l) + if [ $vendor_count -lt 10 ]; then + echo "WARNING: Only $vendor_count vendor directories found (expected 20+)" + echo "Listing vendor contents:" + ls -la package/${{ github.event.repository.name }}/vendor/ + fi + + echo "✓ Package vendor directory verified with source files" + + # Step 9: Create the TAR.GZ archive + - name: Create Tarball + run: | + cd package && tar -czf ../nextcloud-release.tar.gz ${{ github.event.repository.name }} + + # Step 10: Sign the TAR.GZ file with OpenSSL + - name: Sign the TAR.GZ file with OpenSSL + run: | + openssl dgst -sha512 -sign signing-key.key nextcloud-release.tar.gz | openssl base64 -out nextcloud-release.signature + + # Step 10a: Upload tarball as workflow artifact for easy inspection + - name: Upload tarball as artifact + uses: actions/upload-artifact@v4 + with: + name: nextcloud-release-${{ env.NEW_VERSION }} + path: | + nextcloud-release.tar.gz + nextcloud-release.signature + retention-days: 30 + + # Step 11: Generate Git version information + - name: Git Version + id: version + uses: codacy/git-version@2.7.1 + with: + release-branch: main + + # Step 12: Extract repository description + - name: Extract repository description + id: repo-description + run: | + description=$(jq -r '.description' <(curl -s https://api.github.com/repos/${{ github.repository }})) + echo "REPO_DESCRIPTION=$description" >> $GITHUB_ENV + + # Step 14: Output the version + - name: Use the version + run: | + echo ${{ steps.version.outputs.version }} + + # Step 18: Create a new release on GitHub + - name: Upload Release + uses: ncipollo/release-action@v1.12.0 + with: + tag: v${{ env.NEW_VERSION }} + name: Release ${{ env.NEW_VERSION }} + draft: false + prerelease: false + + - name: Attach tarball to github release + uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # v2 + id: attach_to_release + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: nextcloud-release.tar.gz # Corrected spelling + asset_name: ${{ env.APP_NAME }}-${{ env.NEW_VERSION }}.tar.gz + tag: v${{ env.NEW_VERSION }} + overwrite: true + + - name: Upload app to Nextcloud appstore + uses: nextcloud-releases/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1 # v1 + with: + app_name: ${{ env.APP_NAME }} + appstore_token: ${{ secrets.NEXTCLOUD_APPSTORE_TOKEN }} + download_url: https://github.com/${{ github.repository }}/releases/download/v${{ env.NEW_VERSION }}/${{ env.APP_NAME }}-${{ env.NEW_VERSION }}.tar.gz + app_private_key: ${{ secrets.NEXTCLOUD_SIGNING_KEY }} + nightly: false + + - name: Verify version and contents + run: | + echo "App version: ${{ env.NEW_VERSION }}" + echo "Tarball contents:" + tar -tvf nextcloud-release.tar.gz | head -100 + echo "Verify vendor directory in tarball:" + tar -tvf nextcloud-release.tar.gz | grep "vendor/openai-php/client" | head -5 || echo "WARNING: openai-php/client not found in tarball!" + echo "info.xml contents:" + tar -xOf nextcloud-release.tar.gz ${{ env.APP_NAME }}/appinfo/info.xml + + update-changelog: + runs-on: ubuntu-latest + steps: + + - name: Checkout Code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set app env + run: | + echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV + + - name: Get current version and increment + id: increment_version + run: | + current_version=$(grep -oP '(?<=)[^<]+' appinfo/info.xml) + IFS='.' read -ra version_parts <<< "$current_version" + ((version_parts[2]++)) + new_version="${version_parts[0]}.${version_parts[1]}.${version_parts[2]}" + echo "NEW_VERSION=$new_version" >> $GITHUB_ENV + echo "new_version=$new_version" >> $GITHUB_OUTPUT + + # Step 13: Run Changelog CI + - name: Run Changelog CI + if: github.ref == 'refs/heads/main' + uses: saadmk11/changelog-ci@v1.1.2 + with: + persist-credentials: true + release_version: ${{ env.NEW_VERSION }} + config_file: changelog-ci-config.json \ No newline at end of file diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 738484a4..5f999f31 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -5,8 +5,8 @@ * * Built on @conduction/docusaurus-preset for brand defaults (tokens, * theme swizzles for Navbar / Footer, four-locale i18n scaffolding, - * KvK / BTW copyright). Site-specific overrides — locales, sidebar - * path, mermaid theme, custom prism themes, procest-only navbar items — + * KvK / BTW copyright). Site-specific overrides: locales, sidebar + * path, mermaid theme, custom prism themes, procest-only navbar items, * are passed through createConfig() opts. */ @@ -39,7 +39,7 @@ const config = createConfig({ /* The procest docs source lives at the repo root of `docs/` rather than under a `docs/` subfolder, so we override the preset's default `presets:` block to point `docs.path` at './' and disable the blog - plugin. customCss carries procest-specific CSS only — brand tokens + plugin. customCss carries procest-specific CSS only; brand tokens and the theme swizzles are auto-loaded by the brand theme entry in `themes:` below. */ presets: [ @@ -140,7 +140,7 @@ const config = createConfig({ markdown directly so it makes it into the final Docusaurus config. */ config.markdown = { mermaid: true, - /* Tutorial pages (docs/tutorials/) reference screenshots populated by + /* Tutorial pages (docs/user-guide/) reference screenshots populated by tests/e2e/docs-screenshots.spec.ts. The Playwright capture run is separate from the docs build, so the build must succeed even when a fresh checkout doesn't have every PNG yet. Warn instead of failing diff --git a/docs/src/components/HomepageFeatures/index.js b/docs/src/components/HomepageFeatures/index.js index 0f1d1224..945a0514 100644 --- a/docs/src/components/HomepageFeatures/index.js +++ b/docs/src/components/HomepageFeatures/index.js @@ -7,7 +7,7 @@ const FeatureList = [ title: 'Case Management', description: ( <> - Manage cases with configurable case types, status lifecycles, deadlines, and extensions — all built on Nextcloud. + Manage cases with configurable case types, status lifecycles, deadlines, and extensions, all built on Nextcloud. ), }, @@ -23,7 +23,7 @@ const FeatureList = [ title: 'Built on OpenRegister', description: ( <> - All data stored as flexible OpenRegister objects. No custom database tables — just schemas, registers, and standards. + All data stored as flexible OpenRegister objects. No custom database tables: just schemas, registers, and standards. ), }, diff --git a/docs/tutorials/_category_.json b/docs/user-guide/_category_.json similarity index 100% rename from docs/tutorials/_category_.json rename to docs/user-guide/_category_.json diff --git a/docs/tutorials/admin/01-configure-case-types.md b/docs/user-guide/admin/01-configure-case-types.md similarity index 100% rename from docs/tutorials/admin/01-configure-case-types.md rename to docs/user-guide/admin/01-configure-case-types.md diff --git a/docs/tutorials/admin/02-automatic-actions.md b/docs/user-guide/admin/02-automatic-actions.md similarity index 100% rename from docs/tutorials/admin/02-automatic-actions.md rename to docs/user-guide/admin/02-automatic-actions.md diff --git a/docs/tutorials/admin/03-admin-settings.md b/docs/user-guide/admin/03-admin-settings.md similarity index 100% rename from docs/tutorials/admin/03-admin-settings.md rename to docs/user-guide/admin/03-admin-settings.md diff --git a/docs/tutorials/admin/_category_.json b/docs/user-guide/admin/_category_.json similarity index 58% rename from docs/tutorials/admin/_category_.json rename to docs/user-guide/admin/_category_.json index aacf110b..d150ddbc 100644 --- a/docs/tutorials/admin/_category_.json +++ b/docs/user-guide/admin/_category_.json @@ -6,6 +6,6 @@ "link": { "type": "generated-index", "title": "Admin guide", - "description": "Org-wide administration — toggling features, configuring policies, and managing users or groups." + "description": "Org-wide administration: toggling features, configuring policies, and managing users or groups." } } diff --git a/docs/tutorials/user/01-first-launch.md b/docs/user-guide/user/01-first-launch.md similarity index 100% rename from docs/tutorials/user/01-first-launch.md rename to docs/user-guide/user/01-first-launch.md diff --git a/docs/tutorials/user/02-my-work.md b/docs/user-guide/user/02-my-work.md similarity index 100% rename from docs/tutorials/user/02-my-work.md rename to docs/user-guide/user/02-my-work.md diff --git a/docs/tutorials/user/03-view-case.md b/docs/user-guide/user/03-view-case.md similarity index 100% rename from docs/tutorials/user/03-view-case.md rename to docs/user-guide/user/03-view-case.md diff --git a/docs/tutorials/user/04-advance-case.md b/docs/user-guide/user/04-advance-case.md similarity index 100% rename from docs/tutorials/user/04-advance-case.md rename to docs/user-guide/user/04-advance-case.md diff --git a/docs/tutorials/user/05-record-decision.md b/docs/user-guide/user/05-record-decision.md similarity index 100% rename from docs/tutorials/user/05-record-decision.md rename to docs/user-guide/user/05-record-decision.md diff --git a/docs/tutorials/user/06-track-deadlines.md b/docs/user-guide/user/06-track-deadlines.md similarity index 100% rename from docs/tutorials/user/06-track-deadlines.md rename to docs/user-guide/user/06-track-deadlines.md diff --git a/docs/tutorials/user/07-handle-objection.md b/docs/user-guide/user/07-handle-objection.md similarity index 100% rename from docs/tutorials/user/07-handle-objection.md rename to docs/user-guide/user/07-handle-objection.md diff --git a/docs/tutorials/user/08-inspection-checklist.md b/docs/user-guide/user/08-inspection-checklist.md similarity index 100% rename from docs/tutorials/user/08-inspection-checklist.md rename to docs/user-guide/user/08-inspection-checklist.md diff --git a/docs/tutorials/user/_category_.json b/docs/user-guide/user/_category_.json similarity index 54% rename from docs/tutorials/user/_category_.json rename to docs/user-guide/user/_category_.json index cbb3e3bb..7e305ef4 100644 --- a/docs/tutorials/user/_category_.json +++ b/docs/user-guide/user/_category_.json @@ -6,6 +6,6 @@ "link": { "type": "generated-index", "title": "User guide", - "description": "Workflows for individual users — opening the app, customizing what's on screen, and getting day-to-day work done." + "description": "Workflows for individual users: opening the app, customizing what's on screen, and getting day-to-day work done." } } diff --git a/openspec/changes/docs-product-pages-conformance/hydra.json b/openspec/changes/docs-product-pages-conformance/hydra.json new file mode 100644 index 00000000..0e619919 --- /dev/null +++ b/openspec/changes/docs-product-pages-conformance/hydra.json @@ -0,0 +1,147 @@ +{ + "schema_version": 2, + "spec_slug": "docs-product-pages-conformance", + "repo": "https://github.com/ConductionNL/procest", + "issue": 459, + "cycles": [ + { + "cycle": 1, + "trigger": "orchestrator", + "started_at": "2026-05-19T02:25:26Z", + "ended_at": "2026-05-20T07:55:44Z", + "outcome": "done", + "outcome_reason": "pipeline complete \u2014 reviews passed and applier passed (if run)", + "pattern_tags": [], + "stages": [ + { + "stage": "build", + "persona": "Al Gorithm", + "model": "sonnet", + "container": "hydra-builder", + "started_at": "2026-05-18T21:06:12Z", + "ended_at": "2026-05-19T02:25:19Z", + "exit_code": 0, + "turns_used": 158, + "turns_budget": 200, + "checks_run": [], + "checks_skipped": [ + "composer check:strict (phpcs) \u2014 Rule 0b", + "composer test:unit (phpunit) \u2014 Rule 0b" + ], + "findings": [ + { + "id": "build-rule-0b-skipped", + "severity": "CRITICAL", + "gate": "rule-0b", + "rule": "builder did not invoke mandatory pre-push gate (composer check:strict, phpunit)", + "status": "open", + "note": "Rule 0b in images/builder/CLAUDE.md requires composer check:strict + phpunit before push. No invocations found in transcript \u2014 the pipeline will likely catch pre-review-quality failures downstream.", + "autofixable": false + } + ], + "decisions": [], + "verdict": "pass", + "duration_seconds": 19147 + }, + { + "stage": "applier", + "persona": "Axel Pli\u00e9r", + "model": "sonnet", + "container": "hydra-applier", + "started_at": "2026-05-19T04:00:22Z", + "ended_at": "2026-05-19T04:02:04Z", + "exit_code": 1, + "turns_used": 10, + "turns_budget": 20, + "checks_run": [ + "hydra.json-consumer" + ], + "checks_skipped": [], + "findings": [ + { + "id": "applier-fail", + "severity": "CRITICAL", + "gate": "applier", + "rule": "applier returned fail verdict", + "status": "open", + "note": "Applier blocking items should be captured in hydra.json pipeline.applier.blocking[] by the container itself. See logs/pipeline-*/applier.jsonl for detail.", + "autofixable": false + } + ], + "decisions": [], + "verdict": "fail", + "duration_seconds": 102 + }, + { + "stage": "applier", + "persona": "Axel Pli\u00e9r", + "model": "sonnet", + "container": "hydra-applier", + "started_at": "2026-05-19T05:32:44Z", + "ended_at": "2026-05-19T05:36:08Z", + "exit_code": 1, + "turns_used": 10, + "turns_budget": 20, + "checks_run": [ + "hydra.json-consumer" + ], + "checks_skipped": [], + "findings": [ + { + "id": "applier-fail", + "severity": "CRITICAL", + "gate": "applier", + "rule": "applier returned fail verdict", + "status": "open", + "note": "Applier blocking items should be captured in hydra.json pipeline.applier.blocking[] by the container itself. See logs/pipeline-*/applier.jsonl for detail.", + "autofixable": false + } + ], + "decisions": [], + "verdict": "fail", + "duration_seconds": 204 + }, + { + "stage": "applier", + "persona": "Axel Pli\u00e9r", + "model": "sonnet", + "container": "hydra-applier", + "started_at": "2026-05-20T07:53:25Z", + "ended_at": "2026-05-20T07:55:35Z", + "exit_code": 0, + "turns_used": 13, + "turns_budget": 20, + "checks_run": [ + "hydra.json-consumer" + ], + "checks_skipped": [], + "findings": [], + "decisions": [], + "verdict": "pass", + "duration_seconds": 130 + } + ] + } + ], + "totals": { + "runs": 4, + "turns_used": 191, + "cost_usd": 0.0, + "duration_seconds": 19583, + "cycles": 1, + "by_stage": { + "build": { + "runs": 1, + "turns_used": 158, + "cost_usd": 0.0, + "duration_seconds": 19147 + }, + "applier": { + "runs": 3, + "turns_used": 33, + "cost_usd": 0.0, + "duration_seconds": 436 + } + } + } +} diff --git a/openspec/changes/docs-product-pages-conformance/tasks.md b/openspec/changes/docs-product-pages-conformance/tasks.md index 79853e58..7606afdd 100644 --- a/openspec/changes/docs-product-pages-conformance/tasks.md +++ b/openspec/changes/docs-product-pages-conformance/tasks.md @@ -1,35 +1,35 @@ ## 1. Folder renames + Technical folder (git mv preserves history) -- [ ] 1.1 `git mv docs/features docs/Features` (44 feature files + README.md) and `git mv docs/tutorials docs/user-guide` (admin/ + user/ subdirs + all screenshots); verify PNG counts match post-mv. -- [ ] 1.2 Create `docs/Technical/` with `_category_.json` (label "Technical", position 6), then `git mv` legacy root MDs in: `ARCHITECTURE.md` → `Technical/architecture.md`, `DESIGN-REFERENCES.md` → `Technical/design-decisions.md`, `development.md` → `Technical/development-guide.md`, `zgw-implementation.md` → `Technical/zgw-spec.md`, `GOVERNMENT-FEATURES.md` → `Technical/government-compliance.md`, `FEATURES.md` → `Technical/market-analysis.md`. +- [x] 1.1 `git mv docs/features docs/Features` (44 feature files + README.md) and `git mv docs/tutorials docs/user-guide` (admin/ + user/ subdirs + all screenshots); verify PNG counts match post-mv. +- [x] 1.2 Create `docs/Technical/` with `_category_.json` (label "Technical", position 6), then `git mv` legacy root MDs in: `ARCHITECTURE.md` → `Technical/architecture.md`, `DESIGN-REFERENCES.md` → `Technical/design-decisions.md`, `development.md` → `Technical/development-guide.md`, `zgw-implementation.md` → `Technical/zgw-spec.md`, `GOVERNMENT-FEATURES.md` → `Technical/government-compliance.md`, `FEATURES.md` → `Technical/market-analysis.md`. ## 2. Root index + installation + UseCases/Integrations stubs -- [ ] 2.1 `git mv docs/README.md docs/index.md` and add frontmatter (`id: intro`, `title: Introduction`, `sidebar_position: 1`). -- [ ] 2.2 Create `docs/installation.md` (`sidebar_position: 2`) with Prerequisites (Nextcloud 28+, OpenRegister), App Store install + enable, post-install configuration (procest register auto-created via repair step, case-type config, ZGW endpoint mapping in Admin Settings), and Troubleshooting (register-not-found → re-run repair step, ZGW 400 → verify endpoint URLs). -- [ ] 2.3 Create `docs/UseCases/_category_.json` (label "Use Cases", position 4) + `docs/UseCases/index.md` stub (`draft: true`, note tracked in #440), and `docs/Integrations/_category_.json` (label "Integrations", position 5) + `docs/Integrations/index.md` stub (`draft: true`, #440). +- [x] 2.1 `git mv docs/README.md docs/index.md` and add frontmatter (`id: intro`, `title: Introduction`, `sidebar_position: 1`). +- [x] 2.2 Create `docs/installation.md` (`sidebar_position: 2`) with Prerequisites (Nextcloud 28+, OpenRegister), App Store install + enable, post-install configuration (procest register auto-created via repair step, case-type config, ZGW endpoint mapping in Admin Settings), and Troubleshooting (register-not-found → re-run repair step, ZGW 400 → verify endpoint URLs). +- [x] 2.3 Create `docs/UseCases/_category_.json` (label "Use Cases", position 4) + `docs/UseCases/index.md` stub (`draft: true`, note tracked in #440), and `docs/Integrations/_category_.json` (label "Integrations", position 5) + `docs/Integrations/index.md` stub (`draft: true`, #440). ## 3. _category_.json for canonical folders -- [ ] 3.1 Create `docs/Features/_category_.json` (label "Features", position 3) and `docs/user-guide/_category_.json` (label "User Guide", position 2) if not already present. +- [x] 3.1 Create `docs/Features/_category_.json` (label "Features", position 3) and `docs/user-guide/_category_.json` (label "User Guide", position 2) if not already present. ## 4. Redocusaurus + API route -- [ ] 4.1 Add `"redocusaurus": "^2.0.0"` to `docs/package.json` dependencies, create `docs/static/oas/procest.json` with minimal valid OAS shim (`{"openapi":"3.0.0","info":{"title":"Procest","version":"0.0.0"},"paths":{}}`), wire Redocusaurus plugin (`specs: [{spec: 'static/oas/procest.json', route: '/api/'}]`) and add `{to: '/api/', label: 'API Documentation', position: 'left'}` navbar item in `docs/docusaurus.config.js`. +- [x] 4.1 Add `"redocusaurus": "^2.0.0"` to `docs/package.json` dependencies, create `docs/static/oas/procest.json` with minimal valid OAS shim (`{"openapi":"3.0.0","info":{"title":"Procest","version":"0.0.0"},"paths":{}}`), wire Redocusaurus plugin (`specs: [{spec: 'static/oas/procest.json', route: '/api/'}]`) and add `{to: '/api/', label: 'API Documentation', position: 'left'}` navbar item in `docs/docusaurus.config.js`. ## 5. Re-enable nl locale (with escape hatch) -- [ ] 5.1 In `docs/docusaurus.config.js`, update `i18n.locales` to `['en', 'nl']` and add `nl: { label: 'Nederlands' }` to `localeConfigs`. If `npm run build` fails with SSR error on empty `i18n/nl/`, revert `locales` to `['en']` with comment `/* nl reverted: SSR error with empty i18n/nl/ — re-enable once translation backfill lands (issue #441) */`. +- [x] 5.1 In `docs/docusaurus.config.js`, update `i18n.locales` to `['en', 'nl']` and add `nl: { label: 'Nederlands' }` to `localeConfigs`. If `npm run build` fails with SSR error on empty `i18n/nl/`, revert `locales` to `['en']` with comment `/* nl reverted: SSR error with empty i18n/nl/ — re-enable once translation backfill lands (issue #441) */`. ## 6. Em-dash sweep (gate: `git grep -E '—' docs/` returns 0) -- [ ] 6.1 Replace ` — ` with `, ` / `: ` (per context) across the em-dash hot spots in `docs/Technical/market-analysis.md` (11 hits incl. title), `docs/Features/README.md`, and per-feature pages: `administration.md`, `zgw-apis.md`, `bezwaar-beroep-workflow.md`, `workflow-engine-enhancement.md`, `vth-workflow-configuration.md`, `besluitvorming-workflow.md`, `doorlooptijd-dashboard.md`, `deelzaak-support.md`, `app-scaffold.md`, `gis-integration.md`. -- [ ] 6.2 Replace `—` with `-` inside `docs/user-guide/` HTML `` comments, then run `git grep -E '—' docs/` (excluding node_modules) and confirm 0 matches. +- [x] 6.1 Replace ` — ` with `, ` / `: ` (per context) across the em-dash hot spots in `docs/Technical/market-analysis.md` (11 hits incl. title), `docs/Features/README.md`, and per-feature pages: `administration.md`, `zgw-apis.md`, `bezwaar-beroep-workflow.md`, `workflow-engine-enhancement.md`, `vth-workflow-configuration.md`, `besluitvorming-workflow.md`, `doorlooptijd-dashboard.md`, `deelzaak-support.md`, `app-scaffold.md`, `gis-integration.md`. +- [x] 6.2 Replace `—` with `-` inside `docs/user-guide/` HTML `` comments, then run `git grep -E '—' docs/` (excluding node_modules) and confirm 0 matches. ## 7. Internal link updates -- [ ] 7.1 Scan files moved into `docs/Technical/` for relative links to sibling root MDs (e.g. `../ARCHITECTURE.md`) and update to new paths. Scan `docs/Features/` for cross-links to root MDs, update. Verify `docs/index.md` references to `features/` are updated to `Features/`. +- [x] 7.1 Scan files moved into `docs/Technical/` for relative links to sibling root MDs (e.g. `../ARCHITECTURE.md`) and update to new paths. Scan `docs/Features/` for cross-links to root MDs, update. Verify `docs/index.md` references to `features/` are updated to `Features/`. ## 8. Build verification -- [ ] 8.1 `cd docs && npm install --legacy-peer-deps && npm run build` (10-min timeout) must exit 0; apply escape hatch from 5.1 if nl SSR fails, then re-run. Confirm output includes `Features/`, `user-guide/`, `Technical/`, `api/` routes. +- [x] 8.1 `cd docs && npm install --legacy-peer-deps && npm run build` (10-min timeout) must exit 0; apply escape hatch from 5.1 if nl SSR fails, then re-run. Confirm output includes `Features/`, `user-guide/`, `Technical/`, `api/` routes. diff --git a/openspec/config.yaml b/openspec/config.yaml index fdada5f7..7fb5f861 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -1,61 +1,65 @@ -schema: spec-driven - -context: | - Project: Procest - Repo: ConductionNL/procest - Type: Nextcloud App (PHP backend + Vue 2 frontend) - Description: Case management (zaakgericht werken) for Nextcloud — manages cases, tasks, statuses, roles, results, decisions - Key components: Cases, Tasks, Statuses, Roles, Results, Decisions, Dashboard - Database: PostgreSQL (via OpenRegister's ObjectService) - Mount path: /var/www/html/custom_apps/procest - - Standards: - Primary: CMMN 1.1 (OMG) + Schema.org for data model - Semantic: Schema.org JSON-LD type annotations - API mapping: ZGW APIs (Zaken, Besluiten, Catalogi) for Dutch government - Supplementary: BPMN 2.0 (task patterns), DMN (decision logic) - - Architecture: - Pattern: Thin client — Procest owns no database tables - Data layer: OpenRegister (JSON object storage with schema validation) - Frontend: Vue 2.7 + Pinia stores querying OpenRegister API directly - Backend: Minimal — SettingsController + ConfigurationService for register setup - Nextcloud reuse: Calendar (IManager), Contacts (IManager), Files (IRootFolder), - Activity (IManager), Talk (IBroker), Comments (ICommentsManager) - Not reusing Deck: No PHP API, board/stack model doesn't fit case lifecycle - - Sister app: Pipelinq (CRM) — sends cases via request-to-case conversion - Shared specs: See ../openspec/specs/ for cross-project conventions - Project guidelines: See ../project.md for workspace-wide standards - Architecture: See docs/ARCHITECTURE.md for standards research and data model decisions - Features: See docs/FEATURES.md for competitive analysis and feature roadmap - -rules: - proposal: - - Reference shared nextcloud-app spec for app structure requirements - - Reference docs/ARCHITECTURE.md for data model and standards decisions - - Consider impact on Pipelinq (request-to-case bridge) - - Reference docs/FEATURES.md for feature tier (MVP/V1/Enterprise) - - "ADR-011: Before implementing ANY utility (validation, formatting, parsing), search OpenRegister lib/Formats/, lib/Service/, and lib/Handler/ for existing implementations. Common duplications: BSN validation (BsnFormat.php), date formatting, slug generation, UUID handling. If found, reuse via DI or document why duplication is necessary." - specs: - - Use CMMN concepts for case lifecycle (CasePlanModel, HumanTask, Milestone) - - Use Schema.org type annotations for all entities (schema:Project, schema:Action, etc.) - - Include ZGW mapping column where applicable (Zaak, Rol, Besluit, etc.) - - Specify which Nextcloud OCP interfaces are used for integration features - - Reference FEATURES.md tier for each requirement (MVP/V1/Enterprise) - design: - - Uses OpenRegister API directly from frontend (no own backend CRUD) - - Register config in lib/Settings/procest_register.json (OpenAPI 3.0.0 format) - - Imported via ConfigurationService::importFromApp() in repair step - - Reference Nextcloud OCP interfaces for all platform integrations - - Case tasks are OpenRegister objects, NOT Deck cards - tasks: - - Tag tasks with feature tier (MVP/V1/Enterprise) - - Test with OpenRegister to verify schema validation works - - Verify Nextcloud integration features with actual OCP interfaces - - Test request-to-case bridge with Pipelinq - - Follow mandatory task categories from ADRs 005, 009, 010, 011 - archive: - - Merge delta specs from the change's specs/ directory into the canonical spec under openspec/specs/ - # Feature doc creation is handled globally by the opsx:archive command in .claude/commands/opsx/archive.md - # Do NOT duplicate feature doc rules here — they apply to all Conduction apps +schema: conduction + +context: | + Project: Procest + Repo: ConductionNL/procest + Type: Nextcloud App (PHP backend + Vue 2 frontend) + Description: Case management (zaakgericht werken) for Nextcloud — manages cases, tasks, statuses, roles, results, decisions + Key components: Cases, Tasks, Statuses, Roles, Results, Decisions, Dashboard + Database: PostgreSQL (via OpenRegister's ObjectService) + Mount path: /var/www/html/custom_apps/procest + + Standards: + Primary: CMMN 1.1 (OMG) + Schema.org for data model + Semantic: Schema.org JSON-LD type annotations + API mapping: ZGW APIs (Zaken, Besluiten, Catalogi) for Dutch government + Supplementary: BPMN 2.0 (task patterns), DMN (decision logic) + + Architecture: + Pattern: Thin client — Procest owns no database tables + Data layer: OpenRegister (JSON object storage with schema validation) + Frontend: Vue 2.7 + Pinia stores querying OpenRegister API directly + Backend: Minimal — SettingsController + ConfigurationService for register setup + Nextcloud reuse: Calendar (IManager), Contacts (IManager), Files (IRootFolder), + Activity (IManager), Talk (IBroker), Comments (ICommentsManager) + Not reusing Deck: No PHP API, board/stack model doesn't fit case lifecycle + + Sister app: Pipelinq (CRM) — sends cases via request-to-case conversion + Shared specs: See ../openspec/specs/ for cross-project conventions + Project guidelines: See ../project.md for workspace-wide standards + Architecture: See docs/ARCHITECTURE.md for standards research and data model decisions + Features: See docs/FEATURES.md for competitive analysis and feature roadmap + +rules: + proposal: + - Reference shared nextcloud-app spec for app structure requirements + - Reference docs/ARCHITECTURE.md for data model and standards decisions + - Consider impact on Pipelinq (request-to-case bridge) + - Reference docs/FEATURES.md for feature tier (MVP/V1/Enterprise) + - "ADR-011: Check OpenRegister core for existing functionality before proposing new features" + specs: + - Use CMMN concepts for case lifecycle (CasePlanModel, HumanTask, Milestone) + - Use Schema.org type annotations for all entities (schema:Project, schema:Action, etc.) + - Include ZGW mapping column where applicable (Zaak, Rol, Besluit, etc.) + - Specify which Nextcloud OCP interfaces are used for integration features + - Reference FEATURES.md tier for each requirement (MVP/V1/Enterprise) + - "ADR-009: Include test scenarios for all MUST/SHALL requirements" + design: + - Uses OpenRegister API directly from frontend (no own backend CRUD) + - Register config in lib/Settings/procest_register.json (OpenAPI 3.0.0 format) + - Imported via ConfigurationService::importFromApp() in repair step + - Reference Nextcloud OCP interfaces for all platform integrations + - Case tasks are OpenRegister objects, NOT Deck cards + - "ADR-001: All data MUST be stored via OpenRegister — no custom database tables" + - "ADR-012: MUST use @conduction/nextcloud-vue components (CnIndexPage, CnDashboardPage, CnFormDialog, CnDataTable) — do NOT create custom equivalents" + - "ADR-008: Follow Controller → Service → Mapper layering pattern" + - "ADR-003: MUST use NL Design System tokens via Nextcloud CSS variables — no hardcoded colors" + tasks: + - Tag tasks with feature tier (MVP/V1/Enterprise) + - Test with OpenRegister to verify schema validation works + - Verify Nextcloud integration features with actual OCP interfaces + - Test request-to-case bridge with Pipelinq + - "ADR-010: MUST include documentation task with browser screenshots using Playwright MCP" + - "ADR-009: MUST include unit test tasks — minimum 75% coverage for new code" + - "ADR-005: MUST include i18n tasks — Dutch and English translations required" + - "ADR-010: Every user-facing feature MUST have docs in docs/features/ with screenshots" diff --git a/phpunit.xml b/phpunit.xml index fd087a69..e5f1bfce 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,27 +1,16 @@ - - - - - tests/Unit - - - - - lib/ - - - - - - + + + + + ./tests/unit + + + + + ./lib + + + diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 0a5b5a4d..efa37f99 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,14 +1,12 @@ + * @author Conduction Development Team * @copyright 2024 Conduction B.V. * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 * @@ -19,54 +17,32 @@ declare(strict_types=1); +// Define that we're running PHPUnit. define('PHPUNIT_RUN', 1); +// Include Composer's autoloader. require_once __DIR__ . '/../vendor/autoload.php'; -// Register OCP and NCU namespaces from the nextcloud/ocp stub package so that -// PHPUnit can mock OCP interfaces without a full Nextcloud installation. -$loaders = spl_autoload_functions(); -foreach ($loaders as $loader) { - if (is_array($loader) && $loader[0] instanceof \Composer\Autoload\ClassLoader) { - $loader[0]->addPsr4('OCP\\', __DIR__ . '/../vendor/nextcloud/ocp/OCP/'); - $loader[0]->addPsr4('NCU\\', __DIR__ . '/../vendor/nextcloud/ocp/NCU/'); - break; - } -} - -// Load a real Nextcloud server first when one is present (CI). This must happen -// BEFORE the stub files below — base.php declares the real `OC`, the Doctrine -// DBAL classes, etc., and the stubs self-skip via class_exists()/interface_exists() -// guards when those already exist. Loading the stubs first would declare a stub -// `OC` and then crash with "Cannot declare class OC" the moment base.php runs. -if (defined('OC_CONSOLE') === false) { - if (file_exists(__DIR__ . '/../../../lib/base.php') === true) { - require_once __DIR__ . '/../../../lib/base.php'; - } +// Register OCP/NCU classes from nextcloud/ocp package. +// nextcloud/ocp has no autoload section in its composer.json, so we register it manually. +spl_autoload_register(function (string $class): void { + $prefixMap = [ + 'OCP\\' => __DIR__ . '/../vendor/nextcloud/ocp/OCP/', + 'NCU\\' => __DIR__ . '/../vendor/nextcloud/ocp/NCU/', + ]; + + foreach ($prefixMap as $prefix => $dir) { + if (strncmp($class, $prefix, strlen($prefix)) !== 0) { + continue; + } + + $relative = str_replace(search: '\\', replace: '/', subject: substr($class, strlen($prefix))); + $file = $dir . $relative . '.php'; + if (file_exists($file) === true) { + require_once $file; + } - if (file_exists(__DIR__ . '/../../../tests/autoload.php') === true) { - require_once __DIR__ . '/../../../tests/autoload.php'; - } -} - -// Load Doctrine DBAL and OC internal stubs so that PHPUnit can mock -// OCP\IDBConnection and OCP\DB\QueryBuilder\IQueryBuilder, which reference -// Doctrine types not present in this repository's vendor directory. Every -// declaration here is guarded by class_exists()/interface_exists(), so this is -// a no-op when a real Nextcloud (loaded above) already provides the classes. -require_once __DIR__ . '/Unit/Stubs/DoctrineStubs.php'; - -// IMcpToolProvider stub — loaded when the openregister runtime (PR #1466, -// ai-chat-companion-orchestrator) is absent. ProcestToolProvider implements -// OCA\OpenRegister\Mcp\IMcpToolProvider; the stub no-ops when the real -// interface is present (e.g. when the openregister app is installed). Must be -// in place before \OC_App::loadApp('procest') below tries to load that class. -if (interface_exists(\OCA\OpenRegister\Mcp\IMcpToolProvider::class) === false) { - require_once __DIR__ . '/Stubs/Mcp/IMcpToolProvider.php'; -} + break; + }//end foreach -if (defined('OC_CONSOLE') === false && class_exists('\OC_App') === true) { - \OC_App::loadApps(); - \OC_App::loadApp('procest'); - OC_Hook::clear(); -} +}); diff --git a/tests/unit/Controller/SettingsControllerTest.php b/tests/unit/Controller/SettingsControllerTest.php new file mode 100644 index 00000000..ac0e1eba --- /dev/null +++ b/tests/unit/Controller/SettingsControllerTest.php @@ -0,0 +1,120 @@ + + * @copyright 2024 Conduction B.V. + * @license EUPL-1.2 https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * @version GIT: + * + * @link https://procest.nl + */ + +declare(strict_types=1); + +namespace OCA\Procest\Tests\Unit\Controller; + +use OCA\Procest\Controller\SettingsController; +use OCA\Procest\Service\SettingsService; +use OCP\AppFramework\Http\JSONResponse; +use OCP\IRequest; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Tests for SettingsController. + */ +class SettingsControllerTest extends TestCase +{ + + /** + * The controller under test. + * + * @var SettingsController + */ + private SettingsController $controller; + + /** + * Mock IRequest. + * + * @var IRequest&MockObject + */ + private IRequest&MockObject $request; + + /** + * Mock SettingsService. + * + * @var SettingsService&MockObject + */ + private SettingsService&MockObject $settingsService; + + /** + * Set up test fixtures. + * + * @return void + */ + protected function setUp(): void + { + parent::setUp(); + + $this->request = $this->createMock(IRequest::class); + $this->settingsService = $this->createMock(SettingsService::class); + + $this->controller = new SettingsController( + request: $this->request, + settingsService: $this->settingsService, + ); + + }//end setUp() + + /** + * Test that index() returns a JSONResponse with success and config keys. + * + * @return void + */ + public function testIndexReturnsJsonResponseWithExpectedKeys(): void + { + $this->settingsService->expects($this->once()) + ->method('getSettings') + ->willReturn(['openRegisterUrl' => 'http://localhost']); + + $result = $this->controller->index(); + + self::assertInstanceOf(JSONResponse::class, $result); + self::assertTrue($result->getData()['success']); + self::assertArrayHasKey('config', $result->getData()); + + }//end testIndexReturnsJsonResponseWithExpectedKeys() + + /** + * Test that create() calls updateSettings with request params and returns success. + * + * @return void + */ + public function testCreateCallsUpdateSettingsAndReturnsSuccess(): void + { + $params = ['openRegisterUrl' => 'http://new-url']; + + $this->request->expects($this->once()) + ->method('getParams') + ->willReturn($params); + + $this->settingsService->expects($this->once()) + ->method('updateSettings') + ->with($params) + ->willReturn($params); + + $result = $this->controller->create(); + + self::assertInstanceOf(JSONResponse::class, $result); + self::assertTrue($result->getData()['success']); + self::assertArrayHasKey('config', $result->getData()); + + }//end testCreateCallsUpdateSettingsAndReturnsSuccess() + +}//end class