Skip to content

chore(deps): bump actions/checkout from 4 to 7 #375

chore(deps): bump actions/checkout from 4 to 7

chore(deps): bump actions/checkout from 4 to 7 #375

name: NAMS Integration
# Runs the NAMS integration test suite against the live sandbox.
#
# Configuration (Settings → Secrets and variables → Actions):
# * NAMS_SANDBOX_KEY (Secret) — API key. Required; job skips if unset.
# * NAMS_SANDBOX_URL (Variable) — endpoint, e.g.
# https://nams.development.neo4jsandbox.com/v1
# Unset → falls back to the suite default below.
# * NAMS_SANDBOX_WORKSPACE_ID (Variable) — sent as X-Workspace-Id. Required by
# header-scoped deployments (e.g.
# memory.neo4jlabs.com), else 503
# workspace_not_provisioned.
#
# The "Preflight" step resolves and PRINTS the effective config, then makes one
# authenticated request. It fails fast with a clear message on a bad key
# (401/403), an unprovisioned workspace (503), or an unreachable endpoint — so a
# misconfiguration surfaces as an explicit error here rather than a confusing
# stack trace inside pytest. KEY and WORKSPACE come from job-level env; the
# resolved URL is exported to $GITHUB_ENV so every later step inherits the SAME
# endpoint with no per-step `|| fallback` that could silently mask an unset var.
on:
push:
branches: [main]
pull_request:
branches: [main]
schedule:
# Nightly at 06:00 UTC — surfaces endpoint drift in NAMS itself
# within 24 hours.
- cron: "0 6 * * *"
workflow_dispatch:
jobs:
nams-integration:
runs-on: ubuntu-latest
# Skip on PRs from forks (secrets are unavailable there).
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
timeout-minutes: 20
# Inherited by every step. URL is intentionally NOT here — the Preflight
# step resolves it (variable → suite default) and exports it to $GITHUB_ENV
# so a misconfigured variable can be detected instead of silently masked.
env:
NAMS_SANDBOX_KEY: ${{ secrets.NAMS_SANDBOX_KEY }}
NAMS_SANDBOX_WORKSPACE_ID: ${{ vars.NAMS_SANDBOX_WORKSPACE_ID }}
# Suite default used only when the NAMS_SANDBOX_URL variable is unset.
NAMS_DEFAULT_URL: https://nams.development.neo4jsandbox.com/v1
steps:
- uses: actions/checkout@v7
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python
run: uv python install 3.12
- name: Install dependencies
run: uv sync --group dev --all-extras
# Resolve config, make it visible, and validate auth/connectivity up front.
- name: Preflight — resolve & validate NAMS config
id: preflight
env:
NAMS_URL_VAR: ${{ vars.NAMS_SANDBOX_URL }}
run: |
set -uo pipefail
# 1. Key gate — skip cleanly (don't fail) when no key is configured,
# e.g. on branches/forks without the secret.
if [ -z "${NAMS_SANDBOX_KEY:-}" ]; then
echo "::warning::NAMS_SANDBOX_KEY secret is not set. Skipping NAMS integration tests."
echo "skip_tests=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# 2. Resolve endpoint: NAMS_SANDBOX_URL variable wins; else suite default.
if [ -z "${NAMS_URL_VAR:-}" ]; then
echo "::notice::NAMS_SANDBOX_URL variable is not set — using suite default: ${NAMS_DEFAULT_URL}"
URL="${NAMS_DEFAULT_URL}"
else
URL="${NAMS_URL_VAR}"
fi
# Export for every later step (single source of truth, no fallback masking).
echo "NAMS_SANDBOX_URL=${URL}" >> "$GITHUB_ENV"
# 3. Print the effective config so it's never a mystery in the logs.
if [ -n "${NAMS_SANDBOX_WORKSPACE_ID:-}" ]; then WS="set"; else WS="<empty>"; fi
echo "::notice::NAMS endpoint : ${URL}"
echo "::notice::NAMS api key : set (masked)"
echo "::notice::NAMS workspace : ${WS}"
# 3a. Workspace gate. Both deployments this workflow targets (staging
# nams.development.neo4jsandbox.com and sandbox memory.neo4jlabs.com)
# are workspace-scoped: reads may tolerate a missing workspace but
# writes reject it ('400 workspace_id required' / '503
# workspace_not_provisioned'). A GET probe alone won't catch that,
# so fail now with an actionable message rather than mid-test.
if [ -z "${NAMS_SANDBOX_WORKSPACE_ID:-}" ]; then
echo "::error::NAMS_SANDBOX_WORKSPACE_ID is not set, but ${URL} is workspace-scoped and rejects writes without it (you saw '400 workspace_id required'). Set the NAMS_SANDBOX_WORKSPACE_ID repository variable (Settings → Secrets and variables → Actions → Variables tab) to a valid workspace id for this environment."
exit 1
fi
# 4. One authenticated request — fail fast with an actionable message.
# Mirrors the client probe: GET /conversations?limit=1 with
# Authorization: Bearer <key> and optional X-Workspace-Id. Two
# explicit branches (no array) keep this portable under `set -u`.
if [ -n "${NAMS_SANDBOX_WORKSPACE_ID:-}" ]; then
CODE=$(curl -sS -o /tmp/nams_probe.txt -w '%{http_code}' \
-H "Authorization: Bearer ${NAMS_SANDBOX_KEY}" \
-H "X-Workspace-Id: ${NAMS_SANDBOX_WORKSPACE_ID}" \
"${URL}/conversations?limit=1" || echo "000")
else
CODE=$(curl -sS -o /tmp/nams_probe.txt -w '%{http_code}' \
-H "Authorization: Bearer ${NAMS_SANDBOX_KEY}" \
"${URL}/conversations?limit=1" || echo "000")
fi
BODY=$(cat /tmp/nams_probe.txt 2>/dev/null || true)
echo "Probe: GET ${URL}/conversations?limit=1 -> ${CODE}"
case "${CODE}" in
2*)
echo "::notice::NAMS auth + connectivity OK (${CODE})."
;;
401|403)
echo "::error::NAMS rejected the API key (${CODE}) at ${URL}. The key in NAMS_SANDBOX_KEY and the endpoint NAMS_SANDBOX_URL must be from the SAME environment (a staging key only works against the staging URL). Response: ${BODY}"
exit 1
;;
503)
if echo "${BODY}" | grep -q "workspace_not_provisioned"; then
echo "::error::Workspace not provisioned at ${URL}. Set the NAMS_SANDBOX_WORKSPACE_ID repo variable to a provisioned workspace (currently: ${WS}). Response: ${BODY}"
exit 1
fi
echo "::warning::NAMS returned 503 at probe — backend may be transiently down. Letting the suite run so it reports per-test. Response: ${BODY}"
;;
000)
echo "::error::Could not reach ${URL} (DNS/network failure). Check the NAMS_SANDBOX_URL variable."
exit 1
;;
400)
if echo "${BODY}" | grep -qi "workspace"; then
echo "::error::${URL} rejected the workspace (HTTP 400). NAMS_SANDBOX_WORKSPACE_ID may be invalid or not provisioned for this environment. Response: ${BODY}"
exit 1
fi
echo "::warning::Probe returned 400 at ${URL}; continuing so the suite reports detail. Response: ${BODY}"
;;
*)
echo "::warning::Unexpected probe status ${CODE} at ${URL}; continuing so the suite reports detail. Response: ${BODY}"
;;
esac
# Smoke test first — fails fast if the basic flow is broken.
- name: NAMS smoke test
if: steps.preflight.outputs.skip_tests != 'true'
run: |
uv run pytest tests/integration/nams/test_smoke.py \
-v --tb=short --timeout=120
# Auth / lifecycle tests — also fast and foundational.
- name: NAMS auth + lifecycle tests
if: steps.preflight.outputs.skip_tests != 'true'
run: |
uv run pytest tests/integration/nams/test_auth.py \
-v --tb=short --timeout=120
# Full TCK conformance + error suite — collect *all* failures (no -x)
# so we see every endpoint mismatch in one run.
- name: NAMS TCK conformance (Bronze)
if: steps.preflight.outputs.skip_tests != 'true'
run: |
uv run pytest tests/integration/nams/test_tck_bronze.py \
-v --tb=short --timeout=180
continue-on-error: true
- name: NAMS TCK conformance (Silver)
if: steps.preflight.outputs.skip_tests != 'true'
run: |
uv run pytest tests/integration/nams/test_tck_silver.py \
-v --tb=short --timeout=180
continue-on-error: true
- name: NAMS TCK conformance (Gold)
if: steps.preflight.outputs.skip_tests != 'true'
run: |
uv run pytest tests/integration/nams/test_tck_gold.py \
-v --tb=short --timeout=180
continue-on-error: true
- name: NAMS TCK conformance (Platinum)
if: steps.preflight.outputs.skip_tests != 'true'
run: |
uv run pytest tests/integration/nams/test_tck_platinum.py \
-v --tb=short --timeout=180
continue-on-error: true
- name: NAMS error-path tests
if: steps.preflight.outputs.skip_tests != 'true'
run: |
uv run pytest tests/integration/nams/test_errors.py \
-v --tb=short --timeout=180
continue-on-error: true
# Final consolidated run so the workflow exit code reflects overall pass/fail.
# ``--no-header`` keeps the summary tidy. Skipping it would mean the
# tier-specific steps above could all fail and the workflow would still
# report success.
- name: Consolidated pass/fail check
if: steps.preflight.outputs.skip_tests != 'true'
run: |
uv run pytest tests/integration/nams/ \
-v --tb=line --timeout=300 \
--no-header --quiet