From 0b9806c46e0a4d42d1254c4f7e550ce1fc0bc62b Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Fri, 20 Mar 2026 10:44:02 +0000 Subject: [PATCH 1/3] Migrate AI TAP group to Unified CI Infrastructure pattern This commit migrates the ai TAP group from its legacy hook-based approach to the standard test/infra/ Unified CI Infrastructure pattern. Changes: - Added generic hook system to infrastructure scripts: * ensure-infras.bash: Executes group-specific setup-infras.bash hook * run-tests-isolated.bash: Executes group-specific pre-cleanup.bash hook - Migrated ai group infrastructure: * Created infras.lst with infra-mysql84 and docker-pgsql16-single * Renamed seed files: mysql-seed.sql -> seed-mysql.sql, pgsql-seed.sql -> seed-pgsql.sql * Deleted legacy docker-compose.yml, docker-compose-init.bash, docker-compose-destroy.bash * Deleted legacy hooks: pre-proxysql.bash, post-proxysql.bash - Added group-specific MCP configuration: * setup-infras.bash: Configures MCP targets and seeds test data * pre-cleanup.bash: Removes MCP configuration after tests * mcp-config.sql: SQL template for MCP setup * cleanup.sql: SQL template for MCP cleanup * seed-mysql.sql & seed-pgsql.sql: Test data for AI tests - Updated documentation in README.md with usage examples Architecture: - ai is the supergroup (defines infrastructure and shared config) - ai-g1, ai-g2, etc. are subgroups (define test sets in groups.json) - Group-specific hooks live in test/tap/groups/ai/ directory - Infrastructure scripts remain generic and delegate to group hooks --- test/infra/control/ensure-infras.bash | 24 +++ test/infra/control/run-tests-isolated.bash | 16 ++ test/tap/groups/ai-g1/env.sh | 5 + test/tap/groups/ai/README.md | 196 +++++++++++++----- test/tap/groups/ai/cleanup.sql | 15 ++ .../tap/groups/ai/docker-compose-destroy.bash | 23 -- test/tap/groups/ai/docker-compose-init.bash | 55 ----- test/tap/groups/ai/docker-compose.yml | 30 --- test/tap/groups/ai/env.sh | 22 +- test/tap/groups/ai/infras.lst | 2 + test/tap/groups/ai/mcp-config.sql | 56 +++++ test/tap/groups/ai/post-proxysql.bash | 55 ----- test/tap/groups/ai/pre-cleanup.bash | 54 +++++ test/tap/groups/ai/pre-proxysql.bash | 135 ------------ .../ai/{mysql-seed.sql => seed-mysql.sql} | 10 +- .../ai/{pgsql-seed.sql => seed-pgsql.sql} | 4 + test/tap/groups/ai/setup-infras.bash | 122 +++++++++++ 17 files changed, 464 insertions(+), 360 deletions(-) create mode 100644 test/tap/groups/ai-g1/env.sh create mode 100644 test/tap/groups/ai/cleanup.sql delete mode 100755 test/tap/groups/ai/docker-compose-destroy.bash delete mode 100755 test/tap/groups/ai/docker-compose-init.bash delete mode 100644 test/tap/groups/ai/docker-compose.yml create mode 100644 test/tap/groups/ai/infras.lst create mode 100644 test/tap/groups/ai/mcp-config.sql delete mode 100755 test/tap/groups/ai/post-proxysql.bash create mode 100755 test/tap/groups/ai/pre-cleanup.bash delete mode 100755 test/tap/groups/ai/pre-proxysql.bash rename test/tap/groups/ai/{mysql-seed.sql => seed-mysql.sql} (80%) rename test/tap/groups/ai/{pgsql-seed.sql => seed-pgsql.sql} (84%) create mode 100755 test/tap/groups/ai/setup-infras.bash diff --git a/test/infra/control/ensure-infras.bash b/test/infra/control/ensure-infras.bash index 276d4a90fd..de9597ee2d 100755 --- a/test/infra/control/ensure-infras.bash +++ b/test/infra/control/ensure-infras.bash @@ -78,3 +78,27 @@ for INFRA_NAME in ${INFRAS}; do done echo ">>> All required infrastructures for '${TAP_GROUP}' are READY (INFRA_ID: ${INFRA_ID})." + +# 5. Derive DEFAULT_MYSQL_INFRA and DEFAULT_PGSQL_INFRA for hooks +# These are used by group-specific hooks to connect to backends +for INFRA_NAME in ${INFRAS}; do + if [[ "${INFRA_NAME}" == *mysql* ]] || [[ "${INFRA_NAME}" == *mariadb* ]]; then + export DEFAULT_MYSQL_INFRA="${DEFAULT_MYSQL_INFRA:-${INFRA_NAME}}" + fi + if [[ "${INFRA_NAME}" == *pgsql* ]] || [[ "${INFRA_NAME}" == *pgdb* ]]; then + export DEFAULT_PGSQL_INFRA="${DEFAULT_PGSQL_INFRA:-${INFRA_NAME}}" + fi +done + +# 6. Execute group-specific setup hook if it exists +# This allows TAP groups to perform additional setup after all backends are running +SETUP_HOOK="${WORKSPACE}/test/tap/groups/${TAP_GROUP}/setup-infras.bash" +if [ ! -f "${SETUP_HOOK}" ]; then + # Try base group if subgroup doesn't have the hook + SETUP_HOOK="${WORKSPACE}/test/tap/groups/${BASE_GROUP}/setup-infras.bash" +fi + +if [ -f "${SETUP_HOOK}" ]; then + echo ">>> Executing group setup hook: ${SETUP_HOOK}" + "${SETUP_HOOK}" +fi diff --git a/test/infra/control/run-tests-isolated.bash b/test/infra/control/run-tests-isolated.bash index e81383dacb..ded42a880b 100755 --- a/test/infra/control/run-tests-isolated.bash +++ b/test/infra/control/run-tests-isolated.bash @@ -138,6 +138,22 @@ docker run \ python3 "${WORKSPACE}/test/scripts/bin/proxysql-tester.py" " +# Execute group-specific pre-cleanup hook if it exists +# This runs before the test runner container is removed, allowing cleanup +# of ProxySQL-specific configuration while admin is still accessible +if [ -n "${TAP_GROUP}" ]; then + BASE_GROUP="${TAP_GROUP%%-g[0-9]*}" + PRE_CLEANUP_HOOK="${WORKSPACE}/test/tap/groups/${TAP_GROUP}/pre-cleanup.bash" + if [ ! -f "${PRE_CLEANUP_HOOK}" ]; then + PRE_CLEANUP_HOOK="${WORKSPACE}/test/tap/groups/${BASE_GROUP}/pre-cleanup.bash" + fi + + if [ -f "${PRE_CLEANUP_HOOK}" ]; then + echo ">>> Executing group pre-cleanup hook: ${PRE_CLEANUP_HOOK}" + "${PRE_CLEANUP_HOOK}" || true # Allow cleanup to fail + fi +fi + # Clean up only the runner container echo ">>> Cleaning up Test Runner container" docker rm -f "${TEST_CONTAINER}" >/dev/null 2>&1 || true diff --git a/test/tap/groups/ai-g1/env.sh b/test/tap/groups/ai-g1/env.sh new file mode 100644 index 0000000000..1ea85268a6 --- /dev/null +++ b/test/tap/groups/ai-g1/env.sh @@ -0,0 +1,5 @@ +# AI-g1 Subgroup Environment Configuration +# Sources the parent ai group configuration + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "${SCRIPT_DIR}/../ai/env.sh" diff --git a/test/tap/groups/ai/README.md b/test/tap/groups/ai/README.md index b670458396..ec54af5267 100644 --- a/test/tap/groups/ai/README.md +++ b/test/tap/groups/ai/README.md @@ -1,80 +1,172 @@ -# AI TAP Group Local Infra +# AI TAP Group -This group supports running AI/MCP TAP tests with an isolated local backend infra, without relying on Jenkins helper repos. +This group runs AI/MCP TAP tests using the **Unified CI Infrastructure** pattern, with isolated Docker network and automated infrastructure management. -## What it starts +## Architecture -- MySQL 9.0 on `127.0.0.1:13306` -- PostgreSQL 16 on `127.0.0.1:15432` +The AI group uses the standard `test/infra/` pattern: -Both are started from `test/tap/groups/ai/docker-compose.yml`. +- **MySQL Backend**: `infra-mysql84` (3-node MySQL 8.4 cluster with replication) +- **PostgreSQL Backend**: `docker-pgsql16-single` (single PostgreSQL 16 instance) +- **MCP Server**: Configured on port 6071 with MySQL and PostgreSQL targets +- **Network**: Isolated Docker network `${INFRA_ID}_backend` -## Group hooks +## Group Structure -- `pre-proxysql.bash` - - starts local containers (`docker-compose-init.bash`) - - seeds deterministic static-harvest datasets on both backends: - - MySQL: `tap_mysql_static_customers`, `tap_mysql_static_orders` - - PostgreSQL: `tap_pgsql_static_accounts`, `tap_pgsql_static_events` - - enables MCP - - configures backend hostgroups and MCP profiles/targets: - - MySQL target: `tap_mysql_default` - - PostgreSQL target: `tap_pgsql_default` -- `post-proxysql.bash` - - removes group-specific MCP profiles/targets/hostgroups - - destroys local containers (`docker-compose-destroy.bash`) +The `ai` group follows the **supergroup/subgroup** pattern: +- `ai` is the supergroup (defines infrastructure and shared configuration) +- `ai-g1`, `ai-g2`, etc. are subgroups (define specific test sets in `groups.json`) -## Manual run (without Jenkins) +When running with `TAP_GROUP=ai-g1`, the system: +1. Looks for `test/tap/groups/ai/infras.lst` (infrastructure requirements) +2. Looks for `test/tap/groups/ai/env.sh` (environment configuration) +3. Runs only tests tagged with `ai-g1` in `groups.json` -From repository root: +## Infrastructure Files -```bash -source test/tap/groups/ai/env.sh -bash test/tap/groups/ai/pre-proxysql.bash -# run your TAP tests here -bash test/tap/groups/ai/post-proxysql.bash -``` +| File | Purpose | +|------|---------| +| `infras.lst` | Lists required infrastructures (`infra-mysql84`, `docker-pgsql16-single`) | +| `env.sh` | Environment variables for MCP configuration | +| `mcp-config.sql` | SQL template for MCP setup (variables substituted at runtime) | +| `cleanup.sql` | SQL template for MCP cleanup after tests | +| `seed-mysql.sql` | Test data for MySQL (seeded on mysql1, replicated to others) | +| `seed-pgsql.sql` | Test data for PostgreSQL | +| `setup-infras.bash` | **Group hook**: Configures MCP and seeds test data after infrastructure is ready | +| `pre-cleanup.bash` | **Group hook**: Removes MCP configuration before test runner cleanup | + +## MCP Configuration + +The MCP (Model Context Protocol) is automatically configured with: + +- **MySQL Target**: `tap_mysql_default` (hostgroup 9100) +- **PostgreSQL Target**: `tap_pgsql_default` (hostgroup 9200) +- **Port**: 6071 (SSL enabled) + +Test data includes: +- MySQL: `test.tap_mysql_static_customers`, `test.tap_mysql_static_orders` +- PostgreSQL: `public.tap_pgsql_static_accounts`, `public.tap_pgsql_static_events` -For MCP query-rules suite: +## Usage + +### Running the Full AI Group ```bash -source test/tap/groups/ai/env.sh -bash test/tap/groups/ai/pre-proxysql.bash -bash test/tap/tests/test_mcp_query_rules-t.sh -bash test/tap/groups/ai/post-proxysql.bash +# Set unique INFRA_ID to avoid conflicts with other runs +export INFRA_ID="ai-test-$(date +%s)" +export TAP_GROUP="ai-g1" +export WORKSPACE=$(pwd) + +# Source common environment +source test/infra/common/env.sh + +# Start infrastructure (ProxySQL + MySQL + PostgreSQL) +./test/infra/control/ensure-infras.bash + +# Run tests +./test/infra/control/run-tests-isolated.bash + +# Cleanup +./test/infra/control/stop-proxysql-isolated.bash +./test/infra/control/destroy-infras.bash ``` -For static-harvest phase-A suite (mysql + pgsql targets): +### Running a Single Test ```bash -source test/tap/groups/ai/env.sh -bash test/tap/groups/ai/pre-proxysql.bash -bash test/tap/tests/test_mcp_static_harvest-t.sh -bash test/tap/groups/ai/post-proxysql.bash +export INFRA_ID="ai-single-$(date +%s)" +export TAP_GROUP="ai-g1" +export TEST_PY_TAP_INCL="mcp_module-t" +export WORKSPACE=$(pwd) + +source test/infra/common/env.sh +./test/infra/control/ensure-infras.bash +./test/infra/control/run-tests-isolated.bash +./test/infra/control/stop-proxysql-isolated.bash +./test/infra/control/destroy-infras.bash ``` -For phase-B MCP discovery primitives (agent/llm/catalog tools, CI-safe): +### Running Specific MCP Test Suites +**MCP Static Harvest (MySQL + PostgreSQL):** ```bash -source test/tap/groups/ai/env.sh -bash test/tap/groups/ai/pre-proxysql.bash -bash test/tap/tests/test_mcp_llm_discovery_phaseb-t.sh -bash test/tap/groups/ai/post-proxysql.bash +export INFRA_ID="ai-harvest-$(date +%s)" +export TAP_GROUP="ai-g1" +export TEST_PY_TAP_INCL="test_mcp_static_harvest-t" + +source test/infra/common/env.sh +./test/infra/control/ensure-infras.bash +./test/infra/control/run-tests-isolated.bash +./test/infra/control/stop-proxysql-isolated.bash +./test/infra/control/destroy-infras.bash ``` -For Claude headless flow smoke (dry-run + optional real Claude execution): +**MCP Discovery Phase B:** +```bash +export INFRA_ID="ai-discovery-$(date +%s)" +export TAP_GROUP="ai-g1" +export TEST_PY_TAP_INCL="test_mcp_llm_discovery_phaseb-t" + +source test/infra/common/env.sh +./test/infra/control/ensure-infras.bash +./test/infra/control/run-tests-isolated.bash +./test/infra/control/stop-proxysql-isolated.bash +./test/infra/control/destroy-infras.bash +``` +**Claude Headless Flow:** ```bash -source test/tap/groups/ai/env.sh -bash test/tap/groups/ai/pre-proxysql.bash -bash test/tap/tests/test_mcp_claude_headless_flow-t.sh -# Optional real run: -# TAP_RUN_REAL_CLAUDE=1 TAP_CLAUDE_MCP_CONFIG=./scripts/mcp/DiscoveryAgent/ClaudeCode_Headless/mcp_config.json \ -# bash test/tap/tests/test_mcp_claude_headless_flow-t.sh -bash test/tap/groups/ai/post-proxysql.bash +export INFRA_ID="ai-claude-$(date +%s)" +export TAP_GROUP="ai-g1" +export TEST_PY_TAP_INCL="test_mcp_claude_headless_flow-t" + +source test/infra/common/env.sh +./test/infra/control/ensure-infras.bash +./test/infra/control/run-tests-isolated.bash +./test/infra/control/stop-proxysql-isolated.bash +./test/infra/control/destroy-infras.bash + +# Optional: Run with real Claude execution +# export TAP_RUN_REAL_CLAUDE=1 +# export TAP_CLAUDE_MCP_CONFIG=./scripts/mcp/DiscoveryAgent/ClaudeCode_Headless/mcp_config.json ``` +## Test Categories + +Tests included in the AI group (defined in `groups.json`): + +- **AI Module Tests**: `ai_validation-t`, `ai_error_handling_edge_cases-t`, `ai_llm_retry_scenarios-t` +- **Anomaly Detection**: `anomaly_detection-t`, `anomaly_detector_unit-t`, `anomaly_detection_integration-t` +- **GenAI Tests**: `genai_module-t`, `genai_async-t`, `genai_embedding_rerank-t`, `genai_live_validation-t` +- **MCP Tests**: `mcp_module-t`, `mcp_query_rules-t`, `mcp_stats_refresh-t`, `mcp_semantic_lifecycle-t`, etc. +- **NL2SQL Tests**: `nl2sql_integration-t`, `nl2sql_prompt_builder-t`, `nl2sql_model_selection-t`, etc. +- **Vector Tests**: `vector_features-t`, `vector_db_performance-t` + +## Environment Variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `TAP_MCPPORT` | 6071 | MCP server port | +| `MCP_TARGET_ID` | tap_mysql_default | MySQL MCP target ID | +| `MCP_PGSQL_TARGET_ID` | tap_pgsql_default | PostgreSQL MCP target ID | +| `MCP_MYSQL_HOSTGROUP_ID` | 9100 | MySQL hostgroup for MCP | +| `MCP_PGSQL_HOSTGROUP_ID` | 9200 | PostgreSQL hostgroup for MCP | +| `TEST_PY_TAP_INCL` | (see env.sh) | Test filter pattern | + ## Notes -- All variables can be overridden from the environment before running hooks. -- The scripts still work in Jenkins-driven TAP flows because they do not require Jenkins-only paths. +- All containers run on an isolated Docker network (`${INFRA_ID}_backend`) +- MySQL replication automatically propagates seed data from mysql1 to mysql2/mysql3 +- MCP configuration is applied by the group-specific `setup-infras.bash` hook and cleaned up by `pre-cleanup.bash` +- The `INFRA_ID` must be unique for each test run to avoid conflicts + +## Group-Specific Hooks + +The AI group uses the generic hook system provided by the Unified CI Infrastructure: + +| Hook | Executed By | Purpose | +|------|-------------|---------| +| `setup-infras.bash` | `ensure-infras.bash` after backends start | Configures MCP, seeds test data | +| `pre-cleanup.bash` | `run-tests-isolated.bash` before cleanup | Removes MCP configuration | + +These hooks are group-specific and live in the `test/tap/groups/ai/` directory. They are discovered and executed automatically by the infrastructure scripts based on `TAP_GROUP`. diff --git a/test/tap/groups/ai/cleanup.sql b/test/tap/groups/ai/cleanup.sql new file mode 100644 index 0000000000..cf6935c46b --- /dev/null +++ b/test/tap/groups/ai/cleanup.sql @@ -0,0 +1,15 @@ +-- AI Group MCP Cleanup +-- Removes MCP configuration after tests complete +-- Variables are substituted using envsubst before execution + +DELETE FROM mcp_target_profiles WHERE target_id IN ('${MCP_TARGET_ID}', '${MCP_PGSQL_TARGET_ID}'); +DELETE FROM mcp_auth_profiles WHERE auth_profile_id IN ('${MCP_AUTH_PROFILE_ID}', '${MCP_PGSQL_AUTH_PROFILE_ID}'); +LOAD MCP PROFILES TO RUNTIME; +SAVE MCP PROFILES TO DISK; + +DELETE FROM mysql_servers WHERE hostgroup_id IN (${MCP_MYSQL_HOSTGROUP_ID}); +DELETE FROM pgsql_servers WHERE hostgroup_id IN (${MCP_PGSQL_HOSTGROUP_ID}); +LOAD MYSQL SERVERS TO RUNTIME; +SAVE MYSQL SERVERS TO DISK; +LOAD PGSQL SERVERS TO RUNTIME; +SAVE PGSQL SERVERS TO DISK; diff --git a/test/tap/groups/ai/docker-compose-destroy.bash b/test/tap/groups/ai/docker-compose-destroy.bash deleted file mode 100755 index 3763806bc8..0000000000 --- a/test/tap/groups/ai/docker-compose-destroy.bash +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -o pipefail - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -COMPOSE_FILE="${SCRIPT_DIR}/docker-compose.yml" - -compose() { - if docker compose version >/dev/null 2>&1; then - docker compose -f "${COMPOSE_FILE}" "$@" - elif command -v docker-compose >/dev/null 2>&1; then - docker-compose -f "${COMPOSE_FILE}" "$@" - else - echo "[ERROR] docker compose is not available" >&2 - exit 1 - fi -} - -echo "[INFO] Stopping AI TAP group backend containers..." -compose down -v --remove-orphans -echo "[INFO] AI TAP group backend containers removed" - diff --git a/test/tap/groups/ai/docker-compose-init.bash b/test/tap/groups/ai/docker-compose-init.bash deleted file mode 100755 index cd80207b7d..0000000000 --- a/test/tap/groups/ai/docker-compose-init.bash +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -set -o pipefail - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -COMPOSE_FILE="${SCRIPT_DIR}/docker-compose.yml" - -compose() { - if docker compose version >/dev/null 2>&1; then - docker compose -f "${COMPOSE_FILE}" "$@" - elif command -v docker-compose >/dev/null 2>&1; then - docker-compose -f "${COMPOSE_FILE}" "$@" - else - echo "[ERROR] docker compose is not available" >&2 - exit 1 - fi -} - -wait_for_health() { - local service="$1" - local max_wait="${2:-180}" - local waited=0 - local cid - cid="$(compose ps -q "${service}")" - if [ -z "${cid}" ]; then - echo "[ERROR] Service '${service}' has no running container" >&2 - exit 1 - fi - - while [ "${waited}" -lt "${max_wait}" ]; do - local status - status="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "${cid}" 2>/dev/null || true)" - if [ "${status}" = "healthy" ] || [ "${status}" = "running" ]; then - echo "[INFO] ${service} status=${status}" - return 0 - fi - sleep 2 - waited=$((waited + 2)) - done - - echo "[ERROR] Timeout waiting for service '${service}' to become healthy" >&2 - compose ps - compose logs --tail 100 "${service}" || true - exit 1 -} - -echo "[INFO] Starting AI TAP group backend containers..." -compose up -d --remove-orphans - -wait_for_health mysql90 240 -wait_for_health pgsql 240 - -echo "[INFO] AI TAP group backend containers are ready" - diff --git a/test/tap/groups/ai/docker-compose.yml b/test/tap/groups/ai/docker-compose.yml deleted file mode 100644 index 7585144ab4..0000000000 --- a/test/tap/groups/ai/docker-compose.yml +++ /dev/null @@ -1,30 +0,0 @@ -services: - mysql90: - image: mysql:9.0 - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: rootpass - MYSQL_DATABASE: testdb - ports: - - "13306:3306" - healthcheck: - test: ["CMD-SHELL", "mysqladmin ping -h127.0.0.1 -uroot -prootpass --silent"] - interval: 5s - timeout: 3s - retries: 40 - - pgsql: - image: postgres:16 - restart: unless-stopped - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: testdb - ports: - - "15432:5432" - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d testdb -h 127.0.0.1"] - interval: 5s - timeout: 3s - retries: 40 - diff --git a/test/tap/groups/ai/env.sh b/test/tap/groups/ai/env.sh index 8ca1f49e4d..de39e72655 100644 --- a/test/tap/groups/ai/env.sh +++ b/test/tap/groups/ai/env.sh @@ -1,12 +1,18 @@ -export TEST_PY_TAP_INCL="ai_.*-t anomaly_.*-t genai.*-t mcp.*-t nl2sql_.*-t test_mcp.*-t vector.*-t" +# AI TAP Group Environment Configuration +# Defines the primary targets for AI/MCP tests using standard test/infra/ pattern -# Local AI group backend defaults (used by MCP TAP tests). -# These variables can be overridden by the runner environment. -export TAP_MYSQLHOST="${TAP_MYSQLHOST:-127.0.0.1}" -export TAP_MYSQLPORT="${TAP_MYSQLPORT:-13306}" -export TAP_MYSQLUSERNAME="${TAP_MYSQLUSERNAME:-root}" -export TAP_MYSQLPASSWORD="${TAP_MYSQLPASSWORD:-rootpass}" -export TEST_DB_NAME="${TEST_DB_NAME:-testdb}" +export DEFAULT_MYSQL_INFRA="infra-mysql84" +export DEFAULT_PGSQL_INFRA="docker-pgsql16-single" + +# MCP-specific environment variables for AI tests export TAP_MCPPORT="${TAP_MCPPORT:-6071}" export MCP_TARGET_ID="${MCP_TARGET_ID:-tap_mysql_default}" +export MCP_AUTH_PROFILE_ID="${MCP_AUTH_PROFILE_ID:-tap_mysql_auth}" export MCP_PGSQL_TARGET_ID="${MCP_PGSQL_TARGET_ID:-tap_pgsql_default}" +export MCP_PGSQL_AUTH_PROFILE_ID="${MCP_PGSQL_AUTH_PROFILE_ID:-tap_pgsql_auth}" +export MCP_MYSQL_HOSTGROUP_ID="${MCP_MYSQL_HOSTGROUP_ID:-9100}" +export MCP_PGSQL_HOSTGROUP_ID="${MCP_PGSQL_HOSTGROUP_ID:-9200}" + +# Test data database name +export MYSQL_DATABASE="${MYSQL_DATABASE:-test}" +export PGSQL_DATABASE="${PGSQL_DATABASE:-postgres}" diff --git a/test/tap/groups/ai/infras.lst b/test/tap/groups/ai/infras.lst new file mode 100644 index 0000000000..39cd0de873 --- /dev/null +++ b/test/tap/groups/ai/infras.lst @@ -0,0 +1,2 @@ +infra-mysql84 +docker-pgsql16-single diff --git a/test/tap/groups/ai/mcp-config.sql b/test/tap/groups/ai/mcp-config.sql new file mode 100644 index 0000000000..87c651a197 --- /dev/null +++ b/test/tap/groups/ai/mcp-config.sql @@ -0,0 +1,56 @@ +-- AI Group MCP Configuration +-- This SQL is executed by the test runner to configure MCP for AI tests +-- Variables are substituted using envsubst before execution + +-- Configure MCP variables +SET mcp-port='${TAP_MCPPORT}'; +SET mcp-use_ssl='true'; +SET mcp-enabled='false'; +LOAD MCP VARIABLES TO RUNTIME; +SAVE MCP VARIABLES TO DISK; + +-- Configure MySQL servers for MCP +DELETE FROM mysql_servers WHERE hostgroup_id IN (0, ${MCP_MYSQL_HOSTGROUP_ID}); +INSERT INTO mysql_servers (hostgroup_id, hostname, port, status, weight, comment) + VALUES (0, '${TAP_MYSQLHOST}', ${TAP_MYSQLPORT}, 'ONLINE', 1, 'ai local mysql'); +INSERT INTO mysql_servers (hostgroup_id, hostname, port, status, weight, comment) + VALUES (${MCP_MYSQL_HOSTGROUP_ID}, '${TAP_MYSQLHOST}', ${TAP_MYSQLPORT}, 'ONLINE', 1, 'ai mcp mysql'); +LOAD MYSQL SERVERS TO RUNTIME; +SAVE MYSQL SERVERS TO DISK; + +-- Configure PostgreSQL servers for MCP +DELETE FROM pgsql_servers WHERE hostgroup_id IN (${MCP_PGSQL_HOSTGROUP_ID}); +INSERT INTO pgsql_servers (hostgroup_id, hostname, port, status, weight, comment) + VALUES (${MCP_PGSQL_HOSTGROUP_ID}, '${TAP_PGSQLSERVER_HOST}', ${TAP_PGSQLSERVER_PORT}, 'ONLINE', 1, 'ai mcp pgsql'); +LOAD PGSQL SERVERS TO RUNTIME; +SAVE PGSQL SERVERS TO DISK; + +-- Configure frontend users for TAP tests +INSERT OR IGNORE INTO mysql_users (username, password, active, default_hostgroup, comment) + VALUES ('${TAP_MYSQLUSERNAME}', '${TAP_MYSQLPASSWORD}', 1, 0, 'ai local'); +INSERT OR IGNORE INTO mysql_users (username, password, active, default_hostgroup, comment) + VALUES ('testuser', 'testuser', 1, 0, 'ai local'); +LOAD MYSQL USERS TO RUNTIME; +SAVE MYSQL USERS TO DISK; + +-- Configure MCP auth profiles +DELETE FROM mcp_auth_profiles WHERE auth_profile_id IN ('${MCP_AUTH_PROFILE_ID}', '${MCP_PGSQL_AUTH_PROFILE_ID}'); +INSERT INTO mcp_auth_profiles (auth_profile_id, db_username, db_password, default_schema, use_ssl, ssl_mode, comment) + VALUES ('${MCP_AUTH_PROFILE_ID}', '${TAP_MYSQLUSERNAME}', '${TAP_MYSQLPASSWORD}', '${MYSQL_DATABASE}', 0, '', 'ai mysql mcp auth'); +INSERT INTO mcp_auth_profiles (auth_profile_id, db_username, db_password, default_schema, use_ssl, ssl_mode, comment) + VALUES ('${MCP_PGSQL_AUTH_PROFILE_ID}', '${TAP_PGSQLSERVER_USERNAME}', '${TAP_PGSQLSERVER_PASSWORD}', '${PGSQL_DATABASE}', 0, '', 'ai pgsql mcp auth'); + +-- Configure MCP target profiles +DELETE FROM mcp_target_profiles WHERE target_id IN ('${MCP_TARGET_ID}', '${MCP_PGSQL_TARGET_ID}'); +INSERT INTO mcp_target_profiles (target_id, protocol, hostgroup_id, auth_profile_id, description, max_rows, timeout_ms, allow_explain, allow_discovery, active, comment) + VALUES ('${MCP_TARGET_ID}', 'mysql', ${MCP_MYSQL_HOSTGROUP_ID}, '${MCP_AUTH_PROFILE_ID}', 'AI local MySQL target', 200, 5000, 1, 1, 1, 'ai local'); +INSERT INTO mcp_target_profiles (target_id, protocol, hostgroup_id, auth_profile_id, description, max_rows, timeout_ms, allow_explain, allow_discovery, active, comment) + VALUES ('${MCP_PGSQL_TARGET_ID}', 'pgsql', ${MCP_PGSQL_HOSTGROUP_ID}, '${MCP_PGSQL_AUTH_PROFILE_ID}', 'AI local PostgreSQL target', 200, 5000, 1, 1, 1, 'ai local'); + +LOAD MCP PROFILES TO RUNTIME; +SAVE MCP PROFILES TO DISK; + +-- Enable MCP +SET mcp-enabled='true'; +LOAD MCP VARIABLES TO RUNTIME; +SAVE MCP VARIABLES TO DISK; diff --git a/test/tap/groups/ai/post-proxysql.bash b/test/tap/groups/ai/post-proxysql.bash deleted file mode 100755 index a5b65a758c..0000000000 --- a/test/tap/groups/ai/post-proxysql.bash +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash -set -o pipefail -# -# AI TAP group post-hook: -# - cleans MCP targets/profiles created by pre-hook -# - removes isolated compose containers -# - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -MCP_TARGET_ID="${MCP_TARGET_ID:-tap_mysql_default}" -MCP_AUTH_PROFILE_ID="${MCP_AUTH_PROFILE_ID:-tap_mysql_auth}" -MCP_PGSQL_TARGET_ID="${MCP_PGSQL_TARGET_ID:-tap_pgsql_default}" -MCP_PGSQL_AUTH_PROFILE_ID="${MCP_PGSQL_AUTH_PROFILE_ID:-tap_pgsql_auth}" -MCP_MYSQL_HOSTGROUP_ID="${MCP_MYSQL_HOSTGROUP_ID:-9100}" -MCP_PGSQL_HOSTGROUP_ID="${MCP_PGSQL_HOSTGROUP_ID:-9200}" - -ADMIN_HOST="${TAP_ADMINHOST:-127.0.0.1}" -ADMIN_PORT="${TAP_ADMINPORT:-6032}" -ADMIN_USER="${TAP_ADMINUSERNAME:-radmin}" -ADMIN_PASS="${TAP_ADMINPASSWORD:-radmin}" - -if [[ $(mysql --skip-ssl-verify-server-cert -h 2>&1) =~ skip-ssl-verify-server-cert ]]; then - SSLOPT=--skip-ssl-verify-server-cert -else - SSLOPT= -fi - -exec_admin() { - mysql ${SSLOPT} -h"${ADMIN_HOST}" -P"${ADMIN_PORT}" -u"${ADMIN_USER}" -p"${ADMIN_PASS}" -e "$1" 2>&1 | sed '/^mysql: .*Warning/d' -} - -try_admin() { - if ! exec_admin "$1"; then - echo "[WARN] admin cleanup statement failed: $1" >&2 - fi -} - -echo "[INFO] AI post-hook: cleaning ProxySQL MCP/group-specific config" - -try_admin "DELETE FROM mcp_target_profiles WHERE target_id IN ('${MCP_TARGET_ID}', '${MCP_PGSQL_TARGET_ID}');" -try_admin "DELETE FROM mcp_auth_profiles WHERE auth_profile_id IN ('${MCP_AUTH_PROFILE_ID}', '${MCP_PGSQL_AUTH_PROFILE_ID}');" -try_admin "LOAD MCP PROFILES TO RUNTIME; SAVE MCP PROFILES TO DISK;" - -try_admin "DELETE FROM mysql_servers WHERE hostgroup_id IN (${MCP_MYSQL_HOSTGROUP_ID});" -try_admin "LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;" -try_admin "DELETE FROM pgsql_servers WHERE hostgroup_id IN (${MCP_PGSQL_HOSTGROUP_ID});" -try_admin "LOAD PGSQL SERVERS TO RUNTIME; SAVE PGSQL SERVERS TO DISK;" - -echo "[INFO] AI post-hook: destroying group-local containers" -"${SCRIPT_DIR}/docker-compose-destroy.bash" - -echo "[INFO] AI post-hook completed" diff --git a/test/tap/groups/ai/pre-cleanup.bash b/test/tap/groups/ai/pre-cleanup.bash new file mode 100755 index 0000000000..4a68344728 --- /dev/null +++ b/test/tap/groups/ai/pre-cleanup.bash @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -uo pipefail +# +# AI TAP Group Pre-Cleanup Hook +# Executed by run-tests-isolated.bash before test runner cleanup +# Removes MCP configuration from ProxySQL +# + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Check required environment variables +if [ -z "${INFRA_ID:-}" ]; then + echo "WARNING: INFRA_ID is not set, skipping MCP cleanup" + exit 0 +fi + +if [ -z "${WORKSPACE:-}" ]; then + echo "WARNING: WORKSPACE is not set, skipping MCP cleanup" + exit 0 +fi + +export ROOT_PASSWORD=$(echo -n "${INFRA_ID}" | sha256sum | head -c 10) +PROXY_CONTAINER="proxysql.${INFRA_ID}" + +# Check if ProxySQL container is running +if ! docker ps --format '{{.Names}}' | grep -q "^${PROXY_CONTAINER}$"; then + echo ">>> AI pre-cleanup hook: ProxySQL container not running, skipping cleanup" + exit 0 +fi + +# Set MCP-specific environment variables (with defaults) +export MCP_TARGET_ID="${MCP_TARGET_ID:-tap_mysql_default}" +export MCP_AUTH_PROFILE_ID="${MCP_AUTH_PROFILE_ID:-tap_mysql_auth}" +export MCP_PGSQL_TARGET_ID="${MCP_PGSQL_TARGET_ID:-tap_pgsql_default}" +export MCP_PGSQL_AUTH_PROFILE_ID="${MCP_PGSQL_AUTH_PROFILE_ID:-tap_pgsql_auth}" +export MCP_MYSQL_HOSTGROUP_ID="${MCP_MYSQL_HOSTGROUP_ID:-9100}" +export MCP_PGSQL_HOSTGROUP_ID="${MCP_PGSQL_HOSTGROUP_ID:-9200}" + +echo ">>> AI pre-cleanup hook: Removing MCP configuration..." + +CLEANUP_SQL="${SCRIPT_DIR}/cleanup.sql" +if [ -f "${CLEANUP_SQL}" ]; then + if command -v envsubst >/dev/null 2>&1; then + envsubst < "${CLEANUP_SQL}" | docker exec -i "${PROXY_CONTAINER}" mysql -uradmin -pradmin -h127.0.0.1 -P6032 2>/dev/null || true + else + # Fallback: use bash variable expansion + bash -c "cat </dev/null || true + fi + echo ">>> MCP cleanup completed" +else + echo ">>> WARNING: Cleanup SQL not found at ${CLEANUP_SQL}" +fi diff --git a/test/tap/groups/ai/pre-proxysql.bash b/test/tap/groups/ai/pre-proxysql.bash deleted file mode 100755 index b724df6a24..0000000000 --- a/test/tap/groups/ai/pre-proxysql.bash +++ /dev/null @@ -1,135 +0,0 @@ -#!/usr/bin/env bash -set -o pipefail -# -# AI TAP group pre-hook: -# - starts isolated mysql90 + pgsql containers (group-local compose) -# - configures ProxySQL MCP variables, backends, and MCP profiles/targets -# - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# TAP defaults (can be overridden by environment). -TAP_MYSQLHOST="${TAP_MYSQLHOST:-127.0.0.1}" -TAP_MYSQLPORT="${TAP_MYSQLPORT:-13306}" -TAP_MYSQLUSERNAME="${TAP_MYSQLUSERNAME:-root}" -TAP_MYSQLPASSWORD="${TAP_MYSQLPASSWORD:-rootpass}" -TEST_DB_NAME="${TEST_DB_NAME:-testdb}" -TAP_MCPPORT="${TAP_MCPPORT:-6071}" -MCP_TARGET_ID="${MCP_TARGET_ID:-tap_mysql_default}" -MCP_AUTH_PROFILE_ID="${MCP_AUTH_PROFILE_ID:-tap_mysql_auth}" -MCP_PGSQL_TARGET_ID="${MCP_PGSQL_TARGET_ID:-tap_pgsql_default}" -MCP_PGSQL_AUTH_PROFILE_ID="${MCP_PGSQL_AUTH_PROFILE_ID:-tap_pgsql_auth}" -MCP_MYSQL_HOSTGROUP_ID="${MCP_MYSQL_HOSTGROUP_ID:-9100}" -MCP_PGSQL_HOSTGROUP_ID="${MCP_PGSQL_HOSTGROUP_ID:-9200}" -AI_PGSQL_HOST="${AI_PGSQL_HOST:-127.0.0.1}" -AI_PGSQL_PORT="${AI_PGSQL_PORT:-15432}" -AI_PGSQL_USER="${AI_PGSQL_USER:-postgres}" -AI_PGSQL_PASSWORD="${AI_PGSQL_PASSWORD:-postgres}" -AI_PGSQL_DB="${AI_PGSQL_DB:-testdb}" - -ADMIN_HOST="${TAP_ADMINHOST:-127.0.0.1}" -ADMIN_PORT="${TAP_ADMINPORT:-6032}" -ADMIN_USER="${TAP_ADMINUSERNAME:-radmin}" -ADMIN_PASS="${TAP_ADMINPASSWORD:-radmin}" - -if [[ $(mysql --skip-ssl-verify-server-cert -h 2>&1) =~ skip-ssl-verify-server-cert ]]; then - SSLOPT=--skip-ssl-verify-server-cert -else - SSLOPT= -fi - -exec_admin() { - mysql ${SSLOPT} -h"${ADMIN_HOST}" -P"${ADMIN_PORT}" -u"${ADMIN_USER}" -p"${ADMIN_PASS}" -e "$1" 2>&1 | sed '/^mysql: .*Warning/d' -} - -compose() { - if docker compose version >/dev/null 2>&1; then - docker compose -f "${SCRIPT_DIR}/docker-compose.yml" "$@" - elif command -v docker-compose >/dev/null 2>&1; then - docker-compose -f "${SCRIPT_DIR}/docker-compose.yml" "$@" - else - echo "[ERROR] docker compose is not available" >&2 - exit 1 - fi -} - -create_mysql_monitor_user() { - echo "[INFO] AI pre-hook: creating MySQL monitor user monitor/monitor on backend ${TAP_MYSQLHOST}:${TAP_MYSQLPORT}" - mysql -h"${TAP_MYSQLHOST}" -P"${TAP_MYSQLPORT}" -u"${TAP_MYSQLUSERNAME}" -p"${TAP_MYSQLPASSWORD}" -e "\ -CREATE USER IF NOT EXISTS 'monitor'@'%' IDENTIFIED BY 'monitor'; \ -GRANT USAGE, PROCESS, REPLICATION CLIENT ON *.* TO 'monitor'@'%'; \ -FLUSH PRIVILEGES;" -} - -create_pgsql_monitor_user() { - echo "[INFO] AI pre-hook: creating PostgreSQL monitor user monitor/monitor on backend ${AI_PGSQL_HOST}:${AI_PGSQL_PORT}" - local sql="DO \$\$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname='monitor') THEN CREATE ROLE monitor LOGIN PASSWORD 'monitor'; END IF; END \$\$; GRANT pg_monitor TO monitor; GRANT CONNECT ON DATABASE postgres TO monitor; GRANT CONNECT ON DATABASE ${AI_PGSQL_DB} TO monitor;" - if command -v psql >/dev/null 2>&1; then - PGPASSWORD="${AI_PGSQL_PASSWORD}" psql -h "${AI_PGSQL_HOST}" -p "${AI_PGSQL_PORT}" -U "${AI_PGSQL_USER}" -d "${AI_PGSQL_DB}" -v ON_ERROR_STOP=1 -c "${sql}" - else - compose exec -T pgsql psql -U "${AI_PGSQL_USER}" -d "${AI_PGSQL_DB}" -v ON_ERROR_STOP=1 -c "${sql}" - fi -} - -seed_mysql_test_data() { - echo "[INFO] AI pre-hook: seeding MySQL static-harvest test data" - mysql -h"${TAP_MYSQLHOST}" -P"${TAP_MYSQLPORT}" -u"${TAP_MYSQLUSERNAME}" -p"${TAP_MYSQLPASSWORD}" < "${SCRIPT_DIR}/mysql-seed.sql" -} - -seed_pgsql_test_data() { - echo "[INFO] AI pre-hook: seeding PostgreSQL static-harvest test data" - if command -v psql >/dev/null 2>&1; then - PGPASSWORD="${AI_PGSQL_PASSWORD}" psql -h "${AI_PGSQL_HOST}" -p "${AI_PGSQL_PORT}" -U "${AI_PGSQL_USER}" -d "${AI_PGSQL_DB}" -v ON_ERROR_STOP=1 -f "${SCRIPT_DIR}/pgsql-seed.sql" - else - compose exec -T pgsql psql -U "${AI_PGSQL_USER}" -d "${AI_PGSQL_DB}" -v ON_ERROR_STOP=1 < "${SCRIPT_DIR}/pgsql-seed.sql" - fi -} - -echo "[INFO] AI pre-hook: starting group-local containers" -"${SCRIPT_DIR}/docker-compose-init.bash" -create_mysql_monitor_user -create_pgsql_monitor_user -seed_mysql_test_data -seed_pgsql_test_data - -echo "[INFO] AI pre-hook: configuring ProxySQL MCP and backend routing" - -# Configure MCP runtime variables. -exec_admin "SET mcp-port='${TAP_MCPPORT}';" -exec_admin "SET mcp-use_ssl='true';" -exec_admin "SET mcp-enabled='false';" -exec_admin "LOAD MCP VARIABLES TO RUNTIME; SAVE MCP VARIABLES TO DISK;" - -# Keep predictable hostgroups for both direct tests and MCP target routing. -exec_admin "DELETE FROM mysql_servers WHERE hostgroup_id IN (0, ${MCP_MYSQL_HOSTGROUP_ID});" -exec_admin "INSERT INTO mysql_servers (hostgroup_id, hostname, port, status, weight, comment) VALUES (0, '${TAP_MYSQLHOST}', ${TAP_MYSQLPORT}, 'ONLINE', 1, 'ai local mysql');" -exec_admin "INSERT INTO mysql_servers (hostgroup_id, hostname, port, status, weight, comment) VALUES (${MCP_MYSQL_HOSTGROUP_ID}, '${TAP_MYSQLHOST}', ${TAP_MYSQLPORT}, 'ONLINE', 1, 'ai mcp mysql');" -exec_admin "LOAD MYSQL SERVERS TO RUNTIME; SAVE MYSQL SERVERS TO DISK;" - -exec_admin "DELETE FROM pgsql_servers WHERE hostgroup_id IN (${MCP_PGSQL_HOSTGROUP_ID});" -exec_admin "INSERT INTO pgsql_servers (hostgroup_id, hostname, port, status, weight, comment) VALUES (${MCP_PGSQL_HOSTGROUP_ID}, '${AI_PGSQL_HOST}', ${AI_PGSQL_PORT}, 'ONLINE', 1, 'ai mcp pgsql');" -exec_admin "LOAD PGSQL SERVERS TO RUNTIME; SAVE PGSQL SERVERS TO DISK;" - -# Basic frontend mysql users for TAP scripts that connect through ProxySQL. -exec_admin "INSERT OR IGNORE INTO mysql_users (username, password, active, default_hostgroup, comment) VALUES ('${TAP_MYSQLUSERNAME}', '${TAP_MYSQLPASSWORD}', 1, 0, 'ai local');" -exec_admin "INSERT OR IGNORE INTO mysql_users (username, password, active, default_hostgroup, comment) VALUES ('testuser', 'testuser', 1, 0, 'ai local');" -exec_admin "LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK;" - -# Configure MCP auth and target profiles (mysql + pgsql). -exec_admin "DELETE FROM mcp_target_profiles WHERE target_id IN ('${MCP_TARGET_ID}', '${MCP_PGSQL_TARGET_ID}');" -exec_admin "DELETE FROM mcp_auth_profiles WHERE auth_profile_id IN ('${MCP_AUTH_PROFILE_ID}', '${MCP_PGSQL_AUTH_PROFILE_ID}');" - -exec_admin "INSERT INTO mcp_auth_profiles (auth_profile_id, db_username, db_password, default_schema, use_ssl, ssl_mode, comment) VALUES ('${MCP_AUTH_PROFILE_ID}', '${TAP_MYSQLUSERNAME}', '${TAP_MYSQLPASSWORD}', '${TEST_DB_NAME}', 0, '', 'ai mysql mcp auth');" -exec_admin "INSERT INTO mcp_auth_profiles (auth_profile_id, db_username, db_password, default_schema, use_ssl, ssl_mode, comment) VALUES ('${MCP_PGSQL_AUTH_PROFILE_ID}', '${AI_PGSQL_USER}', '${AI_PGSQL_PASSWORD}', '${AI_PGSQL_DB}', 0, '', 'ai pgsql mcp auth');" - -exec_admin "INSERT INTO mcp_target_profiles (target_id, protocol, hostgroup_id, auth_profile_id, description, max_rows, timeout_ms, allow_explain, allow_discovery, active, comment) VALUES ('${MCP_TARGET_ID}', 'mysql', ${MCP_MYSQL_HOSTGROUP_ID}, '${MCP_AUTH_PROFILE_ID}', 'AI local MySQL target', 200, 5000, 1, 1, 1, 'ai local');" -exec_admin "INSERT INTO mcp_target_profiles (target_id, protocol, hostgroup_id, auth_profile_id, description, max_rows, timeout_ms, allow_explain, allow_discovery, active, comment) VALUES ('${MCP_PGSQL_TARGET_ID}', 'pgsql', ${MCP_PGSQL_HOSTGROUP_ID}, '${MCP_PGSQL_AUTH_PROFILE_ID}', 'AI local PostgreSQL target', 200, 5000, 1, 1, 1, 'ai local');" - -exec_admin "LOAD MCP PROFILES TO RUNTIME; SAVE MCP PROFILES TO DISK;" -exec_admin "SET mcp-enabled='true';" -exec_admin "LOAD MCP VARIABLES TO RUNTIME; SAVE MCP VARIABLES TO DISK;" - -sleep 2 -echo "[INFO] AI pre-hook completed" diff --git a/test/tap/groups/ai/mysql-seed.sql b/test/tap/groups/ai/seed-mysql.sql similarity index 80% rename from test/tap/groups/ai/mysql-seed.sql rename to test/tap/groups/ai/seed-mysql.sql index 1907538211..6c2e1791e2 100644 --- a/test/tap/groups/ai/mysql-seed.sql +++ b/test/tap/groups/ai/seed-mysql.sql @@ -1,5 +1,9 @@ -CREATE DATABASE IF NOT EXISTS testdb; -USE testdb; +-- AI Group MySQL Test Data Seeding +-- Creates tables needed by AI/MCP tests +-- This is executed by docker-mysql-post.bash when TAP_GROUP starts with 'ai' + +CREATE DATABASE IF NOT EXISTS test; +USE test; CREATE TABLE IF NOT EXISTS tap_mysql_static_customers ( customer_id INT PRIMARY KEY, @@ -25,3 +29,5 @@ INSERT INTO tap_mysql_static_orders(order_id, customer_id, total_amount) VALUES (101, 1, 42.50), (102, 2, 18.99) ON DUPLICATE KEY UPDATE total_amount=VALUES(total_amount); + +FLUSH TABLES; diff --git a/test/tap/groups/ai/pgsql-seed.sql b/test/tap/groups/ai/seed-pgsql.sql similarity index 84% rename from test/tap/groups/ai/pgsql-seed.sql rename to test/tap/groups/ai/seed-pgsql.sql index 7dad3d1151..7208c9a472 100644 --- a/test/tap/groups/ai/pgsql-seed.sql +++ b/test/tap/groups/ai/seed-pgsql.sql @@ -1,3 +1,7 @@ +-- AI Group PostgreSQL Test Data Seeding +-- Creates tables needed by AI/MCP tests +-- This is executed by docker-pgsql-post.bash when TAP_GROUP starts with 'ai' + CREATE TABLE IF NOT EXISTS public.tap_pgsql_static_accounts ( account_id INT PRIMARY KEY, account_name TEXT NOT NULL UNIQUE, diff --git a/test/tap/groups/ai/setup-infras.bash b/test/tap/groups/ai/setup-infras.bash new file mode 100755 index 0000000000..1daa6aecb7 --- /dev/null +++ b/test/tap/groups/ai/setup-infras.bash @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +set -euo pipefail +# +# AI TAP Group Post-Infrastructure Hook +# Executed by ensure-infras.bash after all backends are running +# Configures MCP and seeds test data +# + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Check required environment variables +if [ -z "${INFRA_ID:-}" ]; then + echo "ERROR: INFRA_ID is not set" + exit 1 +fi + +if [ -z "${WORKSPACE:-}" ]; then + echo "ERROR: WORKSPACE is not set" + exit 1 +fi + +# Derive DEFAULT_MYSQL_INFRA and DEFAULT_PGSQL_INFRA from infras.lst if not set +if [ -z "${DEFAULT_MYSQL_INFRA:-}" ] || [ -z "${DEFAULT_PGSQL_INFRA:-}" ]; then + INFRAS_LST="${SCRIPT_DIR}/infras.lst" + if [ -f "${INFRAS_LST}" ]; then + for INFRA_NAME in $(cat "${INFRAS_LST}"); do + if [[ "${INFRA_NAME}" == *mysql* ]] || [[ "${INFRA_NAME}" == *mariadb* ]]; then + DEFAULT_MYSQL_INFRA="${DEFAULT_MYSQL_INFRA:-${INFRA_NAME}}" + fi + if [[ "${INFRA_NAME}" == *pgsql* ]] || [[ "${INFRA_NAME}" == *pgdb* ]]; then + DEFAULT_PGSQL_INFRA="${DEFAULT_PGSQL_INFRA:-${INFRA_NAME}}" + fi + done + fi +fi + +export ROOT_PASSWORD=$(echo -n "${INFRA_ID}" | sha256sum | head -c 10) +PROXY_CONTAINER="proxysql.${INFRA_ID}" + +# Wait for ProxySQL admin to be ready +echo ">>> AI post-infras hook: Waiting for ProxySQL admin..." +MAX_WAIT=30 +COUNT=0 +while [ $COUNT -lt $MAX_WAIT ]; do + if docker exec "${PROXY_CONTAINER}" mysql -uradmin -pradmin -h127.0.0.1 -P6032 -e "SELECT 1" >/dev/null 2>&1; then + echo ">>> ProxySQL admin is ready" + break + fi + echo -n "." + sleep 1 + COUNT=$((COUNT+1)) +done + +if [ $COUNT -ge $MAX_WAIT ]; then + echo " TIMEOUT - ProxySQL admin not accessible" + exit 1 +fi + +# Configure MCP +echo ">>> AI post-infras hook: Configuring MCP..." + +# Set MCP-specific environment variables (with defaults) +export TAP_MCPPORT="${TAP_MCPPORT:-6071}" +export MCP_TARGET_ID="${MCP_TARGET_ID:-tap_mysql_default}" +export MCP_AUTH_PROFILE_ID="${MCP_AUTH_PROFILE_ID:-tap_mysql_auth}" +export MCP_PGSQL_TARGET_ID="${MCP_PGSQL_TARGET_ID:-tap_pgsql_default}" +export MCP_PGSQL_AUTH_PROFILE_ID="${MCP_PGSQL_AUTH_PROFILE_ID:-tap_pgsql_auth}" +export MCP_MYSQL_HOSTGROUP_ID="${MCP_MYSQL_HOSTGROUP_ID:-9100}" +export MCP_PGSQL_HOSTGROUP_ID="${MCP_PGSQL_HOSTGROUP_ID:-9200}" +export MYSQL_DATABASE="${MYSQL_DATABASE:-test}" +export PGSQL_DATABASE="${PGSQL_DATABASE:-postgres}" + +# Set backend connection variables +export TAP_MYSQLHOST="mysql1.${DEFAULT_MYSQL_INFRA}" +export TAP_MYSQLPORT="3306" +export TAP_MYSQLUSERNAME="root" +export TAP_MYSQLPASSWORD="${ROOT_PASSWORD}" +export TAP_PGSQLSERVER_HOST="pgsql1.${DEFAULT_PGSQL_INFRA}" +export TAP_PGSQLSERVER_PORT="5432" +export TAP_PGSQLSERVER_USERNAME="postgres" +export TAP_PGSQLSERVER_PASSWORD="${ROOT_PASSWORD}" + +# Apply MCP configuration +MCP_CONFIG_SQL="${SCRIPT_DIR}/mcp-config.sql" +if [ -f "${MCP_CONFIG_SQL}" ]; then + if command -v envsubst >/dev/null 2>&1; then + envsubst < "${MCP_CONFIG_SQL}" | docker exec -i "${PROXY_CONTAINER}" mysql -uradmin -pradmin -h127.0.0.1 -P6032 + else + # Fallback: use bash variable expansion + bash -c "cat <>> MCP configuration completed" +else + echo ">>> WARNING: MCP config SQL not found at ${MCP_CONFIG_SQL}" +fi + +# Seed test data on MySQL +echo ">>> AI post-infras hook: Seeding MySQL test data..." +MYSQL_SEED_SQL="${SCRIPT_DIR}/seed-mysql.sql" +if [ -f "${MYSQL_SEED_SQL}" ]; then + # Seed only mysql1 (replication will propagate to others) + MYSQL1_CONTAINER="${DEFAULT_MYSQL_INFRA}-${INFRA_ID}-mysql1-1" + docker exec -i "${MYSQL1_CONTAINER}" mysql -h127.0.0.1 -uroot -p"${ROOT_PASSWORD}" < "${MYSQL_SEED_SQL}" || echo ">>> WARNING: MySQL seed may have partially failed (data may already exist)" + echo ">>> MySQL test data seeding completed" +else + echo ">>> WARNING: MySQL seed SQL not found at ${MYSQL_SEED_SQL}" +fi + +# Seed test data on PostgreSQL +echo ">>> AI post-infras hook: Seeding PostgreSQL test data..." +PGSQL_SEED_SQL="${SCRIPT_DIR}/seed-pgsql.sql" +if [ -f "${PGSQL_SEED_SQL}" ]; then + PGSQL_CONTAINER="${DEFAULT_PGSQL_INFRA}-${INFRA_ID}-pgdb1-1" + docker exec -i "${PGSQL_CONTAINER}" psql -X -Upostgres < "${PGSQL_SEED_SQL}" || echo ">>> WARNING: PostgreSQL seed may have partially failed (data may already exist)" + echo ">>> PostgreSQL test data seeding completed" +else + echo ">>> WARNING: PostgreSQL seed SQL not found at ${PGSQL_SEED_SQL}" +fi + +echo ">>> AI post-infras hook completed" From 2f8e63cef26673187f9da5bb208c5f8d2a4767e9 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Fri, 20 Mar 2026 11:42:43 +0000 Subject: [PATCH 2/3] Address code review feedback for AI TAP group migration Fixes from PR #5461 review: 1. Fix BASE_GROUP derivation inconsistency in run-tests-isolated.bash - Changed from bash pattern matching `${TAP_GROUP%%-g[0-9]*}` to sed - Now uses same pattern as ensure-infras.bash: `sed -E "s/[-_]g[0-9]+.*//"` - Removed redundant BASE_GROUP reassignment 2. Update outdated comments in seed files - seed-mysql.sql and seed-pgsql.sql now correctly reference setup-infras.bash 3. Change INSERT OR IGNORE to INSERT OR REPLACE in mcp-config.sql - Ensures credentials are updated on reruns for deterministic state 4. Add shellcheck directives to env.sh files - Added `# shellcheck shell=bash` to ai/env.sh and ai-g1/env.sh 5. Add explicit validation for derived infrastructure names - setup-infras.bash now validates DEFAULT_MYSQL_INFRA and DEFAULT_PGSQL_INFRA - Provides clear error messages if infras.lst is misconfigured 6. Add WORKSPACE export to README examples - All runnable examples now include `export WORKSPACE=$(pwd)` Test verification: - ai-g1 group infrastructure setup works correctly - legacy-g2 group tests still pass (backward compatibility verified) --- test/infra/control/run-tests-isolated.bash | 3 +-- test/tap/groups/ai-g1/env.sh | 1 + test/tap/groups/ai/README.md | 3 +++ test/tap/groups/ai/env.sh | 1 + test/tap/groups/ai/mcp-config.sql | 4 ++-- test/tap/groups/ai/seed-mysql.sql | 2 +- test/tap/groups/ai/seed-pgsql.sql | 2 +- test/tap/groups/ai/setup-infras.bash | 11 +++++++++++ 8 files changed, 21 insertions(+), 6 deletions(-) diff --git a/test/infra/control/run-tests-isolated.bash b/test/infra/control/run-tests-isolated.bash index ded42a880b..689df18544 100755 --- a/test/infra/control/run-tests-isolated.bash +++ b/test/infra/control/run-tests-isolated.bash @@ -12,7 +12,7 @@ export INFRA_ID="${INFRA_ID:-dev-$USER}" # 1. Determine Required Infras INFRAS_TO_CHECK="" -BASE_GROUP="${TAP_GROUP%%-g[0-9]*}" # Strip -g1, -g2 etc. +BASE_GROUP=$(echo "${TAP_GROUP}" | sed -E "s/[-_]g[0-9]+.*//") # Strip -g1, -g2, _g1, _g2 etc. if [ -n "${TAP_GROUP}" ]; then if [ -f "${WORKSPACE}/test/tap/groups/${TAP_GROUP}/infras.lst" ]; then @@ -142,7 +142,6 @@ docker run \ # This runs before the test runner container is removed, allowing cleanup # of ProxySQL-specific configuration while admin is still accessible if [ -n "${TAP_GROUP}" ]; then - BASE_GROUP="${TAP_GROUP%%-g[0-9]*}" PRE_CLEANUP_HOOK="${WORKSPACE}/test/tap/groups/${TAP_GROUP}/pre-cleanup.bash" if [ ! -f "${PRE_CLEANUP_HOOK}" ]; then PRE_CLEANUP_HOOK="${WORKSPACE}/test/tap/groups/${BASE_GROUP}/pre-cleanup.bash" diff --git a/test/tap/groups/ai-g1/env.sh b/test/tap/groups/ai-g1/env.sh index 1ea85268a6..04635ab7a4 100644 --- a/test/tap/groups/ai-g1/env.sh +++ b/test/tap/groups/ai-g1/env.sh @@ -1,3 +1,4 @@ +# shellcheck shell=bash # AI-g1 Subgroup Environment Configuration # Sources the parent ai group configuration diff --git a/test/tap/groups/ai/README.md b/test/tap/groups/ai/README.md index ec54af5267..a5f8dc8cd7 100644 --- a/test/tap/groups/ai/README.md +++ b/test/tap/groups/ai/README.md @@ -93,6 +93,7 @@ source test/infra/common/env.sh export INFRA_ID="ai-harvest-$(date +%s)" export TAP_GROUP="ai-g1" export TEST_PY_TAP_INCL="test_mcp_static_harvest-t" +export WORKSPACE=$(pwd) source test/infra/common/env.sh ./test/infra/control/ensure-infras.bash @@ -106,6 +107,7 @@ source test/infra/common/env.sh export INFRA_ID="ai-discovery-$(date +%s)" export TAP_GROUP="ai-g1" export TEST_PY_TAP_INCL="test_mcp_llm_discovery_phaseb-t" +export WORKSPACE=$(pwd) source test/infra/common/env.sh ./test/infra/control/ensure-infras.bash @@ -119,6 +121,7 @@ source test/infra/common/env.sh export INFRA_ID="ai-claude-$(date +%s)" export TAP_GROUP="ai-g1" export TEST_PY_TAP_INCL="test_mcp_claude_headless_flow-t" +export WORKSPACE=$(pwd) source test/infra/common/env.sh ./test/infra/control/ensure-infras.bash diff --git a/test/tap/groups/ai/env.sh b/test/tap/groups/ai/env.sh index de39e72655..52b6152e91 100644 --- a/test/tap/groups/ai/env.sh +++ b/test/tap/groups/ai/env.sh @@ -1,3 +1,4 @@ +# shellcheck shell=bash # AI TAP Group Environment Configuration # Defines the primary targets for AI/MCP tests using standard test/infra/ pattern diff --git a/test/tap/groups/ai/mcp-config.sql b/test/tap/groups/ai/mcp-config.sql index 87c651a197..f106b302ec 100644 --- a/test/tap/groups/ai/mcp-config.sql +++ b/test/tap/groups/ai/mcp-config.sql @@ -26,9 +26,9 @@ LOAD PGSQL SERVERS TO RUNTIME; SAVE PGSQL SERVERS TO DISK; -- Configure frontend users for TAP tests -INSERT OR IGNORE INTO mysql_users (username, password, active, default_hostgroup, comment) +INSERT OR REPLACE INTO mysql_users (username, password, active, default_hostgroup, comment) VALUES ('${TAP_MYSQLUSERNAME}', '${TAP_MYSQLPASSWORD}', 1, 0, 'ai local'); -INSERT OR IGNORE INTO mysql_users (username, password, active, default_hostgroup, comment) +INSERT OR REPLACE INTO mysql_users (username, password, active, default_hostgroup, comment) VALUES ('testuser', 'testuser', 1, 0, 'ai local'); LOAD MYSQL USERS TO RUNTIME; SAVE MYSQL USERS TO DISK; diff --git a/test/tap/groups/ai/seed-mysql.sql b/test/tap/groups/ai/seed-mysql.sql index 6c2e1791e2..3425631974 100644 --- a/test/tap/groups/ai/seed-mysql.sql +++ b/test/tap/groups/ai/seed-mysql.sql @@ -1,6 +1,6 @@ -- AI Group MySQL Test Data Seeding -- Creates tables needed by AI/MCP tests --- This is executed by docker-mysql-post.bash when TAP_GROUP starts with 'ai' +-- This is executed by setup-infras.bash as part of AI group infrastructure setup CREATE DATABASE IF NOT EXISTS test; USE test; diff --git a/test/tap/groups/ai/seed-pgsql.sql b/test/tap/groups/ai/seed-pgsql.sql index 7208c9a472..cf14fedad4 100644 --- a/test/tap/groups/ai/seed-pgsql.sql +++ b/test/tap/groups/ai/seed-pgsql.sql @@ -1,6 +1,6 @@ -- AI Group PostgreSQL Test Data Seeding -- Creates tables needed by AI/MCP tests --- This is executed by docker-pgsql-post.bash when TAP_GROUP starts with 'ai' +-- This is executed by setup-infras.bash as part of AI group infrastructure setup CREATE TABLE IF NOT EXISTS public.tap_pgsql_static_accounts ( account_id INT PRIMARY KEY, diff --git a/test/tap/groups/ai/setup-infras.bash b/test/tap/groups/ai/setup-infras.bash index 1daa6aecb7..4bcf43096f 100755 --- a/test/tap/groups/ai/setup-infras.bash +++ b/test/tap/groups/ai/setup-infras.bash @@ -34,6 +34,17 @@ if [ -z "${DEFAULT_MYSQL_INFRA:-}" ] || [ -z "${DEFAULT_PGSQL_INFRA:-}" ]; then fi fi +# Validate that required infrastructure names were resolved +if [ -z "${DEFAULT_MYSQL_INFRA:-}" ]; then + echo "ERROR: Could not resolve DEFAULT_MYSQL_INFRA (check ${SCRIPT_DIR}/infras.lst)" + exit 1 +fi + +if [ -z "${DEFAULT_PGSQL_INFRA:-}" ]; then + echo "ERROR: Could not resolve DEFAULT_PGSQL_INFRA (check ${SCRIPT_DIR}/infras.lst)" + exit 1 +fi + export ROOT_PASSWORD=$(echo -n "${INFRA_ID}" | sha256sum | head -c 10) PROXY_CONTAINER="proxysql.${INFRA_ID}" From 679d758e243ee1b11310cf58bbd930631799d744 Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Fri, 20 Mar 2026 11:45:05 +0000 Subject: [PATCH 3/3] Fix ai-g1 env.sh to be POSIX sh compatible The Python tester sources env files using /bin/sh, but the previous implementation used bash-specific features (BASH_SOURCE[0] and source). Changed to inline the values from ai/env.sh for POSIX compatibility. --- test/tap/groups/ai-g1/env.sh | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/test/tap/groups/ai-g1/env.sh b/test/tap/groups/ai-g1/env.sh index 04635ab7a4..604c8ed850 100644 --- a/test/tap/groups/ai-g1/env.sh +++ b/test/tap/groups/ai-g1/env.sh @@ -1,6 +1,16 @@ -# shellcheck shell=bash # AI-g1 Subgroup Environment Configuration -# Sources the parent ai group configuration +# Inherits from parent ai group - duplicated here for POSIX sh compatibility -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source "${SCRIPT_DIR}/../ai/env.sh" +export DEFAULT_MYSQL_INFRA="infra-mysql84" +export DEFAULT_PGSQL_INFRA="docker-pgsql16-single" + +export TAP_MCPPORT="${TAP_MCPPORT:-6071}" +export MCP_TARGET_ID="${MCP_TARGET_ID:-tap_mysql_default}" +export MCP_AUTH_PROFILE_ID="${MCP_AUTH_PROFILE_ID:-tap_mysql_auth}" +export MCP_PGSQL_TARGET_ID="${MCP_PGSQL_TARGET_ID:-tap_pgsql_default}" +export MCP_PGSQL_AUTH_PROFILE_ID="${MCP_PGSQL_AUTH_PROFILE_ID:-tap_pgsql_auth}" +export MCP_MYSQL_HOSTGROUP_ID="${MCP_MYSQL_HOSTGROUP_ID:-9100}" +export MCP_PGSQL_HOSTGROUP_ID="${MCP_PGSQL_HOSTGROUP_ID:-9200}" + +export MYSQL_DATABASE="${MYSQL_DATABASE:-test}" +export PGSQL_DATABASE="${PGSQL_DATABASE:-postgres}"