ci(api-tests): add API test to CI
This commit is contained in:
@@ -6,9 +6,9 @@ on:
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
|
||||
concurrency:
|
||||
group: rust-build-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
#concurrency:
|
||||
# group: rust-build-${{ github.ref }}
|
||||
# cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
name: Playwright Test (end-to-end)
|
||||
|
||||
# IMPORTANT: using Swatinem/rust-cache@v2 : reuse same cache as ci.yml (minimize non necessary new compilation)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, dev ]
|
||||
pull_request:
|
||||
branches: [ main, dev ]
|
||||
|
||||
#concurrency:
|
||||
# group: rust-build-${{ github.ref }}
|
||||
# cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
api-test:
|
||||
name: Hurl API tests
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache compiled binary
|
||||
id: binary-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/target/debug/oxicloud
|
||||
key: ${{ runner.os }}-oxicloud-binary-${{ hashFiles('src/**', 'Cargo.toml', 'Cargo.lock') }}
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
if: steps.binary-cache.outputs.cache-hit != 'true'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Build server
|
||||
if: steps.binary-cache.outputs.cache-hit != 'true'
|
||||
run: cargo build
|
||||
|
||||
- name: Install Hurl
|
||||
env:
|
||||
HURL_MAJOR: "8"
|
||||
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"
|
||||
|
||||
- name: Run Hurl API tests
|
||||
run: bash tests/api/run.sh
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: hurl-report
|
||||
path: tests/api/storage/
|
||||
retention-days: 7
|
||||
|
||||
front-test:
|
||||
# starts api-test first
|
||||
needs: api-test
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache compiled binary
|
||||
id: binary-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/target/debug/oxicloud
|
||||
key: ${{ runner.os }}-oxicloud-binary-${{ hashFiles('src/**', 'Cargo.toml', 'Cargo.lock') }}
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
if: steps.binary-cache.outputs.cache-hit != 'true'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Build server
|
||||
if: steps.binary-cache.outputs.cache-hit != 'true'
|
||||
run: cargo build
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install Node dependencies
|
||||
working-directory: tests/e2e
|
||||
run: npm ci
|
||||
|
||||
- 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
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: tests/e2e/playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
name: Playwright Test (end-to-end)
|
||||
|
||||
# IMPORTANT: using Swatinem/rust-cache@v2 : reuse same cache as ci.yml (minimize non necessary new compilation)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, dev ]
|
||||
pull_request:
|
||||
branches: [ main, dev ]
|
||||
|
||||
concurrency:
|
||||
group: rust-build-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Cache compiled binary
|
||||
id: binary-cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/target/debug/oxicloud
|
||||
key: ${{ runner.os }}-oxicloud-binary-${{ hashFiles('src/**', 'Cargo.toml', 'Cargo.lock') }}
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
if: steps.binary-cache.outputs.cache-hit != 'true'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Build server
|
||||
if: steps.binary-cache.outputs.cache-hit != 'true'
|
||||
run: cargo build
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install Node dependencies
|
||||
working-directory: tests/e2e
|
||||
run: npm ci
|
||||
|
||||
- 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
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: tests/e2e/playwright-report/
|
||||
retention-days: 30
|
||||
+7
-7
@@ -9,7 +9,7 @@ Tests are written using [Hurl](https://hurl.dev) — a plain-text, CLI-first HTT
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `.env` to match your local instance. This file is the single source of
|
||||
Edit `test.env` to match your local instance. This file is the single source of
|
||||
truth: `run.sh` sources it for shell variables and passes it to Hurl as
|
||||
`--variables-file`.
|
||||
|
||||
@@ -24,19 +24,19 @@ password=TestPassword1!
|
||||
|
||||
```bash
|
||||
# First-time setup (run once on a fresh instance)
|
||||
hurl --variables-file tests/api/hurl.vars --test tests/api/setup.hurl
|
||||
hurl --variables-file tests/api/test.env --test tests/api/setup.hurl
|
||||
|
||||
# Contacts CRUD scenario
|
||||
hurl --variables-file tests/api/hurl.vars --test tests/api/contacts.hurl
|
||||
hurl --variables-file tests/api/test.env --test tests/api/contacts.hurl
|
||||
|
||||
# All scenarios at once
|
||||
hurl --variables-file tests/api/hurl.vars --test tests/api/setup.hurl tests/api/contacts.hurl
|
||||
hurl --variables-file tests/api/test.env --test tests/api/setup.hurl tests/api/contacts.hurl
|
||||
|
||||
# With full request/response output
|
||||
hurl --variables-file tests/api/hurl.vars --test --verbose tests/api/contacts.hurl
|
||||
hurl --variables-file tests/api/test.env --test --verbose tests/api/contacts.hurl
|
||||
|
||||
# Generate an HTML report
|
||||
hurl --variables-file tests/api/hurl.vars --test --report-html /tmp/hurl-report tests/api/contacts.hurl
|
||||
hurl --variables-file tests/api/test.env --test --report-html /tmp/hurl-report tests/api/contacts.hurl
|
||||
```
|
||||
|
||||
## Test files
|
||||
@@ -45,7 +45,7 @@ hurl --variables-file tests/api/hurl.vars --test --report-html /tmp/hurl-report
|
||||
|---|---|
|
||||
| `setup.hurl` | One-time admin account creation; also asserts the endpoint is locked afterwards |
|
||||
| `contacts.hurl` | Full contacts CRUD scenario (13 steps, see below) |
|
||||
| `.env` | Variables: `base_url`, `username`, `email`, `password` — used by both Hurl and `run.sh` |
|
||||
| `test.env` | Variables: `base_url`, `username`, `email`, `password` — used by both Hurl and `run.sh` |
|
||||
|
||||
## Scenario: `contacts.hurl`
|
||||
|
||||
|
||||
+27
-7
@@ -165,6 +165,8 @@ jsonpath "$.phone[0].type" == "mobile"
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 8 – Update John Doe: add nickname, notes, and organisation
|
||||
# Uses If-Match for optimistic concurrency
|
||||
# Captures the refreshed ETag into etag_updated so that
|
||||
# the original etag remains available as a stale value.
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PUT {{base_url}}/api/address-books/{{book_id}}/contacts/{{contact_id}}
|
||||
Authorization: Bearer {{token}}
|
||||
@@ -195,7 +197,7 @@ If-Match: {{etag}}
|
||||
|
||||
HTTP 200
|
||||
[Captures]
|
||||
etag: header "ETag"
|
||||
etag_updated: header "ETag"
|
||||
[Asserts]
|
||||
header "ETag" exists
|
||||
jsonpath "$.id" == {{contact_id}}
|
||||
@@ -207,17 +209,35 @@ jsonpath "$.notes" == "Updated via Hurl test"
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 9 – Delete John Doe (uses refreshed ETag from step 8)
|
||||
# Step 9 – Stale ETag rejection: update with the pre-step-8 ETag
|
||||
# The contact was already modified so this must return 412
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
PUT {{base_url}}/api/address-books/{{book_id}}/contacts/{{contact_id}}
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
If-Match: {{etag}}
|
||||
{
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"full_name": "John Doe",
|
||||
"nickname": "should-not-be-saved"
|
||||
}
|
||||
|
||||
HTTP 412
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 10 – Delete John Doe (uses refreshed ETag from step 8)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/address-books/{{book_id}}/contacts/{{contact_id}}
|
||||
Authorization: Bearer {{token}}
|
||||
If-Match: {{etag}}
|
||||
If-Match: {{etag_updated}}
|
||||
|
||||
HTTP 204
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 10 – Address book must be empty again
|
||||
# Step 11 – Address book must be empty again
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/address-books/{{book_id}}/contacts
|
||||
Authorization: Bearer {{token}}
|
||||
@@ -228,7 +248,7 @@ jsonpath "$" count == 0
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 11 – Delete the personal address book
|
||||
# Step 12 – Delete the personal address book
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
DELETE {{base_url}}/api/address-books/{{book_id}}
|
||||
Authorization: Bearer {{token}}
|
||||
@@ -237,7 +257,7 @@ HTTP 204
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 12 – Verify the address book is gone from the list
|
||||
# Step 13 – Verify the address book is gone from the list
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/address-books
|
||||
Authorization: Bearer {{token}}
|
||||
@@ -248,7 +268,7 @@ jsonpath "$[*].id" not contains {{book_id}}
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
# Step 13 – List the system address book (OxiCloud users)
|
||||
# Step 14 – List the system address book (OxiCloud users)
|
||||
# ─────────────────────────────────────────────────────────────
|
||||
GET {{base_url}}/api/address-books/system/contacts
|
||||
Authorization: Bearer {{token}}
|
||||
|
||||
+4
-4
@@ -13,9 +13,9 @@ REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
COMMON="$REPO_ROOT/tests/common"
|
||||
API_DIR="$REPO_ROOT/tests/api"
|
||||
|
||||
# .env is the single source of truth for connection details and credentials.
|
||||
# shellcheck source=.env
|
||||
source "$API_DIR/.env"
|
||||
# test.env is the single source of truth for connection details and credentials.
|
||||
# shellcheck source=test.env
|
||||
source "$API_DIR/test.env"
|
||||
|
||||
# Derive server port from base_url (e.g. http://localhost:8087 → 8087)
|
||||
SERVER_PORT="${base_url##*:}"
|
||||
@@ -77,7 +77,7 @@ log "Server is ready."
|
||||
# ── 4. Run Hurl tests ─────────────────────────────────────────────────────────
|
||||
|
||||
log "Running Hurl tests..."
|
||||
hurl --variables-file "$API_DIR/.env" --test --jobs 1 \
|
||||
hurl --variables-file "$API_DIR/test.env" --test --jobs 1 \
|
||||
"$API_DIR/setup.hurl" \
|
||||
"$API_DIR/contacts.hurl"
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Test credentials for local/CI API tests — NOT real secrets.
|
||||
base_url=http://localhost:8087
|
||||
username=admin
|
||||
email=admin@example.com
|
||||
# gitguardian:ignore
|
||||
password=TestPassword1!
|
||||
@@ -15,4 +15,4 @@ OXICLOUD_ENABLE_MUSIC=true
|
||||
OXICLOUD_EXPOSE_SYSTEM_USERS=true
|
||||
OXICLOUD_WOPI_ENABLED=false
|
||||
OXICLOUD_OIDC_ENABLED=false
|
||||
RUST_LOG=info
|
||||
RUST_LOG=warn
|
||||
|
||||
Reference in New Issue
Block a user