Files
NeoTachyon/tests/subscription_update_job.sh
Dushnilin 625715fed0 feat: асинхронное обновление списков в LuCI (fix XHR timeout) и форматированный config.json
- backend: добавлены команды list_update_async и list_update_status с отслеживанием шагов в /var/run/tachyon/list-update.status
- ui: кнопка ручного обновления списков теперь запускает операцию в фоне, показывает модалку с индикатором и опрашивает статус (устранен таймаут LuCI XHR в 30 сек)
- json: sing-box config.json теперь форматируется с отступами в 2 пробела (sprintf '%.2J') для удобства чтения
- tests: обновлены тесты и добавлены проверки для list-update-status (122/122 passed)
2026-09-06 17:55:40 +03:00

158 lines
5.5 KiB
Bash

#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
UPDATES_UC="$ROOT_DIR/tachyon/files/usr/lib/components/updates.uc"
UPDATES_RUNTIME="$ROOT_DIR/tachyon/files/usr/lib/updates_runtime.sh"
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
WORK_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
assert_eq() {
local expected="$1"
local actual="$2"
local label="$3"
[ "$actual" = "$expected" ] || fail "$label: expected '$expected', got '$actual'"
}
write_state() {
local name="$1"
local content="$2"
printf '%s\n' "$content" >"$WORK_DIR/$name.json"
printf '%s\n' "$WORK_DIR/$name.json"
}
updates_ucode() {
ucode -L "$TACHYON_LIB" "$UPDATES_UC" "$@"
}
[ ! -e "$UPDATES_RUNTIME" ] ||
fail "updates_runtime.sh shell owner must be removed"
assert_eq "/tmp/sub-jobs/job_1.json" \
"$(updates_ucode subscription-job-state-path /tmp/sub-jobs job_1)" \
"valid subscription job path"
if updates_ucode subscription-job-state-path /tmp/sub-jobs '../bad' >/dev/null 2>&1; then
fail "invalid subscription job id should be rejected"
fi
running="$(write_state running '{"running":true,"pid":"321","started_at":100}')"
assert_eq "$(printf 'pid\t321\t0')" \
"$(updates_ucode subscription-job-refresh-plan "$running" 105 15)" \
"running job within grace"
assert_eq "$(printf 'pid\t321\t1')" \
"$(updates_ucode subscription-job-refresh-plan "$running" 120 15)" \
"running job after grace"
invalid_pid="$(write_state invalid-pid '{"running":true,"pid":"","started_at":100}')"
assert_eq "skip" \
"$(updates_ucode subscription-job-refresh-plan "$invalid_pid" 105 15)" \
"invalid pid within grace"
assert_eq "stale" \
"$(updates_ucode subscription-job-refresh-plan "$invalid_pid" 120 15)" \
"invalid pid after grace"
finished="$(write_state finished '{"running":false,"pid":"321","started_at":100}')"
assert_eq "skip" \
"$(updates_ucode subscription-job-refresh-plan "$finished" 120 15)" \
"finished job refresh"
stale_json="$(updates_ucode subscription-stale-job-state 200 proxy 2 100)"
JSON_VALUE="$stale_json" node - <<'NODE'
const value = JSON.parse(process.env.JSON_VALUE);
if (value.running !== false || value.success !== false || value.section !== "proxy" || value.source_index !== "2") {
console.error("subscription stale state shape mismatch");
process.exit(1);
}
NODE
mkdir -p "$WORK_DIR/bin"
cat >"$WORK_DIR/bin/tachyon" <<'SH'
#!/usr/bin/env sh
if [ "$1" != "subscription_update" ]; then
exit 64
fi
printf 'fake update for %s/%s\n' "$2" "$3"
printf 'Subscription update completed by fake worker\n'
SH
chmod +x "$WORK_DIR/bin/tachyon"
ASYNC_ENV=(
"TACHYON_LIB=$ROOT_DIR/tachyon/files/usr/lib"
"TACHYON_BIN=$WORK_DIR/bin/tachyon"
"TMP_SING_BOX_FOLDER=$WORK_DIR/tmp/sing-box"
"TMP_RULESET_FOLDER=$WORK_DIR/tmp/sing-box/rulesets"
"TMP_SUBSCRIPTION_FOLDER=$WORK_DIR/tmp/sing-box/subscriptions"
"TACHYON_RUNTIME_STATE_DIR=$WORK_DIR/run"
"TACHYON_SUBSCRIPTION_UPDATE_STATE_DIR=$WORK_DIR/run/subscription-update"
"TACHYON_SUBSCRIPTION_UPDATE_JOB_DIR=$WORK_DIR/run/subscription-update-jobs"
"TACHYON_SUBSCRIPTION_LINKS_DIR=$WORK_DIR/run/subscription-links"
"TACHYON_SUBSCRIPTION_METADATA_DIR=$WORK_DIR/run/subscription-metadata"
"TACHYON_OUTBOUND_METADATA_DIR=$WORK_DIR/run/outbound-metadata"
"TACHYON_SECTION_CACHE_DIR=$WORK_DIR/run/section-cache"
"TACHYON_RUNTIME_CACHE_FORMAT_FILE=$WORK_DIR/run/cache-format"
"TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR=$WORK_DIR/persistent/subscription-cache"
"TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT_FILE=$WORK_DIR/persistent/subscription-cache/cache-format"
)
async_response="$(env "${ASYNC_ENV[@]}" ucode -L "$ROOT_DIR/tachyon/files/usr/lib" "$UPDATES_UC" subscription-update-async proxy 1)"
job_id="$(JSON_VALUE="$async_response" node - <<'NODE'
const value = JSON.parse(process.env.JSON_VALUE);
if (value.success !== true || !value.job_id) {
console.error("subscription async response shape mismatch");
process.exit(1);
}
process.stdout.write(value.job_id);
NODE
)"
final_status=""
for _ in $(seq 1 30); do
final_status="$(env "${ASYNC_ENV[@]}" ucode -L "$ROOT_DIR/tachyon/files/usr/lib" "$UPDATES_UC" subscription-update-status "$job_id")"
if JSON_VALUE="$final_status" node -e 'process.exit(JSON.parse(process.env.JSON_VALUE).running ? 1 : 0)' >/dev/null 2>&1; then
break
fi
sleep 1
done
JSON_VALUE="$final_status" node - <<'NODE'
const value = JSON.parse(process.env.JSON_VALUE);
if (value.success !== true || value.running !== false || value.section !== "proxy" || value.source_index !== "1" || value.exit_code !== 0) {
console.error("subscription async final state mismatch");
process.exit(1);
}
if (value.message !== "Subscription update completed by fake worker") {
console.error(`unexpected final message: ${value.message}`);
process.exit(1);
}
NODE
# list-update-status checks
idle_status="$(updates_ucode list-update-status)"
JSON_VALUE="$idle_status" node -e '
const v = JSON.parse(process.env.JSON_VALUE);
if (v.success !== true || v.running !== false) process.exit(1);
' || fail "list-update-status idle state mismatch"
printf '%s\n' "$$" >"$WORK_DIR/tachyon_list_update.pid"
running_status="$(TACHYON_LIST_UPDATE_PID_FILE="$WORK_DIR/tachyon_list_update.pid" updates_ucode list-update-status)"
JSON_VALUE="$running_status" node -e '
const v = JSON.parse(process.env.JSON_VALUE);
if (v.success !== true || v.running !== true) process.exit(1);
' || fail "list-update-status running state mismatch"
rm -f "$WORK_DIR/tachyon_list_update.pid"
printf 'subscription update job checks passed\n'