e3823ce470
Add an end-to-end and unit test suite for the SvelteKit frontend:
- Playwright e2e specs (tests/e2e/spa) with a throwaway container stack,
codegen scenarios, and an Istanbul-based coverage report pipeline.
- Vitest unit tests across API endpoints, components, stores and composables.
- `data-testid` hooks on interactive elements (AppShell, FileViewer,
ShareDialog, search, photos, files breadcrumbs, login/Nextcloud flows,
public share pages) so the e2e suite can target them deterministically.
- Serve the SPA app-shell CSP from a <meta> policy (svelte.config.js) plus a
middleware that skips the CSP header on HTML; move the Nextcloud Login Flow
v2 grant page to the SvelteKit /nextcloud/login route.
- `just front-codegen` recipe and start-server-spa.sh harness.
Make the test environment robust and consistent:
- Install a deterministic in-memory localStorage/sessionStorage in the Vitest
setup so storage behaves identically across Node versions (Node 26 ships a
native Web Storage global that otherwise shadows jsdom's).
- Pin devenv to Node 26 + PostgreSQL 18 and pin every CI job to Node 26.3.0
so the dev shell and CI run the same toolchain versions.
Repair the API/WebDAV (hurl) suite, which had drifted from the backend:
- Migrate the removed `/api/folders/{id}/listing` endpoint to `/resources`
(cursor-paginated `{items:[{resource_type,resource}]}` shape) across the
batch-copy, grants, nested-group, and WebDAV NC tests + the dav_helpers
wipe routine.
- Stop photos_etag from uploading the dedup-tracked fixture so the dedup
blob-lifecycle test can own its content-addressed blob exclusively.
- dedup_create now asserts the idempotent same-content re-upload (201 +
existing file id) instead of the stale 409 expectation.
Generated coverage reports, nyc output and the e2e server runtime data dir
are gitignored rather than committed.
361 lines
11 KiB
YAML
361 lines
11 KiB
YAML
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
|