|
| 1 | +#!/usr/bin/env bash |
| 2 | +set -euo pipefail |
| 3 | + |
| 4 | +# Smoke test for the standalone `recce-cloud` CLI against a live Recce Cloud |
| 5 | +# environment. This exercises the RECCE_API_TOKEN-based workflow end to end, |
| 6 | +# including the session-base (isolated base) upload path. |
| 7 | +# |
| 8 | +# Required env vars: |
| 9 | +# RECCE_API_TOKEN - API token for the test project |
| 10 | +# RECCE_ORG - Organization slug or ID for the test project |
| 11 | +# RECCE_PROJECT - Project slug or ID for the test project |
| 12 | +# |
| 13 | +# Optional env vars: |
| 14 | +# RECCE_CLOUD_API_HOST - Override the cloud host (e.g. staging) |
| 15 | +# PY, DBT - Matrix identifiers (used in the unique session name) |
| 16 | +# GITHUB_RUN_ID - GHA run id (used in the unique session name) |
| 17 | +# GITHUB_RUN_ATTEMPT - GHA run attempt (used in the unique session name) |
| 18 | + |
| 19 | +: "${RECCE_API_TOKEN:?RECCE_API_TOKEN is required}" |
| 20 | +: "${RECCE_ORG:?RECCE_ORG is required}" |
| 21 | +: "${RECCE_PROJECT:?RECCE_PROJECT is required}" |
| 22 | + |
| 23 | +# Distinguish prod vs staging in the session name so runs don't collide |
| 24 | +ENV_NAME="prod" |
| 25 | +if [[ -n "${RECCE_CLOUD_API_HOST:-}" ]]; then |
| 26 | + ENV_NAME="staging" |
| 27 | +fi |
| 28 | + |
| 29 | +SESSION_NAME="smoke-rc-${ENV_NAME}-py${PY:-unknown}-dbt${DBT:-unknown}-${GITHUB_RUN_ID:-local}-${GITHUB_RUN_ATTEMPT:-1}" |
| 30 | +SESSION_ID="" |
| 31 | + |
| 32 | +NEW_WORKSPACE=$(dirname "${GITHUB_WORKSPACE:-$PWD}") |
| 33 | +cd "$NEW_WORKSPACE" |
| 34 | +pwd |
| 35 | + |
| 36 | +# Disable anonymous telemetry for the smoke run |
| 37 | +mkdir -p ~/.recce |
| 38 | +echo "user_id: 00000000000000000000000000000000" > ~/.recce/profile.yml |
| 39 | +echo "anonymous_tracking: false" >> ~/.recce/profile.yml |
| 40 | + |
| 41 | +cleanup() { |
| 42 | + local exit_code=$? |
| 43 | + if [[ -n "$SESSION_ID" ]]; then |
| 44 | + echo "Teardown: deleting session $SESSION_ID" |
| 45 | + recce-cloud delete --session-id "$SESSION_ID" --force || true |
| 46 | + fi |
| 47 | + exit $exit_code |
| 48 | +} |
| 49 | +trap cleanup EXIT |
| 50 | + |
| 51 | +# -------------------------------------------------------------------- |
| 52 | +# 0. Sanity: CLI is installed and runnable |
| 53 | +# -------------------------------------------------------------------- |
| 54 | +recce-cloud version |
| 55 | + |
| 56 | +# -------------------------------------------------------------------- |
| 57 | +# 1. Prepare jaffle_shop fixture |
| 58 | +# -------------------------------------------------------------------- |
| 59 | +GIT_REPO="https://github.com/DataRecce/jaffle_shop_duckdb.git" |
| 60 | +GIT_BRANCH="chore/smoke-test-cloud" |
| 61 | +rm -rf jaffle_shop_duckdb |
| 62 | +git clone --depth 1 --branch $GIT_BRANCH $GIT_REPO |
| 63 | +cd jaffle_shop_duckdb |
| 64 | + |
| 65 | +dbt --version |
| 66 | +dbt deps |
| 67 | + |
| 68 | +# -------------------------------------------------------------------- |
| 69 | +# 2. Build BASE artifacts -> target-base/ |
| 70 | +# -------------------------------------------------------------------- |
| 71 | +git restore models/customers.sql || true |
| 72 | +dbt seed --target-path target-base |
| 73 | +dbt run --target-path target-base |
| 74 | +dbt docs generate --target-path target-base |
| 75 | +test -s target-base/manifest.json |
| 76 | +test -s target-base/catalog.json |
| 77 | + |
| 78 | +# -------------------------------------------------------------------- |
| 79 | +# 3. Build PR artifacts -> target/ |
| 80 | +# -------------------------------------------------------------------- |
| 81 | +echo "where customer_id > 0" >> models/customers.sql |
| 82 | +dbt run |
| 83 | +dbt docs generate |
| 84 | +git restore models/customers.sql |
| 85 | +test -s target/manifest.json |
| 86 | +test -s target/catalog.json |
| 87 | + |
| 88 | +# -------------------------------------------------------------------- |
| 89 | +# 4. Bind project to the test org/project |
| 90 | +# -------------------------------------------------------------------- |
| 91 | +recce-cloud init --org "$RECCE_ORG" --project "$RECCE_PROJECT" |
| 92 | +recce-cloud init --status |
| 93 | + |
| 94 | +# -------------------------------------------------------------------- |
| 95 | +# 5. Doctor baseline (informational) |
| 96 | +# |
| 97 | +# `recce-cloud doctor` exits non-zero if ANY of its four checks fails, |
| 98 | +# and Production Metadata / Dev Session can legitimately be unpopulated |
| 99 | +# in a fresh test project. We only enforce login + project_binding here. |
| 100 | +# -------------------------------------------------------------------- |
| 101 | +recce-cloud doctor --json > doctor.pre.json || true |
| 102 | +echo "--- recce-cloud doctor output ---" |
| 103 | +cat doctor.pre.json |
| 104 | +echo "---------------------------------" |
| 105 | +jq -e '.login.status == "pass"' doctor.pre.json > /dev/null |
| 106 | +jq -e '.project_binding.status == "pass"' doctor.pre.json > /dev/null |
| 107 | + |
| 108 | +# -------------------------------------------------------------------- |
| 109 | +# 6. Upload PR artifacts by session name |
| 110 | +# -------------------------------------------------------------------- |
| 111 | +recce-cloud upload --session-name "$SESSION_NAME" --yes --target-path target |
| 112 | +SESSION_ID=$(recce-cloud list --json | jq -r --arg n "$SESSION_NAME" '.[] | select(.name == $n) | .id' | head -n 1) |
| 113 | +if [[ -z "$SESSION_ID" ]]; then |
| 114 | + echo "ERROR: could not resolve session id for $SESSION_NAME" |
| 115 | + exit 1 |
| 116 | +fi |
| 117 | +echo "Created session: $SESSION_ID ($SESSION_NAME)" |
| 118 | + |
| 119 | +# -------------------------------------------------------------------- |
| 120 | +# 7. Pre-condition: the new session has no isolated base yet |
| 121 | +# -------------------------------------------------------------------- |
| 122 | +recce-cloud list --json | jq -e --arg i "$SESSION_ID" \ |
| 123 | + '.[] | select(.id == $i) | .has_isolated_base == false' > /dev/null |
| 124 | + |
| 125 | +# -------------------------------------------------------------------- |
| 126 | +# 8. Upload SESSION BASE artifacts (isolated base) |
| 127 | +# -------------------------------------------------------------------- |
| 128 | +recce-cloud upload --session-base --session-id "$SESSION_ID" --target-path target-base |
| 129 | +recce-cloud list --json | jq -e --arg i "$SESSION_ID" \ |
| 130 | + '.[] | select(.id == $i) | .has_isolated_base == true' > /dev/null |
| 131 | +echo "Isolated base uploaded successfully" |
| 132 | + |
| 133 | +# -------------------------------------------------------------------- |
| 134 | +# 9. Negative check: --session-base + --type prod must be rejected |
| 135 | +# -------------------------------------------------------------------- |
| 136 | +set +e |
| 137 | +recce-cloud upload --session-base --type prod --target-path target > /dev/null 2>&1 |
| 138 | +NEG_EXIT=$? |
| 139 | +set -e |
| 140 | +if [[ "$NEG_EXIT" -eq 0 ]]; then |
| 141 | + echo "ERROR: --session-base + --type prod should have failed, but exited 0" |
| 142 | + exit 1 |
| 143 | +fi |
| 144 | +echo "Negative check passed (exit $NEG_EXIT)" |
| 145 | + |
| 146 | +# -------------------------------------------------------------------- |
| 147 | +# 10. Download the PR session artifacts |
| 148 | +# -------------------------------------------------------------------- |
| 149 | +rm -rf target-downloaded |
| 150 | +recce-cloud download --session-id "$SESSION_ID" --target-path target-downloaded --force |
| 151 | +test -s target-downloaded/manifest.json |
| 152 | +test -s target-downloaded/catalog.json |
| 153 | + |
| 154 | +# -------------------------------------------------------------------- |
| 155 | +# 11. Download a known production session (read-only sanity) |
| 156 | +# -------------------------------------------------------------------- |
| 157 | +PROD_SESSION_ID=$(recce-cloud list --type prod --json | jq -r '.[0].id // empty') |
| 158 | +if [[ -z "$PROD_SESSION_ID" ]]; then |
| 159 | + echo "ERROR: no production session found in test project; seed one first" |
| 160 | + exit 1 |
| 161 | +fi |
| 162 | +rm -rf target-prod |
| 163 | +recce-cloud download --session-id "$PROD_SESSION_ID" --target-path target-prod --force |
| 164 | +test -s target-prod/manifest.json |
| 165 | + |
| 166 | +# -------------------------------------------------------------------- |
| 167 | +# 12. Dry-run paths (no side effects) |
| 168 | +# -------------------------------------------------------------------- |
| 169 | +recce-cloud upload --dry-run --target-path target |
| 170 | +recce-cloud download --session-id "$SESSION_ID" --dry-run |
| 171 | +recce-cloud delete --session-id "$SESSION_ID" --dry-run |
| 172 | + |
| 173 | +# -------------------------------------------------------------------- |
| 174 | +# 13. Delete session + verify |
| 175 | +# -------------------------------------------------------------------- |
| 176 | +recce-cloud delete --session-id "$SESSION_ID" --force |
| 177 | +recce-cloud list --json | jq -e --arg i "$SESSION_ID" 'all(.[]; .id != $i)' > /dev/null |
| 178 | + |
| 179 | +# Session is gone; skip the EXIT-trap double-delete |
| 180 | +SESSION_ID="" |
| 181 | + |
| 182 | +echo "recce-cloud smoke test completed successfully" |
0 commit comments