name: CI on: push: branches: - main - dev - "feat/**" - "fix/**" pull_request: branches: [ "main", "dev" ] env: CARGO_TERM_COLOR: always RUSTFLAGS: "-Dwarnings" DATABASE_URL: "postgres://postgres:postgres@localhost/oxicloud_test" jobs: # Detect which parts of the codebase changed changes: runs-on: ubuntu-latest outputs: frontend: ${{ steps.filter.outputs.frontend }} backend: ${{ steps.filter.outputs.backend }} wasm: ${{ steps.filter.outputs.wasm }} plugins: ${{ steps.filter.outputs.plugins }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v3 id: filter with: filters: | frontend: - 'frontend/**' backend: - 'src/**' - 'Cargo.toml' - 'Cargo.lock' - 'tests/**' wasm: - 'wasm/**' - 'scripts/build-wasm.sh' plugins: - 'wasm/oxicloud-plugin-hello/**' - 'scripts/build-plugin-hello.sh' - 'tests/fixtures/plugins/**' - 'src/infrastructure/services/plugins/**' - 'src/application/ports/plugin_ports.rs' - 'src/application/adapters/plugin_lifecycle_hook.rs' - 'src/application/adapters/plugin_user_lifecycle_hook.rs' frontend-check: name: Frontend — svelte-check, ESLint, Stylelint, Prettier needs: changes if: needs.changes.outputs.frontend == 'true' runs-on: ubuntu-latest defaults: run: working-directory: frontend steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 26.3.0 cache: npm cache-dependency-path: frontend/package-lock.json - name: Install dependencies run: npm ci - name: Check (svelte-check + eslint + stylelint + prettier) run: npm run check - name: Unit tests run: npm run test:unit rust-fmt: name: Rustfmt needs: changes if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt - run: cargo fmt --all --check rust-clippy: name: Clippy needs: changes if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: clippy - uses: Swatinem/rust-cache@v2 - run: cargo clippy --all-targets --all-features -- -D warnings # Mirrors the `wasm-check` justfile recipe. The wasm crate is a # standalone workspace under `wasm/oxicloud-hash/` and is NOT # built by the server's `cargo build` — these checks have to run # explicitly from inside the sub-workspace. clippy + tests run # against the HOST target (no wasm32 target needed in CI) which # is enough to cover the algorithmic logic; the wasm32 build is # exercised by `scripts/build-wasm.sh` and the frontend tests. wasm-check: name: Wasm — fmt + clippy needs: changes if: needs.changes.outputs.wasm == 'true' runs-on: ubuntu-latest defaults: run: working-directory: wasm/oxicloud-hash steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy - uses: Swatinem/rust-cache@v2 with: workspaces: wasm/oxicloud-hash - run: cargo fmt --all --check - run: cargo clippy --all-features --release -- -D warnings # Mirrors the `wasm-test` justfile recipe. Tests run in release # mode because the FastCDC + BLAKE3 workload takes minutes in # the default debug profile (no inlining / no SIMD). wasm-test: name: Wasm — release tests needs: changes if: needs.changes.outputs.wasm == 'true' runs-on: ubuntu-latest defaults: run: working-directory: wasm/oxicloud-hash steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 with: workspaces: wasm/oxicloud-hash - run: cargo test --release # Plugin runtime (Extism). Rebuilds the committed .wasm fixtures from # wasm/oxicloud-plugin-hello/ and fails if they drift from what is # committed (staleness guard), then runs the plugin-runtime tests with # the `plugins` feature. The wasm32 target is needed only to rebuild the # fixtures; the host tests themselves do not need it. plugins: name: Plugins — fixtures + runtime tests needs: changes if: needs.changes.outputs.plugins == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: targets: wasm32-unknown-unknown - uses: Swatinem/rust-cache@v2 - name: Rebuild committed wasm fixtures run: bash scripts/build-plugin-hello.sh - name: Fail if fixtures are stale (rebuild + commit them) run: git diff --exit-code tests/fixtures/plugins/ - name: Run plugin runtime tests # Quote: the trailing `::` confuses GitHub's YAML parser (mapping # values not allowed) and aborts the whole workflow at load time. run: 'cargo test --features plugins plugins::' rust-test: name: Server Unit and Functionnal Tests needs: changes if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest services: postgres: image: postgres:18-alpine env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: oxicloud_test ports: - 5432:5432 options: >- --health-cmd "pg_isready -U postgres" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Initialize test database # Applies every migration + seeds the integration-test admin row. # Same script used by `just test-integration` locally. run: bash tests/common/init-test-schema.sh env: PGHOST: localhost PGPORT: "5432" PGUSER: postgres PGPASSWORD: postgres PGDATABASE: oxicloud_test - name: Run tests run: cargo test --all-features --workspace env: DATABASE_URL: "postgres://postgres:postgres@localhost/oxicloud_test" - name: Run integration tests run: cargo test --all-features --workspace --tests env: DATABASE_URL: "postgres://postgres:postgres@localhost/oxicloud_test" RUSTFLAGS: "-Dwarnings --cfg integration_tests" rust-audit: name: Security Audit needs: changes if: needs.changes.outputs.backend == 'true' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: rustsec/audit-check@v2.0.0 with: token: ${{ secrets.GITHUB_TOKEN }} build: name: Build runs-on: ubuntu-latest if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 26.3.0 cache: npm cache-dependency-path: frontend/package-lock.json - name: Build SPA (Vite -> static-dist/) working-directory: frontend run: npm ci && npm run build - run: cargo build --release - uses: actions/upload-artifact@v4 with: name: oxicloud-release path: target/release/oxicloud retention-days: 1 api-test: name: API & Webdav tests needs: build if: github.event_name == 'pull_request' timeout-minutes: 30 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: oxicloud-release path: target/release/ - name: Set execute bit on pre-built binary run: chmod +x target/release/oxicloud - name: Install Hurl, b3sum, and xq env: HURL_MAJOR: "8" # sibprogrammer/xq — standalone Go binary, real XPath via libxml2. # Pinned to match the dev-box version so the test scripts can rely # on syntax stability. Bump in lockstep with the dev install. XQ_VERSION: "1.3.0" run: | HURL_VERSION=$(curl -fsSL -H "Authorization: Bearer ${{ github.token }}" \ https://api.github.com/repos/Orange-OpenSource/hurl/releases \ | jq -r "map(select(.tag_name | startswith(\"${HURL_MAJOR}.\"))) | first | .tag_name") curl -fLO "https://github.com/Orange-OpenSource/hurl/releases/download/${HURL_VERSION}/hurl_${HURL_VERSION}_amd64.deb" sudo apt-get install -y "./hurl_${HURL_VERSION}_amd64.deb" b3sum curl -fLO "https://github.com/sibprogrammer/xq/releases/download/v${XQ_VERSION}/xq_${XQ_VERSION}_linux_amd64.tar.gz" tar -xzf "xq_${XQ_VERSION}_linux_amd64.tar.gz" xq sudo install -m 0755 xq /usr/local/bin/xq - name: Run Hurl API tests run: bash tests/api/run.sh env: BUILD_TARGET: release - name: Run Webdav tests run: bash tests/webdav/run.sh env: BUILD_TARGET: release - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: hurl-report path: tests/api/storage/ retention-days: 7 front-test: name: Frontend end-to-end tests (via Playwright) # ensure that api tests are ok before needs: api-test if: github.event_name == 'pull_request' timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/download-artifact@v4 with: name: oxicloud-release path: target/release/ - name: Set execute bit on pre-built binary run: chmod +x target/release/oxicloud - uses: actions/setup-node@v4 with: node-version: 26.3.0 - name: Install Node dependencies working-directory: tests/e2e run: npm ci # The release binary serves the SPA from ./static-dist on disk (not # embedded), and the Build job builds it WITHOUT VITE_E2E so it lacks the # `data-testid` hooks the specs target. Build it here with VITE_E2E=1 so # the server actually serves the e2e SPA the scenarios drive. - name: Build SPA for e2e (VITE_E2E keeps data-testid hooks) working-directory: frontend run: npm ci && VITE_E2E=1 npm run build - name: Install Playwright browsers working-directory: tests/e2e run: npx playwright install --with-deps - name: Run Playwright tests (spawns DB via pretest hook) working-directory: tests/e2e run: npm test env: BUILD_TARGET: release - name: Print server startup log if: always() run: cat tests/e2e/server-startup.log || echo "no server-startup.log produced" - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: playwright-report path: tests/e2e/playwright-report/ retention-days: 30