feat(dev): add a local enterprise test server and a CLI sweep of its qbits

scripts/enterprise-server.sh builds and runs qqq-all's enterprise profile
(branch feat/enterprise-terminal-test in .worktrees/qqq-all-enterprise)
on port 8078 against the UI API 1.9.0 fix-branch build: MOCK auth, every
locally runnable qbit, the qqq sample app, Redis and OpenSearch in Docker
for redis and quick search. --status and --stop only touch the pid it
started and the two containers it names; it never touches the 8077 sample
server.

scripts/enterprise-smoke.sh runs the CLI through each qbit surface (ESB,
alerts, assistant, collaboration, data analysis, DB performance, QA
capture, quick search, release notes, secured data, SQL workbench, jobs,
audits, saved views and reports, exports, webhooks, processes) and records
exit codes, so client and server gaps show as non-zero lines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
james.maesandClaude Fable 5.1 committed 2026-11-01 07:01:16 -06:00
1 parent 3ccb9731b2
commit cf0e610bc1
2 files changed
+232

No files matched your search

+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env bash
# Start, stop or inspect the local "enterprise" QQQ test server: qqq-all's enterprise profile
# (every locally runnable qbit + the qqq sample app) on port 8078, MOCK auth (any user, admin).
#
# enterprise-server.sh [--start] | --status | --stop | --build | --log
#
# It only ever signals the pid it started (pid file in the state dir) and the two Docker
# containers it names (qqq-enterprise-redis, qqq-enterprise-opensearch). It never touches the
# 8077 sample server.
#
# Env:
# QQQ_ALL_ENTERPRISE_DIR qqq-all checkout on feat/enterprise-terminal-test
# (default ../.worktrees/qqq-all-enterprise next to this repo's parent)
# QQQ_ENTERPRISE_M2 Maven local repo holding the qqq build to run against
# (default ../.worktrees/qqq-fixes-m2: UI API 1.9.0 fix branch)
# QQQ_SAMPLE_CLASSES compiled qqq-sample-project classes to merge in
# (default ../.worktrees/qqq-fixes/qqq-sample-project/target/classes;
# missing = the sample app is left out)
# QQQ_ENTERPRISE_STATE pid, log and data directory (default ${TMPDIR:-/tmp}/qqq-enterprise-8078)
# QQQ_ENTERPRISE_PORT HTTP port (default 8078)
# QQQ_ENTERPRISE_DOCKER 0 = do not start Redis/OpenSearch containers (quick search and redis are
# then left out of the instance)
# QQQ_ALL_ENTERPRISE_EXCLUDE comma-separated feature names to leave out (see EnterpriseFeatures)
set -euo pipefail
self="${BASH_SOURCE[0]}"; while [[ -L "$self" ]]; do self="$(readlink "$self")"; done
here="$(cd "$(dirname "$self")" && pwd)"
repo_root="$(cd "$here/.." && pwd)"
# worktrees live in <QRun-IO>/.worktrees; this repo is either <QRun-IO>/qqq-cli or <QRun-IO>/.worktrees/<name>
qrun_root="$(cd "$repo_root/.." && pwd)"; [[ "$(basename "$qrun_root")" == ".worktrees" ]] && qrun_root="$(cd "$qrun_root/.." && pwd)"
qqq_all="${QQQ_ALL_ENTERPRISE_DIR:-$qrun_root/.worktrees/qqq-all-enterprise}"
m2="${QQQ_ENTERPRISE_M2:-$qrun_root/.worktrees/qqq-fixes-m2}"
sample_classes="${QQQ_SAMPLE_CLASSES:-$qrun_root/.worktrees/qqq-fixes/qqq-sample-project/target/classes}"
port="${QQQ_ENTERPRISE_PORT:-8078}"
tmp_root="${TMPDIR:-/tmp}"; state_dir="${QQQ_ENTERPRISE_STATE:-${tmp_root%/}/qqq-enterprise-$port}"
state_dir="${state_dir%/}"
pid_file="$state_dir/server.pid"
log_file="$state_dir/server.log"
data_dir="$state_dir/data"
jar="$qqq_all/qqq-all-app/target/qqq-all-app.jar"
redis_name="qqq-enterprise-redis"; redis_port=6390
search_name="qqq-enterprise-opensearch"; search_port=9210
mkdir -p "$state_dir"
running() { [[ -f "$pid_file" ]] && kill -0 "$(cat "$pid_file")" 2>/dev/null; }
use_docker() { [[ "${QQQ_ENTERPRISE_DOCKER:-1}" != "0" ]] && command -v docker > /dev/null && docker info > /dev/null 2>&1; }
container_up() # name image port container-port [docker args...]
{
local name="$1" image="$2" host_port="$3" container_port="$4"; shift 4
if [[ "$(docker inspect -f '{{.State.Running}}' "$name" 2>/dev/null)" == "true" ]]; then return 0; fi
if docker inspect "$name" > /dev/null 2>&1; then docker start "$name" > /dev/null; return 0; fi
docker run -d --name "$name" -p "127.0.0.1:$host_port:$container_port" "$@" "$image" > /dev/null
}
build()
{
[[ -f "$qqq_all/pom.xml" ]] || { echo "no qqq-all checkout at $qqq_all (set QQQ_ALL_ENTERPRISE_DIR)"; exit 1; }
echo "building qqq-all-app (enterprise profile) against $m2 …"
mvn -B -q -f "$qqq_all/pom.xml" -Dmaven.repo.local="$m2" -DskipTests -pl qqq-all-app -am package
echo "built $jar"
}
case "${1:---start}" in
--build|build) build ;;
--start|start)
if running; then echo "already running (pid $(cat "$pid_file")) on http://localhost:$port"; exit 0; fi
if curl -sf -m 2 "http://localhost:$port/qqq/v1/version" > /dev/null 2>&1; then
echo "port $port is already serving something that this script did not start; not starting"; exit 1
fi
[[ -f "$jar" ]] || build
env_args=(QQQ_ALL_PROFILE=enterprise QQQ_ALL_PORT="$port" QQQ_ALL_DATA_DIR="$data_dir")
if use_docker; then
echo "starting Redis ($redis_name, :$redis_port) and OpenSearch ($search_name, :$search_port) containers"
container_up "$redis_name" redis:7.4-alpine "$redis_port" 6379
container_up "$search_name" opensearchproject/opensearch:2.19.1 "$search_port" 9200 \
-e discovery.type=single-node -e DISABLE_SECURITY_PLUGIN=true -e "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
for _ in $(seq 1 60); do curl -sf -m 2 "http://127.0.0.1:$search_port/_cluster/health" > /dev/null 2>&1 && break; sleep 2; done
env_args+=(QQQ_ALL_OPENSEARCH_HOST=127.0.0.1 QQQ_ALL_OPENSEARCH_PORT="$search_port"
QQQ_ALL_REDIS_HOST=127.0.0.1 QQQ_ALL_REDIS_PORT="$redis_port")
else
echo "Docker not used: quick search and redis are left out"
fi
classpath="$jar"
if [[ -d "$sample_classes" ]]; then classpath="$jar:$sample_classes"; else echo "no sample classes at $sample_classes: sample app left out"; fi
echo "starting enterprise server on port $port (log: $log_file, data: $data_dir)"
nohup env "${env_args[@]}" java -cp "$classpath" com.kingsrook.qqq.all.ProfileLauncher > "$log_file" 2>&1 &
echo $! > "$pid_file"
for _ in $(seq 1 90); do
if curl -sf -m 2 "http://localhost:$port/qqq/v1/version" > /dev/null 2>&1; then
echo "ready: http://localhost:$port (UI API $(curl -s "http://localhost:$port/qqq/v1/version" | sed 's/.*"apiVersion":"\([^"]*\)".*/\1/'))"
echo "login: qqq login --server http://localhost:$port (mock auth: any user is an all-access admin)"
exit 0
fi
if ! running; then echo "server exited early; see $log_file"; tail -20 "$log_file"; rm -f "$pid_file"; exit 1; fi
sleep 2
done
echo "timed out waiting for the server; see $log_file"; exit 1
;;
--stop|stop)
if running; then
kill "$(cat "$pid_file")" 2>/dev/null || true
for _ in $(seq 1 15); do running || break; sleep 1; done
running && kill -9 "$(cat "$pid_file")" 2>/dev/null || true
echo "stopped pid $(cat "$pid_file")"
else
echo "not running (no live pid in $pid_file)"
fi
rm -f "$pid_file"
if use_docker; then docker stop "$redis_name" "$search_name" > /dev/null 2>&1 && echo "stopped containers $redis_name $search_name" || true; fi
;;
--status|status)
if running; then
echo "running: pid $(cat "$pid_file") on http://localhost:$port"
curl -s -m 3 "http://localhost:$port/qqq/v1/version" | python3 -c 'import json,sys; d=json.load(sys.stdin); print("UI API", d["apiVersion"]); print("extensions:", ", ".join(e["name"]+" "+e.get("version","?") for e in d.get("extensions",[])))' 2>/dev/null || echo "(not answering on /qqq/v1/version)"
else
echo "not running"
fi
echo "log: $log_file"
;;
--log|log) tail -n 100 -f "$log_file" ;;
*) echo "usage: $0 [--start] | --status | --stop | --build | --log"; exit 2 ;;
esac
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# CLI sweep of every qbit surface on the enterprise test server (scripts/enterprise-server.sh).
# Runs each command, records its exit code and the first lines of output; non-zero lines are the gaps.
# scripts/enterprise-smoke.sh QQQ="qqq --profile enterprise" OUT=file N=8
# Writes a few records (an audit probe customer, a comment, a follow, a saved view) on the test server.
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
QQQ_CMD=(${QQQ:-node $here/../packages/cli/dist/main.js})
OUT=${OUT:-${TMPDIR:-/tmp}/qqq-enterprise-smoke.txt}; : > "$OUT"
DL=${DL:-$(mktemp -d)}
run() { local label="$1"; shift; local o; o=$("${QQQ_CMD[@]}" "$@" 2>&1); local rc=$?; { echo "### [$rc] $label :: qqq $*"; echo "$o" | head -${N:-8} | cut -c1-240; } >> "$OUT"; }
run whoami whoami
run "apps list" apps list -O text
run home home -O json
run "process list count" process list -O json -Q 'length(@)'
run "widget list count" widget list -O json -Q 'length(@)'
run "find customer" find customer
run "search (global)" search Chicago
run "doctor" doctor
# ESB
run "esb overview" esb overview
run "esb table order" esb table order
run "esb process syncOrder" esb process syncOrder
run "esb messages" esb messages orderEvents --trigger syncOrder.orderEvents
# health (not installed on 8078)
run "health summary" health get-summary
# alerts
run "alerts inbox" alerts get-inbox
run "alerts summary" alerts get-inbox-summary
run "alerts record panel" alerts get-record-panel -q tableName=customer recordId=1
# assistant
run "assistant config" assistant get-config
run "assistant conversations" assistant get-conversations
run "assistant access" assistant get-access
# collaboration
run "collab presence table" collab presence customer
run "collab presence record" collab presence customer 1
run "collab favorites" collab favorites list
run "collab recents" collab recents
run "collab people" collab people admin
run "collab my-work" collab my-work
run "collab comments list" collab comments list customer 1
run "collab comments add" collab comments add customer 1 "enterprise smoke comment"
run "collab tags record" collab tags list customer 1
run "collab follow" collab follow customer 1
run "collab timeline" collab get-records-timeline customer 1
run "collab attachments" collab get-records-attachments customer 1
run "collab share preview" collab post-records-shares-preview customer 1 -b '{"toUserIds":["core:admin"],"message":"look"}'
# data analysis
run "da capabilities customer" data-analysis get-tables-capabilities customer
run "da table profile customer" data-analysis post-tables-table-profile customer -b '{}'
run "da column profile customer.name" data-analysis post-tables-column-profile customer -b '{"fieldName":"name"}'
run "da distinct" data-analysis post-tables-distinct-values order -b '{"fieldName":"status"}'
run "da saved" data-analysis get-saved
# db performance
run "dbperf overview" db-performance get-overview
run "dbperf activity" db-performance get-activity
# qa capture
run "qa capability" qa-capture get-capability
# quick search
run "qs status" quick-search get-status
run "qs suggest" quick-search get-suggest -q q=a
run "table query --search" table query customer --search a
# release notes
run "rn releases" release-notes get-releases
run "rn summary" release-notes get-summary
# secured data
run "sd status" secured-data get-status
run "sd reveal options" secured-data get-reveal-options -q tableName=customer fieldName=email
# sql workbench
run "sqlwb capabilities" sql-workbench get-capabilities
run "sqlwb schema h2" sql-workbench get-schema h2
run "sqlwb execute" sql-workbench post-execute -b '{"backendName":"h2","sql":"select id, name from customer"}'
run "sqlwb history" sql-workbench get-history
run "sqlwb notebooks" sql-workbench get-notebooks
# jobs
run "job list" job list
run "job list --all" job list --all
# reports / saved reports / views / bulk profiles
run "report list" report list
run "saved-report list" saved-report list
run "saved-view list" saved-view list customer
run "bulk-profile list" bulk-profile list customer
# prefs, help
run "prefs list" prefs list
run "help-search" help-search order
# webhooks (tables)
run "webhook query" table query webhook
run "webhook subscription query" table query webhookSubscription
run "webhook actions" table actions webhook
# scripts
run "script tables" table query script --limit 3
# exports
run "export customer csv" table export customer --format csv --out $DL/customer.csv
run "export order xlsx" table export order --format xlsx --out $DL/order.xlsx
run "export person json" table export person --format json --out $DL/person.json
run "qs reindex process" process run quickSearchFullReindex --yes
run "qs suggest after reindex" quick-search get-suggest -q q=ada
run "search after reindex" search ada
run "audit create" table create customer --set name="Audit Probe" --set email=probe@example.test
run "audit update" table update customer 1 --set email=ada2@example.test
run "audit list" audit list customer 1
run "saved view save" saved-view save customer -n "Enterprise smoke" -w "name ~ a" --replace
run "saved view list" saved-view list customer
run "process run calculateOrderTotal" process run calculateOrderTotal -v quantity=3 unitPriceCents=1250
run "process run greet" process run greet -r 1,2 --yes
run "process describe syncOrder" process describe syncOrder
run "webhook test event" process run SendTestEventToWebhook -r 1 --yes
run "widget get first" widget list -O json -Q '[0:5].name'
echo "done: $(grep -c "^### \[0\]" "$OUT") passed, $(grep -c "^### \[[1-9]" "$OUT") non-zero; $OUT"