mirror of https://github.com/sysown/proxysql
Implement phase-A (static harvesting) TAP coverage for MCP multi-target discovery by seeding deterministic schemas on both MySQL and PostgreSQL and validating discovery/catalog behavior per target_id.
What this commit adds:
- AI group deterministic seed datasets
- Added test/tap/groups/ai/mysql-seed.sql with:
- tap_mysql_static_customers
- tap_mysql_static_orders (FK to customers)
- Added test/tap/groups/ai/pgsql-seed.sql with:
- tap_pgsql_static_accounts
- tap_pgsql_static_events (FK to accounts)
- pre-proxysql hook integration
- Updated test/tap/groups/ai/pre-proxysql.bash to seed both backends after container startup:
- seed_mysql_test_data() executes mysql-seed.sql via mysql CLI
- seed_pgsql_test_data() executes pgsql-seed.sql via psql (or docker compose exec fallback)
- Existing monitor-user/profile setup is preserved
- New TAP test: test/tap/tests/test_mcp_static_harvest-t.sh
- Validates MCP/ProxySQL reachability
- Validates list_targets exposes both mysql and pgsql target_id entries
- Runs discovery.run_static for MySQL target_id and validates run_id + protocol=mysql
- Validates catalog.list_objects returns seeded MySQL table for that run
- Runs discovery.run_static for PostgreSQL target_id and validates run_id + protocol=pgsql
- Validates catalog.list_objects returns seeded PostgreSQL table for that run
- Validates run isolation across targets (cross-target run_id lookup fails as expected)
- Documentation update
- Updated test/tap/groups/ai/README.md with seeded-table details and manual run instructions for the new static-harvest test
Notes:
- This commit focuses strictly on phase-A static harvesting, as requested.
- Phase-B (LLM-driven discovery) tests are intentionally not included here.
pull/5386/head
parent
9685cdaa4b
commit
2538e303cf
@ -0,0 +1,27 @@
|
||||
CREATE DATABASE IF NOT EXISTS testdb;
|
||||
USE testdb;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tap_mysql_static_customers (
|
||||
customer_id INT PRIMARY KEY,
|
||||
email VARCHAR(128) NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tap_mysql_static_orders (
|
||||
order_id INT PRIMARY KEY,
|
||||
customer_id INT NOT NULL,
|
||||
total_amount DECIMAL(10,2) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT fk_tap_mysql_orders_customer
|
||||
FOREIGN KEY (customer_id) REFERENCES tap_mysql_static_customers(customer_id)
|
||||
);
|
||||
|
||||
INSERT INTO tap_mysql_static_customers(customer_id, email) VALUES
|
||||
(1, 'seed-mysql-a@example.com'),
|
||||
(2, 'seed-mysql-b@example.com')
|
||||
ON DUPLICATE KEY UPDATE email=VALUES(email);
|
||||
|
||||
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);
|
||||
@ -0,0 +1,24 @@
|
||||
CREATE TABLE IF NOT EXISTS public.tap_pgsql_static_accounts (
|
||||
account_id INT PRIMARY KEY,
|
||||
account_name TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS public.tap_pgsql_static_events (
|
||||
event_id INT PRIMARY KEY,
|
||||
account_id INT NOT NULL,
|
||||
event_type TEXT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT fk_tap_pgsql_events_account
|
||||
FOREIGN KEY (account_id) REFERENCES public.tap_pgsql_static_accounts(account_id)
|
||||
);
|
||||
|
||||
INSERT INTO public.tap_pgsql_static_accounts(account_id, account_name) VALUES
|
||||
(1, 'seed-pg-a'),
|
||||
(2, 'seed-pg-b')
|
||||
ON CONFLICT (account_id) DO UPDATE SET account_name=EXCLUDED.account_name;
|
||||
|
||||
INSERT INTO public.tap_pgsql_static_events(event_id, account_id, event_type) VALUES
|
||||
(201, 1, 'signup'),
|
||||
(202, 2, 'purchase')
|
||||
ON CONFLICT (event_id) DO UPDATE SET event_type=EXCLUDED.event_type;
|
||||
@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# test_mcp_static_harvest-t.sh
|
||||
#
|
||||
# TAP test for MCP static harvesting (phase A) across:
|
||||
# - MySQL target_id
|
||||
# - PostgreSQL target_id
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PLAN=8
|
||||
DONE=0
|
||||
FAIL=0
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
HELPERS="${SCRIPT_DIR}/mcp_rules_testing/mcp_test_helpers.sh"
|
||||
|
||||
if [[ ! -f "${HELPERS}" ]]; then
|
||||
echo "msg: 1..1"
|
||||
echo "msg: not ok 1 - missing helper ${HELPERS}"
|
||||
exit 1
|
||||
fi
|
||||
source "${HELPERS}"
|
||||
|
||||
MCP_MYSQL_TARGET_ID="${MCP_TARGET_ID:-tap_mysql_default}"
|
||||
MCP_PGSQL_TARGET_ID="${MCP_PGSQL_TARGET_ID:-tap_pgsql_default}"
|
||||
|
||||
MYSQL_SEEDED_TABLE="${MYSQL_SEEDED_TABLE:-tap_mysql_static_customers}"
|
||||
PGSQL_SEEDED_TABLE="${PGSQL_SEEDED_TABLE:-tap_pgsql_static_accounts}"
|
||||
|
||||
MYSQL_RUN_ID=""
|
||||
PGSQL_RUN_ID=""
|
||||
|
||||
tap_ok() {
|
||||
DONE=$((DONE + 1))
|
||||
echo "msg: ok ${DONE} - $1"
|
||||
}
|
||||
|
||||
tap_not_ok() {
|
||||
DONE=$((DONE + 1))
|
||||
FAIL=$((FAIL + 1))
|
||||
echo "msg: not ok ${DONE} - $1"
|
||||
if [[ $# -gt 1 ]]; then
|
||||
echo "msg: # $2"
|
||||
fi
|
||||
}
|
||||
|
||||
extract_run_id() {
|
||||
local payload="$1"
|
||||
echo "${payload}" | sed -n 's/.*"run_id"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -n1
|
||||
}
|
||||
|
||||
echo "msg: 1..${PLAN}"
|
||||
echo "msg: # MCP Static Harvest Test Suite"
|
||||
|
||||
if check_proxysql_admin; then
|
||||
tap_ok "ProxySQL admin reachable"
|
||||
else
|
||||
tap_not_ok "ProxySQL admin reachable"
|
||||
fi
|
||||
|
||||
if check_mcp_server; then
|
||||
tap_ok "MCP server reachable"
|
||||
else
|
||||
tap_not_ok "MCP server reachable"
|
||||
fi
|
||||
|
||||
targets_resp="$(mcp_request "query" '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"list_targets","arguments":{}},"id":1}')"
|
||||
if echo "${targets_resp}" | grep -q "\"target_id\":\"${MCP_MYSQL_TARGET_ID}\"" && \
|
||||
echo "${targets_resp}" | grep -q "\"target_id\":\"${MCP_PGSQL_TARGET_ID}\""; then
|
||||
tap_ok "list_targets contains mysql+pgsql target_id"
|
||||
else
|
||||
tap_not_ok "list_targets contains mysql+pgsql target_id" "${targets_resp}"
|
||||
fi
|
||||
|
||||
mysql_harvest_resp="$(mcp_request "query" "{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"discovery.run_static\",\"arguments\":{\"target_id\":\"${MCP_MYSQL_TARGET_ID}\",\"schema_filter\":\"${MYSQL_DATABASE}\",\"notes\":\"tap mysql static harvest\"}},\"id\":2}")"
|
||||
MYSQL_RUN_ID="$(extract_run_id "${mysql_harvest_resp}")"
|
||||
if [[ -n "${MYSQL_RUN_ID}" ]] && echo "${mysql_harvest_resp}" | grep -q "\"target_id\":\"${MCP_MYSQL_TARGET_ID}\"" && echo "${mysql_harvest_resp}" | grep -q "\"protocol\":\"mysql\""; then
|
||||
tap_ok "discovery.run_static mysql target returns run_id/protocol"
|
||||
else
|
||||
tap_not_ok "discovery.run_static mysql target returns run_id/protocol" "${mysql_harvest_resp}"
|
||||
fi
|
||||
|
||||
mysql_catalog_resp="$(mcp_request "query" "{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"catalog.list_objects\",\"arguments\":{\"target_id\":\"${MCP_MYSQL_TARGET_ID}\",\"run_id\":\"${MYSQL_RUN_ID}\",\"object_type\":\"table\",\"schema_name\":\"${MYSQL_DATABASE}\",\"page_size\":200}},\"id\":3}")"
|
||||
if echo "${mysql_catalog_resp}" | grep -q "\"object_name\":\"${MYSQL_SEEDED_TABLE}\""; then
|
||||
tap_ok "catalog.list_objects mysql run exposes seeded mysql table"
|
||||
else
|
||||
tap_not_ok "catalog.list_objects mysql run exposes seeded mysql table" "${mysql_catalog_resp}"
|
||||
fi
|
||||
|
||||
pgsql_harvest_resp="$(mcp_request "query" "{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"discovery.run_static\",\"arguments\":{\"target_id\":\"${MCP_PGSQL_TARGET_ID}\",\"schema_filter\":\"public\",\"notes\":\"tap pgsql static harvest\"}},\"id\":4}")"
|
||||
PGSQL_RUN_ID="$(extract_run_id "${pgsql_harvest_resp}")"
|
||||
if [[ -n "${PGSQL_RUN_ID}" ]] && echo "${pgsql_harvest_resp}" | grep -q "\"target_id\":\"${MCP_PGSQL_TARGET_ID}\"" && echo "${pgsql_harvest_resp}" | grep -q "\"protocol\":\"pgsql\""; then
|
||||
tap_ok "discovery.run_static pgsql target returns run_id/protocol"
|
||||
else
|
||||
tap_not_ok "discovery.run_static pgsql target returns run_id/protocol" "${pgsql_harvest_resp}"
|
||||
fi
|
||||
|
||||
pgsql_catalog_resp="$(mcp_request "query" "{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"catalog.list_objects\",\"arguments\":{\"target_id\":\"${MCP_PGSQL_TARGET_ID}\",\"run_id\":\"${PGSQL_RUN_ID}\",\"object_type\":\"table\",\"schema_name\":\"public\",\"page_size\":200}},\"id\":5}")"
|
||||
if echo "${pgsql_catalog_resp}" | grep -q "\"object_name\":\"${PGSQL_SEEDED_TABLE}\""; then
|
||||
tap_ok "catalog.list_objects pgsql run exposes seeded pgsql table"
|
||||
else
|
||||
tap_not_ok "catalog.list_objects pgsql run exposes seeded pgsql table" "${pgsql_catalog_resp}"
|
||||
fi
|
||||
|
||||
cross_target_resp="$(mcp_request "query" "{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{\"name\":\"catalog.list_objects\",\"arguments\":{\"target_id\":\"${MCP_PGSQL_TARGET_ID}\",\"run_id\":\"${MYSQL_RUN_ID}\",\"object_type\":\"table\",\"page_size\":10}},\"id\":6}")"
|
||||
if echo "${cross_target_resp}" | grep -q "\"error\"" && echo "${cross_target_resp}" | grep -q "target_id"; then
|
||||
tap_ok "catalog run_id cannot be resolved across target_id boundaries"
|
||||
else
|
||||
tap_not_ok "catalog run_id cannot be resolved across target_id boundaries" "${cross_target_resp}"
|
||||
fi
|
||||
|
||||
if [[ "${FAIL}" -ne 0 ]]; then
|
||||
echo "msg: # FAILURES=${FAIL}/${PLAN}"
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Loading…
Reference in new issue