649 lines
27 KiB
YAML
649 lines
27 KiB
YAML
name: API Integration Test
|
||
|
||
on:
|
||
# No `push: main` trigger: the merge_group (merge-queue) run already builds and
|
||
# tests the exact commit that lands on main, so a post-merge push run would be a
|
||
# full-suite duplicate. The merge_group run is now the trusted main-ward build and
|
||
# also warms the shared Rust cache (see save-if on the rust-cache step below).
|
||
pull_request:
|
||
branches:
|
||
- "**"
|
||
# labeled/unlabeled so adding or removing the 'e2e' label re-fires the run and
|
||
# re-evaluates the gate in api_tests_summary.
|
||
types: [opened, reopened, synchronize, labeled, unlabeled]
|
||
merge_group:
|
||
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
env:
|
||
COLUMNS: 150
|
||
ZO_ROOT_USER_EMAIL: root@example.com
|
||
ZO_ROOT_USER_PASSWORD: Complexpass#123
|
||
ZO_BASE_URL: http://localhost:5080/
|
||
ZO_BASE_URL_SC: http://localhost:5080/
|
||
WS_ZO_BASE_URL: ws://localhost:5080/
|
||
ZO_USAGE_REPORTING_ENABLED: true
|
||
ZO_USAGE_PUBLISH_INTERVAL: 5
|
||
ZO_ALERT_SCHEDULE_INTERVAL: 3
|
||
ZO_SMTP_ENABLED: true
|
||
ZO_CREATE_ORG_THROUGH_INGESTION: true
|
||
ZO_INGEST_ALLOWED_UPTO: 48 # Allow backdated data ingestion for backfill tests
|
||
ZO_SSRF_ALLOW_LOOPBACK: true # Allow localhost destinations in API tests
|
||
|
||
jobs:
|
||
check_changes:
|
||
name: check_changes
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
contents: read
|
||
outputs:
|
||
has_changes: ${{ steps.check.outputs.has_changes }}
|
||
steps:
|
||
- uses: actions/checkout@v5
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- id: check
|
||
run: |
|
||
# Skip when a PR touches only docs, images, frontend/UI-test files, or unrelated
|
||
# workflow files (e.g. a dependabot bump to another workflow) — none affect the API.
|
||
# Source and this workflow (api-testing.yml) still run the suite.
|
||
IGNORE_RE='(\.md$|^docs/|^web/|^tests/ui-testing/|^tests/playwright-tests/|\.(png|jpe?g|gif|svg|webp)$|^\.github/workflows/)'
|
||
CHANGED=$(git diff --name-only origin/${{ github.base_ref || 'main' }} ${{ github.sha }})
|
||
# Files not matching IGNORE_RE. Captured (not `grep -qv`) so the check doesn't depend
|
||
# on grep -qv exit semantics, which differ across grep builds; the `|| true` keeps
|
||
# `set -e` from aborting when grep matches nothing (exit 1). The `grep -qE` below is a
|
||
# loop-safe `if`-condition term (`set -e` doesn't fire there), so it needs no `|| true`.
|
||
RELEVANT=$(printf '%s' "$CHANGED" | grep -vE "$IGNORE_RE" || true)
|
||
if [ -z "$CHANGED" ] || [ -n "$RELEVANT" ] || printf '%s' "$CHANGED" | grep -qE '^\.github/workflows/api-testing\.yml$'; then
|
||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||
else
|
||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||
fi
|
||
|
||
api_integration_tests:
|
||
name: api_integration_tests
|
||
needs: [check_changes]
|
||
# API-relevant changes are required in every case. On a pull_request the 'e2e'
|
||
# label is an ADDITIONAL opt-in, not a bypass: the label is shared with the
|
||
# Playwright suite, so a frontend-only PR carries it to run UI tests and must
|
||
# not drag the whole API suite (8 jobs, each a full cargo build) along with it.
|
||
if: >-
|
||
needs.check_changes.outputs.has_changes == 'true' &&
|
||
(github.event_name != 'pull_request' ||
|
||
contains(github.event.pull_request.labels.*.name, 'e2e'))
|
||
runs-on: ubicloud-standard-4
|
||
permissions:
|
||
contents: read # only need to checkout the repo
|
||
steps:
|
||
- name: Remove unused tools
|
||
run: |
|
||
sudo rm -rf /usr/share/dotnet
|
||
sudo rm -rf /opt/ghc
|
||
sudo rm -rf "/usr/local/share/boost"
|
||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||
|
||
- name: Checkout git repo
|
||
uses: actions/checkout@v5
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Setup Rust Toolchain
|
||
uses: dtolnay/rust-toolchain@master
|
||
with:
|
||
toolchain: nightly-2026-05-20
|
||
|
||
- name: Setup Rust Cache
|
||
uses: Swatinem/rust-cache@v2
|
||
with:
|
||
cache-on-failure: true
|
||
shared-key: backend-debug-mimalloc
|
||
# Save the shared cache on merge_group runs (the main-ward build now that
|
||
# push:main is gone); keep the main-ref check for any non-PR fallback.
|
||
save-if: ${{ github.event_name == 'merge_group' || github.ref == 'refs/heads/main' }}
|
||
|
||
- name: Install Protoc
|
||
run: |
|
||
bash ./.github/protoc.sh
|
||
|
||
- name: Build OpenObserve debug binary
|
||
run: cargo build --features mimalloc
|
||
|
||
- name: Setup Python
|
||
uses: actions/setup-python@v7
|
||
with:
|
||
python-version: "3.11.6"
|
||
|
||
- name: Setup rye
|
||
uses: eifinger/setup-rye@v4
|
||
with:
|
||
enable-cache: true
|
||
working-directory: "tests/api-testing/"
|
||
version: "0.27.0"
|
||
|
||
- name: Pin cpython
|
||
run: rye pin cpython@3.11.6
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: Rye sync
|
||
run: rye sync
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: Run default-set tests
|
||
# Phase 6.b: regression matrix tests moved to their own
|
||
# api_regression_tests job below — they now run in parallel
|
||
# with this default-set job instead of sequentially within it.
|
||
# Cuts ~8 min off CI wall time at the cost of more runner minutes.
|
||
# NB: --force-sugar is intentionally omitted. pytest-sugar is already
|
||
# auto-loaded via the entry point; passing --force-sugar registers it
|
||
# a second time, which inflates the progress counter (CI displayed
|
||
# ~3x the real test count). Letting the plugin load via discovery
|
||
# alone keeps the counter honest.
|
||
run: |
|
||
${{ github.workspace }}/target/debug/openobserve > o2_default.log 2>&1 &
|
||
SERVER_PID_DEFAULT=$!
|
||
echo "OpenObserve PID for default tests: $SERVER_PID_DEFAULT"
|
||
for i in $(seq 1 60); do
|
||
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
|
||
echo "Server ready after ${i}s"; break
|
||
fi
|
||
sleep 1
|
||
done
|
||
rye run pytest --ignore=tests/regression --ignore=tests/query_agent --ignore=tests/vortex --junitxml=junit-integration.xml
|
||
kill $SERVER_PID_DEFAULT || true
|
||
working-directory: tests/api-testing/
|
||
|
||
# Test-count metrics sidecar: ship the junit so report_to_openobserve can
|
||
# tally API active/skipped counts (suite=api). always() so counts emit even
|
||
# when pytest fails (junit is written before pytest's non-zero exit).
|
||
- name: Upload API junit (integration)
|
||
if: always()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: api-junit__api_integration_tests__main
|
||
path: tests/api-testing/junit-integration.xml
|
||
if-no-files-found: warn
|
||
retention-days: 7
|
||
|
||
- name: Upload OpenObserve server logs on failure
|
||
if: failure()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openobserve-server-logs
|
||
path: tests/api-testing/o2_*.log
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
# Dedicated query agent SQL regression suite. Runs in parallel with
|
||
# the default-set and regression jobs so the 310-case (155 queries ×
|
||
# 2 phases: memtable + parquet) suite doesn't extend CI wall time.
|
||
api_query_agent_tests:
|
||
name: "api_query_agent / ${{ matrix.config.label }}"
|
||
needs: [check_changes]
|
||
# API-relevant changes are required in every case. On a pull_request the 'e2e'
|
||
# label is an ADDITIONAL opt-in, not a bypass: the label is shared with the
|
||
# Playwright suite, so a frontend-only PR carries it to run UI tests and must
|
||
# not drag the whole API suite (8 jobs, each a full cargo build) along with it.
|
||
if: >-
|
||
needs.check_changes.outputs.has_changes == 'true' &&
|
||
(github.event_name != 'pull_request' ||
|
||
contains(github.event.pull_request.labels.*.name, 'e2e'))
|
||
runs-on: ubicloud-standard-4
|
||
# Run the full SQL regression suite with the single-node physical
|
||
# optimizer both enabled (default) and disabled. The non-optimized
|
||
# RemoteScanRewriter path triggers known upstream DataFusion bugs on
|
||
# a handful of queries — those are tagged skip_without_single_node_opt
|
||
# and filtered by load_all_queries() when SINGLE_NODE_OPT_MODE=false.
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
config:
|
||
- label: single_node_opt_false
|
||
env: "ZO_FEATURE_SINGLE_NODE_OPTIMIZE_ENABLED=false"
|
||
mode: "false"
|
||
- label: single_node_opt_true
|
||
env: "ZO_FEATURE_SINGLE_NODE_OPTIMIZE_ENABLED=true"
|
||
mode: "true"
|
||
permissions:
|
||
contents: read
|
||
steps:
|
||
- name: Remove unused tools
|
||
run: |
|
||
sudo rm -rf /usr/share/dotnet
|
||
sudo rm -rf /opt/ghc
|
||
sudo rm -rf "/usr/local/share/boost"
|
||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||
|
||
- name: Checkout git repo
|
||
uses: actions/checkout@v5
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Setup Rust Toolchain
|
||
uses: dtolnay/rust-toolchain@master
|
||
with:
|
||
toolchain: nightly-2026-05-20
|
||
|
||
- name: Setup Rust Cache
|
||
uses: Swatinem/rust-cache@v2
|
||
with:
|
||
cache-on-failure: true
|
||
shared-key: backend-debug-mimalloc
|
||
save-if: false # main api_integration_tests job writes the cache
|
||
|
||
- name: Install Protoc
|
||
run: bash ./.github/protoc.sh
|
||
|
||
- name: Build OpenObserve debug binary
|
||
run: cargo build --features mimalloc
|
||
|
||
- name: Setup Python
|
||
uses: actions/setup-python@v7
|
||
with:
|
||
python-version: "3.11.6"
|
||
|
||
- name: Setup rye
|
||
uses: eifinger/setup-rye@v4
|
||
with:
|
||
enable-cache: true
|
||
working-directory: "tests/api-testing/"
|
||
version: "0.27.0"
|
||
|
||
- name: Pin cpython
|
||
run: rye pin cpython@3.11.6
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: Rye sync
|
||
run: rye sync
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: Regenerate query-agent expected values
|
||
run: |
|
||
cd ${{ github.workspace }}/tests/test-data/query-agent
|
||
${{ github.workspace }}/tests/api-testing/.venv/bin/python compute_counts.py
|
||
|
||
- name: Run query agent tests
|
||
run: |
|
||
ZO_MAX_FILE_RETENTION_TIME=1 ${{ matrix.config.env }} ${{ github.workspace }}/target/debug/openobserve > o2_query_agent_${{ matrix.config.label }}.log 2>&1 &
|
||
SERVER_PID=$!
|
||
for i in $(seq 1 60); do
|
||
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
|
||
echo "Server ready after ${i}s"; break
|
||
fi
|
||
sleep 1
|
||
done
|
||
# SINGLE_NODE_OPT_MODE controls which queries load_all_queries() includes
|
||
SINGLE_NODE_OPT_MODE=${{ matrix.config.mode }} rye run pytest tests/query_agent/ --ignore=tests/query_agent/test_3_vortex.py -v --junitxml=junit-queryagent-${{ matrix.config.label }}.xml
|
||
kill $SERVER_PID || true
|
||
working-directory: tests/api-testing/
|
||
|
||
# Test-count metrics sidecar (see integration job). Parser dedups the two
|
||
# opt-mode legs by test identity so the same suite isn't counted twice.
|
||
- name: Upload API junit (query_agent)
|
||
if: always()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: api-junit__query_agent_tests__${{ matrix.config.label }}
|
||
path: tests/api-testing/junit-queryagent-${{ matrix.config.label }}.xml
|
||
if-no-files-found: warn
|
||
retention-days: 7
|
||
|
||
- name: Upload server log on failure
|
||
if: failure()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openobserve-query-agent-${{ matrix.config.label }}-log
|
||
path: tests/api-testing/o2_query_agent_${{ matrix.config.label }}.log
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
# Phase 6.b: regression matrix split. The 4 env-flag-sensitive test
|
||
# configurations now run as 4 parallel jobs instead of 4 sequential
|
||
# restarts in one job. Each job builds OO once and runs its single
|
||
# configured pytest target.
|
||
api_regression_tests:
|
||
name: "api_regression / ${{ matrix.config.label }}"
|
||
needs: [check_changes]
|
||
# API-relevant changes are required in every case. On a pull_request the 'e2e'
|
||
# label is an ADDITIONAL opt-in, not a bypass: the label is shared with the
|
||
# Playwright suite, so a frontend-only PR carries it to run UI tests and must
|
||
# not drag the whole API suite (8 jobs, each a full cargo build) along with it.
|
||
if: >-
|
||
needs.check_changes.outputs.has_changes == 'true' &&
|
||
(github.event_name != 'pull_request' ||
|
||
contains(github.event.pull_request.labels.*.name, 'e2e'))
|
||
runs-on: ubicloud-standard-4
|
||
permissions:
|
||
contents: read # only need to checkout the repo
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
config:
|
||
- label: utf8_view_false
|
||
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true ZO_UTF8_VIEW_ENABLED=false"
|
||
target: tests/regression/test_subquery_join_match.py
|
||
- label: utf8_view_true
|
||
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true ZO_UTF8_VIEW_ENABLED=true"
|
||
target: tests/regression/test_subquery_join_match.py
|
||
- label: join_match_one_true
|
||
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=true"
|
||
target: tests/regression/test_logs_to_logs_join.py
|
||
- label: join_match_one_false
|
||
env: "ZO_FEATURE_JOIN_MATCH_ONE_ENABLED=false"
|
||
target: tests/regression/test_logs_to_logs_join.py
|
||
steps:
|
||
- name: Remove unused tools
|
||
run: |
|
||
sudo rm -rf /usr/share/dotnet
|
||
sudo rm -rf /opt/ghc
|
||
sudo rm -rf "/usr/local/share/boost"
|
||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||
|
||
- uses: actions/checkout@v5
|
||
with: { fetch-depth: 0 }
|
||
|
||
- name: Setup Rust Toolchain
|
||
uses: dtolnay/rust-toolchain@master
|
||
with:
|
||
toolchain: nightly-2026-05-20
|
||
|
||
- name: Setup Rust Cache
|
||
uses: Swatinem/rust-cache@v2
|
||
with:
|
||
cache-on-failure: true
|
||
shared-key: backend-debug-mimalloc
|
||
save-if: false # main api_integration_tests job writes the cache
|
||
|
||
- name: Install Protoc
|
||
run: bash ./.github/protoc.sh
|
||
|
||
- name: Build OpenObserve debug binary
|
||
run: cargo build --features mimalloc
|
||
|
||
- uses: actions/setup-python@v7
|
||
with: { python-version: "3.11.6" }
|
||
|
||
- name: Setup rye
|
||
uses: eifinger/setup-rye@v4
|
||
with:
|
||
enable-cache: true
|
||
working-directory: "tests/api-testing/"
|
||
version: "0.27.0"
|
||
|
||
- name: Pin cpython
|
||
run: rye pin cpython@3.11.6
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: Rye sync
|
||
run: rye sync
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: "Run ${{ matrix.config.label }} regression test"
|
||
# Server binary lives at the repo root (target/debug/openobserve);
|
||
# this step uses working-directory: tests/api-testing/, so we must
|
||
# invoke it via ${{ github.workspace }} or the shell will fail with
|
||
# "No such file or directory". --force-sugar omitted on purpose —
|
||
# pytest-sugar auto-loads via entry point, and passing the flag
|
||
# registers it twice and inflates the progress counter ~3x.
|
||
run: |
|
||
${{ matrix.config.env }} ${{ github.workspace }}/target/debug/openobserve > "o2_${{ matrix.config.label }}.log" 2>&1 &
|
||
SERVER_PID=$!
|
||
for i in $(seq 1 60); do
|
||
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then
|
||
echo "Server ready after ${i}s"; break
|
||
fi
|
||
sleep 1
|
||
done
|
||
rye run pytest ${{ matrix.config.target }} -v --junitxml=junit-regression-${{ matrix.config.label }}.xml
|
||
kill $SERVER_PID || true
|
||
working-directory: tests/api-testing/
|
||
|
||
# Test-count metrics sidecar (see integration job). Parser dedups the
|
||
# flag-combo legs that re-run the same target files.
|
||
- name: Upload API junit (regression)
|
||
if: always()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: api-junit__api_integration_tests__regression_${{ matrix.config.label }}
|
||
path: tests/api-testing/junit-regression-${{ matrix.config.label }}.xml
|
||
if-no-files-found: warn
|
||
retention-days: 7
|
||
|
||
- name: Upload server log on failure
|
||
if: failure()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: "openobserve-regression-${{ matrix.config.label }}-log"
|
||
path: "tests/api-testing/o2_${{ matrix.config.label }}.log"
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
# Phase 5: schemathesis fuzz job — informational (continue-on-error),
|
||
# surfaces 5xx panics across the 150 OpenAPI endpoints. Decoupled from
|
||
# api_integration_tests so its findings don't block merges yet; teams
|
||
# can promote it to blocking after the noise floor is understood.
|
||
api_fuzz_tests:
|
||
name: api_fuzz_tests
|
||
needs: [check_changes]
|
||
# API-relevant changes are required in every case. On a pull_request the 'e2e'
|
||
# label is an ADDITIONAL opt-in, not a bypass: the label is shared with the
|
||
# Playwright suite, so a frontend-only PR carries it to run UI tests and must
|
||
# not drag the whole API suite (8 jobs, each a full cargo build) along with it.
|
||
if: >-
|
||
needs.check_changes.outputs.has_changes == 'true' &&
|
||
(github.event_name != 'pull_request' ||
|
||
contains(github.event.pull_request.labels.*.name, 'e2e'))
|
||
runs-on: ubicloud-standard-4
|
||
continue-on-error: true
|
||
permissions:
|
||
contents: read # only need to checkout the repo
|
||
steps:
|
||
- name: Remove unused tools
|
||
run: |
|
||
sudo rm -rf /usr/share/dotnet
|
||
sudo rm -rf /opt/ghc
|
||
sudo rm -rf "/usr/local/share/boost"
|
||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||
|
||
- name: Checkout git repo
|
||
uses: actions/checkout@v5
|
||
with:
|
||
fetch-depth: 0
|
||
|
||
- name: Setup Rust Toolchain
|
||
uses: dtolnay/rust-toolchain@master
|
||
with:
|
||
toolchain: nightly-2026-05-20
|
||
|
||
- name: Setup Rust Cache
|
||
uses: Swatinem/rust-cache@v2
|
||
with:
|
||
cache-on-failure: true
|
||
shared-key: backend-debug-mimalloc
|
||
save-if: false # don't write cache from this job (the main job does)
|
||
|
||
- name: Install Protoc
|
||
run: bash ./.github/protoc.sh
|
||
|
||
- name: Build OpenObserve debug binary
|
||
run: cargo build --features mimalloc
|
||
|
||
- name: Setup Python
|
||
uses: actions/setup-python@v7
|
||
with:
|
||
python-version: "3.11.6"
|
||
|
||
- name: Setup rye
|
||
uses: eifinger/setup-rye@v4
|
||
with:
|
||
enable-cache: true
|
||
working-directory: "tests/api-testing/"
|
||
version: "0.27.0"
|
||
|
||
- name: Pin cpython
|
||
run: rye pin cpython@3.11.6
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: Rye sync (schemathesis dep installed)
|
||
run: rye sync
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: Start OpenObserve
|
||
run: target/debug/openobserve > o2_fuzz.log 2>&1 &
|
||
|
||
- name: Wait for /healthz
|
||
run: |
|
||
for i in $(seq 1 60); do
|
||
if curl -sf http://localhost:5080/healthz > /dev/null 2>&1; then exit 0; fi
|
||
sleep 1
|
||
done
|
||
exit 1
|
||
|
||
- name: Run schemathesis fuzz tests
|
||
# Schemathesis 4.x CLI flag renames vs the 3.x originals:
|
||
# --base-url -> --url / -u
|
||
# --auth-type basic -> removed (USER:PASS implies basic)
|
||
# --hypothesis-max-examples -> --max-examples / -n
|
||
# --hypothesis-deadline -> no equivalent; use --request-timeout
|
||
# (seconds, not ms) to bound per-request
|
||
# --data-generation-method -> --mode / -m
|
||
# `--checks not_a_server_error` keeps focus on 5xx panics (actual
|
||
# backend bugs, not spec-conformance nits). `-n 20` bounds runtime;
|
||
# `--mode positive` keeps inputs realistic (negative inputs are a
|
||
# separate exercise once the noise floor here is understood).
|
||
run: |
|
||
rye run schemathesis run \
|
||
-u http://localhost:5080 \
|
||
--auth "$ZO_ROOT_USER_EMAIL:$ZO_ROOT_USER_PASSWORD" \
|
||
--checks not_a_server_error \
|
||
-n 20 \
|
||
--request-timeout 5 \
|
||
-m positive \
|
||
--suppress-health-check=filter_too_much \
|
||
--exclude-path "/api/{org_id}/v1/logs" \
|
||
--exclude-path "/api/{org_id}/v1/metrics" \
|
||
--exclude-path "/api/{org_id}/v1/traces" \
|
||
--exclude-path "/api/{org_id}/loki/api/v1/push" \
|
||
--exclude-path "/api/{org_id}/prometheus/api/v1/write" \
|
||
--exclude-path "/api/{org_id}/dashboards" \
|
||
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}" \
|
||
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}/panels" \
|
||
--exclude-path "/api/{org_id}/dashboards/{dashboard_id}/panels/{panel_id}" \
|
||
--exclude-path "/api/{org_id}/pipelines/history" \
|
||
--exclude-path "/api/{org_id}/organizations/assume_service_account" \
|
||
http://localhost:5080/api-doc/openapi.json
|
||
working-directory: tests/api-testing/
|
||
|
||
- name: Upload OpenObserve server log on failure
|
||
if: failure()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: openobserve-fuzz-log
|
||
path: tests/api-testing/o2_fuzz.log
|
||
if-no-files-found: warn
|
||
retention-days: 14
|
||
|
||
api_tests_summary:
|
||
runs-on: ubuntu-latest
|
||
permissions: {}
|
||
needs: [check_changes, api_integration_tests, api_query_agent_tests, api_regression_tests]
|
||
if: always()
|
||
steps:
|
||
- name: Check test results
|
||
run: |
|
||
# 1. check_changes must have succeeded.
|
||
if [ "${{ needs.check_changes.result }}" != "success" ]; then
|
||
echo "check_changes job did not succeed: ${{ needs.check_changes.result }}"
|
||
exit 1
|
||
fi
|
||
|
||
# 2. Nothing API-relevant changed → nothing ran → allow, whatever the labels say.
|
||
# This covers the frontend-only PR that carries the 'e2e' label to run the
|
||
# Playwright suite: the API suite doesn't apply to it and isn't required.
|
||
if [ "${{ needs.check_changes.outputs.has_changes }}" != "true" ]; then
|
||
echo "✅ No API-relevant changes — suite not required."
|
||
exit 0
|
||
fi
|
||
|
||
# 3. API-relevant pull_request WITHOUT the 'e2e' label: the suite did not run,
|
||
# so block until the author opts in by adding the label.
|
||
if [ "${{ github.event_name }}" == "pull_request" ] && \
|
||
[ "${{ contains(github.event.pull_request.labels.*.name, 'e2e') }}" != "true" ]; then
|
||
echo "::error::This PR changes API-relevant files but has no 'e2e' label, so the suite did not run."
|
||
echo "::error::Add the 'e2e' label to the PR to run the full test suite before merging."
|
||
exit 1
|
||
fi
|
||
|
||
# 4. Suite was expected to run — the gating jobs must have succeeded.
|
||
if [ "${{ needs.api_integration_tests.result }}" == "success" ] && \
|
||
[ "${{ needs.api_query_agent_tests.result }}" == "success" ] && \
|
||
[ "${{ needs.api_regression_tests.result }}" == "success" ]; then
|
||
echo "All API test jobs passed"
|
||
exit 0
|
||
else
|
||
echo "API tests failed: integration=${{ needs.api_integration_tests.result }}, query_agent=${{ needs.api_query_agent_tests.result }}, regression=${{ needs.api_regression_tests.result }}"
|
||
exit 1
|
||
fi
|
||
|
||
# Non-fatal metrics sidecar: one retry-aware run-summary doc to OpenObserve (stream ci_test_runs,
|
||
# suite=api). Re-runs append one doc per attempt; the dashboard keeps the latest attempt per run_id.
|
||
report_to_openobserve:
|
||
name: Report run metrics to OpenObserve
|
||
needs: [check_changes, api_integration_tests, api_query_agent_tests, api_regression_tests]
|
||
if: always()
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
actions: read
|
||
contents: read
|
||
steps:
|
||
- name: Clone the current repo
|
||
uses: actions/checkout@v5
|
||
# Pull every per-shard junit. NO merge-multiple: each artifact lands in its
|
||
# own subdir (api-junit__<category>__<leg>) so the parser can read the test
|
||
# CATEGORY from the dir name. Missing artifacts are non-fatal.
|
||
- name: Download API junit reports
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
pattern: api-junit__*
|
||
path: junit-xml
|
||
- name: Report run metrics
|
||
continue-on-error: true
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
O2_REPORTING_INGEST_BASE: ${{ secrets.O2_REPORTING_INGEST_BASE }}
|
||
O2_REPORTING_AUTH: ${{ secrets.O2_REPORTING_AUTH }}
|
||
O2_REPORTING_INSECURE: ${{ vars.O2_REPORTING_INSECURE }}
|
||
INTEG: ${{ needs.api_integration_tests.result }}
|
||
AGENT: ${{ needs.api_query_agent_tests.result }}
|
||
REG: ${{ needs.api_regression_tests.result }}
|
||
run: |
|
||
if [ "$INTEG" = "skipped" ] && [ "$AGENT" = "skipped" ] && [ "$REG" = "skipped" ]; then
|
||
echo "::notice::no API tests ran (no relevant changes) — skipping metrics"; exit 0
|
||
fi
|
||
if [ "$INTEG" = "failure" ] || [ "$AGENT" = "failure" ] || [ "$REG" = "failure" ]; then CONCLUSION=failure
|
||
elif [ "$INTEG" = "cancelled" ] || [ "$AGENT" = "cancelled" ] || [ "$REG" = "cancelled" ]; then CONCLUSION=cancelled
|
||
else CONCLUSION=success; fi
|
||
export CONCLUSION STREAM=ci_test_runs SUITE=api
|
||
# Parse junit into per-category + total counts. The TOTAL folds into the
|
||
# run doc (EXTRA_JSON) so suite=api shows up in the same KPI/pie panels
|
||
# as UI; the per-category rows go to ci_test_counts for the per-shard
|
||
# breakdown (the API analog of UI's ci_test_modules). All best-effort.
|
||
if [ -d junit-xml ]; then
|
||
COUNTS=$(python3 .github/scripts/parse-api-junit.py junit-xml 2>/dev/null || echo "")
|
||
if [ -n "$COUNTS" ] && printf '%s' "$COUNTS" | jq -e . >/dev/null 2>&1; then
|
||
echo "API counts: $COUNTS"
|
||
EXTRA_JSON=$(printf '%s' "$COUNTS" | jq -c .total); export EXTRA_JSON
|
||
printf '%s' "$COUNTS" | jq -c --arg run_id "$GITHUB_RUN_ID" --arg repo "$GITHUB_REPOSITORY" \
|
||
'[.by_category[] | {run_id:$run_id, repo:$repo, suite:"api", workflow:"test", ingest_source:"live",
|
||
module:.module, tests_total, tests_passed, tests_failed, tests_flaky, tests_skipped}]' > api_counts_rows.json
|
||
if [ "$(jq 'length' api_counts_rows.json 2>/dev/null || echo 0)" -gt 0 ]; then
|
||
bash .github/scripts/o2-report.sh ci_test_counts api_counts_rows.json
|
||
fi
|
||
fi
|
||
fi
|
||
bash .github/scripts/o2-run-report.sh
|