To spin the docker containers and run the tests:
just docker-reset # only if the containers have been created before
just docker-setup
just docker-test-inactive-tablesTo spin the docker containers and run the tests:
just docker-reset # only if the containers have been created before
just docker-setup
just docker-test-inactive-tables| services: | |
| # Metabase application database (stores cards, dashboards, settings) | |
| postgres: | |
| image: postgres:16-alpine | |
| environment: | |
| POSTGRES_DB: metabase | |
| POSTGRES_USER: metabase | |
| POSTGRES_PASSWORD: metabase | |
| ports: | |
| - "5432:5432" | |
| volumes: | |
| - metabase-data:/var/lib/postgresql/data | |
| healthcheck: | |
| test: ["CMD-SHELL", "pg_isready -U metabase"] | |
| interval: 5s | |
| retries: 5 | |
| # Sample database to create cards against (simulates BigQuery tables) | |
| sample-db: | |
| image: postgres:16-alpine | |
| environment: | |
| POSTGRES_DB: sample | |
| POSTGRES_USER: sample | |
| POSTGRES_PASSWORD: sample | |
| ports: | |
| - "5433:5432" | |
| volumes: | |
| - sample-data:/var/lib/postgresql/data | |
| healthcheck: | |
| test: ["CMD-SHELL", "pg_isready -U sample"] | |
| interval: 5s | |
| retries: 5 | |
| metabase: | |
| image: metabase/metabase:v0.57.16 | |
| ports: | |
| - "3000:3000" | |
| environment: | |
| MB_DB_TYPE: postgres | |
| MB_DB_HOST: postgres | |
| MB_DB_PORT: 5432 | |
| MB_DB_DBNAME: metabase | |
| MB_DB_USER: metabase | |
| MB_DB_PASS: metabase | |
| depends_on: | |
| postgres: | |
| condition: service_healthy | |
| healthcheck: | |
| test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"] | |
| interval: 10s | |
| timeout: 5s | |
| retries: 20 | |
| start_period: 30s | |
| volumes: | |
| metabase-data: | |
| sample-data: |
| #!/usr/bin/env bash | |
| # docker-setup.sh — Idempotent bootstrap for local Metabase development environment. | |
| # | |
| # Takes running Docker containers to a fully seeded Metabase instance in 6 phases: | |
| # 1. Wait for Metabase to be ready | |
| # 2. Admin account setup (or login if already set up) | |
| # 3. Connect sample-db as a Metabase database | |
| # 4. Create test tables in sample-db | |
| # 5. Sync Metabase and wait for tables to appear | |
| # 6. Create test cards (native SQL + query builder) | |
| # | |
| # Usage: bash scripts/docker-setup.sh | |
| set -euo pipefail | |
| # --------------------------------------------------------------------------- | |
| # Configuration | |
| # --------------------------------------------------------------------------- | |
| METABASE_URL="http://localhost:3000" | |
| ADMIN_EMAIL="admin@test.com" | |
| ADMIN_PASSWORD="Testpass1" | |
| ADMIN_FIRST_NAME="Admin" | |
| ADMIN_LAST_NAME="User" | |
| COMPOSE_FILE="docker-compose.yml" | |
| MAX_WAIT_METABASE=90 # seconds to wait for Metabase readiness | |
| MAX_WAIT_SYNC=30 # seconds to wait for table sync | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| info() { echo "==> $*"; } | |
| ok() { echo " ✓ $*"; } | |
| skip() { echo " • $* (already exists)"; } | |
| fail() { echo " ✗ $*" >&2; exit 1; } | |
| # Parse JSON with Python (guaranteed available in this project) | |
| json_get() { | |
| python3 -c "import sys, json; data=json.load(sys.stdin); print($1)" | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Phase 1: Wait for Metabase | |
| # --------------------------------------------------------------------------- | |
| info "Phase 1: Waiting for Metabase to be ready..." | |
| elapsed=0 | |
| while [ $elapsed -lt $MAX_WAIT_METABASE ]; do | |
| if curl -sf "${METABASE_URL}/api/session/properties" > /dev/null 2>&1; then | |
| ok "Metabase is ready (${elapsed}s)" | |
| break | |
| fi | |
| sleep 2 | |
| elapsed=$((elapsed + 2)) | |
| done | |
| if [ $elapsed -ge $MAX_WAIT_METABASE ]; then | |
| fail "Metabase did not become ready within ${MAX_WAIT_METABASE}s" | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Phase 2: Admin setup (idempotent) | |
| # --------------------------------------------------------------------------- | |
| info "Phase 2: Setting up admin account..." | |
| SETUP_TOKEN=$(curl -sf "${METABASE_URL}/api/session/properties" \ | |
| | json_get "data.get('setup-token', '')") | |
| if [ -n "$SETUP_TOKEN" ] && [ "$SETUP_TOKEN" != "None" ]; then | |
| # First-time setup | |
| RESPONSE=$(curl -sf "${METABASE_URL}/api/setup" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"token\": \"${SETUP_TOKEN}\", | |
| \"user\": { | |
| \"email\": \"${ADMIN_EMAIL}\", | |
| \"password\": \"${ADMIN_PASSWORD}\", | |
| \"first_name\": \"${ADMIN_FIRST_NAME}\", | |
| \"last_name\": \"${ADMIN_LAST_NAME}\", | |
| \"site_name\": \"Metabase Dev\" | |
| }, | |
| \"prefs\": { | |
| \"site_name\": \"Metabase Dev\", | |
| \"site_locale\": \"en\", | |
| \"allow_tracking\": false | |
| } | |
| }") | |
| TOKEN=$(echo "$RESPONSE" | json_get "data.get('id', '')") | |
| ok "Admin account created (${ADMIN_EMAIL})" | |
| else | |
| # Already set up — log in | |
| RESPONSE=$(curl -sf "${METABASE_URL}/api/session" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"username\": \"${ADMIN_EMAIL}\", | |
| \"password\": \"${ADMIN_PASSWORD}\" | |
| }") | |
| TOKEN=$(echo "$RESPONSE" | json_get "data['id']") | |
| skip "Admin account" | |
| fi | |
| if [ -z "$TOKEN" ] || [ "$TOKEN" = "None" ]; then | |
| fail "Could not obtain session token" | |
| fi | |
| AUTH_HEADER="X-Metabase-Session: ${TOKEN}" | |
| # --------------------------------------------------------------------------- | |
| # Phase 3: Connect sample-db (idempotent) | |
| # --------------------------------------------------------------------------- | |
| info "Phase 3: Connecting sample database..." | |
| EXISTING_DB=$(curl -sf "${METABASE_URL}/api/database" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "[d['id'] for d in data.get('data', data) if d['name'] == 'Sample DB']") | |
| if [ "$EXISTING_DB" = "[]" ]; then | |
| curl -sf "${METABASE_URL}/api/database" \ | |
| -H "$AUTH_HEADER" \ | |
| -H "Content-Type: application/json" \ | |
| -d '{ | |
| "engine": "postgres", | |
| "name": "Sample DB", | |
| "details": { | |
| "host": "sample-db", | |
| "port": 5432, | |
| "dbname": "sample", | |
| "user": "sample", | |
| "password": "sample" | |
| } | |
| }' > /dev/null | |
| ok "Sample DB connected" | |
| else | |
| skip "Sample DB connection" | |
| fi | |
| # Get the database ID for later use | |
| DB_ID=$(curl -sf "${METABASE_URL}/api/database" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "[d['id'] for d in data.get('data', data) if d['name'] == 'Sample DB'][0]") | |
| if [ -z "$DB_ID" ] || [ "$DB_ID" = "None" ]; then | |
| fail "Could not find Sample DB ID" | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Phase 4: Create test tables (idempotent via IF NOT EXISTS / ON CONFLICT) | |
| # --------------------------------------------------------------------------- | |
| info "Phase 4: Creating test tables..." | |
| docker compose -f "$COMPOSE_FILE" exec -T sample-db \ | |
| psql -U sample -d sample -q <<'SQL' | |
| CREATE SCHEMA IF NOT EXISTS analytics; | |
| CREATE TABLE IF NOT EXISTS analytics.old_user_stats ( | |
| user_id INTEGER PRIMARY KEY, | |
| booking_cnt INTEGER, | |
| total_amount DECIMAL(10,2), | |
| last_booking_date DATE, | |
| user_type VARCHAR(50) | |
| ); | |
| INSERT INTO analytics.old_user_stats (user_id, booking_cnt, total_amount, last_booking_date, user_type) | |
| VALUES | |
| (1, 5, 150.00, '2026-01-15', 'regular'), | |
| (2, 12, 480.50, '2026-02-20', 'power'), | |
| (3, 1, 25.00, '2026-03-01', 'new') | |
| ON CONFLICT (user_id) DO NOTHING; | |
| SQL | |
| ok "Test tables and data created" | |
| # --------------------------------------------------------------------------- | |
| # Phase 5: Sync Metabase and wait for tables to appear | |
| # --------------------------------------------------------------------------- | |
| info "Phase 5: Triggering Metabase sync..." | |
| curl -sf -X POST "${METABASE_URL}/api/database/${DB_ID}/sync_schema" \ | |
| -H "$AUTH_HEADER" > /dev/null | |
| # Wait for old_user_stats to appear in Metabase table list | |
| elapsed=0 | |
| TABLE_ID="" | |
| while [ $elapsed -lt $MAX_WAIT_SYNC ]; do | |
| TABLE_ID=$(curl -sf "${METABASE_URL}/api/table" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "str([t['id'] for t in data if t.get('name') == 'old_user_stats' and t.get('db_id') == ${DB_ID}][0])" 2>/dev/null || echo "") | |
| if [ -n "$TABLE_ID" ] && [ "$TABLE_ID" != "" ] && [ "$TABLE_ID" != "None" ]; then | |
| break | |
| fi | |
| sleep 2 | |
| elapsed=$((elapsed + 2)) | |
| done | |
| if [ -z "$TABLE_ID" ] || [ "$TABLE_ID" = "None" ]; then | |
| fail "Table old_user_stats did not appear in Metabase within ${MAX_WAIT_SYNC}s" | |
| fi | |
| ok "Sync complete — old_user_stats is table ID ${TABLE_ID}" | |
| # --------------------------------------------------------------------------- | |
| # Phase 6: Create test cards (idempotent — check by name) | |
| # --------------------------------------------------------------------------- | |
| info "Phase 6: Creating test cards..." | |
| EXISTING_CARDS=$(curl -sf "${METABASE_URL}/api/card" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "[c['name'] for c in data]") | |
| # --- Native SQL card --- | |
| if echo "$EXISTING_CARDS" | grep -q "Test Native Card"; then | |
| skip "Test Native Card" | |
| else | |
| NATIVE_CARD=$(curl -sf "${METABASE_URL}/api/card" \ | |
| -H "$AUTH_HEADER" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"name\": \"Test Native Card\", | |
| \"display\": \"table\", | |
| \"visualization_settings\": {}, | |
| \"dataset_query\": { | |
| \"type\": \"native\", | |
| \"native\": { | |
| \"query\": \"SELECT user_id, booking_cnt, total_amount FROM analytics.old_user_stats WHERE user_type = {{user_type}}\", | |
| \"template-tags\": { | |
| \"user_type\": { | |
| \"id\": \"b0a3d8a0-1234-5678-9abc-def012345678\", | |
| \"type\": \"text\", | |
| \"name\": \"user_type\", | |
| \"display-name\": \"User Type\" | |
| } | |
| } | |
| }, | |
| \"database\": ${DB_ID} | |
| } | |
| }") | |
| NATIVE_CARD_ID=$(echo "$NATIVE_CARD" | json_get "data['id']") | |
| ok "Test Native Card created (ID: ${NATIVE_CARD_ID})" | |
| fi | |
| # --- Query Builder card --- | |
| if echo "$EXISTING_CARDS" | grep -q "Test Query Builder Card"; then | |
| skip "Test Query Builder Card" | |
| else | |
| QB_CARD=$(curl -sf "${METABASE_URL}/api/card" \ | |
| -H "$AUTH_HEADER" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"name\": \"Test Query Builder Card\", | |
| \"display\": \"table\", | |
| \"visualization_settings\": {}, | |
| \"dataset_query\": { | |
| \"type\": \"query\", | |
| \"query\": { | |
| \"source-table\": ${TABLE_ID} | |
| }, | |
| \"database\": ${DB_ID} | |
| } | |
| }") | |
| QB_CARD_ID=$(echo "$QB_CARD" | json_get "data['id']") | |
| ok "Test Query Builder Card created (ID: ${QB_CARD_ID})" | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Summary | |
| # --------------------------------------------------------------------------- | |
| # Fetch final card IDs for summary | |
| ALL_CARD_IDS=$(curl -sf "${METABASE_URL}/api/card" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "','.join(str(c['id']) for c in data)") | |
| echo "" | |
| echo "============================================================" | |
| echo " Metabase Local Environment Ready!" | |
| echo "============================================================" | |
| echo "" | |
| echo " URL: ${METABASE_URL}" | |
| echo " Login: ${ADMIN_EMAIL} / ${ADMIN_PASSWORD}" | |
| echo " Database: Sample DB (ID: ${DB_ID})" | |
| echo " Table: analytics.old_user_stats (ID: ${TABLE_ID})" | |
| echo " Cards: ${ALL_CARD_IDS}" | |
| echo "" | |
| echo " Next steps:" | |
| echo " just docker-test-inactive-tables # test inactive tables API behavior" | |
| echo "============================================================" |
| #!/usr/bin/env bash | |
| # docker-test-inactive-tables.sh — Integration test proving that deleted tables | |
| # appear as inactive via the Metabase metadata API (remove_inactive=false). | |
| # | |
| # Phases: | |
| # 1. Verify preconditions (table exists and is active) | |
| # 2. Drop the table from the sample database | |
| # 3. Sync Metabase and wait for deletion to be reflected | |
| # 4. Assert the table is now inactive | |
| # 5. Cleanup — restore the table for other tests | |
| # | |
| # Prerequisites: docker-setup.sh must have been run first. | |
| # Usage: bash scripts/docker-test-inactive-tables.sh | |
| set -euo pipefail | |
| # --------------------------------------------------------------------------- | |
| # Configuration | |
| # --------------------------------------------------------------------------- | |
| METABASE_URL="http://localhost:3000" | |
| ADMIN_EMAIL="admin@test.com" | |
| ADMIN_PASSWORD="Testpass1" | |
| COMPOSE_FILE="docker-compose.yml" | |
| MAX_WAIT_SYNC=60 # seconds to wait for sync (longer than docker-setup's 30s) | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| info() { echo "==> $*"; } | |
| ok() { echo " ✓ $*"; } | |
| fail() { echo " ✗ $*" >&2; exit 1; } | |
| # Parse JSON with Python (guaranteed available in this project) | |
| json_get() { | |
| python3 -c "import sys, json; data=json.load(sys.stdin); print($1)" | |
| } | |
| # --------------------------------------------------------------------------- | |
| # Phase 1: Verify preconditions | |
| # --------------------------------------------------------------------------- | |
| info "Phase 1: Verifying preconditions..." | |
| # Login | |
| TOKEN=$(curl -sf "${METABASE_URL}/api/session" \ | |
| -H "Content-Type: application/json" \ | |
| -d "{ | |
| \"username\": \"${ADMIN_EMAIL}\", | |
| \"password\": \"${ADMIN_PASSWORD}\" | |
| }" | json_get "data['id']") | |
| if [ -z "$TOKEN" ] || [ "$TOKEN" = "None" ]; then | |
| fail "Could not obtain session token — is Metabase running?" | |
| fi | |
| AUTH_HEADER="X-Metabase-Session: ${TOKEN}" | |
| # Get Sample DB ID | |
| DB_ID=$(curl -sf "${METABASE_URL}/api/database" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "[d['id'] for d in data.get('data', data) if d['name'] == 'Sample DB'][0]") | |
| if [ -z "$DB_ID" ] || [ "$DB_ID" = "None" ]; then | |
| fail "Could not find Sample DB — has docker-setup.sh been run?" | |
| fi | |
| # Get table ID from /api/table (use next() to safely handle empty list) | |
| TABLE_ID=$(curl -sf "${METABASE_URL}/api/table" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "next(iter([t['id'] for t in data if t.get('name') == 'old_user_stats' and t.get('db_id') == ${DB_ID}]), '')") | |
| if [ -z "$TABLE_ID" ] || [ "$TABLE_ID" = "None" ]; then | |
| fail "Table old_user_stats not found in Metabase — run 'just docker-setup' first" | |
| fi | |
| # SQL check: table is active in Metabase app DB | |
| ACTIVE_FLAG=$(docker compose -f "$COMPOSE_FILE" exec -T postgres \ | |
| psql -U metabase -d metabase -t -A \ | |
| -c "SELECT active FROM metabase_table WHERE name = 'old_user_stats' AND db_id = ${DB_ID};") | |
| if [ "$ACTIVE_FLAG" != "t" ]; then | |
| fail "Table old_user_stats is not active in Metabase DB (expected 't', got '${ACTIVE_FLAG}')" | |
| fi | |
| ok "Table active in Metabase DB (SQL: active=$ACTIVE_FLAG)" | |
| # API check: table is active via metadata endpoint | |
| API_ACTIVE=$(curl -sf "${METABASE_URL}/api/database/${DB_ID}/metadata?skip_fields=true&remove_inactive=false" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "[t['active'] for t in data.get('tables', []) if t['name'] == 'old_user_stats'][0]") | |
| if [ "$API_ACTIVE" != "True" ]; then | |
| fail "Table old_user_stats is not active via API (expected 'True', got '${API_ACTIVE}')" | |
| fi | |
| ok "Table active via API (remove_inactive=false: active=$API_ACTIVE)" | |
| # --------------------------------------------------------------------------- | |
| # Phase 2: Drop the table | |
| # --------------------------------------------------------------------------- | |
| info "Phase 2: Dropping table from sample database..." | |
| docker compose -f "$COMPOSE_FILE" exec -T sample-db \ | |
| psql -U sample -d sample -c "DROP TABLE IF EXISTS analytics.old_user_stats;" | |
| ok "Table analytics.old_user_stats dropped" | |
| # --------------------------------------------------------------------------- | |
| # Phase 3: Sync and wait for deletion to be reflected | |
| # --------------------------------------------------------------------------- | |
| info "Phase 3: Syncing Metabase and waiting for table to become inactive..." | |
| curl -sf -X POST "${METABASE_URL}/api/database/${DB_ID}/sync_schema" \ | |
| -H "$AUTH_HEADER" > /dev/null | |
| elapsed=0 | |
| while [ $elapsed -lt $MAX_WAIT_SYNC ]; do | |
| FOUND=$(curl -sf "${METABASE_URL}/api/table" -H "$AUTH_HEADER" \ | |
| | json_get "[t['id'] for t in data if t.get('name') == 'old_user_stats' and t.get('db_id') == ${DB_ID}]") | |
| if [ "$FOUND" = "[]" ]; then | |
| ok "Table disappeared from active table list (${elapsed}s)" | |
| break | |
| fi | |
| sleep 2 | |
| elapsed=$((elapsed + 2)) | |
| done | |
| if [ $elapsed -ge $MAX_WAIT_SYNC ]; then | |
| fail "Table old_user_stats did not become inactive within ${MAX_WAIT_SYNC}s" | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Phase 4: Assert table is inactive | |
| # --------------------------------------------------------------------------- | |
| info "Phase 4: Asserting table is inactive..." | |
| TEST_PASSED=true | |
| # SQL check: table exists in Metabase app DB with active=false | |
| ACTIVE_FLAG=$(docker compose -f "$COMPOSE_FILE" exec -T postgres \ | |
| psql -U metabase -d metabase -t -A \ | |
| -c "SELECT active FROM metabase_table WHERE name = 'old_user_stats' AND db_id = ${DB_ID};") | |
| if [ "$ACTIVE_FLAG" != "f" ]; then | |
| echo " ✗ Table old_user_stats is not inactive in Metabase DB (expected 'f', got '${ACTIVE_FLAG}')" >&2 | |
| TEST_PASSED=false | |
| else | |
| ok "Table found in Metabase app DB with active=false" | |
| fi | |
| # API check: table should also be returned by the metadata API with remove_inactive=false. | |
| # The test FAILS if the table is in the app DB but missing from the API — that means | |
| # remove_inactive=false does not actually include inactive tables. | |
| API_TABLES=$(curl -sf "${METABASE_URL}/api/database/${DB_ID}/metadata?skip_fields=true&remove_inactive=false" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "[t['name'] for t in data.get('tables', []) if t['name'] == 'old_user_stats']") | |
| if [ "$API_TABLES" = "[]" ]; then | |
| echo " ✗ Table found in Metabase app DB but NOT returned by metadata API with remove_inactive=false" >&2 | |
| TEST_PASSED=false | |
| else | |
| ok "Table returned by metadata API with remove_inactive=false" | |
| fi | |
| # --------------------------------------------------------------------------- | |
| # Phase 5: Cleanup — restore the table | |
| # --------------------------------------------------------------------------- | |
| info "Phase 5: Restoring table for other tests..." | |
| docker compose -f "$COMPOSE_FILE" exec -T sample-db \ | |
| psql -U sample -d sample -q <<'SQL' | |
| CREATE SCHEMA IF NOT EXISTS analytics; | |
| CREATE TABLE IF NOT EXISTS analytics.old_user_stats ( | |
| user_id INTEGER PRIMARY KEY, | |
| booking_cnt INTEGER, | |
| total_amount DECIMAL(10,2), | |
| last_booking_date DATE, | |
| user_type VARCHAR(50) | |
| ); | |
| INSERT INTO analytics.old_user_stats (user_id, booking_cnt, total_amount, last_booking_date, user_type) | |
| VALUES | |
| (1, 5, 150.00, '2026-01-15', 'regular'), | |
| (2, 12, 480.50, '2026-02-20', 'power'), | |
| (3, 1, 25.00, '2026-03-01', 'new') | |
| ON CONFLICT (user_id) DO NOTHING; | |
| SQL | |
| # Trigger sync | |
| curl -sf -X POST "${METABASE_URL}/api/database/${DB_ID}/sync_schema" \ | |
| -H "$AUTH_HEADER" > /dev/null | |
| # Wait for table to reappear | |
| elapsed=0 | |
| while [ $elapsed -lt $MAX_WAIT_SYNC ]; do | |
| FOUND=$(curl -sf "${METABASE_URL}/api/table" -H "$AUTH_HEADER" \ | |
| | json_get "[t['id'] for t in data if t.get('name') == 'old_user_stats' and t.get('db_id') == ${DB_ID}]") | |
| if [ "$FOUND" != "[]" ]; then | |
| ok "Table reappeared in active table list (${elapsed}s)" | |
| break | |
| fi | |
| sleep 2 | |
| elapsed=$((elapsed + 2)) | |
| done | |
| if [ $elapsed -ge $MAX_WAIT_SYNC ]; then | |
| fail "Table old_user_stats did not reappear within ${MAX_WAIT_SYNC}s" | |
| fi | |
| # Verify restored: table is active again | |
| API_ACTIVE=$(curl -sf "${METABASE_URL}/api/database/${DB_ID}/metadata?skip_fields=true&remove_inactive=false" \ | |
| -H "$AUTH_HEADER" \ | |
| | json_get "[t['active'] for t in data.get('tables', []) if t['name'] == 'old_user_stats'][0]") | |
| if [ "$API_ACTIVE" != "True" ]; then | |
| fail "Table old_user_stats should be active again (expected 'True', got '${API_ACTIVE}')" | |
| fi | |
| ok "Table restored and active again" | |
| # --------------------------------------------------------------------------- | |
| # Summary | |
| # --------------------------------------------------------------------------- | |
| echo "" | |
| if [ "$TEST_PASSED" = true ]; then | |
| echo "============================================================" | |
| echo " Inactive Tables Test: PASSED" | |
| echo "============================================================" | |
| echo " All assertions verified:" | |
| echo " ✓ Table initially active in Metabase DB and API" | |
| echo " ✓ After deletion + sync: table found in Metabase app DB with active=false" | |
| echo " ✓ After deletion + sync: table returned by metadata API with remove_inactive=false" | |
| echo " ✓ Table restored and active again" | |
| echo "============================================================" | |
| else | |
| echo "============================================================" | |
| echo " Inactive Tables Test: FAILED" | |
| echo "============================================================" | |
| echo " Assertions:" | |
| echo " ✓ Table initially active in Metabase DB and API" | |
| echo " ✓ After deletion + sync: table found in Metabase app DB with active=false" | |
| echo " ✗ After deletion + sync: table NOT returned by metadata API with remove_inactive=false" | |
| echo " ✓ Table restored and active again" | |
| echo "============================================================" | |
| exit 1 | |
| fi |
| # Inactive tables integration test | |
| # Requires: docker, just, python3 | |
| # Stop containers and destroy volumes (clean slate) | |
| docker-reset: | |
| docker compose -f docker-compose.yml down -v | |
| # Start containers in background | |
| docker-up: | |
| docker compose -f docker-compose.yml up -d | |
| # Bootstrap local Metabase: start containers + seed data + create cards | |
| docker-setup: docker-up | |
| bash docker-setup.sh | |
| # Test that deleted tables appear as inactive via the metadata API | |
| docker-test-inactive-tables: | |
| bash docker-test-inactive-tables.sh |