关联 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
475 行
18 KiB
Bash
可执行文件
475 行
18 KiB
Bash
可执行文件
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${BASE_URL:-http://127.0.0.1:3000}"
|
|
DEVICE_FINGERPRINT="${DEVICE_FINGERPRINT:-regression-audit}"
|
|
EMPLOYEE_IDENTIFIER="${EMPLOYEE_IDENTIFIER:-employee@hyinda.com}"
|
|
EMPLOYEE_PASSWORD="${EMPLOYEE_PASSWORD:-EmployeePassword1234}"
|
|
MANAGER_IDENTIFIER="${MANAGER_IDENTIFIER:-manager@hyinda.com}"
|
|
MANAGER_PASSWORD="${MANAGER_PASSWORD:-ManagerPassword1234}"
|
|
FINANCE_MGR_IDENTIFIER="${FINANCE_MGR_IDENTIFIER:-finance_mgr@hyinda.com}"
|
|
FINANCE_MGR_PASSWORD="${FINANCE_MGR_PASSWORD:-FinanceMgrPassword1234}"
|
|
CEO_IDENTIFIER="${CEO_IDENTIFIER:-ceo@hyinda.com}"
|
|
|
|
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
|
|
|
|
if ! command -v node >/dev/null 2>&1; then
|
|
echo "[ERROR] node 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
|
|
}
|
|
|
|
assert_error_code() {
|
|
local file_path="$1"
|
|
local expected="$2"
|
|
local name="$3"
|
|
local actual
|
|
actual="$(jq -r '.error.code // empty' "$file_path")"
|
|
if [[ "$actual" != "$expected" ]]; then
|
|
echo "[ERROR] $name expected error code $expected but got $actual" >&2
|
|
cat "$file_path" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
assert_jq() {
|
|
local file_path="$1"
|
|
local jq_expr="$2"
|
|
local name="$3"
|
|
|
|
if ! jq -e "$jq_expr" "$file_path" >/dev/null; then
|
|
echo "[ERROR] $name assertion failed: $jq_expr" >&2
|
|
cat "$file_path" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
api_json_auth() {
|
|
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
|
|
}
|
|
|
|
login_user() {
|
|
local identifier="$1"
|
|
local password="$2"
|
|
local fingerprint="$3"
|
|
local out_file="$4"
|
|
|
|
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: $fingerprint" \
|
|
-d "{\"identifier\":\"$identifier\",\"password\":\"$password\"}")"
|
|
|
|
assert_status "$status" "200" "login $identifier" "$out_file"
|
|
jq -er '.data.accessToken' "$out_file"
|
|
}
|
|
|
|
auth_get() {
|
|
local url="$1"
|
|
local token="$2"
|
|
local out_file="$3"
|
|
|
|
curl -sS -o "$out_file" -w "%{http_code}" \
|
|
-X GET "$url" \
|
|
-H "authorization: Bearer $token"
|
|
}
|
|
|
|
get_me_id() {
|
|
local token="$1"
|
|
local out_file="$2"
|
|
|
|
local status
|
|
status="$(auth_get "$BASE_URL/api/me" "$token" "$out_file")"
|
|
assert_status "$status" "200" "get me" "$out_file"
|
|
jq -er '.data.id' "$out_file"
|
|
}
|
|
|
|
create_delegation() {
|
|
local token="$1"
|
|
local delegate_id="$2"
|
|
local valid_from="$3"
|
|
local valid_to="$4"
|
|
local out_file="$5"
|
|
|
|
local status
|
|
status="$(api_json_auth POST "$BASE_URL/api/delegations" "$token" "$out_file" "{\"delegateEmployeeId\":\"$delegate_id\",\"scope\":\"all\",\"validFrom\":\"$valid_from\",\"validTo\":\"$valid_to\"}")"
|
|
assert_status "$status" "200" "create delegation" "$out_file"
|
|
jq -er '.data.delegation.id' "$out_file"
|
|
}
|
|
|
|
revoke_delegation() {
|
|
local token="$1"
|
|
local delegation_id="$2"
|
|
local out_file="$3"
|
|
|
|
local status
|
|
status="$(api_json_auth POST "$BASE_URL/api/delegations/$delegation_id/revoke" "$token" "$out_file" '{}')"
|
|
assert_status "$status" "200" "revoke delegation" "$out_file"
|
|
}
|
|
|
|
upload_invoice() {
|
|
local token="$1"
|
|
local file_path="$2"
|
|
local out_file="$3"
|
|
local ocr_type="${4:-}"
|
|
|
|
if [[ -n "$ocr_type" ]]; then
|
|
curl -sS -o "$out_file" -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/invoice-pool/upload" \
|
|
-H "authorization: Bearer $token" \
|
|
-F "file=@$file_path" \
|
|
-F "ocr_type=$ocr_type"
|
|
else
|
|
curl -sS -o "$out_file" -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/invoice-pool/upload" \
|
|
-H "authorization: Bearer $token" \
|
|
-F "file=@$file_path"
|
|
fi
|
|
}
|
|
|
|
create_reimbursement() {
|
|
local token="$1"
|
|
local out_file="$2"
|
|
|
|
local status
|
|
status="$(api_json_auth POST "$BASE_URL/api/reimbursements" "$token" "$out_file" '{}')"
|
|
assert_status "$status" "200" "create reimbursement" "$out_file"
|
|
jq -er '.data.id' "$out_file"
|
|
}
|
|
|
|
bind_invoice() {
|
|
local token="$1"
|
|
local invoice_id="$2"
|
|
local reimbursement_id="$3"
|
|
local out_file="$4"
|
|
|
|
local status
|
|
status="$(api_json_auth POST "$BASE_URL/api/invoice-pool/$invoice_id/bind" "$token" "$out_file" "{\"reimbursementId\":\"$reimbursement_id\"}")"
|
|
assert_status "$status" "200" "bind invoice" "$out_file"
|
|
}
|
|
|
|
patch_invoice() {
|
|
local token="$1"
|
|
local invoice_id="$2"
|
|
local body="$3"
|
|
local out_file="$4"
|
|
|
|
local status
|
|
status="$(api_json_auth PATCH "$BASE_URL/api/invoice-pool/$invoice_id" "$token" "$out_file" "$body")"
|
|
assert_status "$status" "200" "patch invoice" "$out_file"
|
|
}
|
|
|
|
archive_invoice_manual() {
|
|
local token="$1"
|
|
local invoice_id="$2"
|
|
local out_file="$3"
|
|
|
|
local status
|
|
status="$(api_json_auth POST "$BASE_URL/api/invoice-pool/$invoice_id/archive" "$token" "$out_file" '{}')"
|
|
assert_status "$status" "200" "archive invoice" "$out_file"
|
|
}
|
|
|
|
issue_signed_url() {
|
|
local token="$1"
|
|
local invoice_id="$2"
|
|
local out_file="$3"
|
|
|
|
local status
|
|
status="$(api_json_auth POST "$BASE_URL/api/invoice-pool/$invoice_id/signed-url" "$token" "$out_file" '{}')"
|
|
assert_status "$status" "200" "issue signed url" "$out_file"
|
|
jq -er '.data.signedUrl' "$out_file"
|
|
}
|
|
|
|
override_duplicate() {
|
|
local token="$1"
|
|
local invoice_id="$2"
|
|
local ref_form_number="$3"
|
|
local reason="$4"
|
|
local out_file="$5"
|
|
|
|
local status
|
|
status="$(api_json_auth POST "$BASE_URL/api/invoice-pool/$invoice_id/override-duplicate" "$token" "$out_file" "{\"refFormNumber\":\"$ref_form_number\",\"reason\":\"$reason\"}")"
|
|
echo "$status"
|
|
}
|
|
|
|
retry_ocr() {
|
|
local token="$1"
|
|
local invoice_id="$2"
|
|
local out_file="$3"
|
|
|
|
api_json_auth POST "$BASE_URL/api/invoice-pool/$invoice_id/retry-ocr" "$token" "$out_file" '{}'
|
|
}
|
|
|
|
assert_audit_has_target() {
|
|
local file_path="$1"
|
|
local target_id="$2"
|
|
local scenario="$3"
|
|
|
|
if ! jq -e --arg tid "$target_id" '.data.items | any(.targetId == $tid)' "$file_path" >/dev/null; then
|
|
echo "[ERROR] $scenario expected audit log targetId=$target_id" >&2
|
|
cat "$file_path" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
create_invoice_record() {
|
|
local owner_email="$1"
|
|
local marker="$2"
|
|
local status="$3"
|
|
local attempts="$4"
|
|
local last_requested_at="$5"
|
|
|
|
DATABASE_URL="$DATABASE_URL" OWNER_EMAIL="$owner_email" MARKER="$marker" STATUS="$status" ATTEMPTS="$attempts" LAST_REQUESTED_AT="$last_requested_at" 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 email = process.env.OWNER_EMAIL;
|
|
const marker = process.env.MARKER || 'MOCK_OK';
|
|
const status = process.env.STATUS || 'ocr_failed';
|
|
const attempts = Number(process.env.ATTEMPTS || 0);
|
|
const lastRequestedAtRaw = process.env.LAST_REQUESTED_AT || '';
|
|
|
|
const config = getConfig();
|
|
const { prisma, pool } = createPrismaClient();
|
|
await prisma.$connect();
|
|
|
|
try {
|
|
const owner = await prisma.employee.findUnique({
|
|
where: { email },
|
|
select: { id: true },
|
|
});
|
|
|
|
if (!owner) {
|
|
throw new Error(`employee not found: ${email}`);
|
|
}
|
|
|
|
const relPath = path.join('audit-regression', `${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: `audit-${Date.now()}.txt`,
|
|
mime_type: 'text/plain',
|
|
file_size: Buffer.byteLength(marker),
|
|
file_sha256: fileSha256,
|
|
status,
|
|
ocr_type: 'vat',
|
|
ocr_attempts: attempts,
|
|
ocr_last_requested_at: lastRequestedAtRaw ? new Date(lastRequestedAtRaw) : 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
|
|
}
|
|
|
|
echo "[1/6] login users"
|
|
EMPLOYEE_TOKEN="$(login_user "$EMPLOYEE_IDENTIFIER" "$EMPLOYEE_PASSWORD" "$DEVICE_FINGERPRINT-employee-$RUN_SUFFIX" "$TMP_DIR/login-employee.json")"
|
|
MANAGER_TOKEN="$(login_user "$MANAGER_IDENTIFIER" "$MANAGER_PASSWORD" "$DEVICE_FINGERPRINT-manager-$RUN_SUFFIX" "$TMP_DIR/login-manager.json")"
|
|
FINANCE_MGR_TOKEN="$(login_user "$FINANCE_MGR_IDENTIFIER" "$FINANCE_MGR_PASSWORD" "$DEVICE_FINGERPRINT-finance-mgr-$RUN_SUFFIX" "$TMP_DIR/login-finance-mgr.json")"
|
|
CEO_TOKEN="$(login_user "$CEO_IDENTIFIER" "${CEO_PASSWORD:-CeoPassword1234}" "$DEVICE_FINGERPRINT-ceo-$RUN_SUFFIX" "$TMP_DIR/login-ceo.json")"
|
|
|
|
echo "[2/6] permission check: non-audit role forbidden"
|
|
perm_status="$(auth_get "$BASE_URL/api/audit-logs?page=1&pageSize=5" "$EMPLOYEE_TOKEN" "$TMP_DIR/audit-perm.json")"
|
|
assert_status "$perm_status" "403" "audit permission" "$TMP_DIR/audit-perm.json"
|
|
assert_error_code "$TMP_DIR/audit-perm.json" "PERM_001" "audit permission"
|
|
|
|
echo "[3/6] create deterministic audit records"
|
|
CEO_ID="$(get_me_id "$CEO_TOKEN" "$TMP_DIR/me-ceo.json")"
|
|
VALID_FROM="$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")"
|
|
VALID_TO="$(date -u -d "+1 day" +"%Y-%m-%dT%H:%M:%S.000Z")"
|
|
DELEGATION_ID="$(create_delegation "$MANAGER_TOKEN" "$CEO_ID" "$VALID_FROM" "$VALID_TO" "$TMP_DIR/delegation-create.json")"
|
|
revoke_delegation "$MANAGER_TOKEN" "$DELEGATION_ID" "$TMP_DIR/delegation-revoke.json"
|
|
|
|
echo "[4/6] pagination and base response"
|
|
page_status="$(auth_get "$BASE_URL/api/audit-logs?page=1&pageSize=3" "$FINANCE_MGR_TOKEN" "$TMP_DIR/audit-page.json")"
|
|
assert_status "$page_status" "200" "audit list page" "$TMP_DIR/audit-page.json"
|
|
assert_jq "$TMP_DIR/audit-page.json" '.data.page == 1 and .data.pageSize == 3 and (.data.total|type=="number") and (.data.items|type=="array")' "audit list page"
|
|
|
|
echo "[5/6] action/targetType/date filters"
|
|
filter_status="$(auth_get "$BASE_URL/api/audit-logs?action=DELEGATION_CREATE&targetType=delegation_grant&page=1&pageSize=20" "$FINANCE_MGR_TOKEN" "$TMP_DIR/audit-filter-action.json")"
|
|
assert_status "$filter_status" "200" "audit filter action" "$TMP_DIR/audit-filter-action.json"
|
|
assert_jq "$TMP_DIR/audit-filter-action.json" '.data.items | any(.action == "DELEGATION_CREATE" and .targetType == "delegation_grant")' "audit filter action"
|
|
|
|
range_status="$(auth_get "$BASE_URL/api/audit-logs?dateFrom=2026-01-01T00:00:00.000Z&dateTo=2026-12-31T23:59:59.000Z&page=1&pageSize=10" "$FINANCE_MGR_TOKEN" "$TMP_DIR/audit-filter-date.json")"
|
|
assert_status "$range_status" "200" "audit filter date" "$TMP_DIR/audit-filter-date.json"
|
|
assert_jq "$TMP_DIR/audit-filter-date.json" '.data.items|type=="array"' "audit filter date"
|
|
|
|
echo "[6/6] invalid query check"
|
|
invalid_status="$(auth_get "$BASE_URL/api/audit-logs?page=0&pageSize=20" "$FINANCE_MGR_TOKEN" "$TMP_DIR/audit-invalid-page.json")"
|
|
assert_status "$invalid_status" "400" "audit invalid page" "$TMP_DIR/audit-invalid-page.json"
|
|
assert_error_code "$TMP_DIR/audit-invalid-page.json" "VALID_001" "audit invalid page"
|
|
|
|
date_invalid_status="$(auth_get "$BASE_URL/api/audit-logs?dateFrom=2026-02-10T00:00:00.000Z&dateTo=2026-02-01T00:00:00.000Z" "$FINANCE_MGR_TOKEN" "$TMP_DIR/audit-invalid-date.json")"
|
|
assert_status "$date_invalid_status" "400" "audit invalid date" "$TMP_DIR/audit-invalid-date.json"
|
|
assert_error_code "$TMP_DIR/audit-invalid-date.json" "VALID_001" "audit invalid date"
|
|
assert_jq "$TMP_DIR/audit-invalid-date.json" '.error.details.dateTo == "must_be_after_dateFrom"' "audit invalid date"
|
|
|
|
echo "[7/10] invoice edit/archive + signed-url/download audit"
|
|
EMPLOYEE_ID="$(get_me_id "$EMPLOYEE_TOKEN" "$TMP_DIR/me-employee.json")"
|
|
FINANCE_MGR_ID="$(get_me_id "$FINANCE_MGR_TOKEN" "$TMP_DIR/me-fm.json")"
|
|
|
|
INVOICE_FILE="$TMP_DIR/invoice-audit-$RUN_SUFFIX.pdf"
|
|
printf '%%PDF-1.4\nINVOICE-AUDIT-%s\n' "$RUN_SUFFIX" > "$INVOICE_FILE"
|
|
|
|
upload_status="$(upload_invoice "$EMPLOYEE_TOKEN" "$INVOICE_FILE" "$TMP_DIR/invoice-upload.json")"
|
|
assert_status "$upload_status" "200" "upload invoice audit" "$TMP_DIR/invoice-upload.json"
|
|
INVOICE_ID="$(jq -er '.data.invoicePoolId' "$TMP_DIR/invoice-upload.json")"
|
|
|
|
patch_invoice "$EMPLOYEE_TOKEN" "$INVOICE_ID" "{\"remarks\":\"audit-$RUN_SUFFIX\",\"invoiceCode\":\"AUD$RUN_SUFFIX\",\"invoiceNumber\":\"$RUN_SUFFIX\"}" "$TMP_DIR/invoice-patch.json"
|
|
archive_invoice_manual "$EMPLOYEE_TOKEN" "$INVOICE_ID" "$TMP_DIR/invoice-archive.json"
|
|
|
|
SIGNED_URL="$(issue_signed_url "$EMPLOYEE_TOKEN" "$INVOICE_ID" "$TMP_DIR/invoice-signed.json")"
|
|
download_status="$(curl -sS -L -o "$TMP_DIR/invoice-download.bin" -w "%{http_code}" "$SIGNED_URL")"
|
|
assert_status "$download_status" "200" "invoice signed url download"
|
|
if [[ ! -s "$TMP_DIR/invoice-download.bin" ]]; then
|
|
echo "[ERROR] invoice signed url download is empty" >&2
|
|
exit 1
|
|
fi
|
|
|
|
for action in INVOICE_POOL_EDITED INVOICE_POOL_ARCHIVED INVOICE_SIGNED_URL_ISSUED INVOICE_FILE_DOWNLOADED; do
|
|
audit_file="$TMP_DIR/audit-$action.json"
|
|
audit_status="$(auth_get "$BASE_URL/api/audit-logs?action=$action&targetType=invoice_pool&page=1&pageSize=50" "$FINANCE_MGR_TOKEN" "$audit_file")"
|
|
assert_status "$audit_status" "200" "audit query $action" "$audit_file"
|
|
assert_audit_has_target "$audit_file" "$INVOICE_ID" "audit $action"
|
|
done
|
|
|
|
echo "[8/10] duplicate override audit"
|
|
RID_OVERRIDE_A="$(create_reimbursement "$EMPLOYEE_TOKEN" "$TMP_DIR/override-reim-a.json")"
|
|
RID_OVERRIDE_B="$(create_reimbursement "$EMPLOYEE_TOKEN" "$TMP_DIR/override-reim-b.json")"
|
|
|
|
OV_FILE="$TMP_DIR/override-$RUN_SUFFIX.pdf"
|
|
printf '%%PDF-1.4\nMOCK_DUP:%s\n' "$RUN_SUFFIX" > "$OV_FILE"
|
|
|
|
upload_status="$(upload_invoice "$EMPLOYEE_TOKEN" "$OV_FILE" "$TMP_DIR/override-upload-a.json")"
|
|
assert_status "$upload_status" "200" "upload override invoice A" "$TMP_DIR/override-upload-a.json"
|
|
IID_OVERRIDE_A="$(jq -er '.data.invoicePoolId' "$TMP_DIR/override-upload-a.json")"
|
|
bind_invoice "$EMPLOYEE_TOKEN" "$IID_OVERRIDE_A" "$RID_OVERRIDE_A" "$TMP_DIR/override-bind-a.json"
|
|
|
|
upload_status="$(upload_invoice "$EMPLOYEE_TOKEN" "$OV_FILE" "$TMP_DIR/override-upload-b.json")"
|
|
assert_status "$upload_status" "200" "upload override invoice B" "$TMP_DIR/override-upload-b.json"
|
|
IID_OVERRIDE_B="$(jq -er '.data.invoicePoolId' "$TMP_DIR/override-upload-b.json")"
|
|
|
|
bind_status="$(api_json_auth POST "$BASE_URL/api/invoice-pool/$IID_OVERRIDE_B/bind" "$EMPLOYEE_TOKEN" "$TMP_DIR/override-bind-b.json" "{\"reimbursementId\":\"$RID_OVERRIDE_B\"}")"
|
|
assert_status "$bind_status" "409" "bind duplicate before override" "$TMP_DIR/override-bind-b.json"
|
|
assert_error_code "$TMP_DIR/override-bind-b.json" "VALID_003" "bind duplicate before override"
|
|
|
|
override_status="$(override_duplicate "$FINANCE_MGR_TOKEN" "$IID_OVERRIDE_B" "REF-$RUN_SUFFIX" "regression override" "$TMP_DIR/override.json")"
|
|
assert_status "$override_status" "200" "override duplicate" "$TMP_DIR/override.json"
|
|
|
|
bind_invoice "$EMPLOYEE_TOKEN" "$IID_OVERRIDE_B" "$RID_OVERRIDE_B" "$TMP_DIR/override-bind-b2.json"
|
|
|
|
override_audit_status="$(auth_get "$BASE_URL/api/audit-logs?action=INVOICE_DUPLICATE_OVERRIDDEN&targetType=invoice_pool&actorId=$FINANCE_MGR_ID&page=1&pageSize=50" "$FINANCE_MGR_TOKEN" "$TMP_DIR/audit-override.json")"
|
|
assert_status "$override_audit_status" "200" "audit override" "$TMP_DIR/audit-override.json"
|
|
assert_audit_has_target "$TMP_DIR/audit-override.json" "$IID_OVERRIDE_B" "audit override"
|
|
|
|
echo "[9/10] OCR retry audit + VALID_006/VALID_007"
|
|
NOW_ISO="$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")"
|
|
OLD_ISO="$(date -u -d "-2 minutes" +"%Y-%m-%dT%H:%M:%S.000Z")"
|
|
|
|
INVOICE_FREQ="$(create_invoice_record "$EMPLOYEE_IDENTIFIER" "MOCK_OK:$RUN_SUFFIX" "ocr_failed" "0" "$NOW_ISO")"
|
|
retry_freq_status="$(retry_ocr "$EMPLOYEE_TOKEN" "$INVOICE_FREQ" "$TMP_DIR/retry-freq.json")"
|
|
assert_status "$retry_freq_status" "409" "retry too frequent" "$TMP_DIR/retry-freq.json"
|
|
assert_error_code "$TMP_DIR/retry-freq.json" "VALID_007" "retry too frequent"
|
|
|
|
INVOICE_EXCEEDED="$(create_invoice_record "$EMPLOYEE_IDENTIFIER" "MOCK_OK:$RUN_SUFFIX" "ocr_failed" "3" "$OLD_ISO")"
|
|
retry_exceeded_status="$(retry_ocr "$EMPLOYEE_TOKEN" "$INVOICE_EXCEEDED" "$TMP_DIR/retry-exceeded.json")"
|
|
assert_status "$retry_exceeded_status" "409" "retry attempts exceeded" "$TMP_DIR/retry-exceeded.json"
|
|
assert_error_code "$TMP_DIR/retry-exceeded.json" "VALID_006" "retry attempts exceeded"
|
|
|
|
INVOICE_RETRY_OK="$(create_invoice_record "$EMPLOYEE_IDENTIFIER" "MOCK_OK:$RUN_SUFFIX" "ocr_failed" "1" "$OLD_ISO")"
|
|
retry_ok_status="$(retry_ocr "$EMPLOYEE_TOKEN" "$INVOICE_RETRY_OK" "$TMP_DIR/retry-ok.json")"
|
|
assert_status "$retry_ok_status" "200" "retry ok" "$TMP_DIR/retry-ok.json"
|
|
assert_jq "$TMP_DIR/retry-ok.json" '.data.status == "pending_ocr"' "retry ok status"
|
|
|
|
OCR_PROVIDER=mock OCR_MAX_ATTEMPTS=3 npm run --silent job:ocr-once >"$TMP_DIR/ocr-retry-run.log"
|
|
|
|
invoice_after_ocr_status="$(auth_get "$BASE_URL/api/invoice-pool/$INVOICE_RETRY_OK" "$EMPLOYEE_TOKEN" "$TMP_DIR/invoice-after-ocr.json")"
|
|
assert_status "$invoice_after_ocr_status" "200" "invoice after ocr" "$TMP_DIR/invoice-after-ocr.json"
|
|
assert_jq "$TMP_DIR/invoice-after-ocr.json" '.data.status == "available"' "invoice after ocr"
|
|
|
|
retry_audit_status="$(auth_get "$BASE_URL/api/audit-logs?action=INVOICE_OCR_RETRIED&targetType=invoice_pool&actorId=$EMPLOYEE_ID&page=1&pageSize=50" "$FINANCE_MGR_TOKEN" "$TMP_DIR/audit-retry.json")"
|
|
assert_status "$retry_audit_status" "200" "audit retry" "$TMP_DIR/audit-retry.json"
|
|
assert_audit_has_target "$TMP_DIR/audit-retry.json" "$INVOICE_RETRY_OK" "audit retry"
|
|
|
|
echo "[OK] Audit regression passed"
|