feat(usable): package gui-host validation snapshot
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
95
scripts/qa/run-failure-path-evidence-sidecar.sh
Executable file
95
scripts/qa/run-failure-path-evidence-sidecar.sh
Executable file
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ] || [ "$#" -gt 2 ]; then
|
||||
echo "usage: scripts/qa/run-failure-path-evidence-sidecar.sh <binary-path> [output-dir]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
binary_path="$1"
|
||||
output_dir="${2:-./tmp/failure-path-evidence}"
|
||||
|
||||
if [ ! -x "$binary_path" ]; then
|
||||
echo "error: binary is not executable: $binary_path" >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
sidecar_image="${QA_SIDECAR_IMAGE:-paperclip-local-db-codex}"
|
||||
docker_network="${QA_DOCKER_NETWORK:-dbtool-cli-v1_default}"
|
||||
postgres_container="${QA_POSTGRES_CONTAINER:-dbtool-cli-v1-postgres-1}"
|
||||
mysql_container="${QA_MYSQL_CONTAINER:-dbtool-cli-v1-mysql-1}"
|
||||
postgres_user="${QA_POSTGRES_USER:-dbtool}"
|
||||
postgres_db="${QA_POSTGRES_DB:-dbtool_demo}"
|
||||
mysql_root_password="${QA_MYSQL_ROOT_PASSWORD:-dbtoolroot}"
|
||||
sidecar_root="/tmp/work"
|
||||
sidecar_output_dir="$sidecar_root/out"
|
||||
host_label="${QA_HOST_LABEL:-$(hostname 2>/dev/null || echo unknown-host)}"
|
||||
binary_label="${QA_BINARY_PATH_LABEL:-$binary_path}"
|
||||
binary_version="${QA_BINARY_VERSION:-$("$binary_path" --version 2>/dev/null | head -n 1 || echo unknown-version)}"
|
||||
commit_sha="$(git -C "$root_dir" rev-parse HEAD 2>/dev/null || echo unknown-commit)"
|
||||
branch_name="$(git -C "$root_dir" branch --show-current 2>/dev/null || echo unknown-branch)"
|
||||
|
||||
cid=""
|
||||
cleanup() {
|
||||
if [ -n "$cid" ]; then
|
||||
docker rm -f "$cid" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
docker image inspect "$sidecar_image" >/dev/null
|
||||
|
||||
docker exec -i "$postgres_container" \
|
||||
psql -v ON_ERROR_STOP=1 -U "$postgres_user" -d "$postgres_db" \
|
||||
< "$root_dir/examples/fixtures/postgres/init.sql" >/dev/null
|
||||
|
||||
docker exec -i "$mysql_container" \
|
||||
mysql --default-character-set=utf8mb4 -uroot "-p$mysql_root_password" \
|
||||
< "$root_dir/examples/fixtures/mysql/init.sql" >/dev/null
|
||||
|
||||
cid="$(docker create --network "$docker_network" "$sidecar_image" sleep 300)"
|
||||
docker start "$cid" >/dev/null
|
||||
|
||||
docker exec "$cid" mkdir -p \
|
||||
"$sidecar_root/examples/sql/postgres" \
|
||||
"$sidecar_root/examples/sql/mysql" \
|
||||
"$sidecar_root/scripts/qa"
|
||||
|
||||
docker cp "$binary_path" "$cid":"$sidecar_root/dbtool"
|
||||
docker cp "$root_dir/examples/sql/postgres/failing_query.sql" \
|
||||
"$cid":"$sidecar_root/examples/sql/postgres/failing_query.sql"
|
||||
docker cp "$root_dir/examples/sql/mysql/failing_query.sql" \
|
||||
"$cid":"$sidecar_root/examples/sql/mysql/failing_query.sql"
|
||||
docker cp "$root_dir/scripts/qa/run-failure-path-evidence.sh" \
|
||||
"$cid":"$sidecar_root/scripts/qa/run-failure-path-evidence.sh"
|
||||
|
||||
docker exec "$cid" chmod +x \
|
||||
"$sidecar_root/dbtool" \
|
||||
"$sidecar_root/scripts/qa/run-failure-path-evidence.sh"
|
||||
|
||||
docker exec \
|
||||
-e QA_OPERATOR="${QA_OPERATOR:-qa-engineer}" \
|
||||
-e QA_HOST_LABEL="$host_label" \
|
||||
-e QA_BINARY_PATH_LABEL="$binary_label" \
|
||||
-e QA_BINARY_VERSION="$binary_version" \
|
||||
-e QA_EXECUTION_PATH=sidecar \
|
||||
-e QA_COMMIT_SHA="$commit_sha" \
|
||||
-e QA_BRANCH_NAME="$branch_name" \
|
||||
-e DBTOOL_POSTGRES_HOST=postgres \
|
||||
-e DBTOOL_POSTGRES_PORT=5432 \
|
||||
-e DBTOOL_POSTGRES_NET_PORT=59999 \
|
||||
-e DBTOOL_MYSQL_HOST=mysql \
|
||||
-e DBTOOL_MYSQL_PORT=3306 \
|
||||
-e DBTOOL_MYSQL_NET_PORT=59998 \
|
||||
"$cid" \
|
||||
bash -lc \
|
||||
"cd '$sidecar_root' && ./scripts/qa/run-failure-path-evidence.sh ./dbtool all '$sidecar_output_dir'"
|
||||
|
||||
rm -rf "$output_dir"
|
||||
mkdir -p "$output_dir"
|
||||
docker cp "$cid":"$sidecar_output_dir"/. "$output_dir"/
|
||||
|
||||
echo "sidecar evidence written to: $output_dir"
|
||||
276
scripts/qa/run-failure-path-evidence.sh
Executable file
276
scripts/qa/run-failure-path-evidence.sh
Executable file
@@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ] || [ "$#" -gt 3 ]; then
|
||||
echo "usage: scripts/qa/run-failure-path-evidence.sh <binary-path> [postgres|mysql|all] [output-dir]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
binary_path="$1"
|
||||
scope="${2:-all}"
|
||||
output_dir="${3:-./tmp/failure-path-evidence}"
|
||||
|
||||
if [ ! -x "$binary_path" ]; then
|
||||
echo "error: binary is not executable: $binary_path" >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
case "$scope" in
|
||||
postgres|mysql|all) ;;
|
||||
*)
|
||||
echo "error: scope must be one of: postgres, mysql, all" >&2
|
||||
exit 4
|
||||
;;
|
||||
esac
|
||||
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
operator="${QA_OPERATOR:-qa-engineer}"
|
||||
host_name="${QA_HOST_LABEL:-$(hostname 2>/dev/null || echo unknown-host)}"
|
||||
utc_now="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
||||
binary_label="${QA_BINARY_PATH_LABEL:-$binary_path}"
|
||||
binary_version="${QA_BINARY_VERSION:-$("$binary_path" --version 2>/dev/null | head -n 1 || echo unknown-version)}"
|
||||
execution_path="${QA_EXECUTION_PATH:-host}"
|
||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
commit_sha="${QA_COMMIT_SHA:-$(git -C "$repo_root" rev-parse HEAD 2>/dev/null || echo unknown-commit)}"
|
||||
branch_name="${QA_BRANCH_NAME:-$(git -C "$repo_root" branch --show-current 2>/dev/null || echo unknown-branch)}"
|
||||
postgres_host="${DBTOOL_POSTGRES_HOST:-127.0.0.1}"
|
||||
postgres_port="${DBTOOL_POSTGRES_PORT:-55432}"
|
||||
mysql_host="${DBTOOL_MYSQL_HOST:-127.0.0.1}"
|
||||
mysql_port="${DBTOOL_MYSQL_PORT:-53306}"
|
||||
postgres_net_port="${DBTOOL_POSTGRES_NET_PORT:-59999}"
|
||||
mysql_net_port="${DBTOOL_MYSQL_NET_PORT:-59998}"
|
||||
|
||||
has_missing_traceability_value() {
|
||||
local value="$1"
|
||||
local placeholder="${2:-}"
|
||||
|
||||
if [ -z "$value" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "$placeholder" ] && [ "$value" = "$placeholder" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
contains_secret_leakage() {
|
||||
local raw_output_file="$1"
|
||||
local password_value="$2"
|
||||
local leakage_mode="${3:-contextual}"
|
||||
|
||||
[ -z "$password_value" ] && return 1
|
||||
|
||||
if [ "$leakage_mode" = "exact" ] && grep -Fq "$password_value" "$raw_output_file"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local patterns=(
|
||||
"\"password\":\"$password_value\""
|
||||
"\"password\": \"$password_value\""
|
||||
"password=$password_value"
|
||||
"password: $password_value"
|
||||
"password $password_value"
|
||||
"DBTOOL_PASSWORD=$password_value"
|
||||
":$password_value@"
|
||||
)
|
||||
|
||||
local pattern
|
||||
for pattern in "${patterns[@]}"; do
|
||||
if grep -Fq "$pattern" "$raw_output_file"; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
extract_json_string_field() {
|
||||
local raw_output_file="$1"
|
||||
local field_name="$2"
|
||||
|
||||
sed -nE "s/.*\"${field_name}\"[[:space:]]*:[[:space:]]*\"([^\"]+)\".*/\\1/p" "$raw_output_file" \
|
||||
| head -n 1
|
||||
}
|
||||
|
||||
run_scenario() {
|
||||
local scenario_id="$1"
|
||||
local driver="$2"
|
||||
local expected_kind="$3"
|
||||
local password_value="$4"
|
||||
local leakage_mode="$5"
|
||||
shift 5
|
||||
local args=("$@")
|
||||
|
||||
local raw_output_file="$output_dir/${scenario_id}.out"
|
||||
local markdown_file="$output_dir/${scenario_id}.md"
|
||||
|
||||
(
|
||||
export DBTOOL_PASSWORD="$password_value"
|
||||
set +e
|
||||
"$binary_path" "${args[@]}" >"$raw_output_file" 2>&1
|
||||
local exit_code=$?
|
||||
set -e
|
||||
|
||||
local output_excerpt
|
||||
output_excerpt="$(sed -n '1,120p' "$raw_output_file")"
|
||||
|
||||
local status_value
|
||||
status_value="$(extract_json_string_field "$raw_output_file" "status")"
|
||||
local state_value
|
||||
state_value="$(extract_json_string_field "$raw_output_file" "state")"
|
||||
local kind_value
|
||||
kind_value="$(extract_json_string_field "$raw_output_file" "kind")"
|
||||
local key_output_summary="status=${status_value:-missing}, state=${state_value:-missing}, error.kind=${kind_value:-missing}"
|
||||
|
||||
local leakage_status="not-detected"
|
||||
if contains_secret_leakage "$raw_output_file" "$password_value" "$leakage_mode"; then
|
||||
leakage_status="detected"
|
||||
fi
|
||||
|
||||
local command_text="$binary_label ${args[*]}"
|
||||
local traceability_gaps=()
|
||||
if has_missing_traceability_value "$utc_now"; then
|
||||
traceability_gaps+=("date")
|
||||
fi
|
||||
if has_missing_traceability_value "$operator"; then
|
||||
traceability_gaps+=("operator")
|
||||
fi
|
||||
if has_missing_traceability_value "$host_name" "unknown-host"; then
|
||||
traceability_gaps+=("host")
|
||||
fi
|
||||
if has_missing_traceability_value "$binary_label"; then
|
||||
traceability_gaps+=("binary_path")
|
||||
fi
|
||||
if has_missing_traceability_value "$binary_version" "unknown-version"; then
|
||||
traceability_gaps+=("binary_version")
|
||||
fi
|
||||
if has_missing_traceability_value "$execution_path"; then
|
||||
traceability_gaps+=("execution_path")
|
||||
fi
|
||||
if has_missing_traceability_value "$commit_sha" "unknown-commit"; then
|
||||
traceability_gaps+=("commit_sha")
|
||||
fi
|
||||
if has_missing_traceability_value "$branch_name" "unknown-branch"; then
|
||||
traceability_gaps+=("branch")
|
||||
fi
|
||||
if has_missing_traceability_value "$driver"; then
|
||||
traceability_gaps+=("driver")
|
||||
fi
|
||||
if has_missing_traceability_value "$scenario_id"; then
|
||||
traceability_gaps+=("scenario_id")
|
||||
fi
|
||||
if has_missing_traceability_value "$command_text"; then
|
||||
traceability_gaps+=("command")
|
||||
fi
|
||||
if has_missing_traceability_value "$key_output_summary"; then
|
||||
traceability_gaps+=("key_output")
|
||||
fi
|
||||
|
||||
local traceability_status="complete"
|
||||
local traceability_summary="none"
|
||||
if [ "${#traceability_gaps[@]}" -gt 0 ]; then
|
||||
traceability_status="incomplete"
|
||||
traceability_summary="$(printf '%s, ' "${traceability_gaps[@]}")"
|
||||
traceability_summary="${traceability_summary%, }"
|
||||
fi
|
||||
|
||||
local verdict="fail"
|
||||
if [ "$exit_code" -ne 0 ] \
|
||||
&& grep -Eq '"status"[[:space:]]*:[[:space:]]*"error"' "$raw_output_file" \
|
||||
&& grep -Eq '"state"[[:space:]]*:[[:space:]]*"error"' "$raw_output_file" \
|
||||
&& grep -Eq "\"kind\"[[:space:]]*:[[:space:]]*\"${expected_kind}\"" "$raw_output_file" \
|
||||
&& [ "$leakage_status" = "not-detected" ] \
|
||||
&& [ "$traceability_status" = "complete" ]; then
|
||||
verdict="pass"
|
||||
fi
|
||||
|
||||
cat >"$markdown_file" <<EOF
|
||||
### ${scenario_id}
|
||||
|
||||
- Date (UTC): \`${utc_now}\`
|
||||
- Operator: \`${operator}\`
|
||||
- Host: \`${host_name}\`
|
||||
- Execution Path: \`${execution_path}\`
|
||||
- Binary Path: \`${binary_label}\`
|
||||
- Binary Version: \`${binary_version}\`
|
||||
- Commit SHA: \`${commit_sha}\`
|
||||
- Branch: \`${branch_name}\`
|
||||
- Driver: \`${driver}\`
|
||||
- Scenario ID: \`${scenario_id}\`
|
||||
- Command: \`${command_text}\`
|
||||
- Exit Code: \`${exit_code}\`
|
||||
- Key Output: \`${key_output_summary}\`
|
||||
|
||||
Output (excerpt):
|
||||
\`\`\`
|
||||
${output_excerpt}
|
||||
\`\`\`
|
||||
|
||||
Verdict:
|
||||
- Expected: non-zero exit, \`status=error\`, \`state=error\`, \`error.kind=${expected_kind}\`, no secret leakage
|
||||
- Traceability check: \`${traceability_status}\` (${traceability_summary})
|
||||
- Secret leakage check: \`${leakage_status}\` (${leakage_mode})
|
||||
- Actual: see output excerpt above
|
||||
- Result: \`${verdict}\`
|
||||
EOF
|
||||
|
||||
echo "${scenario_id} -> ${markdown_file} (${verdict})"
|
||||
)
|
||||
}
|
||||
|
||||
if [ "$scope" = "postgres" ] || [ "$scope" = "all" ]; then
|
||||
run_scenario \
|
||||
"PG-CONN-AUTH-001" \
|
||||
"postgres" \
|
||||
"authentication" \
|
||||
"wrong-password" \
|
||||
"exact" \
|
||||
connect --driver postgres --host "$postgres_host" --port "$postgres_port" --database dbtool_demo --username dbtool --password-env DBTOOL_PASSWORD --result-format json
|
||||
|
||||
run_scenario \
|
||||
"PG-CONN-NET-001" \
|
||||
"postgres" \
|
||||
"connection" \
|
||||
"dbtool" \
|
||||
"contextual" \
|
||||
connect --driver postgres --host "$postgres_host" --port "$postgres_net_port" --database dbtool_demo --username dbtool --password-env DBTOOL_PASSWORD --result-format json
|
||||
|
||||
run_scenario \
|
||||
"PG-QUERY-SQL-001" \
|
||||
"postgres" \
|
||||
"query" \
|
||||
"dbtool" \
|
||||
"contextual" \
|
||||
query --driver postgres --host "$postgres_host" --port "$postgres_port" --database dbtool_demo --username dbtool --password-env DBTOOL_PASSWORD --file examples/sql/postgres/failing_query.sql --result-format json
|
||||
fi
|
||||
|
||||
if [ "$scope" = "mysql" ] || [ "$scope" = "all" ]; then
|
||||
run_scenario \
|
||||
"MY-CONN-AUTH-001" \
|
||||
"mysql" \
|
||||
"authentication" \
|
||||
"wrong-password" \
|
||||
"exact" \
|
||||
connect --driver mysql --host "$mysql_host" --port "$mysql_port" --database qa_demo --username dbtool --password-env DBTOOL_PASSWORD --result-format json
|
||||
|
||||
run_scenario \
|
||||
"MY-CONN-NET-001" \
|
||||
"mysql" \
|
||||
"connection" \
|
||||
"dbtool" \
|
||||
"contextual" \
|
||||
connect --driver mysql --host "$mysql_host" --port "$mysql_net_port" --database qa_demo --username dbtool --password-env DBTOOL_PASSWORD --result-format json
|
||||
|
||||
run_scenario \
|
||||
"MY-QUERY-SQL-001" \
|
||||
"mysql" \
|
||||
"query" \
|
||||
"dbtool" \
|
||||
"contextual" \
|
||||
query --driver mysql --host "$mysql_host" --port "$mysql_port" --database qa_demo --username dbtool --password-env DBTOOL_PASSWORD --file examples/sql/mysql/failing_query.sql --result-format json
|
||||
fi
|
||||
|
||||
echo "evidence written to: $output_dir"
|
||||
168
scripts/qa/test-failure-path-evidence-sidecar.sh
Executable file
168
scripts/qa/test-failure-path-evidence-sidecar.sh
Executable file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
root_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
tmp_dir="$(mktemp -d)"
|
||||
state_dir="$tmp_dir/state"
|
||||
fake_bin_dir="$tmp_dir/bin"
|
||||
output_dir="$tmp_dir/out"
|
||||
mkdir -p "$state_dir/container_fs" "$fake_bin_dir" "$output_dir"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
cat >"$tmp_dir/fake-dbtool" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
if [ "${1:-}" = "--version" ]; then
|
||||
echo "fake-dbtool 0.1.0"
|
||||
exit 0
|
||||
fi
|
||||
printf '{"status":"error","state":"error","error":{"kind":"authentication"}}\n'
|
||||
exit 2
|
||||
EOF
|
||||
chmod +x "$tmp_dir/fake-dbtool"
|
||||
|
||||
cat >"$fake_bin_dir/docker" <<'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
state_dir="${MOCK_DOCKER_STATE_DIR:?}"
|
||||
container_fs="$state_dir/container_fs"
|
||||
|
||||
cmd="${1:?}"
|
||||
shift
|
||||
|
||||
copy_into_container() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
mkdir -p "$(dirname "$dest")"
|
||||
cp "$src" "$dest"
|
||||
}
|
||||
|
||||
copy_from_container() {
|
||||
local src="$1"
|
||||
local dest="$2"
|
||||
mkdir -p "$dest"
|
||||
cp -R "$src"/. "$dest"/
|
||||
}
|
||||
|
||||
case "$cmd" in
|
||||
image)
|
||||
[ "${1:-}" = "inspect" ] || exit 90
|
||||
exit 0
|
||||
;;
|
||||
create)
|
||||
echo "mock-sidecar-id"
|
||||
exit 0
|
||||
;;
|
||||
start)
|
||||
exit 0
|
||||
;;
|
||||
rm)
|
||||
exit 0
|
||||
;;
|
||||
cp)
|
||||
src="${1:?}"
|
||||
dest="${2:?}"
|
||||
if [[ "$src" == *:* ]]; then
|
||||
container_src="${src#*:}"
|
||||
container_src="${container_src%/.}"
|
||||
copy_from_container "$container_fs$container_src" "$dest"
|
||||
else
|
||||
container_dest="${dest#*:}"
|
||||
copy_into_container "$src" "$container_fs$container_dest"
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
exec)
|
||||
env_pairs=()
|
||||
while [ "${1:-}" = "-e" ]; do
|
||||
env_pairs+=("${2:?}")
|
||||
shift 2
|
||||
done
|
||||
|
||||
if [ "${1:-}" = "-i" ]; then
|
||||
shift
|
||||
shift
|
||||
cat >/dev/null
|
||||
exit 0
|
||||
fi
|
||||
|
||||
target="${1:?}"
|
||||
shift
|
||||
|
||||
case "${1:-}" in
|
||||
mkdir)
|
||||
shift
|
||||
[ "${1:-}" = "-p" ] || exit 91
|
||||
shift
|
||||
for path in "$@"; do
|
||||
mkdir -p "$container_fs$path"
|
||||
done
|
||||
exit 0
|
||||
;;
|
||||
chmod)
|
||||
exit 0
|
||||
;;
|
||||
bash)
|
||||
[ "${2:-}" = "-lc" ] || exit 92
|
||||
for pair in "${env_pairs[@]}"; do
|
||||
export "$pair"
|
||||
done
|
||||
out_dir="$container_fs/tmp/work/out"
|
||||
mkdir -p "$out_dir"
|
||||
cat >"$out_dir/PG-CONN-AUTH-001.md" <<EOM
|
||||
### PG-CONN-AUTH-001
|
||||
|
||||
- Host: \`${QA_HOST_LABEL:-missing-host}\`
|
||||
- Execution Path: \`${QA_EXECUTION_PATH:-missing-execution-path}\`
|
||||
- Binary Path: \`${QA_BINARY_PATH_LABEL:-missing-binary-path}\`
|
||||
- Binary Version: \`${QA_BINARY_VERSION:-missing-binary-version}\`
|
||||
- Commit SHA: \`${QA_COMMIT_SHA:-missing-commit}\`
|
||||
- Branch: \`${QA_BRANCH_NAME:-missing-branch}\`
|
||||
- Result: \`pass\`
|
||||
EOM
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "unsupported docker exec target command: $*" >&2
|
||||
exit 93
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo "unsupported docker command: $cmd" >&2
|
||||
exit 94
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$fake_bin_dir/docker"
|
||||
|
||||
export PATH="$fake_bin_dir:$PATH"
|
||||
export MOCK_DOCKER_STATE_DIR="$state_dir"
|
||||
export QA_HOST_LABEL="runner-host-01"
|
||||
export QA_BINARY_PATH_LABEL="./target/release/dbtool"
|
||||
export QA_BINARY_VERSION="dbtool 0.1.0"
|
||||
|
||||
"$root_dir/scripts/qa/run-failure-path-evidence-sidecar.sh" \
|
||||
"$tmp_dir/fake-dbtool" \
|
||||
"$output_dir" >/dev/null
|
||||
|
||||
evidence_file="$output_dir/PG-CONN-AUTH-001.md"
|
||||
[ -f "$evidence_file" ] || {
|
||||
echo "expected evidence file not copied back: $evidence_file" >&2
|
||||
exit 95
|
||||
}
|
||||
|
||||
grep -Fq 'Host: `runner-host-01`' "$evidence_file"
|
||||
grep -Fq 'Execution Path: `sidecar`' "$evidence_file"
|
||||
grep -Fq 'Binary Path: `./target/release/dbtool`' "$evidence_file"
|
||||
grep -Fq 'Binary Version: `dbtool 0.1.0`' "$evidence_file"
|
||||
grep -Fq 'Commit SHA: `' "$evidence_file"
|
||||
grep -Fq 'Branch: `' "$evidence_file"
|
||||
grep -Fq 'Result: `pass`' "$evidence_file"
|
||||
|
||||
echo "sidecar helper fixture passed"
|
||||
155
scripts/tui/demo-stack.sh
Executable file
155
scripts/tui/demo-stack.sh
Executable file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
COMPOSE_FILE="${COMPOSE_FILE:-$ROOT_DIR/docker-compose.demo.yml}"
|
||||
POSTGRES_CONTAINER_NAME="${POSTGRES_CONTAINER_NAME:-dbtool-cli-v1-postgres-1}"
|
||||
MYSQL_CONTAINER_NAME="${MYSQL_CONTAINER_NAME:-dbtool-cli-v1-mysql-1}"
|
||||
DEMO_NETWORK_NAME="${DEMO_NETWORK_NAME:-dbtool-cli-v1_default}"
|
||||
|
||||
usage() {
|
||||
echo "usage: scripts/tui/demo-stack.sh <up|down|seed|reset|status>" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
docker_compose_available() {
|
||||
docker compose version >/dev/null 2>&1
|
||||
}
|
||||
|
||||
container_exists() {
|
||||
local container_name="$1"
|
||||
docker container inspect "$container_name" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
container_running() {
|
||||
local container_name="$1"
|
||||
[ "$(docker inspect -f '{{.State.Running}}' "$container_name" 2>/dev/null || true)" = "true" ]
|
||||
}
|
||||
|
||||
ensure_network() {
|
||||
if ! docker network inspect "$DEMO_NETWORK_NAME" >/dev/null 2>&1; then
|
||||
docker network create "$DEMO_NETWORK_NAME" >/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_health() {
|
||||
local container_name="$1"
|
||||
local max_attempts="${2:-60}"
|
||||
local attempt=1
|
||||
while [ "$attempt" -le "$max_attempts" ]; do
|
||||
local health
|
||||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}running{{end}}' "$container_name" 2>/dev/null || true)"
|
||||
if [ "$health" = "healthy" ] || [ "$health" = "running" ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
echo "error: container did not become healthy: $container_name" >&2
|
||||
docker logs --tail 50 "$container_name" >&2 || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
run_postgres_container() {
|
||||
if container_exists "$POSTGRES_CONTAINER_NAME"; then
|
||||
if ! container_running "$POSTGRES_CONTAINER_NAME"; then
|
||||
docker start "$POSTGRES_CONTAINER_NAME" >/dev/null
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
ensure_network
|
||||
docker run -d \
|
||||
--name "$POSTGRES_CONTAINER_NAME" \
|
||||
--network "$DEMO_NETWORK_NAME" \
|
||||
-p 55432:5432 \
|
||||
-e POSTGRES_DB=dbtool_demo \
|
||||
-e POSTGRES_USER=dbtool \
|
||||
-e POSTGRES_PASSWORD=dbtool \
|
||||
--health-cmd 'pg_isready -U dbtool -d dbtool_demo' \
|
||||
--health-interval 5s \
|
||||
--health-timeout 5s \
|
||||
--health-retries 20 \
|
||||
postgres:16 >/dev/null
|
||||
}
|
||||
|
||||
run_mysql_container() {
|
||||
if container_exists "$MYSQL_CONTAINER_NAME"; then
|
||||
if ! container_running "$MYSQL_CONTAINER_NAME"; then
|
||||
docker start "$MYSQL_CONTAINER_NAME" >/dev/null
|
||||
fi
|
||||
return
|
||||
fi
|
||||
|
||||
ensure_network
|
||||
docker run -d \
|
||||
--name "$MYSQL_CONTAINER_NAME" \
|
||||
--network "$DEMO_NETWORK_NAME" \
|
||||
-p 53306:3306 \
|
||||
-e MYSQL_DATABASE=dbtool_demo \
|
||||
-e MYSQL_USER=dbtool \
|
||||
-e MYSQL_PASSWORD=dbtool \
|
||||
-e MYSQL_ROOT_PASSWORD=dbtoolroot \
|
||||
--health-cmd 'mysqladmin ping -uroot -pdbtoolroot' \
|
||||
--health-interval 5s \
|
||||
--health-timeout 5s \
|
||||
--health-retries 30 \
|
||||
mysql:8.4 \
|
||||
mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci >/dev/null
|
||||
}
|
||||
|
||||
up_stack() {
|
||||
if docker_compose_available; then
|
||||
docker compose -f "$COMPOSE_FILE" up -d postgres mysql
|
||||
else
|
||||
run_postgres_container
|
||||
run_mysql_container
|
||||
fi
|
||||
wait_for_health "$POSTGRES_CONTAINER_NAME"
|
||||
wait_for_health "$MYSQL_CONTAINER_NAME"
|
||||
echo "Demo stack is ready."
|
||||
}
|
||||
|
||||
down_stack() {
|
||||
if docker_compose_available; then
|
||||
docker compose -f "$COMPOSE_FILE" down -v
|
||||
return
|
||||
fi
|
||||
|
||||
docker rm -f "$POSTGRES_CONTAINER_NAME" "$MYSQL_CONTAINER_NAME" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
seed_stack() {
|
||||
"$ROOT_DIR/examples/scripts/bootstrap-postgres.sh"
|
||||
"$ROOT_DIR/examples/scripts/bootstrap-mysql.sh"
|
||||
}
|
||||
|
||||
status_stack() {
|
||||
docker ps --filter "name=$POSTGRES_CONTAINER_NAME" --filter "name=$MYSQL_CONTAINER_NAME" \
|
||||
--format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
|
||||
}
|
||||
|
||||
command="${1:-}"
|
||||
|
||||
case "$command" in
|
||||
up)
|
||||
up_stack
|
||||
;;
|
||||
down)
|
||||
down_stack
|
||||
;;
|
||||
seed)
|
||||
seed_stack
|
||||
;;
|
||||
reset)
|
||||
down_stack
|
||||
up_stack
|
||||
seed_stack
|
||||
;;
|
||||
status)
|
||||
status_stack
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
79
scripts/tui/live-network-smoke.sh
Executable file
79
scripts/tui/live-network-smoke.sh
Executable file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 2 ] || [ "$#" -gt 3 ]; then
|
||||
echo "usage: scripts/tui/live-network-smoke.sh <postgres|mysql> <binary-path> [log-path]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v script >/dev/null 2>&1; then
|
||||
echo "error: \`script\` command is required for TTY smoke." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
target="$1"
|
||||
binary_path="$2"
|
||||
log_path="${3:-/tmp/dbtool-tui-$target-live.log}"
|
||||
|
||||
if [ ! -x "$binary_path" ]; then
|
||||
echo "error: binary is not executable: $binary_path" >&2
|
||||
exit 4
|
||||
fi
|
||||
|
||||
case "$target" in
|
||||
postgres)
|
||||
down_presses=1
|
||||
endpoint='host.docker.internal:55432'
|
||||
profile_name='reporting-postgres'
|
||||
export_path='/tmp/dbtool-tui-reporting-postgres-account-ticket-summary-sql.csv'
|
||||
activation_wait=4
|
||||
;;
|
||||
mysql)
|
||||
down_presses=2
|
||||
endpoint='host.docker.internal:53306'
|
||||
profile_name='orders-mysql'
|
||||
export_path='/tmp/dbtool-tui-orders-mysql-account-ticket-summary-sql.csv'
|
||||
activation_wait=5
|
||||
;;
|
||||
*)
|
||||
echo "error: unsupported target: $target" >&2
|
||||
exit 5
|
||||
;;
|
||||
esac
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
"$ROOT_DIR/scripts/tui/demo-stack.sh" up
|
||||
"$ROOT_DIR/scripts/tui/demo-stack.sh" seed
|
||||
|
||||
export DBTOOL_PASSWORD="${DBTOOL_PASSWORD:-dbtool}"
|
||||
|
||||
rm -f "$log_path"
|
||||
{
|
||||
sleep 1
|
||||
printf '2'
|
||||
if [ "$down_presses" -ge 1 ]; then
|
||||
printf '\033[B'
|
||||
fi
|
||||
if [ "$down_presses" -ge 2 ]; then
|
||||
printf '\033[B'
|
||||
fi
|
||||
printf '\r'
|
||||
sleep "$activation_wait"
|
||||
printf '\033'
|
||||
printf '\r'
|
||||
sleep 2
|
||||
printf 'x'
|
||||
sleep 2
|
||||
printf 'q'
|
||||
} | script -qec "stty rows 40 cols 120; \"$binary_path\"" "$log_path" >/dev/null
|
||||
|
||||
grep -aq "$profile_name" "$log_path"
|
||||
grep -aq "Validation: Connected" "$log_path"
|
||||
grep -aq "rows returned in" "$log_path"
|
||||
[ -f "$export_path" ]
|
||||
|
||||
echo "TUI live smoke passed for $target."
|
||||
echo "binary: $binary_path"
|
||||
echo "endpoint: $endpoint"
|
||||
echo "export: $export_path"
|
||||
echo "log: $log_path"
|
||||
33
scripts/tui/smoke-tty.sh
Executable file
33
scripts/tui/smoke-tty.sh
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -lt 1 ] || [ "$#" -gt 4 ]; then
|
||||
echo "usage: scripts/tui/smoke-tty.sh <binary-path> [rows] [cols] [log-path]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! command -v script >/dev/null 2>&1; then
|
||||
echo "error: \`script\` command is required for TTY smoke." >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
binary_path="$1"
|
||||
rows="${2:-40}"
|
||||
cols="${3:-120}"
|
||||
log_path="${4:-/tmp/dbtool-tui-smoke.log}"
|
||||
|
||||
if [ ! -x "$binary_path" ]; then
|
||||
echo "error: binary is not executable: $binary_path" >&2
|
||||
exit 4
|
||||
fi
|
||||
|
||||
command_string="$(printf 'stty rows %s cols %s; "%s"' "$rows" "$cols" "$binary_path")"
|
||||
|
||||
rm -f "$log_path"
|
||||
printf 'q' | script -qec "$command_string" "$log_path" >/dev/null
|
||||
|
||||
echo "TTY smoke passed."
|
||||
echo "binary: $binary_path"
|
||||
echo "rows: $rows"
|
||||
echo "cols: $cols"
|
||||
echo "log: $log_path"
|
||||
Reference in New Issue
Block a user