Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/workflows/upload-smoke-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Upload Smoke Test (recce-cloud)

on:
push:
branches: [main]
paths:
- "recce_cloud/**"
- "integration_tests/recce_cloud/**"
pull_request:
branches: [main]
paths:
- "recce_cloud/**"
- "integration_tests/recce_cloud/**"

permissions:
contents: read
pull-requests: read

jobs:
authorize:
runs-on: ubuntu-latest
outputs:
authorized: ${{ steps.check.outputs.authorized }}
steps:
- name: Check if PR author has write permission
id: check
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
echo "authorized=true" >> $GITHUB_OUTPUT
exit 0
fi
PERMISSION=$(gh api repos/${{ github.repository }}/collaborators/${{ github.triggering_actor }}/permission --jq '.permission' 2>/dev/null || echo "none")
if [[ "$PERMISSION" == "write" || "$PERMISSION" == "admin" ]]; then
echo "authorized=true" >> $GITHUB_OUTPUT
else
echo "authorized=false" >> $GITHUB_OUTPUT
echo "::warning::Upload smoke test skipped — requires write permission."
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

upload-smoke-test:
needs: authorize
if: needs.authorize.outputs.authorized == 'true'
concurrency:
group: upload-smoke-test
cancel-in-progress: false
runs-on: ubuntu-latest
strategy:
max-parallel: 1
matrix:
python-version: ["3.11"]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@v4
with:
version: "latest"

- name: Install recce-cloud
working-directory: recce_cloud
run: uv sync --no-dev --python ${{ matrix.python-version }}

- name: Run upload smoke test
working-directory: recce_cloud
run: uv run ../integration_tests/recce_cloud/smoke_test_upload.sh
env:
GITHUB_TOKEN: ${{ secrets.RECCE_CLOUD_TOKEN }}
SMOKE_TEST_API_TOKEN: ${{ secrets.SMOKE_TEST_API_TOKEN }}
SMOKE_TEST_ORG: ${{ secrets.SMOKE_TEST_ORG }}
SMOKE_TEST_PROJECT: ${{ secrets.SMOKE_TEST_PROJECT }}
Comment on lines +69 to +74
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description mentions a required SMOKE_TEST_GITHUB_REPO secret, but this workflow doesn't pass it to the smoke test script. If the repo should be configurable, add SMOKE_TEST_GITHUB_REPO: ${{ secrets.SMOKE_TEST_GITHUB_REPO }} (or a repo variable) to env: and ensure the script reads it.

Copilot uses AI. Check for mistakes.
6 changes: 0 additions & 6 deletions integration_tests/dbt/smoke_test_cloud.sh
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ recce cloud download-artifacts --branch $GIT_BRANCH
# Recce Run
recce run --cloud

# Recce state
recce cloud download
recce cloud purge --force
recce cloud upload recce_state.json

# Recce Summary
recce summary --cloud

Expand All @@ -66,6 +61,5 @@ function check_server_status() {
echo "Starting the server (cloud and review mode)..."
recce server --cloud --review &
check_server_status
recce cloud purge --force

export GITHUB_EVENT_PATH="$HOLD_GITHUB_EVENT_PATH"
13 changes: 13 additions & 0 deletions integration_tests/recce_cloud/fixtures/minimal-target/catalog.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"metadata": {
"dbt_schema_version": "https://schemas.getdbt.com/dbt/catalog/v1/catalog.json",
"dbt_version": "1.8.0",
"adapter_type": "duckdb",
"generated_at": "2026-01-01T00:00:00.000000Z",
"invocation_id": "00000000-0000-0000-0000-000000000000",
"env": {}
},
"nodes": {},
"sources": {},
"errors": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"metadata": {
"dbt_schema_version": "https://schemas.getdbt.com/dbt/manifest/v11/manifest.json",
"dbt_version": "1.8.0",
"adapter_type": "duckdb",
"generated_at": "2026-01-01T00:00:00.000000Z",
"invocation_id": "00000000-0000-0000-0000-000000000000",
"env": {}
},
"nodes": {},
"sources": {},
"macros": {},
"docs": {},
"exposures": {},
"metrics": {},
"groups": {},
"selectors": {},
"disabled": [],
"parent_map": {},
"child_map": {}
}
217 changes: 217 additions & 0 deletions integration_tests/recce_cloud/smoke_test_upload.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#!/usr/bin/env bash
set -euo pipefail

# ============================================================
# Smoke test for `recce-cloud upload`
#
# Tests all upload flows against the staging server:
# 1. GitHub PR Upload (GITHUB_TOKEN, platform-specific)
# 2. GitHub Prod Upload (GITHUB_TOKEN, --type prod)
# 3. RECCE_API_TOKEN PR Upload (generic endpoint)
# 4. RECCE_API_TOKEN Prod Upload (generic endpoint, --type prod)
# 5. Session Name Upload (--session-name, dev session)
# 6. Session ID Upload (--session-id, reuses session from test 5)
#
# Required env vars:
# GITHUB_TOKEN - PAT with repo scope for the test repo
#
# Optional env vars:
# SMOKE_TEST_API_TOKEN - RECCE_API_TOKEN for generic endpoint tests (tests 3-6)
# SMOKE_TEST_ORG - Recce Cloud org ID for session-name/ID tests (tests 5-6)
# SMOKE_TEST_PROJECT - Recce Cloud project ID for session-name/ID tests (tests 5-6)
#
# Provided by GitHub Actions (already set):
# GITHUB_ACTIONS, GITHUB_SHA, GITHUB_REF_NAME
# ============================================================

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FIXTURES_DIR="${SCRIPT_DIR}/fixtures/minimal-target"
SMOKE_TEST_GITHUB_REPO="DataRecce/recce-smoke-test"
TEST_BRANCH="smoke-test-upload-$(date +%s)"
DEV_SESSION_NAME="smoke-test-dev-$(date +%s)"
Comment on lines +29 to +31
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SMOKE_TEST_GITHUB_REPO is hard-coded here, but the PR description says the workflow requires a SMOKE_TEST_GITHUB_REPO secret/config. If this should be configurable, read it from an env var (with this value as a default) and pass it from the workflow; otherwise, update the PR description to match the implementation.

Copilot uses AI. Check for mistakes.
DEV_SESSION_ID=""
PASS_COUNT=0
FAIL_COUNT=0

# ---- Prerequisite checks ----
if [[ "${GITHUB_ACTIONS:-}" != "true" ]]; then
echo "ERROR: This test must run inside GitHub Actions"
exit 1
fi

if [[ -z "${GITHUB_TOKEN:-}" ]]; then
echo "ERROR: GITHUB_TOKEN is required"
exit 1
fi

# ---- Recce Cloud config ----
export RECCE_CLOUD_API_HOST="${RECCE_CLOUD_API_HOST:-https://staging.cloud.datarecce.io}"

# Disable anonymous tracking
mkdir -p ~/.recce
cat > ~/.recce/profile.yml <<EOF
user_id: 00000000000000000000000000000000
anonymous_tracking: false
EOF

# ---- Create synthetic GitHub event file ----
SYNTHETIC_EVENT=$(mktemp)
cat > "$SYNTHETIC_EVENT" <<EOF
{
"pull_request": {
"number": 1
}
}
EOF

# ---- Cleanup trap ----
cleanup() {
echo ""
echo "=== Cleanup ==="
# Delete PR sessions created during tests.
# Uses GITHUB_TOKEN (platform auto-detect delete) — works for both
# GitHub-flow and RECCE_API_TOKEN-flow created sessions.
env -u RECCE_API_TOKEN \
GITHUB_REPOSITORY="$SMOKE_TEST_GITHUB_REPO" \
GITHUB_EVENT_PATH="$SYNTHETIC_EVENT" \
GITHUB_HEAD_REF="$TEST_BRANCH" \
GITHUB_BASE_REF="main" \
recce-cloud delete --force 2>/dev/null || true

# Delete dev session created by session-name test (if any).
if [[ -n "$DEV_SESSION_ID" ]]; then
env RECCE_API_TOKEN="$SMOKE_TEST_API_TOKEN" \
recce-cloud delete --session-id "$DEV_SESSION_ID" --force 2>/dev/null || true
fi

rm -f "$SYNTHETIC_EVENT"
echo "Cleanup complete"
}
trap cleanup EXIT

# ---- Test helper ----
run_test() {
local test_name="$1"
shift
echo ""
echo "=== $test_name ==="
if "$@"; then
echo "PASS: $test_name"
PASS_COUNT=$((PASS_COUNT + 1))
else
echo "FAIL: $test_name (exit code: $?)"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
}

echo "============================================"
echo "Upload Smoke Test"
echo "============================================"
echo "Test repo: $SMOKE_TEST_GITHUB_REPO"
echo "Test branch: $TEST_BRANCH"
echo "Fixtures: $FIXTURES_DIR"
echo "API host: $RECCE_CLOUD_API_HOST"
echo "API token: ${SMOKE_TEST_API_TOKEN:+set}${SMOKE_TEST_API_TOKEN:-not set}"
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The line that prints API token will output the full value of SMOKE_TEST_API_TOKEN into the CI logs, exposing a live Recce API token to anyone with log access. An attacker who can read these logs can reuse the token to authenticate against the staging Recce Cloud API and potentially escalate if tokens are reused or misconfigured. Update this output to only indicate whether the token is set (or print a masked value) instead of echoing the raw secret.

Suggested change
echo "API token: ${SMOKE_TEST_API_TOKEN:+set}${SMOKE_TEST_API_TOKEN:-not set}"
echo "API token: $( if [[ -n ${SMOKE_TEST_API_TOKEN:-} ]]; then echo 'set'; else echo 'not set'; fi )"

Copilot uses AI. Check for mistakes.
echo "Org/Project: ${SMOKE_TEST_ORG:-not set}/${SMOKE_TEST_PROJECT:-not set}"

# ==== Test 1: GitHub PR Upload ====
# Uses GITHUB_TOKEN (Priority 2 path). RECCE_API_TOKEN must NOT be set.
run_test "Test 1: GitHub PR Upload" \
env -u RECCE_API_TOKEN \
GITHUB_REPOSITORY="$SMOKE_TEST_GITHUB_REPO" \
GITHUB_EVENT_PATH="$SYNTHETIC_EVENT" \
GITHUB_HEAD_REF="$TEST_BRANCH" \
GITHUB_BASE_REF="main" \
recce-cloud upload --target-path "$FIXTURES_DIR"

# ==== Test 2: GitHub Prod Upload ====
# Uses GITHUB_TOKEN + --type prod. RECCE_API_TOKEN must NOT be set.
run_test "Test 2: GitHub Prod Upload" \
env -u RECCE_API_TOKEN \
GITHUB_REPOSITORY="$SMOKE_TEST_GITHUB_REPO" \
GITHUB_EVENT_PATH="$SYNTHETIC_EVENT" \
GITHUB_HEAD_REF="$TEST_BRANCH" \
GITHUB_BASE_REF="main" \
recce-cloud upload --target-path "$FIXTURES_DIR" --type prod

# ==== Test 3: RECCE_API_TOKEN PR Upload ====
if [[ -n "${SMOKE_TEST_API_TOKEN:-}" ]]; then
# Uses RECCE_API_TOKEN (Priority 1 path). CI env vars still needed for platform detection.
run_test "Test 3: RECCE_API_TOKEN PR Upload" \
env RECCE_API_TOKEN="$SMOKE_TEST_API_TOKEN" \
GITHUB_REPOSITORY="$SMOKE_TEST_GITHUB_REPO" \
GITHUB_EVENT_PATH="$SYNTHETIC_EVENT" \
GITHUB_HEAD_REF="$TEST_BRANCH" \
GITHUB_BASE_REF="main" \
recce-cloud upload --target-path "$FIXTURES_DIR"

# ==== Test 4: RECCE_API_TOKEN Prod Upload ====
run_test "Test 4: RECCE_API_TOKEN Prod Upload" \
env RECCE_API_TOKEN="$SMOKE_TEST_API_TOKEN" \
GITHUB_REPOSITORY="$SMOKE_TEST_GITHUB_REPO" \
GITHUB_EVENT_PATH="$SYNTHETIC_EVENT" \
GITHUB_HEAD_REF="$TEST_BRANCH" \
GITHUB_BASE_REF="main" \
recce-cloud upload --target-path "$FIXTURES_DIR" --type prod
else
echo ""
echo "SKIP: Tests 3-4 (RECCE_API_TOKEN) — SMOKE_TEST_API_TOKEN not set"
fi

# ==== Test 5: Session Name Upload (dev session) ====
# ==== Test 6: Session ID Upload (reuses session from test 5) ====
if [[ -n "${SMOKE_TEST_API_TOKEN:-}" && -n "${SMOKE_TEST_ORG:-}" && -n "${SMOKE_TEST_PROJECT:-}" ]]; then
# Test 5: Upload with --session-name (creates a dev session)
run_test "Test 5: Session Name Upload" \
env RECCE_API_TOKEN="$SMOKE_TEST_API_TOKEN" \
RECCE_ORG="$SMOKE_TEST_ORG" \
RECCE_PROJECT="$SMOKE_TEST_PROJECT" \
recce-cloud upload --target-path "$FIXTURES_DIR" \
--session-name "$DEV_SESSION_NAME" --yes

# Look up the session ID for the dev session we just created.
DEV_SESSION_ID=$(
env RECCE_API_TOKEN="$SMOKE_TEST_API_TOKEN" \
RECCE_ORG="$SMOKE_TEST_ORG" \
RECCE_PROJECT="$SMOKE_TEST_PROJECT" \
recce-cloud list --type dev --json 2>/dev/null \
| python3 -c "
import sys, json
sessions = json.load(sys.stdin)
for s in sessions:
if s.get('name') == '$DEV_SESSION_NAME':
print(s['id'])
break
" 2>/dev/null || true
)

if [[ -n "$DEV_SESSION_ID" ]]; then
echo "Dev session ID: $DEV_SESSION_ID"

# Test 6: Upload with --session-id (reuses the session from test 5)
run_test "Test 6: Session ID Upload" \
env RECCE_API_TOKEN="$SMOKE_TEST_API_TOKEN" \
recce-cloud upload --target-path "$FIXTURES_DIR" \
--session-id "$DEV_SESSION_ID"
else
echo "FAIL: Could not retrieve dev session ID for test 6"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
else
echo ""
echo "SKIP: Tests 5-6 (session-name/ID) — SMOKE_TEST_API_TOKEN, SMOKE_TEST_ORG, or SMOKE_TEST_PROJECT not set"
fi

# ==== TODO: GitLab CI Upload ====
# Cannot test from GitHub Actions — server verifies CI_JOB_TOKEN
# by calling GitLab /job API. Needs a real GitLab CI pipeline.

# ==== Summary ====
echo ""
echo "============================================"
echo "Results: $PASS_COUNT passed, $FAIL_COUNT failed"
echo "============================================"

if [[ $FAIL_COUNT -gt 0 ]]; then
exit 1
fi
Loading