文件
hyb-oa/server/scripts/regression-verification.sh
T
wangsli e39e41287e feat(P25-1/P25-5/P26-1/P26-3/P24-1/P29-4): 落地内部OA够用版后端与试运行能力
关联 Issue: P25-1, P25-2, P25-3, P25-4, P25-5, P26-1, P26-3, P24-1, P24-2, P29-1, P29-2, P29-3, P29-4
2026-03-21 18:03:20 +08:00

231 行
6.6 KiB
Bash
可执行文件

#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${BASE_URL:-http://127.0.0.1:3000}"
DEVICE_FINGERPRINT="${DEVICE_FINGERPRINT:-regression-verification}"
FINANCE_MGR_IDENTIFIER="${FINANCE_MGR_IDENTIFIER:-finance_mgr@hyinda.com}"
FINANCE_MGR_PASSWORD="${FINANCE_MGR_PASSWORD:-FinanceMgrPassword1234}"
if ! command -v curl >/dev/null 2>&1; then
echo "[ERROR] curl not found" >&2
exit 1
fi
if ! command -v jq >/dev/null 2>&1; then
echo "[ERROR] jq not found" >&2
exit 1
fi
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
# shellcheck source=scripts/lib/load-env-defaults.sh
source "$ROOT_DIR/scripts/lib/load-env-defaults.sh"
load_env_defaults ".env"
if [[ -z "${DATABASE_URL:-}" ]]; then
echo "[ERROR] DATABASE_URL is required (export it or provide server/.env)" >&2
exit 1
fi
TMP_DIR="$(mktemp -d)"
RUN_SUFFIX="$(date +%s)-$RANDOM"
cleanup() {
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
assert_status() {
local actual="$1"
local expected="$2"
local name="$3"
local body_file="${4:-}"
if [[ "$actual" != "$expected" ]]; then
echo "[ERROR] $name expected HTTP $expected but got $actual" >&2
if [[ -n "$body_file" && -f "$body_file" ]]; then
cat "$body_file" >&2
fi
exit 1
fi
}
login() {
local identifier="$1"
local password="$2"
local out_file="$3"
local status
status="$(curl -sS -o "$out_file" -w "%{http_code}" \
-X POST "$BASE_URL/api/auth/login" \
-H "content-type: application/json" \
-H "x-device-fingerprint: $DEVICE_FINGERPRINT" \
-d "{\"identifier\":\"$identifier\",\"password\":\"$password\"}")"
assert_status "$status" "200" "login" "$out_file"
jq -er '.data.accessToken' "$out_file"
}
api_json() {
local method="$1"
local url="$2"
local token="$3"
local out_file="$4"
local body="${5:-}"
if [[ -n "$body" ]]; then
curl -sS -o "$out_file" -w "%{http_code}" \
-X "$method" "$url" \
-H "authorization: Bearer $token" \
-H "content-type: application/json" \
-d "$body"
else
curl -sS -o "$out_file" -w "%{http_code}" \
-X "$method" "$url" \
-H "authorization: Bearer $token"
fi
}
create_available_invoice() {
local marker="$1"
DATABASE_URL="$DATABASE_URL" RUN_SUFFIX="$RUN_SUFFIX" MARKER="$marker" node <<'NODE'
const fs = require('node:fs/promises');
const path = require('node:path');
const crypto = require('node:crypto');
const { createPrismaClient } = require('./src/prismaClient');
const { getConfig } = require('./src/config');
(async () => {
const marker = process.env.MARKER || 'VERIFY_OK';
const runSuffix = process.env.RUN_SUFFIX || 'default';
const config = getConfig();
const { prisma, pool } = createPrismaClient();
await prisma.$connect();
try {
const owner = await prisma.employee.findUnique({
where: { email: '[email protected]' },
select: { id: true },
});
if (!owner) {
throw new Error('finance_mgr not found');
}
const relPath = path.join('verification-regression', runSuffix, `${crypto.randomUUID()}.txt`);
const absPath = path.resolve(config.storageDir, relPath);
await fs.mkdir(path.dirname(absPath), { recursive: true });
await fs.writeFile(absPath, marker, 'utf8');
const fileSha256 = crypto.createHash('sha256').update(marker).digest('hex');
const created = await prisma.invoicePool.create({
data: {
owner_employee_id: owner.id,
uploaded_by_employee_id: owner.id,
storage_path: relPath,
original_name: `verify-${runSuffix}.txt`,
mime_type: 'text/plain',
file_size: Buffer.byteLength(marker),
file_sha256: fileSha256,
status: 'available',
ocr_type: 'general',
invoice_code: 'MOCKPASS',
invoice_number: 'PASS',
remarks: marker.includes('FAIL') ? 'verify-fail' : null,
created_by: owner.id,
updated_by: owner.id,
},
select: { id: true },
});
process.stdout.write(`${created.id}\n`);
} finally {
await prisma.$disconnect();
await pool.end();
}
})().catch((error) => {
console.error(error);
process.exit(1);
});
NODE
}
set_verification_flag() {
local mode="$1"
DATABASE_URL="$DATABASE_URL" MODE="$mode" node <<'NODE'
require("dotenv/config");
const { createPrismaClient } = require("./src/prismaClient");
(async () => {
const mode = process.env.MODE;
const { prisma, pool } = createPrismaClient();
await prisma.$connect();
try {
await prisma.featureFlag.upsert({
where: { flag_key: "invoice_verification" },
create: {
flag_key: "invoice_verification",
override_mode: mode,
rollout_type: "global",
department_ids: [],
note: "regression-verification",
},
update: {
override_mode: mode,
rollout_type: "global",
department_ids: [],
note: "regression-verification",
version: { increment: 1 },
},
});
} finally {
await prisma.$disconnect();
await pool.end();
}
})().catch((error) => {
console.error(error);
process.exit(1);
});
NODE
}
echo "[0/6] Ensure verification feature flag force_on"
set_verification_flag "force_on"
echo "[1/6] Login finance_mgr"
TOKEN="$(login "$FINANCE_MGR_IDENTIFIER" "$FINANCE_MGR_PASSWORD" "$TMP_DIR/login.json")"
echo "[2/6] Create synthetic available invoice"
INVOICE_ID="$(create_available_invoice "VERIFY_OK")"
echo "[3/6] Request verification"
status="$(api_json POST "$BASE_URL/api/invoice-pool/$INVOICE_ID/verify" "$TOKEN" "$TMP_DIR/verify.json" '{}')"
assert_status "$status" "200" "invoice verify" "$TMP_DIR/verify.json"
VERIFICATION_ID="$(jq -er '.data.verificationId' "$TMP_DIR/verify.json")"
echo "[4/6] Run verification worker inline"
VERIFICATION_RECORD_ID="$VERIFICATION_ID" INVOICE_VERIFICATION_ENABLED=true VERIFICATION_PROVIDER=mock npm run --silent job:verification-once >"$TMP_DIR/verify-once.log"
sleep 0.2
echo "[5/6] Query verification records"
status="$(api_json GET "$BASE_URL/api/invoice-pool/$INVOICE_ID/verification?limit=5" "$TOKEN" "$TMP_DIR/records.json")"
assert_status "$status" "200" "list verification" "$TMP_DIR/records.json"
jq -e '.data.items | length >= 1' "$TMP_DIR/records.json" >/dev/null
jq -e '.data.items[0].status == "passed"' "$TMP_DIR/records.json" >/dev/null
echo "[6/6] Retry should be throttled immediately"
status="$(api_json POST "$BASE_URL/api/invoice-pool/$INVOICE_ID/verification/retry" "$TOKEN" "$TMP_DIR/retry.json" '{}')"
if [[ "$status" != "409" ]]; then
echo "[ERROR] expected retry throttled 409 but got $status" >&2
cat "$TMP_DIR/retry.json" >&2
exit 1
fi
echo "[OK] Verification regression passed"
echo "[post] Reset verification feature flag force_off"
set_verification_flag "force_off"