test(acceptance): reproducible Stage 8 acceptance package

Add test/acceptance/ so the customer's reviewers can re-run what the
Stage 8 documents claim on an isolated stand built from this branch:

- compose.acceptance.yml + stand.sh: compose project gis-acceptance
  (app 18080, postgres 15432, minio 19000/19001, rabbitmq mgmt 15672),
  POSTGIS_PLATFORM / MINIO_IMAGE knobs for arm64 and amd64 hosts.
- functional.sh: the 89 test-stand checks of Приложение 7 section 5
  (F01-F38, N01-N38, L02-L05, L07-L15), own prefixed test data,
  PostGIS/MinIO confirmation via the stand containers, full cleanup.
- lp_gen.py / lp_run.sh / lp_verify.py: synthetic ЛП inputs, a run from
  stage 0, and an independent numpy re-implementation of every node that
  compares all 15 outputs pixel by pixel; read-only (GET) mode works
  against any stand, in row/column windows for 22219x14977 rasters.
- load.sh + seed.sh: scenarios S1-S6 of Приложение 3 with hey 0.1.5
  running inside the stand network; CSV/JSON summaries, criteria K1-K4.
- scan.sh: govulncheck (source and binary), gosec, trivy fs/image,
  golangci-lint; fails on any High/Critical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 22:37:28 +05:00
parent c6229c46f7
commit ca87d6961b
22 changed files with 3136 additions and 0 deletions

72
test/acceptance/lp_run.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
# Landscape Program (ЛП) end-to-end check on the acceptance stand:
# 1. lp_gen.py — 13 synthetic input rasters (default 3000 x 2000)
# 2. upload — POST /landscape/slots/{code} for the 13 slots
# 3. run — POST /landscape/runs {"from_stage":0}, wait for it
# 4. lp_verify.py — pixel-by-pixel check of all 15 outputs (mode a)
#
# test/acceptance/lp_run.sh # BASE_URL defaults to http://localhost:18080
# LP_WIDTH=6000 LP_HEIGHT=4000 test/acceptance/lp_run.sh
#
# The uploaded inputs and the run's outputs are left in place so the run can be
# re-verified later (python lp_verify.py --run <id>); they are ordinary datasets
# of the "landscape" category. WRITES to the stand: never point it at production.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
BASE_URL="${BASE_URL:-http://localhost:18080}"
BASE_URL="${BASE_URL%/}"
PYTHON="${PYTHON:-python3}"
WAIT_TIMEOUT="${WAIT_TIMEOUT:-600}"
LP_WIDTH="${LP_WIDTH:-3000}"
LP_HEIGHT="${LP_HEIGHT:-2000}"
case "$BASE_URL" in
*dssgis.dwh.kz*) echo "refusing to write to $BASE_URL (production); use lp_verify.py for read-only checks" >&2; exit 2;;
esac
TMP="$(mktemp -d "${TMPDIR:-/tmp}/gis-lp.XXXXXX")"
trap 'rm -rf "$TMP"' EXIT
STAMP="$(date +%Y%m%d-%H%M%S)"
SLOTS="lp-z-biodiversity lp-z-lcr lp-z-soil-protection lp-z-wr lp-u-surf-water lp-u-soil lp-u-crop lp-u-veg lp-p-irrigation lp-p-snow lp-p-crop lp-p-pasture lp-p-tourism"
echo "== 1. generate inputs (${LP_WIDTH}x${LP_HEIGHT})"
"$PYTHON" "$HERE/lp_gen.py" --out "$TMP/in" --width "$LP_WIDTH" --height "$LP_HEIGHT"
echo "== 2. upload into 13 slots ($BASE_URL)"
ids=""
for s in $SLOTS; do
out="$(curl -sS -w ' %{http_code}' -X POST "$BASE_URL/landscape/slots/$s" -F "file=@$TMP/in/$s.tif" \
-F "name=ЛП приёмка $s $STAMP" -F start_date=2025-01-01 -F end_date=2025-12-31)"
code="${out##* }"; body="${out% *}"
[ "$code" = 201 ] || { echo "upload $s failed: HTTP $code $body" >&2; exit 1; }
id="$(jq -r .id <<< "$body")"; ids="$ids $id"
printf ' %-22s %s HTTP %s\n' "$s" "$id" "$code"
done
for id in $ids; do
deadline=$(( $(date +%s) + WAIT_TIMEOUT ))
until [ "$(curl -sS "$BASE_URL/datasets/$id" | jq -r .status)" = ready ]; do
[ "$(date +%s)" -lt "$deadline" ] || { echo "dataset $id not ready" >&2; exit 1; }
sleep 1
done
done
echo " all 13 inputs ready"
echo "== 3. run {\"from_stage\":0}"
run="$(curl -sS -X POST "$BASE_URL/landscape/runs" -H 'Content-Type: application/json' -d '{"from_stage":0}')"
RUN="$(jq -r .id <<< "$run")"
[ -n "$RUN" ] && [ "$RUN" != null ] || { echo "run not created: $run" >&2; exit 1; }
echo " run $RUN queued"
deadline=$(( $(date +%s) + WAIT_TIMEOUT ))
while :; do
st="$(curl -sS "$BASE_URL/landscape/runs/$RUN" | jq -r .status)"
case "$st" in succeeded|failed) break;; esac
[ "$(date +%s)" -lt "$deadline" ] || { echo "run $RUN still $st" >&2; exit 1; }
sleep 1
done
curl -sS "$BASE_URL/landscape/runs/$RUN" > "$TMP/run.json"
echo " run $RUN $st: $(jq -r '[.nodes[] | "\(.node_code):\(.status)"] | sort | join(" ")' "$TMP/run.json")"
"$PYTHON" "$HERE/lib/helpers.py" durations "$TMP/run.json" | sed 's/^/ /'
[ "$st" = succeeded ] || { echo "run failed: $(jq -r .error "$TMP/run.json")" >&2; exit 1; }
echo "== 4. lp_verify.py --base-url $BASE_URL --run $RUN"
"$PYTHON" "$HERE/lp_verify.py" --base-url "$BASE_URL" --run "$RUN" --cache "$TMP/cache"