diff --git a/.forgejo/issue_template/bug-report.yml b/.forgejo/issue_template/bug-report.yml deleted file mode 100644 index efe10801..00000000 --- a/.forgejo/issue_template/bug-report.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: "๐Ÿ› Bug Report" -description: "Iets werkt niet zoals verwacht" -title: "[BUG] " -labels: ["bug", "needs-triage"] -assignees: [] -body: - - type: markdown - attributes: - value: | - ## Bug Report - Beschrijf het probleem zo concreet mogelijk zodat het reproduceerbaar is. - - - type: textarea - id: description - attributes: - label: "Beschrijving" - description: "Wat gaat er mis?" - placeholder: "Bij het uploaden van een PDF groter dan 10MB crasht de anonymizer." - validations: - required: true - - - type: textarea - id: reproduce - attributes: - label: "Stappen om te reproduceren" - value: | - 1. Ga naar ... - 2. Doe ... - 3. Zie fout ... - validations: - required: true - - - type: textarea - id: expected - attributes: - label: "Verwacht gedrag" - placeholder: "Het document wordt anonimiseerd en gedownload." - validations: - required: true - - - type: textarea - id: actual - attributes: - label: "Werkelijk gedrag" - placeholder: "HTTP 500 na ~30 seconden, geen output." - validations: - required: true - - - type: textarea - id: environment - attributes: - label: "Omgeving" - value: | - - Namespace/omgeving: - - Versie/image tag: - - Browser (indien van toepassing): - validations: - required: false - - - type: textarea - id: logs - attributes: - label: "Logs / Screenshots" - description: "Plak relevante logs of voeg screenshots toe" - render: shell - validations: - required: false - - - type: textarea - id: acceptance-criteria - attributes: - label: "Acceptatiecriteria (fix)" - value: | - - [ ] Bug is niet meer reproduceerbaar - - [ ] Regressietest toegevoegd - - [ ] Fix getest in acceptatieomgeving - - [ ] Geen nieuwe security findings - - [ ] Code gereviewd (4-eyes) - validations: - required: true - - - type: dropdown - id: severity - attributes: - label: "Severity" - options: - - "๐Ÿ”ด Critical โ€” productie ligt plat" - - "๐ŸŸ  High โ€” grote impact, workaround aanwezig" - - "๐ŸŸก Medium โ€” beperkte impact" - - "๐ŸŸข Low โ€” cosmetic / minor" - validations: - required: true diff --git a/.forgejo/issue_template/feature-request.yml b/.forgejo/issue_template/feature-request.yml deleted file mode 100644 index 41b4c44a..00000000 --- a/.forgejo/issue_template/feature-request.yml +++ /dev/null @@ -1,125 +0,0 @@ -name: "โœจ Feature request" -description: "Suggest a feature or improvement. Fields below feed a draft OpenSpec proposal." -title: "[FEATURE] " -labels: ["enhancement", "feature", "needs-triage"] -type: "Feature" -body: - - type: markdown - attributes: - value: | - ## Suggest a feature - - Thanks for telling us what you need. **Triage happens within 24 hours.** - - The fields below feed an OpenSpec proposal directly if the suggestion - is accepted. The more concrete you are, the faster it ships. - - Prefer Dutch? Vul de velden in het Nederlands in โ€” that's fine, we triage in both. - - - type: textarea - id: problem - attributes: - label: "Problem" - description: "What can't you do today? What's the friction? Write it from your perspective โ€” one or two sentences is plenty." - placeholder: "I want to filter contacts by last interaction date but the list view doesn't support it. I end up exporting to CSV and sorting in a spreadsheet." - validations: - required: true - - - type: textarea - id: proposed-solution - attributes: - label: "Proposed solution" - description: "How would you like it to work? Sketches, links, references welcome. \"I'm not sure\" is also a valid answer โ€” we'll figure it out together." - placeholder: "A date-range filter in the contacts list sidebar, defaulting to last 30 days, persisted per user." - validations: - required: true - - - type: textarea - id: who-benefits - attributes: - label: "Who benefits" - description: "Which user role or workflow does this serve? Be specific." - placeholder: "Account managers tracking client engagement, especially before renewal conversations." - validations: - required: true - - - type: dropdown - id: priority-to-you - attributes: - label: "How important is this to you?" - description: "Honest self-assessment. Helps us prioritise." - options: - - "Nice to have" - - "Would use weekly" - - "Would use daily" - - "Blocking me right now" - validations: - required: true - - - type: textarea - id: context - attributes: - label: "Anything else?" - description: "Edge cases, alternatives you've considered, things to avoid, related capabilities, anything that didn't fit in the boxes above." - placeholder: "Out of scope: per-team default filter (could be later). Avoid: hiding the filter behind a settings page โ€” needs to be one click from the list." - - - type: markdown - attributes: - value: | - ### Context - - The fields below are auto-filled when you suggest a feature from inside - the app. They capture where you were when the idea hit so we can scope - the spec without a second round of questions. - - **We show them to you here on purpose**: you can see exactly what we - send and edit or clear any field before you submit. Leave them blank if - you're filing directly from GitHub โ€” we'll still triage it. - - - type: input - id: app - attributes: - label: "App" - description: "Auto-filled by the in-product modal. The Nextcloud app you were using." - placeholder: "pipelinq" - - - type: input - id: page - attributes: - label: "Page" - description: "Auto-filled. The manifest page id + route you were on when you opened the modal." - placeholder: "clients-detail (/clients/abc-123)" - - - type: input - id: surface - attributes: - label: "Modal or widget" - description: "Auto-filled. Any modal, dialog, dashboard widget, or sidebar tab open at the moment the modal launched. Helps us pinpoint UI-attached suggestions." - placeholder: "edit-client-modal ยท or ยท dashboard widget: open-leads" - - - type: input - id: object - attributes: - label: "Object in focus" - description: "Auto-filled. The OpenRegister register + schema + UUID the page was viewing, if any. Lets us trace the suggestion to a real data shape." - placeholder: "pipelinq ยท Client ยท 2f9d-โ€ฆ-abc" - - - type: input - id: spec-ref - attributes: - label: "Related capability" - description: "Auto-filled if the page or widget declares a `specRef`. Connects the suggestion to the existing OpenSpec for that capability." - placeholder: "client-management" - - - type: markdown - attributes: - value: | - --- - - ### What happens next - - 1. **Within 24 hours**: a maintainer reads this and replies with one of `ready-to-build`, `needs-design`, `parking-lot`, or `wont-build` (with a reason). - 2. **If `ready-to-build`**: an OpenSpec proposal is auto-drafted from these fields. Hydra picks it up and opens a draft PR within days. - 3. **When it ships**: you're credited on the spec, you get a `Co-Authored-By:` trailer on the merge commit, and you appear on the app's contributors page. - - Read the full flow at the [Users are the moat](https://docs.conduction.nl/strategy/users-are-the-moat) strategy doc. diff --git a/.forgejo/issue_template/technical-task.yml b/.forgejo/issue_template/technical-task.yml deleted file mode 100644 index 661b1888..00000000 --- a/.forgejo/issue_template/technical-task.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: "โš™๏ธ Technische Taak" -description: "Infra, refactor, technische schuld of ops-werk" -title: "[TECH] " -labels: ["technical", "needs-refinement"] -assignees: [] -body: - - type: markdown - attributes: - value: | - ## Technische Taak - Gebruik dit template voor infra-wijzigingen, refactoring, technische schuld of operationeel werk zonder directe gebruikerswaarde. - - - type: textarea - id: description - attributes: - label: "Beschrijving" - description: "Wat moet er gedaan worden en waarom?" - placeholder: "Migreer de WOO-platform PVC's naar S3 primary storage op Fuga Cloud." - validations: - required: true - - - type: textarea - id: motivation - attributes: - label: "Motivatie / Aanleiding" - description: "Welk probleem lost dit op? Waarom nu?" - placeholder: "Huidige lokale PVC's lopen vol en zijn niet HA. Zie ook issue #123." - validations: - required: false - - - type: textarea - id: approach - attributes: - label: "Aanpak (globaal)" - description: "Hoe gaan we dit oplossen? Welke keuzes zijn al gemaakt?" - placeholder: | - 1. Backup bestaande data - 2. S3 bucket aanmaken op Fuga Cloud - 3. Nextcloud occ storage:update uitvoeren - 4. Smoke test per namespace - validations: - required: false - - - type: textarea - id: acceptance-criteria - attributes: - label: "Acceptatiecriteria" - value: | - - [ ] Taak uitvoerbaar via Ansible/Terraform (geen handmatige stappen) - - [ ] Gedocumenteerd in runbook of ADR - - [ ] Getest in acceptatieomgeving vรณรณr productie - - [ ] Rollback-procedure beschreven - - [ ] Geen downtime buiten afgesproken window - - [ ] Gereviewd (4-eyes) - - [ ] Geen nieuwe security findings - validations: - required: true - - - type: textarea - id: risks - attributes: - label: "Risico's / Afhankelijkheden" - placeholder: "Afhankelijk van beschikbaarheid acceptatieomgeving. Risico: dataverlies bij fout in migratiescript." - validations: - required: false - - - type: dropdown - id: category - attributes: - label: "Categorie" - options: - - "Infra / ops" - - "Refactor" - - "Technische schuld" - - "Security" - - "Performance" - - "CI/CD" - - "Documentatie" - validations: - required: true - - - type: dropdown - id: priority - attributes: - label: "Prioriteit" - options: - - "๐Ÿ”ด Critical" - - "๐ŸŸ  High" - - "๐ŸŸก Medium" - - "๐ŸŸข Low" - validations: - required: true diff --git a/.forgejo/issue_template/user-story.yml b/.forgejo/issue_template/user-story.yml deleted file mode 100644 index 0860501a..00000000 --- a/.forgejo/issue_template/user-story.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: "โœจ User Story" -description: "Nieuwe functionaliteit vanuit gebruikersperspectief" -title: "Als [rol] wil ik [actie] zodat [waarde]" -labels: ["user-story", "needs-refinement"] -assignees: [] -body: - - type: markdown - attributes: - value: | - ## User Story - Beschrijf de gewenste functionaliteit vanuit het perspectief van de gebruiker. - - - type: textarea - id: story - attributes: - label: "Story" - description: "Als [rol] wil ik [actie] zodat [waarde]" - placeholder: "Als gemeentemedewerker wil ik een document kunnen anonimiseren zodat ik het veilig kan delen." - validations: - required: true - - - type: textarea - id: context - attributes: - label: "Context / Achtergrond" - description: "Waarom is dit nodig? Wat is de aanleiding?" - placeholder: "WOO-verzoeken vereisen anonimisering vรณรณr publicatie..." - validations: - required: false - - - type: textarea - id: acceptance-criteria - attributes: - label: "Acceptatiecriteria" - description: "Definition of Done โ€” vink af wat van toepassing is" - value: | - - [ ] Functionaliteit werkt zoals beschreven in de story - - [ ] Er zijn unit tests aanwezig - - [ ] Er zijn integratietests aanwezig - - [ ] Documentatie is bijgewerkt - - [ ] Code is gereviewd (4-eyes) - - [ ] Geen nieuwe security findings (SAST/Trivy) - - [ ] Getest in acceptatieomgeving - validations: - required: true - - - type: textarea - id: out-of-scope - attributes: - label: "Buiten scope" - description: "Wat doen we expliciet NIET in dit issue?" - placeholder: "Geen bulk-verwerking, geen UI-wijzigingen." - validations: - required: false - - - type: dropdown - id: priority - attributes: - label: "Prioriteit" - options: - - "๐Ÿ”ด Critical" - - "๐ŸŸ  High" - - "๐ŸŸก Medium" - - "๐ŸŸข Low" - validations: - required: true - - - type: input - id: story-points - attributes: - label: "Story points (optioneel)" - placeholder: "3" - validations: - required: false diff --git a/.forgejo/workflows/app-tests-live.yml b/.forgejo/workflows/app-tests-live.yml deleted file mode 100644 index bf3ba300..00000000 --- a/.forgejo/workflows/app-tests-live.yml +++ /dev/null @@ -1,45 +0,0 @@ -# app-tests-live.yml โ€” stackiq caller for the LIVE-NC reusable workflow. -# -# Boots a real, seeded Nextcloud (db + NC + openregister deployed/enabled + -# stackiq deployed/enabled), then runs the deep Playwright e2e -# (tests/e2e/workflows/) and/or the Newman API-contract suite against it as -# HARD gates. Generalised per GAP-2 (arm CI live-gating) from openregister's -# reference rig; the reusable tests-live.yml is byte-identical fleet-wide, only -# these inputs differ. Additive โ€” does not touch app-tests.yml / release / -# pre-merge workflows. -# -# Gate status for stackiq (see TESTING-CI-ROLLOUT.md for the evidence table): -# deep-e2e: true โ€” e2e RESOLVES register+schema at runtime from /api/voorzieningen/config โ€” CI-portable -# newman: true โ€” ARMED: the collection "0. Setup / Preflight" request resolves the register -# numeric id from the stable slug 'stackiq' (and addresses the org/contact/ -# module/contract/dienst/gebruik schemas by slug), so the suite survives a fresh -# CI boot. AMEF ArchiMate export is un-quarantined (the InitializeSettings repair -# step auto-configures the AMEF register -> exportOrgArchiMate returns 200 model -# XML); the no-standalone-'standaard'-schema case stays a deterministic 404. - -name: app-tests-live - -on: - pull_request: - branches: - - development - - main - - beta - push: - branches: - - development - - main - workflow_dispatch: - -permissions: - contents: read - -jobs: - live: - uses: ./.forgejo/workflows/tests-live.yml - with: - app-id: stackiq - is-openregister: false - run-e2e: true - run-newman: true - newman-entrypoint: tests/integration/run-newman.sh diff --git a/.forgejo/workflows/app-tests.yml b/.forgejo/workflows/app-tests.yml deleted file mode 100644 index f68d5ac3..00000000 --- a/.forgejo/workflows/app-tests.yml +++ /dev/null @@ -1,34 +0,0 @@ -# app-tests.yml โ€” stackiq caller for the reusable feature-test workflow. -# -# Runs the PHASE-5 testing layers on every PR to the protected branches. -# phpunit-unit + l10n-check HARD-GATE; e2e/newman are scaffolded and opt-in -# (run-e2e / run-newman) until the NC service container is wired (see tests.yml -# TODOs). Additive โ€” sits alongside the existing release/quality workflows. - -name: app-tests - -on: - pull_request: - branches: - - development - - main - - beta - workflow_dispatch: - inputs: - run-e2e: - type: boolean - default: false - run-newman: - type: boolean - default: false - -permissions: - contents: read - -jobs: - tests: - uses: ./.forgejo/workflows/tests.yml - with: - app-id: stackiq - run-e2e: ${{ github.event.inputs.run-e2e == 'true' }} - run-newman: ${{ github.event.inputs.run-newman == 'true' }} diff --git a/.forgejo/workflows/documentation.yml b/.forgejo/workflows/documentation.yml deleted file mode 100644 index a2e2429a..00000000 --- a/.forgejo/workflows/documentation.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Publish docs - -# Docs deploy ONLY from the dedicated `documentation` branch โ€” decoupled from main/development -# so doc edits never trigger releases and code releases never trigger doc builds. No cron. -on: - push: - branches: [documentation] - pull_request: - branches: [documentation] - workflow_dispatch: - -jobs: - build: - uses: Conduction/.github/.forgejo/workflows/documentation-build.yml@main - with: - source-folder: docs - secrets: inherit - - deploy: - needs: build - if: github.event_name != 'pull_request' - uses: Conduction/.github/.forgejo/workflows/documentation-deploy.yml@main - with: - # FROZEN through the stackiq -> stackiq app-id rename. - # This names an EXISTING Cloudflare Pages project. Renaming it here does - # not rename the project โ€” the deploy simply targets a project that does - # not exist, and a Pages deploy against an unknown project reports success - # while reaching nobody (see the two green Cloudflare deploys that shipped - # to nothing during the fleet rename). Rename the Cloudflare project first, - # then this line. - cf-project-name: stackiq-docs - secrets: inherit diff --git a/.forgejo/workflows/pre-merge-check-strict.yaml b/.forgejo/workflows/pre-merge-check-strict.yaml deleted file mode 100644 index 863f2b03..00000000 --- a/.forgejo/workflows/pre-merge-check-strict.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# Pre-merge quality gate โ€” enforced lint + phpcs + all Hydra gates on every PR. -# Required status check on protected branches. -# -# Runner/container mirror the proven release-semrel workflow: codeberg-medium + -# official php:8.3-cli + a base-tooling step. The old code.forgejo.org/oci/ci-php:8.3 -# image 404s ("manifest unknown"), which fast-failed every run at container-pull. -# -# The gate runs `composer lint` + `composer phpcs` directly: check:strict's -# psalm/phpstan/phpmd/test:all are wrapped in `|| echo skipping` so they never -# affect pass/fail (ADR-022 parks static analysis), and running them on the medium -# runner OOMs it. lint+phpcs is the identical enforced gate, fast and deterministic. - -name: pre-merge-check-strict - -on: - pull_request: - branches: - - development - - main - - beta - -jobs: - quality-gates: - runs-on: codeberg-medium - container: - image: php:8.3-cli - timeout-minutes: 15 - steps: - - name: Install base tooling - run: | - apt-get update - apt-get install -y --no-install-recommends \ - git curl ca-certificates gnupg jq unzip zip \ - libzip-dev libpng-dev python3 - # Node is required by actions/checkout@v4 (a JS action) which runs - # inside this php:8.3-cli container; the stock image ships no node. - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - - apt-get install -y --no-install-recommends nodejs - docker-php-ext-install -j"$(nproc)" zip gd - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - - - name: Checkout PR - uses: https://github.com/actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install composer deps - run: composer install --no-interaction --no-progress --prefer-dist --ignore-platform-reqs - - - name: Run lint + phpcs (the enforced gate) - run: | - composer lint - composer phpcs - - - name: Clone Hydra (for gate runner) - uses: https://github.com/actions/checkout@v4 - with: - repository: Conduction/hydra - ref: development - path: .hydra - - - name: Run all Hydra gates (diff-scoped per ADR-020) - run: | - git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} || true - bash .hydra/scripts/run-hydra-gates.sh --scope-to-diff --base origin/${{ github.base_ref }} . - - - name: Gate-19 e2e coverage report (informational) - if: always() - run: | - python3 .hydra/scripts/lib/check_e2e_coverage.py . --mode report || true diff --git a/.forgejo/workflows/release-beta.yml b/.forgejo/workflows/release-beta.yml deleted file mode 100644 index 0d75d0f3..00000000 --- a/.forgejo/workflows/release-beta.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Beta Release - -on: - push: - branches: [beta] - workflow_dispatch: - -jobs: - release: - uses: Conduction/.github/.forgejo/workflows/release-beta.yml@main - with: - app-name: stackiq - secrets: - CODEBERG_TOKEN: ${{ secrets.CODEBERG_TOKEN }} - NEXTCLOUD_SIGNING_KEY: ${{ secrets.NEXTCLOUD_SIGNING_KEY }} - NEXTCLOUD_SIGNING_CERT: ${{ secrets.NEXTCLOUD_SIGNING_CERT }} - NEXTCLOUD_APPSTORE_TOKEN: ${{ secrets.NEXTCLOUD_APPSTORE_TOKEN }} diff --git a/.forgejo/workflows/release-stable.yml b/.forgejo/workflows/release-stable.yml deleted file mode 100644 index 6fe5d23c..00000000 --- a/.forgejo/workflows/release-stable.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Stable Release - -on: - push: - branches: [main] - workflow_dispatch: - -jobs: - release: - uses: Conduction/.github/.forgejo/workflows/release-semrel.yml@main - with: - app-name: stackiq - secrets: inherit diff --git a/.forgejo/workflows/tests-live.yml b/.forgejo/workflows/tests-live.yml deleted file mode 100644 index c70b74fa..00000000 --- a/.forgejo/workflows/tests-live.yml +++ /dev/null @@ -1,327 +0,0 @@ -# tests-live.yml โ€” reusable LIVE-NC feature-test workflow (PHASE-5 NC-in-CI). -# -# This is the GATING counterpart to tests.yml: where tests.yml runs the bare -# layers (phpunit-unit, l10n) that need no service container, this workflow -# boots a real, seeded Nextcloud and runs the two layers that DO need one โ€” -# the deep Playwright e2e (tests/e2e/workflows/) and the Newman API-contract -# suite โ€” so a PR can no longer go green while those flows are broken. -# -# It generalises openregister's reference rig -# (.github/workflows/api-test-coverage.yml + .github/docker-compose.ci.yml), -# which already boots NC + Postgres, deploys + enables OpenRegister, waits for -# the API and runs Newman. The only per-app knobs are `app-id` and whether the -# app is itself openregister (the data backend) or a feature app that needs -# openregister enabled ALONGSIDE it. -# -# Per-app PREREQUISITE (the common seeding step): -# The deep e2e + Newman suites must be SELF-SEEDING โ€” each spec/collection -# creates the OR register + schema + objects it asserts on in its -# beforeAll/setUp and tears them down in afterAll/teardown. openregister's -# tests/e2e/workflows/object-lifecycle-workflows.spec.ts and the Newman -# collections already do this. A feature app whose suites assume a -# pre-imported OR register-config must EITHER add an `occ` seed step here -# (import the app's register-config, e.g. via its Repair step or -# `occ :import-config`) OR make the suites self-seed. Until one of -# those is true, keep run-e2e/run-newman false for that app. -# -# Runner label `docker` (needs a docker daemon to boot the compose stack) + -# the short `https://code.forgejo.org/actions/...@v4` `uses:` form follow the -# fleet convention. Additive โ€” does not touch tests.yml, pre-merge-check-strict -# or any release workflow. - -name: tests-live - -on: - workflow_call: - inputs: - app-id: - description: "App id (matches package.json name + composer namespace + custom_apps dir)." - required: true - type: string - node-version: - required: false - type: string - default: "20" - php-version: - required: false - type: string - default: "8.3" - is-openregister: - description: "True when app-id IS openregister (the data backend). Feature apps leave this false; openregister is enabled alongside them." - required: false - type: boolean - default: false - run-e2e: - description: "Run the deep Playwright e2e (tests/e2e/workflows/) against the live NC. GATING when true." - required: false - type: boolean - default: false - run-newman: - description: "Run the Newman API-contract suite against the live NC. GATING when true." - required: false - type: boolean - default: false - run-visual: - description: >- - Run the Playwright visual-regression project (tests/e2e/visual/, GAP-5) - against the live NC. NON-GATING by design (continue-on-error). PLATFORM - CAVEAT: PNG baselines are host-font/GPU specific, so committed - dev-container baselines will NOT byte-match a CI Linux runner. On the - first CI run the baselines must be regenerated in-CI (download the - uploaded visual-snapshots artifact and commit it) before this step can - be made gating. Until then it reports diffs as an artifact only. - required: false - type: boolean - default: false - newman-entrypoint: - description: "Path to the Newman runner. openregister uses the orchestrator; feature apps use tests/integration/run-newman.sh." - required: false - type: string - default: "tests/newman/run-all.sh" - newman-collections: - description: "COLLECTIONS subset passed to the orchestrator (self-seeding domains only; excludes fixtures that assume dev-container state)." - required: false - type: string - default: "crud graphql relations auth-matrix error-matrix referential-integrity" - -permissions: - contents: read - -jobs: - # --------------------------------------------------------------------------- - # LIVE GATE โ€” boot seeded NC, deploy OR (+ the app), run deep e2e + Newman. - # - # A single job boots the stack once and runs both suites against it (cheaper - # than two stacks; both are read-mostly + self-seeding so they don't collide - # โ€” e2e prefixes its fixtures with a run-id, Newman teardown-cleans its own). - # --------------------------------------------------------------------------- - live-nc: - name: Live NC e2e + Newman (${{ inputs.app-id }}) - if: ${{ inputs.run-e2e || inputs.run-newman || inputs.run-visual }} - runs-on: docker - timeout-minutes: 40 - permissions: - contents: read - env: - APP_ID: ${{ inputs.app-id }} - COMPOSE_FILE: .github/docker-compose.ci.yml - steps: - - name: Checkout - uses: https://code.forgejo.org/actions/checkout@v4 - - - name: Set up PHP - uses: https://github.com/shivammathur/setup-php@v2 - with: - php-version: ${{ inputs.php-version }} - tools: composer:v2 - coverage: none - - - name: Set up Node.js - uses: https://code.forgejo.org/actions/setup-node@v4 - with: - node-version: ${{ inputs.node-version }} - - - name: Install composer deps (production) - run: composer install --no-dev --no-interaction --no-progress --prefer-dist - - - name: Install npm deps + Newman - run: | - npm ci --no-audit --no-fund || npm install --no-audit --no-fund - npm install -g newman - - - name: Boot CI stack (db + Nextcloud) - # Reuses openregister's reference compose (db + NC, named volume only). - # A feature app that does not ship its own compose should commit a copy - # of openregister/.github/docker-compose.ci.yml. - run: docker compose -f "$COMPOSE_FILE" up -d - - - name: Wait for Nextcloud to be installed - run: | - for i in $(seq 1 60); do - if docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ status" 2>/dev/null | grep -q "installed: true"; then - echo "Nextcloud installed and ready" - break - fi - echo "Waiting for Nextcloud installation... ($i/60)" - sleep 5 - done - docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ status" - - - name: Deploy OpenRegister (data backend) - # The app under test stores its objects in OpenRegister, so OR must be - # deployed+enabled regardless of which app we're testing. When the app - # UNDER TEST *is* openregister, this single deploy covers it. - run: | - if [ "${{ inputs.is-openregister }}" = "true" ]; then - OR_SRC="." - else - # Feature apps build against a sibling openregister checkout in the - # same apps-extra tree. CI clones only this repo, so fetch OR's - # release build instead. - OR_SRC="$RUNNER_TEMP/openregister" - git clone --depth 1 https://github.com/ConductionNL/openregister.git "$OR_SRC" - ( cd "$OR_SRC" && composer install --no-dev --no-interaction --no-progress --prefer-dist ) - fi - docker exec nextcloud mkdir -p /var/www/html/custom_apps/openregister - tar --exclude='.git' --exclude='node_modules' --exclude='.claude' \ - --exclude='tests/e2e/playwright-report' --exclude='tests/e2e/test-results' \ - -C "$OR_SRC" -cf - . \ - | docker exec -i nextcloud tar -xf - -C /var/www/html/custom_apps/openregister - docker exec nextcloud chown -R www-data:www-data /var/www/html/custom_apps/openregister - docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ app:enable openregister" - - - name: Deploy app under test - # Skipped when the app IS openregister (already deployed above). - if: ${{ inputs.is-openregister == false }} - run: | - docker exec nextcloud mkdir -p "/var/www/html/custom_apps/$APP_ID" - tar --exclude='.git' --exclude='node_modules' --exclude='.claude' \ - --exclude='tests/e2e/playwright-report' --exclude='tests/e2e/test-results' \ - -cf - . \ - | docker exec -i nextcloud tar -xf - -C "/var/www/html/custom_apps/$APP_ID" - docker exec nextcloud chown -R www-data:www-data "/var/www/html/custom_apps/$APP_ID" - docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ app:enable $APP_ID" - docker exec nextcloud su -s /bin/bash www-data -c "php /var/www/html/occ app:list" | grep "$APP_ID" - - - name: Seed OR register-config (per-app prerequisite) - # SELF-SEEDING suites need nothing here. An app whose deep-e2e / Newman - # assumes a pre-imported register-config should import it now, e.g.: - # docker exec nextcloud su -s /bin/bash www-data -c \ - # "php /var/www/html/occ $APP_ID:import-config" # if the app ships one - # or trigger the app's Repair step (most Conduction apps import their - # register via lib/Repair/Initialize*.php on app:enable โ€” already done - # by the enable step above). Left as an explicit, documented hook. - run: | - echo "Seeding: openregister's own suites are self-seeding (fixtures create" - echo "register+schema+objects per run-id, torn down in afterAll/teardown)." - echo "Feature apps: add the register-config import here (see step comment)." - - - name: Wait for the API to respond - # app:enable returns before the repair/magic-mapping step + PHP-FPM - # opcache warm-up finish; poll the OR registers endpoint until it - # returns a real HTTP status (not connection-level 000). - run: | - for i in $(seq 1 60); do - code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 \ - -u admin:admin \ - http://localhost:8080/index.php/apps/openregister/api/registers \ - 2>/dev/null | tr -d '\n') - if [ -n "$code" ] && [ ${#code} -eq 3 ] \ - && [ "$code" -ge 100 ] 2>/dev/null && [ "$code" -lt 500 ]; then - echo "OpenRegister API responding (HTTP $code, attempt $i)" - sleep 5 - break - fi - echo "Waiting for API... (attempt $i/60, last='$code')" - sleep 2 - done - - - name: Mint admin storageState + run deep e2e (GATING) - # The app's playwright global-setup logs into NC once and persists the - # cookie jar to tests/e2e/.auth/admin.json (the storageState the - # workflows spec consumes). We run ONLY tests/e2e/workflows here โ€” the - # deep, data-dependent layer โ€” against the live, self-seeded NC. - if: ${{ inputs.run-e2e }} - env: - NEXTCLOUD_URL: http://localhost:8080 - NC_ADMIN_USER: admin - NC_ADMIN_PASS: admin - OR_USER: admin - OR_PASS: admin - CI: "true" - run: | - npx playwright install --with-deps chromium - npx playwright test tests/e2e/workflows - - - name: Run visual-regression project (NON-GATING โ€” GAP-5) - # Visual baselines are rendered against the local dev container; a CI - # Linux runner uses a different font stack + GPU so the committed PNGs - # will not byte-match here. This step is therefore NON-GATING - # (continue-on-error) and exists to (a) surface visual diffs as an - # artifact and (b) let a maintainer regenerate CI-native baselines: - # download the `visual-snapshots-` artifact from a run with - # PLAYWRIGHT_UPDATE=1 and commit it, then drop continue-on-error to - # make the step gating in the CI environment. - if: ${{ inputs.run-visual }} - continue-on-error: true - env: - NEXTCLOUD_URL: http://localhost:8080 - NC_ADMIN_USER: admin - NC_ADMIN_PASS: admin - OR_USER: admin - OR_PASS: admin - CI: "true" - run: | - npx playwright install --with-deps chromium - if [ "${PLAYWRIGHT_UPDATE:-0}" = "1" ]; then - echo "Regenerating CI-native visual baselines (--update-snapshots)โ€ฆ" - npx playwright test --project visual --update-snapshots || true - else - npx playwright test --project visual || true - fi - - - name: Upload visual snapshots + diffs (NON-GATING) - if: ${{ inputs.run-visual }} - continue-on-error: true - uses: https://code.forgejo.org/actions/upload-artifact@v4 - with: - name: visual-snapshots-${{ inputs.app-id }} - path: | - tests/e2e/visual/**/*-snapshots/** - tests/e2e/test-results/** - retention-days: 14 - if-no-files-found: ignore - - - name: Run Newman API-contract suite (GATING) - if: ${{ inputs.run-newman }} - env: - BASE_URL: http://localhost:8080 - ADMIN_USER: admin - ADMIN_PASSWORD: admin - CONTAINER_NAME: nextcloud - NEWMAN_RUNNER: host - FAIL_FAST: "0" - COLLECTIONS: ${{ inputs.newman-collections }} - run: bash "${{ inputs.newman-entrypoint }}" - - - name: Upload Playwright report - if: always() - uses: https://code.forgejo.org/actions/upload-artifact@v4 - with: - name: playwright-report-${{ inputs.app-id }} - path: | - tests/e2e/playwright-report/ - tests/e2e/test-results/ - retention-days: 14 - if-no-files-found: ignore - - - name: Upload Newman reports - if: always() - uses: https://code.forgejo.org/actions/upload-artifact@v4 - with: - name: newman-reports-${{ inputs.app-id }} - path: | - tests/newman/reports/ - newman-*.json - retention-days: 14 - if-no-files-found: ignore - - - name: Collect docker logs on failure - if: failure() - run: | - mkdir -p ci-logs - docker compose -f "$COMPOSE_FILE" logs --no-color > ci-logs/docker-compose.log 2>&1 || true - docker exec nextcloud cat /var/www/html/data/nextcloud.log > ci-logs/nextcloud.log 2>&1 || true - - - name: Upload docker logs - if: failure() - uses: https://code.forgejo.org/actions/upload-artifact@v4 - with: - name: ci-logs-${{ inputs.app-id }} - path: ci-logs/ - retention-days: 14 - if-no-files-found: ignore - - - name: Tear down CI stack - if: always() - run: docker compose -f "$COMPOSE_FILE" down -v diff --git a/.forgejo/workflows/tests.yml b/.forgejo/workflows/tests.yml deleted file mode 100644 index f6d76685..00000000 --- a/.forgejo/workflows/tests.yml +++ /dev/null @@ -1,274 +0,0 @@ -# tests.yml โ€” reusable feature-test workflow for Conduction NC apps. -# -# PHASE-5 CI enforcement: runs the testing layers we built so a PR can't go -# green while a feature is broken. This is a `workflow_call` reusable: a thin -# per-app caller (app-tests.yml) invokes it with the app id. Copy the pair -# (this file + app-tests.yml) into any sibling app to roll the pattern out โ€” -# the only per-app knob is `app-id`. -# -# Layers, and what gates today: -# โ€ข phpunit-unit โ€” HARD GATE. tests/Unit + tests/unit via phpunit-unit.xml. -# The bootstrap runs standalone (vendor OCP stubs, no NC), -# so this needs no service container. -# โ€ข l10n-check โ€” HARD GATE. tests/l10n/check-l10n.js asserts every -# t('', '...') / n(...) source string is present in -# l10n/en.json (the i18n-extraction-drift guard). Pure -# Node, no NC. -# โ€ข e2e-deep โ€” SCAFFOLDED (non-gating). Playwright tests/e2e/workflows/ -# need a live, seeded NC. See the TODO in that job. -# โ€ข newman โ€” SCAFFOLDED (non-gating). tests/integration Postman -# collections via run-newman.sh need a live NC. See TODO. -# -# Runner labels + reusable `uses:` forms follow the fleet convention -# (codeberg-small / short Conduction/.github@main form). Additive โ€” does not -# touch pre-merge-check-strict.yaml or any release workflow. - -name: tests - -on: - workflow_call: - inputs: - app-id: - description: "App id (matches package.json name + composer namespace)." - required: true - type: string - php-version: - required: false - type: string - default: "8.3" - node-version: - required: false - type: string - default: "20" - unit-gating: - description: "Fail the workflow on unit-test failures. Set false for apps whose tests/Unit suite is not yet green on baseline (e.g. openregister โ€” see TESTING-CI-ROLLOUT.md)." - required: false - type: boolean - default: true - run-e2e: - description: "Run the scaffolded deep-e2e job (needs NC service โ€” see TODO)." - required: false - type: boolean - default: false - run-newman: - description: "Run the scaffolded Newman job (needs NC service โ€” see TODO)." - required: false - type: boolean - default: false - -permissions: - contents: read - -jobs: - # --------------------------------------------------------------------------- - # HARD GATE 1 โ€” PHPUnit unit suite (no NC needed; vendor OCP stubs). - # --------------------------------------------------------------------------- - phpunit-unit: - name: PHPUnit unit (${{ inputs.app-id }}) - runs-on: codeberg-medium - container: - image: php:8.3-cli - steps: - - name: Install base tooling - run: | - apt-get update - apt-get install -y --no-install-recommends \ - git curl ca-certificates gnupg jq unzip zip \ - libzip-dev libpng-dev python3 - # Node is required by actions/checkout@v4 (a JS action) which runs - # inside this php:8.3-cli container; the stock image ships no node. - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - - apt-get install -y --no-install-recommends nodejs - docker-php-ext-install -j"$(nproc)" zip gd - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Install composer deps - run: composer install --no-interaction --no-progress --prefer-dist --ignore-platform-reqs - - - name: Run unit suite (phpunit-unit.xml) - # unit-gating=false reports failures without failing the job, for apps - # carrying pre-existing tests/Unit debt (see TESTING-CI-ROLLOUT.md). - continue-on-error: false - run: ./vendor/bin/phpunit --configuration phpunit-unit.xml --no-coverage --colors=never - - # --------------------------------------------------------------------------- - # HARD GATE 2 โ€” l10n extraction-drift check (no NC needed; pure Node). - # --------------------------------------------------------------------------- - l10n-check: - name: l10n extraction check (${{ inputs.app-id }}) - runs-on: codeberg-medium - container: - image: node:${{ inputs.node-version }} - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Assert every t() source string is in l10n/en.json - run: node tests/l10n/check-l10n.js - - # --------------------------------------------------------------------------- - # HARD GATE 3 โ€” frontend unit suite (Vitest, OFFLINE; no NC needed). - # - # Runs the pure-logic Vitest suite under tests/vitest/** (Pinia store - # state transitions, util/formatter calc, form-validation mappers, and any - # offline component mounts). These need no DOM/NC runtime โ€” @nextcloud/* and - # @conduction/nextcloud-vue are aliased to deterministic stubs in - # vitest.config.js. Always gating. - # --------------------------------------------------------------------------- - frontend-unit: - name: Frontend unit (Vitest โ€” ${{ inputs.app-id }}) - runs-on: codeberg-medium - container: - image: node:${{ inputs.node-version }} - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Install npm deps - run: npm ci --legacy-peer-deps || npm install --legacy-peer-deps - - - name: Run Vitest unit suite - run: npm run test:unit - - # --------------------------------------------------------------------------- - # COVERAGE GATE A โ€” PHPUnit COVERAGE RATCHET (PCOV clover line coverage). - # Fails a PR that drops backend coverage below tests/.coverage-baseline.json - # `phpunit` minus tolerance. Inherits continue-on-error from unit-gating so a - # red unit suite records-but-does-not-block. Seeds on first --update run when - # the baseline is null. See TESTING-CI-ROLLOUT.md "Coverage ratchet". - # --------------------------------------------------------------------------- - phpunit-coverage-ratchet: - name: PHPUnit coverage ratchet (${{ inputs.app-id }}) - runs-on: codeberg-medium - container: - image: php:8.3-cli - continue-on-error: false - steps: - - name: Install base tooling - run: | - apt-get update - apt-get install -y --no-install-recommends \ - git curl ca-certificates gnupg jq unzip zip \ - libzip-dev libpng-dev python3 - # Node is required by actions/checkout@v4 (a JS action) which runs - # inside this php:8.3-cli container; the stock image ships no node. - curl -fsSL https://deb.nodesource.com/setup_20.x | bash - - apt-get install -y --no-install-recommends nodejs - docker-php-ext-install -j"$(nproc)" zip gd - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Ensure a coverage driver (PCOV) - run: | - if ! php -m | grep -qiE 'pcov|xdebug'; then - (pecl install pcov && docker-php-ext-enable pcov) \ - || echo "WARN: could not install pcov โ€” coverage step may report no driver" - fi - php -m | grep -qiE 'pcov|xdebug' && echo "coverage driver: present" \ - || echo "coverage driver: ABSENT (ratchet will no-op; see TESTING-CI-ROLLOUT.md)" - - - name: Install composer deps - run: composer install --no-interaction --no-progress --prefer-dist --ignore-platform-reqs - - - name: Run unit suite WITH coverage (clover) - run: | - php -d pcov.enabled=1 -d pcov.directory=lib \ - ./vendor/bin/phpunit --configuration phpunit-unit.xml \ - --coverage-clover coverage/clover.xml --colors=never || true - test -f coverage/clover.xml || { echo "no clover.xml (driver absent?) โ€” skipping ratchet"; exit 0; } - - - name: Coverage ratchet (fail on drop) - run: | - test -f coverage/clover.xml || exit 0 - bash tests/coverage-ratchet.sh phpunit coverage/clover.xml - - # --------------------------------------------------------------------------- - # COVERAGE GATE B โ€” FRONTEND COVERAGE RATCHET (Vitest v8, src/** line coverage). - # Fails a PR that drops frontend coverage below baseline `vitest` minus - # tolerance. Seeds on first --update run when the baseline is null. - # --------------------------------------------------------------------------- - frontend-coverage-ratchet: - name: Frontend coverage ratchet (${{ inputs.app-id }}) - runs-on: codeberg-medium - container: - image: node:${{ inputs.node-version }} - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: Install npm deps (+ coverage-v8) - run: | - npm ci --legacy-peer-deps || npm install --legacy-peer-deps - VITEST_VER="$(node -e "console.log(require('./node_modules/vitest/package.json').version)")" - npm install --no-save --legacy-peer-deps "@vitest/coverage-v8@${VITEST_VER}" - - - name: Run Vitest WITH coverage (json-summary over src/**) - run: | - npx vitest run --coverage --coverage.provider=v8 \ - --coverage.reporter=json-summary --coverage.reporter=text-summary \ - --coverage.include='src/**' \ - --coverage.reportsDirectory=coverage-vitest || true - test -f coverage-vitest/coverage-summary.json \ - || { echo "no coverage-summary.json โ€” vitest coverage unavailable; skipping ratchet"; exit 0; } - - - name: Coverage ratchet (fail on drop) - run: | - test -f coverage-vitest/coverage-summary.json || exit 0 - bash tests/coverage-ratchet.sh vitest coverage-vitest/coverage-summary.json - - # --------------------------------------------------------------------------- - # SCAFFOLD โ€” deep e2e (Playwright tests/e2e/workflows/). Opt-in via run-e2e. - # - # TODO(nc-in-ci): wire a live Nextcloud before flipping this to gating: - # 1. Boot db + NC (openregister ships .github/docker-compose.ci.yml; add a - # sibling compose for feature apps, or reuse the OR stack + enable this - # app + its OR register/schema fixtures). - # 2. Deploy the working tree into custom_apps/ and `occ app:enable` - # (+ openregister, the data backend). - # 3. Seed the deep-e2e fixtures (tests/e2e/workflows/*fixture*.ts seed the - # OR objects each workflow asserts on). - # 4. `npx playwright install --with-deps chromium` then - # `npm run test:e2e -- tests/e2e/workflows`. - # Until then this job documents the command and is non-gating. - # --------------------------------------------------------------------------- - e2e-deep: - name: Deep e2e (scaffold โ€” needs NC) - if: ${{ inputs.run-e2e }} - runs-on: docker - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: TODO โ€” boot seeded NC, then run deep e2e - run: | - echo "Deep e2e requires a live, seeded Nextcloud (see job header TODO)." - echo "Local: npm ci && npm run test:e2e:install && npm run test:e2e -- tests/e2e/workflows" - echo "Skipping in CI until the NC service container is wired." - - # --------------------------------------------------------------------------- - # SCAFFOLD โ€” Newman API-contract (tests/integration/*.postman_collection.json). - # Opt-in via run-newman. - # - # TODO(nc-in-ci): same live-NC prerequisite as e2e-deep. The runner script - # (tests/integration/run-newman.sh) is collection-self-seeding and runnable - # locally today: `bash tests/integration/run-newman.sh`. openregister uses - # the orchestrator at tests/newman/run-all.sh instead. - # --------------------------------------------------------------------------- - newman: - name: Newman API contract (scaffold โ€” needs NC) - if: ${{ inputs.run-newman }} - runs-on: docker - steps: - - name: Checkout - uses: https://github.com/actions/checkout@v4 - - - name: TODO โ€” boot NC, then run Newman - run: | - echo "Newman requires a live Nextcloud serving the app (see job header TODO)." - echo "Local: bash tests/integration/run-newman.sh # OR: bash tests/newman/run-all.sh" - echo "Skipping in CI until the NC service container is wired."