Skip to content

Commit 7dd9d62

Browse files
feat: add acme_erp multi-schema fixture for policy testing
Adds seeded Postgres database with crm/sales/finance/inventory/hr/marts schemas, duplicate bare table names for collision testing, and a seed script to register the ACME ERP connection. Co-authored-by: Venkat SF <venkatesh.sakamuri@stayflexi.com>
1 parent 611c199 commit 7dd9d62

3 files changed

Lines changed: 243 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,11 @@ only covers cloud-specific, non-obvious caveats.
186186
`HERMES_WEBUI_ALLOWED_ORIGINS` (upstream env name).
187187
- A demo target DB `demo_shop` (same Postgres server, sample `customers`/`products`/`orders`)
188188
exists for exercising connection/schema features without an external database.
189+
- A multi-schema fixture DB `acme_erp` (schemas: `crm`, `sales`, `finance`, `inventory`,
190+
`hr`, `marts`) exists for chat-access-policy and multi-schema tests. Seed with:
191+
`sudo -u postgres psql -f docker/postgres/init/11_create_acme_erp.sql` then
192+
`bash scripts/seed-acme-erp.sh` (registers `ACME ERP (Multi-Schema)` when backend auth
193+
is disabled or you have an admin session cookie).
189194

190195
### Non-obvious setup caveats (each cost real debugging time)
191196

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
-- ACME ERP — multi-schema fixture for Brain, MCP, and chat-access-policy tests.
2+
-- Schemas: crm, sales, finance, inventory, hr, marts (analytics mart).
3+
-- Duplicate bare table names (customers, orders) across crm/sales on purpose.
4+
5+
SELECT 'Creating acme_erp multi-schema database' AS status;
6+
7+
DROP DATABASE IF EXISTS acme_erp;
8+
CREATE DATABASE acme_erp;
9+
10+
\connect acme_erp
11+
12+
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
13+
14+
CREATE SCHEMA crm;
15+
CREATE SCHEMA sales;
16+
CREATE SCHEMA finance;
17+
CREATE SCHEMA inventory;
18+
CREATE SCHEMA hr;
19+
CREATE SCHEMA marts;
20+
21+
-- CRM
22+
CREATE TABLE crm.customers (
23+
id SERIAL PRIMARY KEY,
24+
name TEXT NOT NULL,
25+
email TEXT,
26+
amount NUMERIC(12, 2) DEFAULT 0,
27+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
28+
);
29+
30+
CREATE TABLE crm.accounts (
31+
id SERIAL PRIMARY KEY,
32+
customer_id INT REFERENCES crm.customers(id),
33+
balance NUMERIC(14, 2) NOT NULL DEFAULT 0
34+
);
35+
36+
-- Sales (same bare names as crm)
37+
CREATE TABLE sales.customers (
38+
id SERIAL PRIMARY KEY,
39+
name TEXT NOT NULL,
40+
email TEXT,
41+
revenue NUMERIC(12, 2) DEFAULT 0
42+
);
43+
44+
CREATE TABLE sales.orders (
45+
id SERIAL PRIMARY KEY,
46+
customer_id INT REFERENCES sales.customers(id),
47+
amount NUMERIC(12, 2) NOT NULL,
48+
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
49+
status TEXT NOT NULL DEFAULT 'open',
50+
ordered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
51+
);
52+
53+
-- Finance
54+
CREATE TABLE finance.ledger (
55+
id SERIAL PRIMARY KEY,
56+
account_code TEXT NOT NULL,
57+
amount NUMERIC(14, 2) NOT NULL,
58+
currency VARCHAR(3) NOT NULL DEFAULT 'USD',
59+
posted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
60+
);
61+
62+
-- Inventory
63+
CREATE TABLE inventory.products (
64+
id SERIAL PRIMARY KEY,
65+
sku TEXT UNIQUE NOT NULL,
66+
name TEXT NOT NULL,
67+
stock_qty INT NOT NULL DEFAULT 0,
68+
unit_cost NUMERIC(10, 2)
69+
);
70+
71+
-- HR
72+
CREATE TABLE hr.employees (
73+
id SERIAL PRIMARY KEY,
74+
full_name TEXT NOT NULL,
75+
email TEXT,
76+
salary NUMERIC(12, 2),
77+
department TEXT
78+
);
79+
80+
-- Marts — policy-test schema (numeric amount + string currency, like production marts)
81+
CREATE TABLE marts.fct_enrollment (
82+
id SERIAL PRIMARY KEY,
83+
program_code TEXT NOT NULL,
84+
amount NUMERIC(12, 2) NOT NULL,
85+
currency VARCHAR(3) NOT NULL,
86+
enrolled_at DATE NOT NULL
87+
);
88+
89+
CREATE TABLE marts.dim_ott_subscription (
90+
id SERIAL PRIMARY KEY,
91+
subscriber_id TEXT NOT NULL,
92+
amount NUMERIC(10, 2) NOT NULL,
93+
currency VARCHAR(3) NOT NULL,
94+
plan_name TEXT
95+
);
96+
97+
CREATE TABLE marts.rpt_revenue_monthly (
98+
month DATE NOT NULL,
99+
region TEXT NOT NULL,
100+
amount NUMERIC(14, 2) NOT NULL,
101+
currency VARCHAR(3) NOT NULL,
102+
PRIMARY KEY (month, region)
103+
);
104+
105+
-- Seed rows
106+
INSERT INTO crm.customers (name, email, amount) VALUES
107+
('Acme Corp', 'acme@example.com', 1200.50),
108+
('Globex', 'globex@example.com', 800.00);
109+
110+
INSERT INTO crm.accounts (customer_id, balance) VALUES (1, 500.00), (2, 250.75);
111+
112+
INSERT INTO sales.customers (name, email, revenue) VALUES
113+
('Retail One', 'r1@example.com', 4200.00),
114+
('Retail Two', 'r2@example.com', 3100.25);
115+
116+
INSERT INTO sales.orders (customer_id, amount, currency, status) VALUES
117+
(1, 199.99, 'USD', 'shipped'),
118+
(2, 89.50, 'EUR', 'open');
119+
120+
INSERT INTO finance.ledger (account_code, amount, currency) VALUES
121+
('CASH', 10000.00, 'USD'),
122+
('AR', 2500.00, 'USD');
123+
124+
INSERT INTO inventory.products (sku, name, stock_qty, unit_cost) VALUES
125+
('SKU-001', 'Widget', 120, 4.50),
126+
('SKU-002', 'Gadget', 45, 12.00);
127+
128+
INSERT INTO hr.employees (full_name, email, salary, department) VALUES
129+
('Jane Doe', 'jane@acme.com', 95000.00, 'Engineering'),
130+
('John Smith', 'john@acme.com', 82000.00, 'Sales');
131+
132+
INSERT INTO marts.fct_enrollment (program_code, amount, currency, enrolled_at) VALUES
133+
('IEO', 150.00, 'USD', CURRENT_DATE - 30),
134+
('OTT', 9.99, 'INR', CURRENT_DATE - 7);
135+
136+
INSERT INTO marts.dim_ott_subscription (subscriber_id, amount, currency, plan_name) VALUES
137+
('sub-100', 12.99, 'USD', 'Premium'),
138+
('sub-200', 499.00, 'INR', 'Annual');
139+
140+
INSERT INTO marts.rpt_revenue_monthly (month, region, amount, currency) VALUES
141+
(DATE_TRUNC('month', CURRENT_DATE)::date, 'NA', 125000.00, 'USD'),
142+
(DATE_TRUNC('month', CURRENT_DATE)::date, 'APAC', 98000.00, 'INR');

scripts/seed-acme-erp.sh

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/env bash
2+
# seed-acme-erp.sh — Create the acme_erp multi-schema Postgres DB and register a DeepSQL connection.
3+
#
4+
# Native (non-Docker) cloud VM usage:
5+
# sudo -u postgres psql -f docker/postgres/init/11_create_acme_erp.sql
6+
# bash scripts/seed-acme-erp.sh
7+
#
8+
# Requires backend on :8080 with dev auth bypass (SECURITY_AUTH_ENABLED=false).
9+
10+
set -euo pipefail
11+
12+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13+
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
14+
ENV_FILE="${DEEPSQL_ENV_FILE:-$ROOT_DIR/.env}"
15+
SQL_FILE="$ROOT_DIR/docker/postgres/init/11_create_acme_erp.sql"
16+
CONNECTION_NAME="${DEEPSQL_ACME_CONNECTION_NAME:-ACME ERP (Multi-Schema)}"
17+
18+
if [[ -f "$ENV_FILE" ]]; then
19+
set -a
20+
# shellcheck disable=SC1090
21+
source "$ENV_FILE"
22+
set +a
23+
fi
24+
25+
: "${DEEPSQL_BACKEND_PORT:=8080}"
26+
: "${DB_PASSWORD:=postgres}"
27+
28+
base="http://127.0.0.1:${DEEPSQL_BACKEND_PORT}/api"
29+
30+
echo "=========================================="
31+
echo "ACME ERP multi-schema fixture"
32+
echo "=========================================="
33+
34+
if [[ ! -f "$SQL_FILE" ]]; then
35+
echo "Missing SQL file: $SQL_FILE" >&2
36+
exit 1
37+
fi
38+
39+
echo ""
40+
echo "Step 1: Applying $SQL_FILE ..."
41+
if command -v pg_ctlcluster >/dev/null 2>&1; then
42+
sudo pg_ctlcluster 16 main status >/dev/null 2>&1 || sudo pg_ctlcluster 16 main start
43+
fi
44+
sudo -u postgres psql -v ON_ERROR_STOP=1 -f "$SQL_FILE"
45+
46+
echo ""
47+
echo "Step 2: Registering DeepSQL connection '$CONNECTION_NAME' ..."
48+
49+
existing="$(curl -fsS "$base/connections" 2>/dev/null || echo '[]')"
50+
connection_id="$(printf '%s' "$existing" | python3 -c "
51+
import json, sys, os
52+
name = os.environ.get('CONNECTION_NAME', '')
53+
try:
54+
data = json.load(sys.stdin)
55+
except Exception:
56+
data = []
57+
items = data if isinstance(data, list) else data.get('connections') or data.get('data') or []
58+
for conn in items:
59+
if conn.get('connectionName') == name:
60+
print(conn.get('id') or conn.get('connectionId') or '')
61+
break
62+
" CONNECTION_NAME="$CONNECTION_NAME")"
63+
64+
if [[ -n "$connection_id" ]]; then
65+
echo " Connection already exists: $connection_id"
66+
else
67+
payload="$(cat <<EOF
68+
{
69+
"connectionName": "$CONNECTION_NAME",
70+
"dbType": "postgres",
71+
"host": "127.0.0.1",
72+
"port": 5432,
73+
"database": "acme_erp",
74+
"username": "postgres",
75+
"password": "$DB_PASSWORD",
76+
"sslEnabled": false
77+
}
78+
EOF
79+
)"
80+
save_json="$(curl -sS -w '\n%{http_code}' -H 'Content-Type: application/json' -X POST "$base/connections" -d "$payload" || true)"
81+
http_code="$(printf '%s' "$save_json" | tail -n1)"
82+
body="$(printf '%s' "$save_json" | sed '$d')"
83+
connection_id="$(printf '%s' "$body" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('connectionId') or d.get('id') or '')" 2>/dev/null || true)"
84+
if [[ -z "$connection_id" ]]; then
85+
echo " Warning: could not create connection (HTTP ${http_code:-?}). Body: $body" >&2
86+
else
87+
echo " Created connection: $connection_id"
88+
fi
89+
fi
90+
91+
echo ""
92+
echo "Schemas in acme_erp:"
93+
sudo -u postgres psql -d acme_erp -At -c "SELECT nspname FROM pg_namespace WHERE nspname NOT LIKE 'pg_%' AND nspname <> 'information_schema' ORDER BY 1;"
94+
95+
echo ""
96+
echo "Done. Use connection '$CONNECTION_NAME'${connection_id:+ (id=$connection_id)} for multi-schema / policy tests."

0 commit comments

Comments
 (0)