@resolveio/server-lib 22.3.92 → 22.3.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/collections/email-history.collection.js +7 -0
- package/collections/email-history.collection.js.map +1 -1
- package/managers/method.manager.d.ts +1 -0
- package/managers/method.manager.js +30 -7
- package/managers/method.manager.js.map +1 -1
- package/models/email-history.model.d.ts +1 -0
- package/models/email-history.model.js.map +1 -1
- package/package.json +1 -1
- package/util/ai-qa-policy.js +1 -1
- package/util/ai-qa-policy.js.map +1 -1
- package/util/ai-runner-qa-auth.js +9 -0
- package/util/ai-runner-qa-auth.js.map +1 -1
- package/util/ai-runner-qa-tools.d.ts +2 -0
- package/util/ai-runner-qa-tools.js +686 -33
- package/util/ai-runner-qa-tools.js.map +1 -1
- package/util/runner-process-janitor.d.ts +2 -0
- package/util/runner-process-janitor.js +10 -1
- package/util/runner-process-janitor.js.map +1 -1
|
@@ -30,6 +30,8 @@ function buildResolveIORunnerQaEnvScript(options) {
|
|
|
30
30
|
var jobId = shellDoubleQuote(options.jobId || '');
|
|
31
31
|
var ownerId = shellDoubleQuote(options.ownerId || '');
|
|
32
32
|
var runnerToken = shellDoubleQuote(options.runnerToken || '');
|
|
33
|
+
var runnerRunId = shellDoubleQuote(options.runnerRunId || '');
|
|
34
|
+
var runnerGeneration = shellDoubleQuote(String(options.runnerGeneration || ''));
|
|
33
35
|
var mongoRuntimePath = shellDoubleQuote(options.mongoRuntimePath || '');
|
|
34
36
|
var qaLiveDataRequired = options.qaLiveDataRequired === true ? 'true' : 'false';
|
|
35
37
|
var toolsBinPath = options.toolsBinPath || '';
|
|
@@ -67,6 +69,19 @@ function buildResolveIORunnerQaEnvScript(options) {
|
|
|
67
69
|
"export ".concat(mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME', "=\"").concat('${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME') + ':-' + homeRoot + '}', "\""),
|
|
68
70
|
"RESOLVEIO_QA_TMP=\"".concat('${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_TMP' : 'RESOLVEIO_RUNNER_QA_TMP') + '}', "\""),
|
|
69
71
|
"RESOLVEIO_QA_HOME=\"".concat('${' + (mode === 'support' ? 'RESOLVEIO_SUPPORT_QA_HOME' : 'RESOLVEIO_RUNNER_QA_HOME') + '}', "\""),
|
|
72
|
+
'RESOLVEIO_QA_TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
|
|
73
|
+
'if [ -z "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" ] && [ -f "$RESOLVEIO_QA_TOOLS_DIR/current-codex-lane" ]; then',
|
|
74
|
+
' RESOLVEIO_SUPPORT_CODEX_LANE="$(tr -d "\\r\\n\\t " < "$RESOLVEIO_QA_TOOLS_DIR/current-codex-lane" 2>/dev/null || true)"',
|
|
75
|
+
' export RESOLVEIO_SUPPORT_CODEX_LANE',
|
|
76
|
+
'fi',
|
|
77
|
+
'resolveio_support_codex_lane_is_build() {',
|
|
78
|
+
' case "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" in build|support_build|support-build) return 0 ;; *) return 1 ;; esac',
|
|
79
|
+
'}',
|
|
80
|
+
'resolveio_support_codex_block_build_lane_qa() {',
|
|
81
|
+
' resolveio_support_codex_lane_is_build || return 0',
|
|
82
|
+
' echo "ResolveIO support lane guard: build lane cannot run local QA/browser/server/client commands; return a build fix and let the QA lane run browser validation." >&2',
|
|
83
|
+
' return 86',
|
|
84
|
+
'}',
|
|
70
85
|
'mkdir -p "$RESOLVEIO_QA_HOME/.nvm" "$RESOLVEIO_QA_TMP/npm-cache" "$RESOLVEIO_QA_TMP/mongodb-binaries" "$RESOLVEIO_QA_TMP/mongodb-memory-server-core"',
|
|
71
86
|
'if [ ! -f "$RESOLVEIO_QA_HOME/.nvm/nvm.sh" ]; then',
|
|
72
87
|
' cat > "$RESOLVEIO_QA_HOME/.nvm/nvm.sh" <<\'RESOLVEIO_NVM_SHIM\'',
|
|
@@ -81,6 +96,129 @@ function buildResolveIORunnerQaEnvScript(options) {
|
|
|
81
96
|
'fi',
|
|
82
97
|
'export HOME="$RESOLVEIO_QA_HOME"',
|
|
83
98
|
'export NVM_DIR="$RESOLVEIO_QA_HOME/.nvm"',
|
|
99
|
+
'export RESOLVEIO_QA_COMMAND_GUARD_BIN="$RESOLVEIO_QA_TMP/command-guard-bin"',
|
|
100
|
+
'mkdir -p "$RESOLVEIO_QA_COMMAND_GUARD_BIN"',
|
|
101
|
+
'resolveio_qa_find_real_command() {',
|
|
102
|
+
' local name="$1"',
|
|
103
|
+
' local old_ifs="$IFS"',
|
|
104
|
+
' IFS=":"',
|
|
105
|
+
' for dir in $PATH; do',
|
|
106
|
+
' [ -n "$dir" ] || continue',
|
|
107
|
+
' [ "$dir" = "$RESOLVEIO_QA_COMMAND_GUARD_BIN" ] && continue',
|
|
108
|
+
' if [ -x "$dir/$name" ]; then',
|
|
109
|
+
' echo "$dir/$name"',
|
|
110
|
+
' IFS="$old_ifs"',
|
|
111
|
+
' return 0',
|
|
112
|
+
' fi',
|
|
113
|
+
' done',
|
|
114
|
+
' IFS="$old_ifs"',
|
|
115
|
+
' return 1',
|
|
116
|
+
'}',
|
|
117
|
+
'export RESOLVEIO_QA_REAL_NPM="${RESOLVEIO_QA_REAL_NPM:-$(resolveio_qa_find_real_command npm || true)}"',
|
|
118
|
+
'if [ -n "$RESOLVEIO_QA_REAL_NPM" ]; then',
|
|
119
|
+
' cat > "$RESOLVEIO_QA_COMMAND_GUARD_BIN/npm" <<\'RESOLVEIO_QA_NPM_GUARD\'',
|
|
120
|
+
'#!/usr/bin/env bash',
|
|
121
|
+
'set -u',
|
|
122
|
+
'REAL_NPM="${RESOLVEIO_QA_REAL_NPM:-}"',
|
|
123
|
+
'GUARD_BIN="${RESOLVEIO_QA_COMMAND_GUARD_BIN:-}"',
|
|
124
|
+
'if [ -z "$REAL_NPM" ] || [ "$REAL_NPM" = "$0" ]; then',
|
|
125
|
+
' OLD_IFS="$IFS"',
|
|
126
|
+
' IFS=":"',
|
|
127
|
+
' for dir in $PATH; do',
|
|
128
|
+
' [ -n "$dir" ] || continue',
|
|
129
|
+
' [ -n "$GUARD_BIN" ] && [ "$dir" = "$GUARD_BIN" ] && continue',
|
|
130
|
+
' if [ -x "$dir/npm" ]; then REAL_NPM="$dir/npm"; break; fi',
|
|
131
|
+
' done',
|
|
132
|
+
' IFS="$OLD_IFS"',
|
|
133
|
+
'fi',
|
|
134
|
+
'if [ -z "$REAL_NPM" ] || [ ! -x "$REAL_NPM" ]; then',
|
|
135
|
+
' echo "ResolveIO QA command guard: unable to locate real npm." >&2',
|
|
136
|
+
' exit 127',
|
|
137
|
+
'fi',
|
|
138
|
+
'if [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "build" ] || [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "support_build" ] || [ "${RESOLVEIO_SUPPORT_CODEX_LANE:-}" = "support-build" ]; then',
|
|
139
|
+
' if [ "${1:-}" = "run" ]; then',
|
|
140
|
+
' case "${2:-}" in',
|
|
141
|
+
' server|client|start|dev|serve|local-qa|qa|qa:*|test:browser|test:e2e|e2e)',
|
|
142
|
+
' echo "ResolveIO support lane guard: refusing npm run ${2:-} from the 5.3 build lane. Browser/server/client QA belongs to the 5.4 mini QA lane." >&2',
|
|
143
|
+
' exit 86',
|
|
144
|
+
' ;;',
|
|
145
|
+
' esac',
|
|
146
|
+
' fi',
|
|
147
|
+
'fi',
|
|
148
|
+
'is_angular_build=0',
|
|
149
|
+
'if [ "${1:-}" = "run" ]; then',
|
|
150
|
+
' case "${2:-}" in',
|
|
151
|
+
' build|build-dev|build-prod) is_angular_build=1 ;;',
|
|
152
|
+
' esac',
|
|
153
|
+
'fi',
|
|
154
|
+
'resolveio_lock_live() {',
|
|
155
|
+
' local file="$1"',
|
|
156
|
+
' local ttl="${RESOLVEIO_QA_COMMAND_LOCK_TTL_SECONDS:-1800}"',
|
|
157
|
+
' [ -f "$file" ] || return 1',
|
|
158
|
+
' node - "$file" "$ttl" <<\'RESOLVEIO_QA_LOCK_LIVE\'',
|
|
159
|
+
'const fs = require("fs");',
|
|
160
|
+
'const [file, ttlRaw] = process.argv.slice(2);',
|
|
161
|
+
'const ttlMs = Math.max(1, Number(ttlRaw || 1800)) * 1000;',
|
|
162
|
+
'try {',
|
|
163
|
+
' const data = JSON.parse(fs.readFileSync(file, "utf8"));',
|
|
164
|
+
' const pid = Number(data.pid || 0);',
|
|
165
|
+
' const heartbeat = Date.parse(data.heartbeat_at || data.started_at || "");',
|
|
166
|
+
' if (!pid || !heartbeat || Date.now() - heartbeat > ttlMs) process.exit(1);',
|
|
167
|
+
' process.kill(pid, 0);',
|
|
168
|
+
' process.exit(0);',
|
|
169
|
+
'} catch (error) { process.exit(1); }',
|
|
170
|
+
'RESOLVEIO_QA_LOCK_LIVE',
|
|
171
|
+
'}',
|
|
172
|
+
'if [ "$is_angular_build" = "1" ]; then',
|
|
173
|
+
' PROJECT_ROOT="${RESOLVEIO_SUPPORT_QA_PROJECT_ROOT:-${RESOLVEIO_RUNNER_QA_PROJECT_ROOT:-$(pwd)}}"',
|
|
174
|
+
' PROJECT_ROOT="$(cd "$PROJECT_ROOT" 2>/dev/null && pwd || pwd)"',
|
|
175
|
+
' ARTIFACT_DIR="$PROJECT_ROOT/qa-artifacts"',
|
|
176
|
+
' mkdir -p "$ARTIFACT_DIR/.command-locks"',
|
|
177
|
+
' QA_LOCK="$ARTIFACT_DIR/.qa.lock/lock.json"',
|
|
178
|
+
' if resolveio_lock_live "$QA_LOCK"; then',
|
|
179
|
+
' echo "ResolveIO QA command guard: refusing npm run ${2:-} while local QA harness is active for $PROJECT_ROOT. Stop QA first with stop-local-qa.sh." >&2',
|
|
180
|
+
' exit 86',
|
|
181
|
+
' fi',
|
|
182
|
+
' BUILD_LOCK_DIR="$ARTIFACT_DIR/.command-locks/angular-build.lock"',
|
|
183
|
+
' BUILD_LOCK_JSON="$BUILD_LOCK_DIR/lock.json"',
|
|
184
|
+
' if ! mkdir "$BUILD_LOCK_DIR" 2>/dev/null; then',
|
|
185
|
+
' if resolveio_lock_live "$BUILD_LOCK_JSON"; then',
|
|
186
|
+
' echo "ResolveIO QA command guard: refusing duplicate npm run ${2:-}; another Angular build is active for $PROJECT_ROOT." >&2',
|
|
187
|
+
' exit 86',
|
|
188
|
+
' fi',
|
|
189
|
+
' rm -rf "$BUILD_LOCK_DIR" 2>/dev/null || true',
|
|
190
|
+
' if ! mkdir "$BUILD_LOCK_DIR" 2>/dev/null; then',
|
|
191
|
+
' echo "ResolveIO QA command guard: unable to acquire Angular build lock for $PROJECT_ROOT." >&2',
|
|
192
|
+
' exit 86',
|
|
193
|
+
' fi',
|
|
194
|
+
' fi',
|
|
195
|
+
' cat > "$BUILD_LOCK_JSON" <<EOF',
|
|
196
|
+
'{"pid": $$, "command": "npm $*", "project_root": "$PROJECT_ROOT", "started_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "heartbeat_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)"}',
|
|
197
|
+
'EOF',
|
|
198
|
+
' ( while true; do node - "$BUILD_LOCK_JSON" <<\'RESOLVEIO_QA_BUILD_HEARTBEAT\'',
|
|
199
|
+
'const fs = require("fs");',
|
|
200
|
+
'const [file] = process.argv.slice(2);',
|
|
201
|
+
'try {',
|
|
202
|
+
' const data = JSON.parse(fs.readFileSync(file, "utf8"));',
|
|
203
|
+
' data.heartbeat_at = new Date().toISOString();',
|
|
204
|
+
' fs.writeFileSync(file, JSON.stringify(data, null, 2));',
|
|
205
|
+
'} catch (error) {}',
|
|
206
|
+
'RESOLVEIO_QA_BUILD_HEARTBEAT',
|
|
207
|
+
' sleep 10',
|
|
208
|
+
' done ) >/dev/null 2>&1 &',
|
|
209
|
+
' HEARTBEAT_PID="$!"',
|
|
210
|
+
' cleanup_build_lock() {',
|
|
211
|
+
' kill "$HEARTBEAT_PID" >/dev/null 2>&1 || true',
|
|
212
|
+
' rm -rf "$BUILD_LOCK_DIR" >/dev/null 2>&1 || true',
|
|
213
|
+
' }',
|
|
214
|
+
' trap cleanup_build_lock EXIT INT TERM',
|
|
215
|
+
'fi',
|
|
216
|
+
'exec "$REAL_NPM" "$@"',
|
|
217
|
+
'RESOLVEIO_QA_NPM_GUARD',
|
|
218
|
+
' chmod +x "$RESOLVEIO_QA_COMMAND_GUARD_BIN/npm"',
|
|
219
|
+
' export PATH="$RESOLVEIO_QA_COMMAND_GUARD_BIN:$PATH"',
|
|
220
|
+
' hash -r 2>/dev/null || true',
|
|
221
|
+
'fi',
|
|
84
222
|
toolsBinPath ? "export PATH=\"".concat(toolsBinPath, ":$PATH\"") : '',
|
|
85
223
|
'export NODE_ENV=development',
|
|
86
224
|
'# Local QA must start the application HTTP server even when launched from a dedicated support-manager worker.',
|
|
@@ -125,6 +263,10 @@ function buildResolveIORunnerQaEnvScript(options) {
|
|
|
125
263
|
"export ".concat(envVar(altMode, 'OWNER_ID'), "=\"").concat('${' + envVar(altMode, 'OWNER_ID') + ':-${' + envVar(mode, 'OWNER_ID') + '}}', "\""),
|
|
126
264
|
"export ".concat(envVar(mode, 'RUNNER_TOKEN'), "=\"").concat('${' + envVar(mode, 'RUNNER_TOKEN') + ':-${' + envVar(altMode, 'RUNNER_TOKEN') + ':-' + runnerToken + '}}', "\""),
|
|
127
265
|
"export ".concat(envVar(altMode, 'RUNNER_TOKEN'), "=\"").concat('${' + envVar(altMode, 'RUNNER_TOKEN') + ':-${' + envVar(mode, 'RUNNER_TOKEN') + '}}', "\""),
|
|
266
|
+
"export ".concat(envVar(mode, 'RUNNER_RUN_ID'), "=\"").concat('${' + envVar(mode, 'RUNNER_RUN_ID') + ':-${' + envVar(altMode, 'RUNNER_RUN_ID') + ':-' + runnerRunId + '}}', "\""),
|
|
267
|
+
"export ".concat(envVar(altMode, 'RUNNER_RUN_ID'), "=\"").concat('${' + envVar(altMode, 'RUNNER_RUN_ID') + ':-${' + envVar(mode, 'RUNNER_RUN_ID') + '}}', "\""),
|
|
268
|
+
"export ".concat(envVar(mode, 'RUNNER_GENERATION'), "=\"").concat('${' + envVar(mode, 'RUNNER_GENERATION') + ':-${' + envVar(altMode, 'RUNNER_GENERATION') + ':-' + runnerGeneration + '}}', "\""),
|
|
269
|
+
"export ".concat(envVar(altMode, 'RUNNER_GENERATION'), "=\"").concat('${' + envVar(altMode, 'RUNNER_GENERATION') + ':-${' + envVar(mode, 'RUNNER_GENERATION') + '}}', "\""),
|
|
128
270
|
mongoRuntimePath ? "export ".concat(envVar(mode, 'MONGO_RUNTIME_PATH'), "=\"").concat('${' + envVar(mode, 'MONGO_RUNTIME_PATH') + ':-${' + envVar(altMode, 'MONGO_RUNTIME_PATH') + ':-' + mongoRuntimePath + '}}', "\"") : '',
|
|
129
271
|
mongoRuntimePath ? "export ".concat(envVar(altMode, 'MONGO_RUNTIME_PATH'), "=\"").concat('${' + envVar(altMode, 'MONGO_RUNTIME_PATH') + ':-${' + envVar(mode, 'MONGO_RUNTIME_PATH') + '}}', "\"") : '',
|
|
130
272
|
mongoRuntimePath ? "export RESOLVEIO_QA_MONGO_RUNTIME_PATH=\"".concat('${RESOLVEIO_QA_MONGO_RUNTIME_PATH:-$' + envVar(mode, 'MONGO_RUNTIME_PATH') + '}', "\"") : '',
|
|
@@ -168,6 +310,10 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
168
310
|
'set -u',
|
|
169
311
|
'TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
|
|
170
312
|
'source "$TOOLS_DIR/env.sh"',
|
|
313
|
+
'if resolveio_support_codex_lane_is_build 2>/dev/null; then',
|
|
314
|
+
' resolveio_support_codex_block_build_lane_qa',
|
|
315
|
+
' exit 86',
|
|
316
|
+
'fi',
|
|
171
317
|
'PROJECT_ROOT="${1:-$(pwd)}"',
|
|
172
318
|
'PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"',
|
|
173
319
|
'ARTIFACT_DIR="$PROJECT_ROOT/qa-artifacts"',
|
|
@@ -442,6 +588,134 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
442
588
|
' done',
|
|
443
589
|
' return 1',
|
|
444
590
|
'}',
|
|
591
|
+
'mongo_tcp_ready() {',
|
|
592
|
+
' node - "$MONGO_PORT" <<\'RESOLVEIO_QA_MONGO_TCP\'',
|
|
593
|
+
'const net = require("net");',
|
|
594
|
+
'const port = Number(process.argv[2] || 3001);',
|
|
595
|
+
'const socket = net.createConnection({ host: "127.0.0.1", port, timeout: 1200 });',
|
|
596
|
+
'socket.once("connect", () => { socket.destroy(); process.exit(0); });',
|
|
597
|
+
'socket.once("timeout", () => { socket.destroy(); process.exit(1); });',
|
|
598
|
+
'socket.once("error", () => process.exit(1));',
|
|
599
|
+
'RESOLVEIO_QA_MONGO_TCP',
|
|
600
|
+
'}',
|
|
601
|
+
'start_local_mongo_for_prestart_seed() {',
|
|
602
|
+
' if mongo_tcp_ready >/dev/null 2>&1; then return 0; fi',
|
|
603
|
+
' if command -v mongod >/dev/null 2>&1; then',
|
|
604
|
+
' mkdir -p "$PROJECT_ROOT/server/mongo/data/db" "$PROJECT_ROOT/server/mongo/log"',
|
|
605
|
+
' mongod --fork --dbpath "$PROJECT_ROOT/server/mongo/data/db" --logpath "$PROJECT_ROOT/server/mongo/log/mongo.log" --port "$MONGO_PORT" --replSet rs0 --bind_ip 127.0.0.1 >/dev/null 2>&1 || true',
|
|
606
|
+
' fi',
|
|
607
|
+
' local end=$((SECONDS + 45))',
|
|
608
|
+
' while [ "$SECONDS" -lt "$end" ]; do',
|
|
609
|
+
' if mongo_tcp_ready >/dev/null 2>&1; then break; fi',
|
|
610
|
+
' sleep 2',
|
|
611
|
+
' done',
|
|
612
|
+
' if ! mongo_tcp_ready >/dev/null 2>&1; then return 1; fi',
|
|
613
|
+
' if command -v mongosh >/dev/null 2>&1; then',
|
|
614
|
+
' timeout 8s mongosh "mongodb://127.0.0.1:$MONGO_PORT/admin?directConnection=true" --quiet --eval "try { rs.initiate({_id: \'rs0\', members: [{ _id: 0, host: \'127.0.0.1:$MONGO_PORT\' }]}) } catch (e) {}" >/dev/null 2>&1 || true',
|
|
615
|
+
' fi',
|
|
616
|
+
' sleep 3',
|
|
617
|
+
' mongo_tcp_ready >/dev/null 2>&1',
|
|
618
|
+
'}',
|
|
619
|
+
'materialize_live_mongo_runtime() {',
|
|
620
|
+
' local runtime_file="$ARTIFACT_DIR/.mongo-runtime.json"',
|
|
621
|
+
' node - "$runtime_file" "$PROJECT_ROOT" "$REPO_ROOT" <<\'RESOLVEIO_QA_MONGO_RUNTIME\'',
|
|
622
|
+
'const fs = require("fs");',
|
|
623
|
+
'const path = require("path");',
|
|
624
|
+
'const [runtimeFile, projectRootArg, repoRootArg] = process.argv.slice(2);',
|
|
625
|
+
'function parseEnv(filePath) {',
|
|
626
|
+
' const out = {};',
|
|
627
|
+
' try {',
|
|
628
|
+
' for (const raw of fs.readFileSync(filePath, "utf8").split(/\\n/)) {',
|
|
629
|
+
' const line = raw.trim();',
|
|
630
|
+
' if (!line || line.startsWith("#")) continue;',
|
|
631
|
+
' if (line.includes("=")) {',
|
|
632
|
+
' const index = line.indexOf("=");',
|
|
633
|
+
' out[line.slice(0, index).trim().replace(/^[\\\'"]|[\\\'"]$/g, "")] = line.slice(index + 1).trim().replace(/^[\\\'"]|[\\\'"],?$/g, "");',
|
|
634
|
+
' continue;',
|
|
635
|
+
' }',
|
|
636
|
+
' const jsonish = line.match(/^[\\\'"]?([A-Z0-9_]+)[\\\'"]?\\s*:\\s*[\\\'"]?(.+?)[\\\'"]?,?$/i);',
|
|
637
|
+
' if (jsonish) out[jsonish[1]] = jsonish[2].trim();',
|
|
638
|
+
' }',
|
|
639
|
+
' } catch (error) {}',
|
|
640
|
+
' return out;',
|
|
641
|
+
'}',
|
|
642
|
+
'function readJson(filePath) { try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { return {}; } }',
|
|
643
|
+
'function pick(obj, keys) { for (const key of keys) { if (obj && typeof obj[key] === "string" && obj[key].trim()) return obj[key].trim(); } return ""; }',
|
|
644
|
+
'function isLocalMongo(uri) { return /mongodb(?:\\+srv)?:\\/\\/(?:[^@/]+@)?(?:127\\.0\\.0\\.1|localhost)(?::\\d+)?\\//i.test(String(uri || "")); }',
|
|
645
|
+
'function unique(values) { return Array.from(new Set(values.filter(Boolean).map((value) => path.resolve(String(value))))); }',
|
|
646
|
+
'const appName = projectRootArg ? path.basename(projectRootArg) : "";',
|
|
647
|
+
'const runtimeFileCandidates = unique([',
|
|
648
|
+
' process.env.RESOLVEIO_QA_MONGO_RUNTIME_PATH,',
|
|
649
|
+
' process.env.RESOLVEIO_SUPPORT_QA_MONGO_RUNTIME_PATH,',
|
|
650
|
+
' process.env.RESOLVEIO_RUNNER_QA_MONGO_RUNTIME_PATH,',
|
|
651
|
+
' repoRootArg && path.join(repoRootArg, "mongo-context", ".mongo-runtime.json"),',
|
|
652
|
+
' projectRootArg && path.join(projectRootArg, "mongo-context", ".mongo-runtime.json"),',
|
|
653
|
+
' repoRootArg && path.join(repoRootArg, ".resolveio-support-context", "mongo-context", ".mongo-runtime.json"),',
|
|
654
|
+
' projectRootArg && path.join(projectRootArg, ".resolveio-support-context", "mongo-context", ".mongo-runtime.json"),',
|
|
655
|
+
' repoRootArg && path.join(repoRootArg, ".resolveio-context", "mongo-context", ".mongo-runtime.json"),',
|
|
656
|
+
' projectRootArg && path.join(projectRootArg, ".resolveio-context", "mongo-context", ".mongo-runtime.json")',
|
|
657
|
+
']);',
|
|
658
|
+
'for (const candidate of runtimeFileCandidates) {',
|
|
659
|
+
' const parsed = readJson(candidate);',
|
|
660
|
+
' const uri = pick(parsed, ["mongo_uri", "MONGO_URL"]);',
|
|
661
|
+
' const database = pick(parsed, ["mongo_db", "DATABASE", "database"]);',
|
|
662
|
+
' if (uri && !isLocalMongo(uri)) {',
|
|
663
|
+
' fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });',
|
|
664
|
+
' fs.writeFileSync(runtimeFile, JSON.stringify({ mongo_uri: uri, mongo_db: database || "" }, null, 2));',
|
|
665
|
+
' process.exit(0);',
|
|
666
|
+
' }',
|
|
667
|
+
'}',
|
|
668
|
+
'const runtimeRoots = unique([',
|
|
669
|
+
' process.env.RESOLVEIO_QA_RUNTIME_ROOT,',
|
|
670
|
+
' process.env.RESOLVEIO_SUPPORT_QA_RUNTIME_ROOT,',
|
|
671
|
+
' projectRootArg && path.join(projectRootArg, "server"),',
|
|
672
|
+
' projectRootArg,',
|
|
673
|
+
' repoRootArg,',
|
|
674
|
+
' appName && path.join("/var/app/current", appName, "server"),',
|
|
675
|
+
' appName && path.join("/var/app/current", appName),',
|
|
676
|
+
' appName === "resolveio" && "/var/app/current"',
|
|
677
|
+
']);',
|
|
678
|
+
'for (const root of runtimeRoots) {',
|
|
679
|
+
' const env = Object.assign({}, parseEnv(path.join(root, ".env")), parseEnv(path.join(root, ".env.production")), parseEnv(path.join(root, ".env.runtime")), parseEnv(path.join(root, ".env.codex")));',
|
|
680
|
+
' const settings = Object.assign({}, readJson(path.join(root, "settings.json")), readJson(path.join(root, "settings.local.json")));',
|
|
681
|
+
' const uri = pick(env, ["MONGO_URL"]) || pick(settings, ["MONGO_URL", "mongo_url"]);',
|
|
682
|
+
' const database = pick(env, ["DATABASE"]) || pick(settings, ["DATABASE", "database"]);',
|
|
683
|
+
' if (uri && !isLocalMongo(uri)) {',
|
|
684
|
+
' fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });',
|
|
685
|
+
' fs.writeFileSync(runtimeFile, JSON.stringify({ mongo_uri: uri, mongo_db: database || "" }, null, 2));',
|
|
686
|
+
' process.exit(0);',
|
|
687
|
+
' }',
|
|
688
|
+
'}',
|
|
689
|
+
'const fallbackUri = pick(process.env, ["RESOLVEIO_QA_LIVE_MONGO_URL", "RESOLVEIO_SUPPORT_QA_LIVE_MONGO_URL", "RESOLVEIO_RUNNER_QA_LIVE_MONGO_URL"]);',
|
|
690
|
+
'if (fallbackUri && !isLocalMongo(fallbackUri)) {',
|
|
691
|
+
' const fallbackDatabase = pick(process.env, ["RESOLVEIO_QA_LIVE_MONGO_DB", "RESOLVEIO_SUPPORT_QA_LIVE_MONGO_DB", "RESOLVEIO_RUNNER_QA_LIVE_MONGO_DB"]);',
|
|
692
|
+
' fs.mkdirSync(path.dirname(runtimeFile), { recursive: true });',
|
|
693
|
+
' fs.writeFileSync(runtimeFile, JSON.stringify({ mongo_uri: fallbackUri, mongo_db: fallbackDatabase || "" }, null, 2));',
|
|
694
|
+
' process.exit(0);',
|
|
695
|
+
'}',
|
|
696
|
+
'process.exit(0);',
|
|
697
|
+
'RESOLVEIO_QA_MONGO_RUNTIME',
|
|
698
|
+
' if [ -s "$runtime_file" ]; then export RESOLVEIO_QA_MONGO_RUNTIME_PATH="$runtime_file"; fi',
|
|
699
|
+
'}',
|
|
700
|
+
'prestart_seed_live_data() {',
|
|
701
|
+
' local required="${RESOLVEIO_RUNNER_QA_LIVE_DATA_REQUIRED:-${RESOLVEIO_SUPPORT_QA_LIVE_DATA_REQUIRED:-false}}"',
|
|
702
|
+
' truthy "$required" || return 0',
|
|
703
|
+
' [ -f "$TOOLS_DIR/qa-live-data-seed.js" ] || return 0',
|
|
704
|
+
' echo "ResolveIO AI runner QA pre-start live data seed/cleanup starting before app server startup." | tee -a "$ARTIFACT_DIR/runner.log"',
|
|
705
|
+
' if ! start_local_mongo_for_prestart_seed; then',
|
|
706
|
+
' echo "ResolveIO AI runner QA pre-start local Mongo did not become ready for live-data seed." | tee -a "$ARTIFACT_DIR/runner.log"',
|
|
707
|
+
' return 7',
|
|
708
|
+
' fi',
|
|
709
|
+
' materialize_live_mongo_runtime',
|
|
710
|
+
' node "$TOOLS_DIR/qa-live-data-seed.js" "$PROJECT_ROOT" >> "$ARTIFACT_DIR/runner.log" 2>&1',
|
|
711
|
+
' local seed_rc="$?"',
|
|
712
|
+
' if [ "$seed_rc" != "0" ]; then',
|
|
713
|
+
' echo "ResolveIO AI runner QA pre-start live data seed failed before app server startup (exit $seed_rc)." | tee -a "$ARTIFACT_DIR/runner.log"',
|
|
714
|
+
' return "$seed_rc"',
|
|
715
|
+
' fi',
|
|
716
|
+
' export RESOLVEIO_QA_PRESTART_MONGO_READY=1',
|
|
717
|
+
' return 0',
|
|
718
|
+
'}',
|
|
445
719
|
'if reuse_running_app_if_ready; then RUNNER_REUSED_READY=1; exit 0; fi',
|
|
446
720
|
'if ! mkdir "$LOCK_DIR" 2>/dev/null; then',
|
|
447
721
|
' if janitor_lock_is_live; then',
|
|
@@ -465,9 +739,13 @@ function buildResolveIORunnerLocalQaScript() {
|
|
|
465
739
|
'janitor_start_heartbeat',
|
|
466
740
|
'echo "$RUNNER_JANITOR_HEARTBEAT_PID" > "$ARTIFACT_DIR/heartbeat.pid"',
|
|
467
741
|
'janitor_check_resources || exit $?',
|
|
742
|
+
'prestart_seed_live_data || exit $?',
|
|
468
743
|
'if [ -d "$PROJECT_ROOT/server" ]; then',
|
|
469
744
|
' SERVER_REQUIRED=1',
|
|
470
|
-
' if
|
|
745
|
+
' if [ "${RESOLVEIO_QA_PRESTART_MONGO_READY:-}" = "1" ] && [ -x "$PROJECT_ROOT/server/node_modules/gulp/bin/gulp.js" ] && [ -f "$PROJECT_ROOT/server/gulpfile.js" ] && grep -q "mongo-start" "$PROJECT_ROOT/server/gulpfile.js"; then',
|
|
746
|
+
' echo "ResolveIO AI runner QA using pre-seeded local Mongo; starting server without mongo-start." | tee -a "$ARTIFACT_DIR/runner.log"',
|
|
747
|
+
' (cd "$PROJECT_ROOT/server" && source "$TOOLS_DIR/env.sh" && if [ -f "$HOME/.nvm/nvm.sh" ]; then source "$HOME/.nvm/nvm.sh"; nvm use 22 >/dev/null 2>&1 || true; fi; node_modules/gulp/bin/gulp.js 2>&1 | tee "$ARTIFACT_DIR/server.log") &',
|
|
748
|
+
' elif node -e "const p=require(process.argv[1]); process.exit(p.scripts&&p.scripts.server?0:1)" "$PROJECT_ROOT/server/package.json" >/dev/null 2>&1; then',
|
|
471
749
|
' (cd "$PROJECT_ROOT/server" && source "$TOOLS_DIR/env.sh" && npm run server 2>&1 | tee "$ARTIFACT_DIR/server.log") &',
|
|
472
750
|
' elif [ -x "$PROJECT_ROOT/server/start_server.sh" ]; then',
|
|
473
751
|
' (cd "$PROJECT_ROOT/server" && source "$TOOLS_DIR/env.sh" && ./start_server.sh 2>&1 | tee "$ARTIFACT_DIR/server.log") &',
|
|
@@ -681,6 +959,11 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
681
959
|
'const fs = require("fs");',
|
|
682
960
|
'const path = require("path");',
|
|
683
961
|
'',
|
|
962
|
+
'if (isBuildLane()) {',
|
|
963
|
+
' console.error("ResolveIO support lane guard: live-data seeding is owned by the QA lane, not the 5.3 build lane.");',
|
|
964
|
+
' process.exit(86);',
|
|
965
|
+
'}',
|
|
966
|
+
'',
|
|
684
967
|
'const projectRoot = path.resolve(process.argv[2] || process.cwd());',
|
|
685
968
|
'const repoRoot = findRepoRoot(projectRoot);',
|
|
686
969
|
'const artifactDir = path.join(projectRoot, "qa-artifacts");',
|
|
@@ -689,11 +972,16 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
689
972
|
'',
|
|
690
973
|
'function writeResult(payload, exitCode = 0) {',
|
|
691
974
|
' const full = { ...payload, created_at: new Date().toISOString() };',
|
|
975
|
+
' normalizeCoverageMatrixFromLiveSeed(full);',
|
|
692
976
|
' fs.writeFileSync(resultPath, JSON.stringify(full, null, 2));',
|
|
693
977
|
' console.log(JSON.stringify(full, null, 2));',
|
|
694
978
|
' process.exit(exitCode);',
|
|
695
979
|
'}',
|
|
696
980
|
'',
|
|
981
|
+
'function isBuildLane() {',
|
|
982
|
+
' return /^(build|support_build|support-build)$/i.test(String(process.env.RESOLVEIO_SUPPORT_CODEX_LANE || "").trim());',
|
|
983
|
+
'}',
|
|
984
|
+
'',
|
|
697
985
|
'function findRepoRoot(start) {',
|
|
698
986
|
' let current = start;',
|
|
699
987
|
' for (let i = 0; i < 8; i += 1) {',
|
|
@@ -709,13 +997,59 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
709
997
|
' try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { return null; }',
|
|
710
998
|
'}',
|
|
711
999
|
'',
|
|
1000
|
+
'function normalizeCoverageMatrixFromLiveSeed(payload) {',
|
|
1001
|
+
' const bolContext = payload && payload.selected && payload.selected.truck_treating_bol_context;',
|
|
1002
|
+
' const notes = Array.isArray(payload && payload.notes) ? payload.notes.map((note) => String(note || "")) : [];',
|
|
1003
|
+
' const billingDisabled = notes.some((note) => /billing fixtures are disabled/i.test(note));',
|
|
1004
|
+
' if (!bolContext || !billingDisabled) return;',
|
|
1005
|
+
' const matrixPath = path.join(artifactDir, "qa-coverage-matrix.json");',
|
|
1006
|
+
' const matrix = readJsonIfExists(matrixPath) || { status: "started", seeded: true, source: "qa-live-data-seed" };',
|
|
1007
|
+
' const browserRoutes = bolContext.browser_routes && typeof bolContext.browser_routes === "object" ? bolContext.browser_routes : {};',
|
|
1008
|
+
' const routeHints = Array.isArray(bolContext.route_name_hints) ? bolContext.route_name_hints.map((value) => String(value || "").trim()).filter(Boolean) : [];',
|
|
1009
|
+
' const linkedRoutes = Array.isArray(bolContext.linked_truck_treating_routes) ? bolContext.linked_truck_treating_routes.map((value) => String(value || "").trim()).filter(Boolean) : [];',
|
|
1010
|
+
' const bolIds = Array.isArray(bolContext.bol_ids) ? bolContext.bol_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
|
|
1011
|
+
' const routeIds = Array.isArray(bolContext.route_ids) ? bolContext.route_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
|
|
1012
|
+
' const truckTreatingRouteIds = Array.isArray(bolContext.truck_treating_route_ids) ? bolContext.truck_treating_route_ids.map((value) => String(value || "").trim()).filter(Boolean) : [];',
|
|
1013
|
+
' const primaryStatus = String(bolContext.primary_bol_status || "");',
|
|
1014
|
+
' const delivered = Boolean(bolContext.primary_bol_delivered) || /delivered/i.test(primaryStatus);',
|
|
1015
|
+
' const hintedBol = routeHints.join(" ");',
|
|
1016
|
+
' const bolNumber = (/\\bBOL\\s*#?\\s*(\\d+)\\b/i.exec(hintedBol) || [])[1];',
|
|
1017
|
+
' const bolLabel = bolNumber ? `BOL ${bolNumber}` : (bolIds[0] ? `BOL ${bolIds[0]}` : "the requested BOL");',
|
|
1018
|
+
' const routeHint = routeHints.find((hint) => !/\\bBOL\\b/i.test(hint) && !/^\\d+$/.test(hint)) || "the ticket route hint";',
|
|
1019
|
+
' const routeSummary = linkedRoutes.length ? linkedRoutes.join(", ") : (truckTreatingRouteIds.length ? truckTreatingRouteIds.join(", ") : routeIds.slice(0, 6).join(", "));',
|
|
1020
|
+
' const deliveredNote = delivered ? ` The live BOL status is ${primaryStatus || "Delivered"}, so the direct delivery route is an action route that may correctly redirect; QA must use delivered Open File/detail proof or a localhost-only deliverable fixture copied from this live context for Treat/Deliver actions.` : "";',
|
|
1021
|
+
' const liveAssertion = `Live Mongo seeded ${bolLabel} and proved ${routeHint} is not a linked truck-treating route for that BOL; QA must continue against linked live route data: ${routeSummary}.${deliveredNote}`;',
|
|
1022
|
+
' matrix.status = "in_progress";',
|
|
1023
|
+
' matrix.source = "qa-live-data-seed-live-bol-context";',
|
|
1024
|
+
' matrix.live_seed_context_artifact = "qa-artifacts/qa-live-data-seed-result.json";',
|
|
1025
|
+
' matrix.billing_rows_removed_reason = "Live data seed disabled billing fixtures for this ticket; invoice/billing matrix rows do not apply to truck-treating BOL QA.";',
|
|
1026
|
+
' matrix.rows = [',
|
|
1027
|
+
' { workflow: "Truck treating BOL live-data target and route validation", route: browserRoutes.delivery || browserRoutes.detail || browserRoutes.list || "Deliver BOL live seeded BOL context", assertion: liveAssertion, required_proof: "Live Mongo seed artifact showing BOL id/status, linked route ids, ticket route-hint match result, and browser route.", status: "pass", result: "pass", screenshot: "", caption: `Live data seeded ${bolLabel}; the ticket route label did not match the BOL-linked live routes, so QA continued against ${routeSummary}.`, artifact: "qa-artifacts/qa-live-data-seed-result.json", persisted_assertion: `qa-live-data-seed-result.json route_hint_matched=${bolContext.route_hint_matched}; bol_ids=${bolIds.join(",") || "none"}; route_ids=${routeIds.length}; truck_treating_route_ids=${truckTreatingRouteIds.length}; primary_bol_status=${primaryStatus || "unknown"}.` },',
|
|
1028
|
+
' { workflow: "Delivered BOL detail/Open File proof", route: browserRoutes.detail || browserRoutes.list || browserRoutes.delivery || "/", assertion: `Open the seeded ${bolLabel} in the truck-treating delivered/detail workflow and prove the local QA database loaded the customer-facing BOL screen without relying on the already-delivered action route.`, required_proof: "Customer-facing screenshot of the BOL detail/list/Open File screen plus BOL id/status assertion from local Mongo.", status: "pending", screenshot: "", caption: "" },',
|
|
1029
|
+
' { workflow: "Deliver BOL service/flush rows do not render as missing chemicals", route: browserRoutes.delivery || browserRoutes.detail || "/", assertion: "Service/flush/non-chemical treatment plans must not be injected into the Deliver BOL chemical list as red missing chemical placeholders; chemical treatment rows still appear normally.", required_proof: "Customer-facing screenshot of the Deliver BOL/detail workflow or a localhost-only deliverable fixture copied from the seeded live BOL context, plus DOM/local Mongo assertion that missing placeholders are limited to Chemical treatment plans.", status: "pending", screenshot: "", caption: "" }',
|
|
1030
|
+
' ];',
|
|
1031
|
+
' fs.writeFileSync(matrixPath, JSON.stringify(matrix, null, 2));',
|
|
1032
|
+
'}',
|
|
1033
|
+
'',
|
|
712
1034
|
'function preserveExistingSeedResult(reason) {',
|
|
713
1035
|
' const existing = readJsonIfExists(resultPath);',
|
|
714
1036
|
' const status = String(existing && existing.status || "").toLowerCase();',
|
|
715
|
-
'
|
|
1037
|
+
' const desiredProfile = shouldSeedBillingDashboardFixtures() ? "billing_inventory" : "live_context";',
|
|
1038
|
+
' if (existing && existing.profile && existing.profile !== desiredProfile) return;',
|
|
1039
|
+
' if (existing && existing.profile === "billing_inventory" && shouldSeedBillingDashboardFixtures() && !(existing.selected && existing.selected.qa_billing_fixture)) {',
|
|
1040
|
+
' return;',
|
|
1041
|
+
' }',
|
|
1042
|
+
' if (existing && extractBolIdentifiers().length && !(existing.selected && existing.selected.truck_treating_bol_context)) {',
|
|
1043
|
+
' return;',
|
|
1044
|
+
' }',
|
|
1045
|
+
' if (existing && extractBolIdentifiers().length && existing.selected && existing.selected.truck_treating_bol_context && !(existing.selected.truck_treating_bol_context.browser_routes && existing.selected.truck_treating_bol_context.browser_routes.delivery)) {',
|
|
1046
|
+
' return;',
|
|
1047
|
+
' }',
|
|
1048
|
+
' if (existing && extractBolIdentifiers().length && existing.selected && existing.selected.truck_treating_bol_context && !Object.prototype.hasOwnProperty.call(existing.selected.truck_treating_bol_context, "primary_bol_status")) {',
|
|
716
1049
|
' return;',
|
|
717
1050
|
' }',
|
|
718
1051
|
' if (["pass", "needs-data"].includes(status)) {',
|
|
1052
|
+
' normalizeCoverageMatrixFromLiveSeed(existing);',
|
|
719
1053
|
' const preserved = { ...existing, reused_existing: true, reuse_reason: reason, checked_at: new Date().toISOString() };',
|
|
720
1054
|
' console.log(JSON.stringify(preserved, null, 2));',
|
|
721
1055
|
' process.exit(0);',
|
|
@@ -723,9 +1057,6 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
723
1057
|
'}',
|
|
724
1058
|
'',
|
|
725
1059
|
'function resolveRuntimeSource() {',
|
|
726
|
-
' const envUri = process.env.RESOLVEIO_QA_LIVE_MONGO_URL || process.env.RESOLVEIO_SUPPORT_QA_LIVE_MONGO_URL || process.env.RESOLVEIO_RUNNER_QA_LIVE_MONGO_URL || "";',
|
|
727
|
-
' const envDb = process.env.RESOLVEIO_QA_LIVE_MONGO_DB || process.env.RESOLVEIO_SUPPORT_QA_LIVE_MONGO_DB || process.env.RESOLVEIO_RUNNER_QA_LIVE_MONGO_DB || "";',
|
|
728
|
-
' if (envUri) return { uri: envUri, database: envDb };',
|
|
729
1060
|
' const explicitRuntimePaths = [',
|
|
730
1061
|
' process.env.RESOLVEIO_QA_MONGO_RUNTIME_PATH,',
|
|
731
1062
|
' process.env.RESOLVEIO_SUPPORT_QA_MONGO_RUNTIME_PATH,',
|
|
@@ -744,6 +1075,9 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
744
1075
|
' const parsed = readJsonIfExists(candidate);',
|
|
745
1076
|
' if (parsed && parsed.mongo_uri) return { uri: String(parsed.mongo_uri || ""), database: String(parsed.mongo_db || "") };',
|
|
746
1077
|
' }',
|
|
1078
|
+
' const envUri = process.env.RESOLVEIO_QA_LIVE_MONGO_URL || process.env.RESOLVEIO_SUPPORT_QA_LIVE_MONGO_URL || process.env.RESOLVEIO_RUNNER_QA_LIVE_MONGO_URL || "";',
|
|
1079
|
+
' const envDb = process.env.RESOLVEIO_QA_LIVE_MONGO_DB || process.env.RESOLVEIO_SUPPORT_QA_LIVE_MONGO_DB || process.env.RESOLVEIO_RUNNER_QA_LIVE_MONGO_DB || "";',
|
|
1080
|
+
' if (envUri) return { uri: envUri, database: envDb };',
|
|
747
1081
|
' return { uri: "", database: "" };',
|
|
748
1082
|
'}',
|
|
749
1083
|
'',
|
|
@@ -759,6 +1093,40 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
759
1093
|
' return /^(true|1|yes|on)$/i.test(String(process.env.RESOLVEIO_QA_LIVE_DATA_REQUIRED || process.env.RESOLVEIO_SUPPORT_QA_LIVE_DATA_REQUIRED || process.env.RESOLVEIO_RUNNER_QA_LIVE_DATA_REQUIRED || ""));',
|
|
760
1094
|
'}',
|
|
761
1095
|
'',
|
|
1096
|
+
'function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }',
|
|
1097
|
+
'',
|
|
1098
|
+
'async function waitForTargetMongo(MongoClient, targetUri) {',
|
|
1099
|
+
' const deadline = Date.now() + Number(process.env.RESOLVEIO_QA_LOCAL_MONGO_WAIT_MS || process.env.RESOLVEIO_SUPPORT_QA_LOCAL_MONGO_WAIT_MS || process.env.RESOLVEIO_RUNNER_QA_LOCAL_MONGO_WAIT_MS || 60000);',
|
|
1100
|
+
' let lastError = null;',
|
|
1101
|
+
' while (Date.now() < deadline) {',
|
|
1102
|
+
' const client = new MongoClient(targetUri, { serverSelectionTimeoutMS: 2500 });',
|
|
1103
|
+
' try {',
|
|
1104
|
+
' await client.connect();',
|
|
1105
|
+
' const admin = client.db("admin");',
|
|
1106
|
+
' await admin.command({ ping: 1 });',
|
|
1107
|
+
' let hello = null;',
|
|
1108
|
+
' try { hello = await admin.command({ hello: 1 }); } catch (error) {',
|
|
1109
|
+
' try { hello = await admin.command({ isMaster: 1 }); } catch (inner) { hello = null; }',
|
|
1110
|
+
' }',
|
|
1111
|
+
' const writable = !hello || hello.isWritablePrimary === true || hello.ismaster === true || !hello.setName;',
|
|
1112
|
+
' if (!writable) {',
|
|
1113
|
+
' lastError = new Error(`target_local_mongo_not_primary: ${hello && (hello.primary || hello.me || hello.setName) || "unknown"}`);',
|
|
1114
|
+
' await client.close().catch(() => undefined);',
|
|
1115
|
+
' await delay(1500);',
|
|
1116
|
+
' continue;',
|
|
1117
|
+
' }',
|
|
1118
|
+
' await client.close().catch(() => undefined);',
|
|
1119
|
+
' return;',
|
|
1120
|
+
' } catch (error) {',
|
|
1121
|
+
' lastError = error;',
|
|
1122
|
+
' await client.close().catch(() => undefined);',
|
|
1123
|
+
' await delay(1500);',
|
|
1124
|
+
' }',
|
|
1125
|
+
' }',
|
|
1126
|
+
' const detail = lastError && (lastError.message || String(lastError)) || "local Mongo did not become ready";',
|
|
1127
|
+
' throw new Error(`target_local_mongo_unavailable: ${detail}`);',
|
|
1128
|
+
'}',
|
|
1129
|
+
'',
|
|
762
1130
|
'function requireMongo() {',
|
|
763
1131
|
' const candidates = [',
|
|
764
1132
|
' path.join(projectRoot, "server", "node_modules", "mongodb"),',
|
|
@@ -801,6 +1169,43 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
801
1169
|
' return chunks.filter(Boolean).join("\\n");',
|
|
802
1170
|
'}',
|
|
803
1171
|
'',
|
|
1172
|
+
'function isUsefulSeedIdentifier(value) {',
|
|
1173
|
+
' const cleaned = String(value || "").trim().replace(/^[#:\\-]+|[.,;:)\\]]+$/g, "");',
|
|
1174
|
+
' if (!cleaned) return false;',
|
|
1175
|
+
' if (/^00?4\\d{3}$/.test(cleaned)) return false;',
|
|
1176
|
+
' if (/^(screen|tab|are|which|and|render|context|workflow|build|snapshot|state|charge|plan|data|document|delivery|deliveries)$/i.test(cleaned)) return false;',
|
|
1177
|
+
' return /\\d/.test(cleaned) || /^[A-Z][A-Z0-9._-]{3,}$/.test(cleaned);',
|
|
1178
|
+
'}',
|
|
1179
|
+
'',
|
|
1180
|
+
'function extractBolIdentifiers() {',
|
|
1181
|
+
' const text = readSeedHintText();',
|
|
1182
|
+
' const identifiers = new Set();',
|
|
1183
|
+
' const explicit = /\\bBOL\\s*(?:#|no\\.?|number|num)?\\s*[:#-]?\\s*([A-Z0-9][A-Z0-9._-]{2,})\\b/gi;',
|
|
1184
|
+
' let match;',
|
|
1185
|
+
' while ((match = explicit.exec(text)) !== null) {',
|
|
1186
|
+
' const value = String(match[1] || "").trim();',
|
|
1187
|
+
' if (isUsefulSeedIdentifier(value)) identifiers.add(value);',
|
|
1188
|
+
' }',
|
|
1189
|
+
' return Array.from(identifiers).slice(0, 8);',
|
|
1190
|
+
'}',
|
|
1191
|
+
'',
|
|
1192
|
+
'function shouldAutoDiscoverTruckTreatingBolContext() {',
|
|
1193
|
+
' const text = readSeedHintText();',
|
|
1194
|
+
' return /\\b(deliver(?:y|ed|ies)?|bol|bill\\s+of\\s+lading|truck\\s*treat(?:ing)?|flush(?:es)?|selected\\s+treatments?|chemical|red)\\b/i.test(text);',
|
|
1195
|
+
'}',
|
|
1196
|
+
'',
|
|
1197
|
+
'function extractRouteNameHints() {',
|
|
1198
|
+
' const text = readSeedHintText();',
|
|
1199
|
+
' const routes = new Set();',
|
|
1200
|
+
' const routePattern = /\\b(?:on|for)\\s+(?:the\\s+)?([A-Za-z0-9][A-Za-z0-9 &._/-]{1,48}?)\\s+route\\b/gi;',
|
|
1201
|
+
' let match;',
|
|
1202
|
+
' while ((match = routePattern.exec(text)) !== null) {',
|
|
1203
|
+
' const routeName = String(match[1] || "").trim().replace(/^(?:the|a|an)\\s+/i, "").replace(/\\s+/g, " ");',
|
|
1204
|
+
' if (routeName && !/^(?:this|that|selected)$/i.test(routeName)) routes.add(routeName);',
|
|
1205
|
+
' }',
|
|
1206
|
+
' return Array.from(routes).slice(0, 8);',
|
|
1207
|
+
'}',
|
|
1208
|
+
'',
|
|
804
1209
|
'function extractSeedIdentifiers() {',
|
|
805
1210
|
' const text = readSeedHintText();',
|
|
806
1211
|
' const identifiers = new Set();',
|
|
@@ -811,10 +1216,18 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
811
1216
|
' while ((match = numeric.exec(text)) !== null) identifiers.add(match[0]);',
|
|
812
1217
|
' return Array.from(identifiers)',
|
|
813
1218
|
' .map((value) => String(value || "").trim())',
|
|
814
|
-
' .filter((value) =>
|
|
1219
|
+
' .filter((value) => isUsefulSeedIdentifier(value))',
|
|
815
1220
|
' .slice(0, 20);',
|
|
816
1221
|
'}',
|
|
817
1222
|
'',
|
|
1223
|
+
'function shouldSeedBillingDashboardFixtures() {',
|
|
1224
|
+
' const explicit = process.env.RESOLVEIO_QA_INCLUDE_BILLING_FIXTURES || process.env.RESOLVEIO_SUPPORT_QA_INCLUDE_BILLING_FIXTURES || process.env.RESOLVEIO_RUNNER_QA_INCLUDE_BILLING_FIXTURES || "";',
|
|
1225
|
+
' if (/^(true|1|yes|on)$/i.test(explicit)) return true;',
|
|
1226
|
+
' if (/^(false|0|no|off)$/i.test(explicit)) return false;',
|
|
1227
|
+
' const text = readSeedHintText();',
|
|
1228
|
+
' return /\\b(invoice|invoicing|billing|billable|tax|taxes|surcharge|pricing|line\\s*item|line\\s*items|inventory|checkout|check\\s*out|pick\\s*ticket)\\b/i.test(text);',
|
|
1229
|
+
'}',
|
|
1230
|
+
'',
|
|
818
1231
|
'function identifierQuery(identifiers) {',
|
|
819
1232
|
' const ors = [];',
|
|
820
1233
|
' const fields = ["_id", "invoice_number", "invoice_number_string", "order_number", "order_number_string", "activity_number", "bol_number", "bol_string", "ticket_number", "ticket", "wo_number", "work_order_number", "delivery_number", "treatment_number"];',
|
|
@@ -846,6 +1259,164 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
846
1259
|
' return docs;',
|
|
847
1260
|
'}',
|
|
848
1261
|
'',
|
|
1262
|
+
'function bolLookupQuery(identifiers) {',
|
|
1263
|
+
' const ors = [];',
|
|
1264
|
+
' for (const identifier of identifiers || []) {',
|
|
1265
|
+
' const value = String(identifier || "").trim();',
|
|
1266
|
+
' if (!value) continue;',
|
|
1267
|
+
' const escaped = value.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");',
|
|
1268
|
+
' const contains = new RegExp(`(^|[^0-9A-Za-z])${escaped}([^0-9A-Za-z]|$)`, "i");',
|
|
1269
|
+
' ors.push({ _id: value }, { bol_number: value }, { bol_string: value }, { bol_number: contains }, { bol_string: contains });',
|
|
1270
|
+
' if (/^\\d+$/.test(value)) ors.push({ bol_number: Number(value) });',
|
|
1271
|
+
' }',
|
|
1272
|
+
' return ors.length ? { $or: ors } : null;',
|
|
1273
|
+
'}',
|
|
1274
|
+
'',
|
|
1275
|
+
'async function discoverTruckTreatingBolIds(sourceDb, routeHints, limit = 5) {',
|
|
1276
|
+
' const routeRegexes = (routeHints || []).map((hint) => new RegExp(String(hint).replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&"), "i"));',
|
|
1277
|
+
' const routeHintMatch = routeRegexes.length ? { $or: [',
|
|
1278
|
+
' { location: { $in: routeRegexes } },',
|
|
1279
|
+
' { well_group: { $in: routeRegexes } },',
|
|
1280
|
+
' { route: { $in: routeRegexes } },',
|
|
1281
|
+
' { route_name: { $in: routeRegexes } }',
|
|
1282
|
+
' ] } : null;',
|
|
1283
|
+
' const baseMatch = {',
|
|
1284
|
+
' id_bol: { $type: "string", $ne: "" },',
|
|
1285
|
+
' items: { $elemMatch: { item_type: { $in: ["Service", "Flush"] } } }',
|
|
1286
|
+
' };',
|
|
1287
|
+
' const pipeline = [',
|
|
1288
|
+
' { $match: routeHintMatch ? { $and: [baseMatch, routeHintMatch] } : baseMatch },',
|
|
1289
|
+
' { $sort: { date: -1, date_ship: -1, updatedAt: -1 } },',
|
|
1290
|
+
' { $group: { _id: "$id_bol", route_count: { $sum: 1 }, latest: { $max: "$updatedAt" } } },',
|
|
1291
|
+
' { $sort: { latest: -1, route_count: -1 } },',
|
|
1292
|
+
' { $limit: limit },',
|
|
1293
|
+
' { $project: { _id: 1 } }',
|
|
1294
|
+
' ];',
|
|
1295
|
+
' const rows = await sourceDb.collection("production-routes").aggregate(pipeline).toArray();',
|
|
1296
|
+
' return rows.map((row) => row && row._id).filter(Boolean);',
|
|
1297
|
+
'}',
|
|
1298
|
+
'',
|
|
1299
|
+
'function collectNestedStringValues(docs, keys) {',
|
|
1300
|
+
' const values = new Set();',
|
|
1301
|
+
' const visit = (value) => {',
|
|
1302
|
+
' if (!value) return;',
|
|
1303
|
+
' if (Array.isArray(value)) { value.forEach(visit); return; }',
|
|
1304
|
+
' if (typeof value === "object") {',
|
|
1305
|
+
' for (const key of keys) {',
|
|
1306
|
+
' const entry = value[key];',
|
|
1307
|
+
' if (typeof entry === "string" && entry.trim()) values.add(entry.trim());',
|
|
1308
|
+
' else if (Array.isArray(entry) || (entry && typeof entry === "object")) visit(entry);',
|
|
1309
|
+
' }',
|
|
1310
|
+
' for (const nested of Object.values(value)) if (Array.isArray(nested)) visit(nested);',
|
|
1311
|
+
' }',
|
|
1312
|
+
' };',
|
|
1313
|
+
' visit(docs || []);',
|
|
1314
|
+
' return Array.from(values);',
|
|
1315
|
+
'}',
|
|
1316
|
+
'',
|
|
1317
|
+
'async function copyTruckTreatingBolContext(sourceDb, targetDb, summary) {',
|
|
1318
|
+
' const bolIdentifiers = extractBolIdentifiers();',
|
|
1319
|
+
' const routeHints = extractRouteNameHints();',
|
|
1320
|
+
' summary.selected.bol_identifiers = bolIdentifiers;',
|
|
1321
|
+
' if (routeHints.length) summary.selected.route_name_hints = routeHints;',
|
|
1322
|
+
' let query = bolLookupQuery(bolIdentifiers);',
|
|
1323
|
+
' let discoveryMode = "";',
|
|
1324
|
+
' if (!query && shouldAutoDiscoverTruckTreatingBolContext()) {',
|
|
1325
|
+
' const discoveredBolIds = await discoverTruckTreatingBolIds(sourceDb, routeHints, 8);',
|
|
1326
|
+
' if (discoveredBolIds.length) {',
|
|
1327
|
+
' summary.selected.discovered_truck_treating_bol_ids = discoveredBolIds;',
|
|
1328
|
+
' query = { _id: { $in: discoveredBolIds } };',
|
|
1329
|
+
' discoveryMode = "auto_discovered_from_live_production_routes";',
|
|
1330
|
+
' }',
|
|
1331
|
+
' }',
|
|
1332
|
+
' if (!query) return [];',
|
|
1333
|
+
' let bols = await copyQuery(sourceDb, targetDb, "bols", query, summary, 8, { date_shipped: -1, date: -1, updatedAt: -1 });',
|
|
1334
|
+
' if (!bols.length) {',
|
|
1335
|
+
' summary.notes.push(`No live BOL matched ticket BOL identifiers: ${bolIdentifiers.join(", ")}`);',
|
|
1336
|
+
' if (shouldAutoDiscoverTruckTreatingBolContext()) {',
|
|
1337
|
+
' const discoveredBolIds = await discoverTruckTreatingBolIds(sourceDb, routeHints, 8);',
|
|
1338
|
+
' if (discoveredBolIds.length) {',
|
|
1339
|
+
' summary.selected.discovered_truck_treating_bol_ids = discoveredBolIds;',
|
|
1340
|
+
' discoveryMode = "auto_discovered_after_identifier_miss";',
|
|
1341
|
+
' bols = await copyQuery(sourceDb, targetDb, "bols", { _id: { $in: discoveredBolIds } }, summary, 8, { date_shipped: -1, date: -1, updatedAt: -1 });',
|
|
1342
|
+
' }',
|
|
1343
|
+
' }',
|
|
1344
|
+
' if (!bols.length) return [];',
|
|
1345
|
+
' }',
|
|
1346
|
+
' const bolIds = bols.map((doc) => doc._id).filter(Boolean);',
|
|
1347
|
+
' const productionRoutes = await copyQuery(sourceDb, targetDb, "production-routes", { id_bol: { $in: bolIds } }, summary, 600, { route_order: 1, route_number: 1 });',
|
|
1348
|
+
' const routeIds = productionRoutes.map((doc) => doc._id).filter(Boolean);',
|
|
1349
|
+
' const truckTreatingRouteIds = idValues(productionRoutes, ["id_truck_treating_route"]);',
|
|
1350
|
+
' const productionSalesOrderIds = idValues(productionRoutes, ["id_pso", "id_activity", "id_production_sales_order", "id_sales_order"]);',
|
|
1351
|
+
' const productionDeliveries = await copyQuery(sourceDb, targetDb, "production-deliveries", { $or: [{ id_bol: { $in: bolIds } }, { id_activity: { $in: productionSalesOrderIds } }] }, summary, 600, { route_order: 1, updatedAt: -1 });',
|
|
1352
|
+
' const routeTruckTreatDeliveryIds = collectNestedStringValues(productionRoutes, ["id_truck_treat_delivery", "id_truck_treating_delivery"]);',
|
|
1353
|
+
' const truckTreatingDeliveries = await copyQuery(sourceDb, targetDb, "truck-treating-deliveries", { $or: [{ id_bol: { $in: bolIds } }, { _id: { $in: routeTruckTreatDeliveryIds } }] }, summary, 600, { route_order: 1, updatedAt: -1 });',
|
|
1354
|
+
' const copiedTruckTreatingRoutes = await copyByIds(sourceDb, targetDb, "truck-treating-routes", truckTreatingRouteIds, summary, 200);',
|
|
1355
|
+
' await copyByIds(sourceDb, targetDb, "production-sales-orders", productionSalesOrderIds, summary, 200);',
|
|
1356
|
+
' const locationIds = unique([...idValues(productionRoutes, ["id_location"]), ...idValues(productionDeliveries, ["id_location"]), ...idValues(truckTreatingDeliveries, ["id_location"])]);',
|
|
1357
|
+
' const wellGroupIds = unique([...idValues(productionRoutes, ["id_well_group"]), ...idValues(productionDeliveries, ["id_well_group"]), ...idValues(truckTreatingDeliveries, ["id_well_group"])]);',
|
|
1358
|
+
' const copiedLocations = await copyByIds(sourceDb, targetDb, "production-locations", locationIds, summary, 300);',
|
|
1359
|
+
' const copiedWellGroups = await copyByIds(sourceDb, targetDb, "well-groups", wellGroupIds, summary, 300);',
|
|
1360
|
+
' const copiedTreatmentPlans = await copyQuery(sourceDb, targetDb, "treatment-plans", { $or: [',
|
|
1361
|
+
' { "truck_treating.id_truck_treating_route": { $in: truckTreatingRouteIds } },',
|
|
1362
|
+
' { id_location: { $in: locationIds } },',
|
|
1363
|
+
' { id_well_group: { $in: wellGroupIds } }',
|
|
1364
|
+
' ] }, summary, 800, { route_order: 1, updatedAt: -1 });',
|
|
1365
|
+
' const itemIds = unique([',
|
|
1366
|
+
' ...collectNestedStringValues(productionRoutes, ["id_item", "id_chemical"]),',
|
|
1367
|
+
' ...idValues(productionDeliveries, ["id_item", "id_chemical"]),',
|
|
1368
|
+
' ...idValues(truckTreatingDeliveries, ["id_item", "id_chemical"]),',
|
|
1369
|
+
' ...idValues(copiedTreatmentPlans, ["id_item", "id_chemical"])',
|
|
1370
|
+
' ]);',
|
|
1371
|
+
' await copyByIds(sourceDb, targetDb, "items", itemIds, summary, 300);',
|
|
1372
|
+
' await copyByIds(sourceDb, targetDb, "chemicals", itemIds, summary, 300);',
|
|
1373
|
+
' await copyByIds(sourceDb, targetDb, "customers", unique([...idValues(copiedLocations, ["id_customer"]), ...idValues(copiedWellGroups, ["id_customer"]), ...idValues(productionDeliveries, ["id_customer"]), ...idValues(truckTreatingDeliveries, ["id_customer"])]), summary, 100);',
|
|
1374
|
+
' await copyByIds(sourceDb, targetDb, "users", unique([...idValues(bols, ["id_user", "id_driver"]), ...idValues(productionDeliveries, ["id_user_approved", "id_driver", "id_driver_scheduled", "id_account_manager", "id_foreman"])]), summary, 100);',
|
|
1375
|
+
' await copyQuery(sourceDb, targetDb, "chemical-field-transfers", { id_bol: { $in: bolIds } }, summary, 200, { updatedAt: -1 });',
|
|
1376
|
+
' await copyQuery(sourceDb, targetDb, "generic-samples", { id_bol: { $in: bolIds } }, summary, 200, { updatedAt: -1 });',
|
|
1377
|
+
' const linkedRouteNames = copiedTruckTreatingRoutes.map((doc) => String(doc.name || doc.route || doc.truck_treating_route || doc._id || "")).filter(Boolean);',
|
|
1378
|
+
' const routeHintMatched = routeHints.length ? routeHints.some((hint) => linkedRouteNames.some((name) => name.toLowerCase().includes(hint.toLowerCase()))) : null;',
|
|
1379
|
+
' const driverIds = unique([...idValues(bols, ["id_driver", "id_user"]), ...idValues(productionDeliveries, ["id_driver", "id_driver_scheduled", "id_user_approved"]), ...idValues(truckTreatingDeliveries, ["id_driver", "id_driver_scheduled", "id_user_approved"])]);',
|
|
1380
|
+
' const bolStatuses = bols.map((doc) => ({',
|
|
1381
|
+
' id: String(doc && doc._id || ""),',
|
|
1382
|
+
' bol_number: String(doc && (doc.bol_string || doc.bol_number || "") || ""),',
|
|
1383
|
+
' status: String(doc && doc.status || ""),',
|
|
1384
|
+
' delivered: /delivered/i.test(String(doc && doc.status || ""))',
|
|
1385
|
+
' })).filter((entry) => entry.id);',
|
|
1386
|
+
' const primaryDriverId = driverIds[0] || "";',
|
|
1387
|
+
' const primaryBolId = bolIds[0] || "";',
|
|
1388
|
+
' const primaryBolStatus = String((bolStatuses.find((entry) => entry.id === primaryBolId) || bolStatuses[0] || {}).status || "");',
|
|
1389
|
+
' const primaryBolDelivered = /delivered/i.test(primaryBolStatus);',
|
|
1390
|
+
' const browserRoutes = primaryDriverId && primaryBolId ? {',
|
|
1391
|
+
' delivery: `/dashboard/driver/truck-treating/bol-delivery/${primaryDriverId}/${primaryBolId}`,',
|
|
1392
|
+
' detail: `/dashboard/driver/truck-treating/bol-detail/${primaryDriverId}/${primaryBolId}`,',
|
|
1393
|
+
' list: `/dashboard/driver/truck-treating/bol-list/${primaryDriverId}`',
|
|
1394
|
+
' } : {};',
|
|
1395
|
+
' summary.selected.truck_treating_bol_context = {',
|
|
1396
|
+
' bol_ids: bolIds,',
|
|
1397
|
+
' driver_ids: driverIds,',
|
|
1398
|
+
' route_ids: routeIds.slice(0, 100),',
|
|
1399
|
+
' route_count: productionRoutes.length,',
|
|
1400
|
+
' truck_treating_route_ids: truckTreatingRouteIds,',
|
|
1401
|
+
' linked_truck_treating_routes: linkedRouteNames,',
|
|
1402
|
+
' browser_routes: browserRoutes,',
|
|
1403
|
+
' bol_statuses: bolStatuses,',
|
|
1404
|
+
' primary_bol_status: primaryBolStatus,',
|
|
1405
|
+
' primary_bol_delivered: primaryBolDelivered,',
|
|
1406
|
+
' delivery_route_is_action_route: true,',
|
|
1407
|
+
' delivery_route_requires_not_delivered_bol: true,',
|
|
1408
|
+
' route_name_hints: routeHints,',
|
|
1409
|
+
' route_hint_matched: routeHintMatched',
|
|
1410
|
+
' };',
|
|
1411
|
+
' if (discoveryMode) summary.selected.truck_treating_bol_context.discovery_mode = discoveryMode;',
|
|
1412
|
+
' if (browserRoutes.delivery) summary.notes.push(`Derived truck-treating BOL browser target from live Mongo: ${browserRoutes.delivery}`);',
|
|
1413
|
+
' else summary.notes.push("Could not derive truck-treating BOL browser route because live BOL context did not include both id_driver and id_bol.");',
|
|
1414
|
+
' if (primaryBolDelivered) summary.notes.push(`Live BOL ${primaryBolId} is already Delivered; direct bol-delivery route is an action route and may correctly redirect. QA should use delivered Open File/detail proof or create a localhost-only deliverable fixture from the fetched live context before testing Treat/Deliver actions.`);',
|
|
1415
|
+
' if (routeHints.length && routeHintMatched === false) summary.notes.push(`Ticket route hint(s) ${routeHints.join(", ")} did not match linked live BOL route(s): ${linkedRouteNames.join(", ") || "none"}. QA should use linked live BOL routes and report the mismatch as data evidence, not synthesize a route.`);',
|
|
1416
|
+
' summary.notes.push(`Seeded live truck-treating BOL context for ${bolIdentifiers.join(", ")}: ${bols.length} BOL(s), ${productionRoutes.length} production route(s), ${copiedTreatmentPlans.length} treatment plan(s).`);',
|
|
1417
|
+
' return [...bols, ...productionRoutes, ...productionDeliveries, ...truckTreatingDeliveries, ...copiedTreatmentPlans];',
|
|
1418
|
+
'}',
|
|
1419
|
+
'',
|
|
849
1420
|
'async function ensureBillingSurchargePricingFixtures(targetDb, summary, serviceItems, customerIds) {',
|
|
850
1421
|
' const now = new Date();',
|
|
851
1422
|
' const activeServiceItems = (serviceItems || []).filter((doc) => {',
|
|
@@ -885,6 +1456,21 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
885
1456
|
' if (pricingDocs.length) summary.notes.push(`Ensured ${pricingDocs.length} local pricing-items for service/misc surcharge override/default QA.`);',
|
|
886
1457
|
'}',
|
|
887
1458
|
'',
|
|
1459
|
+
'async function cleanupSyntheticBillingDashboardQaFixtures(targetDb, summary) {',
|
|
1460
|
+
' const fixtureIdQuery = { _id: /^qa-billing-/ };',
|
|
1461
|
+
' const fixtureCollections = ["customers", "yards", "items", "production-locations", "bols", "production-sales-orders", "production-deliveries", "invoices"];',
|
|
1462
|
+
' let removed = 0;',
|
|
1463
|
+
' for (const collectionName of fixtureCollections) {',
|
|
1464
|
+
' const result = await targetDb.collection(collectionName).deleteMany(fixtureIdQuery).catch(() => ({ deletedCount: 0 }));',
|
|
1465
|
+
' removed += Number(result && result.deletedCount || 0);',
|
|
1466
|
+
' }',
|
|
1467
|
+
' const invoiceResult = await targetDb.collection("invoices").deleteMany({ $or: [{ invoice_string: /^QA-INV-/ }, { invoice_number: 4333 }] }).catch(() => ({ deletedCount: 0 }));',
|
|
1468
|
+
' removed += Number(invoiceResult && invoiceResult.deletedCount || 0);',
|
|
1469
|
+
' const psoResult = await targetDb.collection("production-sales-orders").deleteMany({ $or: [{ order_number_string: /^QA-PSO-/ }, { activity_number: /^QA-PSO-/ }] }).catch(() => ({ deletedCount: 0 }));',
|
|
1470
|
+
' removed += Number(psoResult && psoResult.deletedCount || 0);',
|
|
1471
|
+
' if (removed) summary.notes.push(`Removed ${removed} stale synthetic billing dashboard QA fixture document(s) from the local Mongo profile.`);',
|
|
1472
|
+
'}',
|
|
1473
|
+
'',
|
|
888
1474
|
'async function ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, serviceItems) {',
|
|
889
1475
|
' const now = new Date();',
|
|
890
1476
|
' const customerId = (customerIds && customerIds[0]) || "qa-billing-customer";',
|
|
@@ -990,9 +1576,15 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
990
1576
|
'}',
|
|
991
1577
|
'',
|
|
992
1578
|
'async function seedBillingInventory(sourceDb, targetDb) {',
|
|
993
|
-
' const
|
|
1579
|
+
' const includeBillingFixtures = shouldSeedBillingDashboardFixtures();',
|
|
1580
|
+
' const summary = { profile: includeBillingFixtures ? "billing_inventory" : "live_context", collections: {}, selected: {}, notes: [] };',
|
|
994
1581
|
' const identifiers = extractSeedIdentifiers();',
|
|
995
1582
|
' summary.selected.seed_identifiers = identifiers;',
|
|
1583
|
+
' const truckTreatingBolContextDocs = await copyTruckTreatingBolContext(sourceDb, targetDb, summary);',
|
|
1584
|
+
' if (!includeBillingFixtures) {',
|
|
1585
|
+
' summary.notes.push("Using live-context seed profile: billing dashboard fixtures are disabled because ticket context does not mention billing, invoices, taxes, inventory, pricing, surcharges, checkout, or pick tickets.");',
|
|
1586
|
+
' await cleanupSyntheticBillingDashboardQaFixtures(targetDb, summary);',
|
|
1587
|
+
' }',
|
|
996
1588
|
' const billableDeliveryQuery = { $and: [',
|
|
997
1589
|
' { $or: [{ type: "Delivery" }, { type: "Pickup" }] },',
|
|
998
1590
|
' { $or: [{ consignment: { $exists: false } }, { consignment: false }] },',
|
|
@@ -1027,7 +1619,7 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
1027
1619
|
' summary.notes.push("No live billable delivery or truck-treatment records matched the Billing Dashboard awaiting-invoice filters.");',
|
|
1028
1620
|
' }',
|
|
1029
1621
|
'',
|
|
1030
|
-
' const sourceDocs = [...productionDeliveries, ...truckTreatingDeliveries];',
|
|
1622
|
+
' const sourceDocs = [...truckTreatingBolContextDocs, ...productionDeliveries, ...truckTreatingDeliveries];',
|
|
1031
1623
|
' const customerIds = idValues(sourceDocs, ["id_customer"]);',
|
|
1032
1624
|
' const locationIds = idValues(sourceDocs, ["id_location"]);',
|
|
1033
1625
|
' const wellGroupIds = idValues(sourceDocs, ["id_well_group"]);',
|
|
@@ -1058,30 +1650,32 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
1058
1650
|
' ] }, summary, 40, { updatedAt: -1 });',
|
|
1059
1651
|
' summary.selected.service_or_surcharge_items = copiedServiceItems.map((doc) => doc._id);',
|
|
1060
1652
|
'',
|
|
1061
|
-
'
|
|
1062
|
-
'
|
|
1063
|
-
'
|
|
1064
|
-
'
|
|
1065
|
-
'
|
|
1066
|
-
'
|
|
1067
|
-
'
|
|
1068
|
-
'
|
|
1069
|
-
'
|
|
1070
|
-
'
|
|
1653
|
+
' if (includeBillingFixtures) {',
|
|
1654
|
+
' await copyQuery(sourceDb, targetDb, "sales-taxes", {}, summary, 80, { updatedAt: -1 });',
|
|
1655
|
+
' await copyQuery(sourceDb, targetDb, "state-counties", {}, summary, 80, { updatedAt: -1 });',
|
|
1656
|
+
' await copyQuery(sourceDb, targetDb, "accounting-codes", {}, summary, 80, { updatedAt: -1 });',
|
|
1657
|
+
' await copyQuery(sourceDb, targetDb, "pricing-items", { $or: [',
|
|
1658
|
+
' { sub_type: { $in: ["misc", "service"] } },',
|
|
1659
|
+
' { id_item: { $in: copiedServiceItems.map((doc) => doc._id) } },',
|
|
1660
|
+
' { name: /surcharge|fuel|delivery|service|misc/i }',
|
|
1661
|
+
' ] }, summary, 80, { updatedAt: -1 });',
|
|
1662
|
+
' await ensureBillingSurchargePricingFixtures(targetDb, summary, copiedServiceItems, customerIds);',
|
|
1663
|
+
' await ensureBillingDashboardQaFixtures(targetDb, summary, customerIds, yardIds, copiedServiceItems);',
|
|
1071
1664
|
'',
|
|
1072
|
-
'
|
|
1073
|
-
'
|
|
1074
|
-
'
|
|
1075
|
-
'
|
|
1076
|
-
'
|
|
1077
|
-
'
|
|
1078
|
-
'
|
|
1079
|
-
'
|
|
1080
|
-
'
|
|
1081
|
-
'
|
|
1082
|
-
'
|
|
1665
|
+
' const inventoryLocationQuery = { $or: [',
|
|
1666
|
+
' { id_item: { $in: unique([...itemIds, ...copiedItems.map((doc) => doc._id), ...copiedServiceItems.map((doc) => doc._id)]) } },',
|
|
1667
|
+
' { id_chemical: { $in: unique([...itemIds, ...copiedChemicals.map((doc) => doc._id)]) } },',
|
|
1668
|
+
' { id_yard: { $in: yardIds } }',
|
|
1669
|
+
' ] };',
|
|
1670
|
+
' const inventoryLocations = await copyQuery(sourceDb, targetDb, "inventory-locations", inventoryLocationQuery, summary, 80, { updatedAt: -1 });',
|
|
1671
|
+
' await copyQuery(sourceDb, targetDb, "inventory-transactions", { $or: [',
|
|
1672
|
+
' { id_inventory_location: { $in: inventoryLocations.map((doc) => doc._id) } },',
|
|
1673
|
+
' { id_item: { $in: itemIds } },',
|
|
1674
|
+
' { id_chemical: { $in: itemIds } }',
|
|
1675
|
+
' ] }, summary, 120, { date: -1, updatedAt: -1 });',
|
|
1676
|
+
' }',
|
|
1083
1677
|
'',
|
|
1084
|
-
' summary.ready = productionDeliveries.length > 0 || truckTreatingDeliveries.length > 0;',
|
|
1678
|
+
' summary.ready = truckTreatingBolContextDocs.length > 0 || productionDeliveries.length > 0 || truckTreatingDeliveries.length > 0;',
|
|
1085
1679
|
' return summary;',
|
|
1086
1680
|
'}',
|
|
1087
1681
|
'',
|
|
@@ -1095,6 +1689,7 @@ function buildResolveIORunnerQaLiveDataSeederScript() {
|
|
|
1095
1689
|
' }',
|
|
1096
1690
|
' if (!isLocalMongoUri(targetUri)) writeResult({ status: "failed", reason: "target_mongo_must_be_localhost", target_uri_redacted: redactUri(targetUri), result_path: resultPath }, 3);',
|
|
1097
1691
|
' if (String(source.uri) === String(targetUri)) writeResult({ status: "failed", reason: "source_and_target_mongo_match", result_path: resultPath }, 3);',
|
|
1692
|
+
' await waitForTargetMongo(MongoClient, targetUri);',
|
|
1098
1693
|
' const sourceClient = new MongoClient(source.uri, { readPreference: "secondaryPreferred", serverSelectionTimeoutMS: 15000 });',
|
|
1099
1694
|
' const targetClient = new MongoClient(targetUri, { serverSelectionTimeoutMS: 15000 });',
|
|
1100
1695
|
' try {',
|
|
@@ -1124,6 +1719,10 @@ function buildResolveIORunnerBugfixComparisonQaScript() {
|
|
|
1124
1719
|
'set -u',
|
|
1125
1720
|
'TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"',
|
|
1126
1721
|
'source "$TOOLS_DIR/env.sh"',
|
|
1722
|
+
'if resolveio_support_codex_lane_is_build 2>/dev/null; then',
|
|
1723
|
+
' resolveio_support_codex_block_build_lane_qa',
|
|
1724
|
+
' exit 86',
|
|
1725
|
+
'fi',
|
|
1127
1726
|
'PROJECT_ROOT="${1:-}"',
|
|
1128
1727
|
'if [ -z "$PROJECT_ROOT" ]; then',
|
|
1129
1728
|
' echo "Usage: $0 <project-root> [baseline-ref] -- <qa-command...>" >&2',
|
|
@@ -1218,11 +1817,33 @@ function buildResolveIORunnerBugfixComparisonQaScript() {
|
|
|
1218
1817
|
' export RESOLVEIO_SUPPORT_QA_PHASE="$phase"',
|
|
1219
1818
|
' export RESOLVEIO_RUNNER_QA_ARTIFACT_DIR="$ARTIFACT_DIR/$phase"',
|
|
1220
1819
|
' export RESOLVEIO_SUPPORT_QA_ARTIFACT_DIR="$ARTIFACT_DIR/$phase"',
|
|
1820
|
+
' export RESOLVEIO_RUNNER_QA_PROJECT_ROOT="$PROJECT_ROOT"',
|
|
1821
|
+
' export RESOLVEIO_SUPPORT_QA_PROJECT_ROOT="$PROJECT_ROOT"',
|
|
1822
|
+
' export RESOLVEIO_RUNNER_QA_TOOLS_DIR="$TOOLS_DIR"',
|
|
1823
|
+
' export RESOLVEIO_SUPPORT_QA_TOOLS_DIR="$TOOLS_DIR"',
|
|
1221
1824
|
' mkdir -p "$ARTIFACT_DIR/$phase"',
|
|
1222
1825
|
' find "$ARTIFACT_DIR/$phase" -maxdepth 1 -type f \\( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" -o -name "*.json" -o -name "*.txt" -o -name "*.log" -o -name "*.zip" \\) -delete 2>/dev/null || true',
|
|
1223
1826
|
' find "$ARTIFACT_DIR" -maxdepth 1 -type f \\( -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" -o -name "*.webp" -o -name "*proof.json" -o -name "auth-bootstrap-result.json" \\) -delete 2>/dev/null || true',
|
|
1827
|
+
' RESOLVEIO_SUPPORT_QA_KEEPALIVE=true RESOLVEIO_RUNNER_QA_KEEPALIVE=true "$TOOLS_DIR/run-local-qa.sh" "$PROJECT_ROOT"',
|
|
1828
|
+
' local stack_rc="$?"',
|
|
1829
|
+
' if [ "$stack_rc" != "0" ]; then',
|
|
1830
|
+
' echo "ResolveIO bugfix comparison QA $phase phase blocked: local QA stack did not start (exit $stack_rc)." | tee "$ARTIFACT_DIR/$phase/qa-command.log"',
|
|
1831
|
+
' snapshot_phase_artifacts "$phase"',
|
|
1832
|
+
' stop_local_qa',
|
|
1833
|
+
' return "$stack_rc"',
|
|
1834
|
+
' fi',
|
|
1835
|
+
' if [ -f "$TOOLS_DIR/qa-live-data-seed.js" ]; then',
|
|
1836
|
+
' node "$TOOLS_DIR/qa-live-data-seed.js" "$PROJECT_ROOT"',
|
|
1837
|
+
' local seed_rc="$?"',
|
|
1838
|
+
' if [ "$seed_rc" != "0" ]; then',
|
|
1839
|
+
' echo "ResolveIO bugfix comparison QA $phase phase blocked: live data seed failed (exit $seed_rc)." | tee "$ARTIFACT_DIR/$phase/qa-command.log"',
|
|
1840
|
+
' snapshot_phase_artifacts "$phase"',
|
|
1841
|
+
' stop_local_qa',
|
|
1842
|
+
' return "$seed_rc"',
|
|
1843
|
+
' fi',
|
|
1844
|
+
' fi',
|
|
1224
1845
|
' set +e',
|
|
1225
|
-
' (cd "$
|
|
1846
|
+
' (cd "$PROJECT_ROOT" && "${QA_COMMAND[@]}") 2>&1 | tee "$ARTIFACT_DIR/$phase/qa-command.log"',
|
|
1226
1847
|
' local rc="${PIPESTATUS[0]}"',
|
|
1227
1848
|
' set +e',
|
|
1228
1849
|
' snapshot_phase_artifacts "$phase"',
|
|
@@ -1266,10 +1887,15 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
|
|
|
1266
1887
|
'const https = require("https");',
|
|
1267
1888
|
'const path = require("path");',
|
|
1268
1889
|
'',
|
|
1890
|
+
'if (isBuildLane()) {',
|
|
1891
|
+
' console.error("ResolveIO support lane guard: browser workflow probes are owned by the QA lane, not the 5.3 build lane.");',
|
|
1892
|
+
' process.exit(86);',
|
|
1893
|
+
'}',
|
|
1894
|
+
'',
|
|
1269
1895
|
'const projectRoot = path.resolve(process.argv[2] || process.cwd());',
|
|
1270
|
-
'const routeArg = process.argv[3] || process.env.RESOLVEIO_RUNNER_QA_TARGET_ROUTE || process.env.RESOLVEIO_SUPPORT_QA_TARGET_ROUTE || "/";',
|
|
1271
|
-
'const targetRoute = routeArg.startsWith("/") ? routeArg : `/${routeArg}`;',
|
|
1272
1896
|
'const artifactDir = path.resolve(process.env.RESOLVEIO_RUNNER_QA_ARTIFACT_DIR || process.env.RESOLVEIO_SUPPORT_QA_ARTIFACT_DIR || path.join(projectRoot, "qa-artifacts"));',
|
|
1897
|
+
'const routeArg = process.argv[3] || process.env.RESOLVEIO_RUNNER_QA_TARGET_ROUTE || process.env.RESOLVEIO_SUPPORT_QA_TARGET_ROUTE || "";',
|
|
1898
|
+
'const targetRoute = resolveTargetRoute(routeArg);',
|
|
1273
1899
|
'const matrixPath = path.join(artifactDir, "qa-coverage-matrix.json");',
|
|
1274
1900
|
'const resultPath = path.join(artifactDir, "qa-workflow-probe-result.json");',
|
|
1275
1901
|
'const passScreenshotPath = path.join(artifactDir, "qa-workflow-route-ready.jpg");',
|
|
@@ -1281,10 +1907,37 @@ function buildResolveIORunnerQaWorkflowProbeScript() {
|
|
|
1281
1907
|
'const viewportWidth = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_WIDTH || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_WIDTH || 1920);',
|
|
1282
1908
|
'const viewportHeight = Number(process.env.RESOLVEIO_RUNNER_QA_VIEWPORT_HEIGHT || process.env.RESOLVEIO_SUPPORT_QA_VIEWPORT_HEIGHT || 1080);',
|
|
1283
1909
|
'',
|
|
1910
|
+
'function isBuildLane() {',
|
|
1911
|
+
' return /^(build|support_build|support-build)$/i.test(String(process.env.RESOLVEIO_SUPPORT_CODEX_LANE || "").trim());',
|
|
1912
|
+
'}',
|
|
1913
|
+
'',
|
|
1284
1914
|
'function stripTrailingSlash(value) { return String(value || "").replace(/\\/+$/, ""); }',
|
|
1285
1915
|
'function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }',
|
|
1286
1916
|
'function writeJson(filePath, payload) { fs.mkdirSync(path.dirname(filePath), { recursive: true }); fs.writeFileSync(filePath, JSON.stringify(payload, null, 2)); }',
|
|
1287
1917
|
'function readJson(filePath) { try { return JSON.parse(fs.readFileSync(filePath, "utf8")); } catch (error) { return null; } }',
|
|
1918
|
+
'function normalizeRoute(candidate) {',
|
|
1919
|
+
' const value = String(candidate || "").trim();',
|
|
1920
|
+
' if (!value) return "";',
|
|
1921
|
+
' if (/^https?:\\/\\//i.test(value)) {',
|
|
1922
|
+
' try { return new URL(value).pathname || "/"; } catch (error) { return ""; }',
|
|
1923
|
+
' }',
|
|
1924
|
+
' return value.startsWith("/") ? value : `/${value}`;',
|
|
1925
|
+
'}',
|
|
1926
|
+
'function resolveTargetRoute(explicitRoute) {',
|
|
1927
|
+
' const explicit = normalizeRoute(explicitRoute);',
|
|
1928
|
+
' if (explicit) return explicit;',
|
|
1929
|
+
' const seedCandidates = [',
|
|
1930
|
+
' path.join(artifactDir, "qa-live-data-seed-result.json"),',
|
|
1931
|
+
' path.join(artifactDir, "candidate", "qa-live-data-seed-result.json")',
|
|
1932
|
+
' ];',
|
|
1933
|
+
' for (const seedPath of seedCandidates) {',
|
|
1934
|
+
' const seed = readJson(seedPath);',
|
|
1935
|
+
' const routes = seed && seed.selected && seed.selected.truck_treating_bol_context && seed.selected.truck_treating_bol_context.browser_routes;',
|
|
1936
|
+
' const route = normalizeRoute(routes && (routes.delivery || routes.detail || routes.list));',
|
|
1937
|
+
' if (route) return route;',
|
|
1938
|
+
' }',
|
|
1939
|
+
' return "/";',
|
|
1940
|
+
'}',
|
|
1288
1941
|
'function requestReady(url) {',
|
|
1289
1942
|
' return new Promise((resolve) => {',
|
|
1290
1943
|
' try {',
|