520 lines
22 KiB
Bash
520 lines
22 KiB
Bash
#!/usr/bin/env bash
|
|
set -eo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
CACHE_UC="$ROOT_DIR/tachyon/files/usr/lib/subscription/cache.uc"
|
|
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'"
|
|
}
|
|
|
|
cache_ucode() {
|
|
ucode -L "$TACHYON_LIB" "$CACHE_UC" "$@"
|
|
}
|
|
|
|
assert_eq "first second" \
|
|
"$(cache_ucode append-state-list-once "first" second)" \
|
|
"append new state item"
|
|
assert_eq "first second" \
|
|
"$(cache_ucode append-state-list-once "first second" second)" \
|
|
"do not append duplicate state item"
|
|
assert_eq "first second" \
|
|
"$(cache_ucode append-state-list-once " first second " "")" \
|
|
"empty state item keeps normalized list"
|
|
|
|
cache_ucode state-list-contains "first second" second >/dev/null ||
|
|
fail "state list should contain second"
|
|
if cache_ucode state-list-contains "first second" third >/dev/null 2>&1; then
|
|
fail "state list should not contain third"
|
|
fi
|
|
|
|
SUBSCRIPTION_RUNTIME_SH="$ROOT_DIR/tachyon/files/usr/lib/subscription_runtime.sh"
|
|
UPDATES_RUNTIME_SH="$ROOT_DIR/tachyon/files/usr/lib/updates_runtime.sh"
|
|
|
|
[ ! -e "$SUBSCRIPTION_RUNTIME_SH" ] ||
|
|
fail "subscription_runtime.sh shell owner must be removed"
|
|
[ ! -e "$UPDATES_RUNTIME_SH" ] ||
|
|
fail "updates_runtime.sh shell owner must be removed"
|
|
|
|
if grep -R -n "subscription_runtime.sh" "$ROOT_DIR/tachyon/files" >/dev/null 2>&1; then
|
|
fail "runtime files must not reference subscription_runtime.sh"
|
|
fi
|
|
|
|
subscription_runtime_shell_symbols='subscription_runtime_|prepare_subscription_caches_for_startup|prepare_subscription_caches_for_runtime_generation|run_deferred_subscription_bootstrap|stop_deferred_subscription_bootstrap_retry_worker'
|
|
if grep -R -n -E "$subscription_runtime_shell_symbols" "$ROOT_DIR/tachyon/files/usr/bin/tachyon" "$ROOT_DIR/tachyon/files/usr/lib" --include='*.sh' >/dev/null 2>&1; then
|
|
fail "subscription_runtime shell symbols must not remain in runtime shell"
|
|
fi
|
|
|
|
if grep -R -n -E 'get_subscription_metadata_path|get_outbound_metadata_path' "$ROOT_DIR/tachyon/files/usr/bin/tachyon" "$ROOT_DIR/tachyon/files/usr/lib" --include='*.sh' >/dev/null 2>&1; then
|
|
fail "subscription metadata path helpers must be owned by subscription/cache.uc"
|
|
fi
|
|
|
|
if grep -n -E 'require\("uci"\)\.cursor|uci -q' "$CACHE_UC" >/dev/null 2>&1; then
|
|
fail "subscription/cache.uc must use core.uci instead of owning direct UCI cursor or CLI calls"
|
|
fi
|
|
|
|
move_file_source="$(sed -n '/^function move_file(/,/^}/p' "$CACHE_UC")"
|
|
if grep -Fq 'unlink_path(target)' <<<"$move_file_source"; then
|
|
fail "subscription cache replacement must keep the current file until atomic rename succeeds"
|
|
fi
|
|
if grep -Fq 'remove-legacy-server-country-cache' "$CACHE_UC"; then
|
|
fail "subscription/cache.uc must not expose the migrated legacy cache cleanup"
|
|
fi
|
|
|
|
grep -q 'mode == "ensure-runtime-dirs"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose ensure-runtime-dirs owner mode"
|
|
grep -q 'mode == "ensure-runtime-cache-format"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose ensure-runtime-cache-format owner mode"
|
|
grep -q 'mode == "prepare-caches"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose prepare-caches owner mode"
|
|
grep -q 'mode == "update-source"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose update-source owner mode"
|
|
grep -q 'mode == "update-request"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose update-request owner mode"
|
|
grep -q 'mode == "update-section"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose update-section owner mode"
|
|
grep -q 'mode == "section-is-subscription-proxy"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose subscription proxy predicate mode"
|
|
grep -q 'mode == "subscription-metadata-path"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose subscription metadata path mode"
|
|
grep -q 'mode == "outbound-metadata-path"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose outbound metadata path mode"
|
|
grep -q 'mode == "run-deferred-bootstrap"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose deferred bootstrap owner mode"
|
|
grep -q 'mode == "stop-deferred-bootstrap-worker"' "$CACHE_UC" ||
|
|
fail "subscription/cache.uc must expose deferred worker stop owner mode"
|
|
assert_eq "proxy-subscription-2" \
|
|
"$(cache_ucode source-id proxy 2)" \
|
|
"source id mode"
|
|
assert_eq "/var/run/tachyon/subscription-metadata/proxy.json" \
|
|
"$(cache_ucode subscription-metadata-path proxy)" \
|
|
"subscription metadata path mode"
|
|
assert_eq "/var/run/tachyon/outbound-metadata/proxy.json" \
|
|
"$(cache_ucode outbound-metadata-path proxy)" \
|
|
"outbound metadata path mode"
|
|
if cache_ucode subscription-metadata-path '../bad' >/dev/null 2>&1; then
|
|
fail "subscription metadata path mode should reject unsafe section names"
|
|
fi
|
|
|
|
cat >"$WORK_DIR/normalized-subscription.json" <<'JSON'
|
|
{
|
|
"version": 1,
|
|
"format": "uri-list",
|
|
"skipped": 2,
|
|
"outbounds": [
|
|
{ "type": "vless", "tag": "one" },
|
|
{ "type": "trojan", "tag": "two" }
|
|
]
|
|
}
|
|
JSON
|
|
assert_eq "2 proxy entries, 2 skipped entries" \
|
|
"$(cache_ucode subscription-import-stats "$WORK_DIR/normalized-subscription.json")" \
|
|
"subscription import stats"
|
|
assert_eq "Subscription source 1 for rule 'proxy' imported: 2 proxy entries, 2 skipped entries" \
|
|
"$(cache_ucode subscription-source-summary proxy 1 "$WORK_DIR/normalized-subscription.json" imported)" \
|
|
"subscription source imported summary"
|
|
assert_eq "Subscription source 1 for rule 'proxy' is unchanged: 2 proxy entries, 2 skipped entries" \
|
|
"$(cache_ucode subscription-source-summary proxy 1 "$WORK_DIR/normalized-subscription.json" unchanged)" \
|
|
"subscription source unchanged summary"
|
|
|
|
runtime_env() {
|
|
TMP_SING_BOX_FOLDER="$WORK_DIR/runtime/tmp-sing-box" \
|
|
TMP_RULESET_FOLDER="$WORK_DIR/runtime/tmp-sing-box/rulesets" \
|
|
TMP_SUBSCRIPTION_FOLDER="$WORK_DIR/runtime/tmp-sing-box/subscriptions" \
|
|
TACHYON_RUNTIME_STATE_DIR="$WORK_DIR/runtime/run" \
|
|
TACHYON_SUBSCRIPTION_UPDATE_STATE_DIR="$WORK_DIR/runtime/run/subscription-update" \
|
|
TACHYON_SUBSCRIPTION_LINKS_DIR="$WORK_DIR/runtime/run/subscription-links" \
|
|
TACHYON_SUBSCRIPTION_METADATA_DIR="$WORK_DIR/runtime/run/subscription-metadata" \
|
|
TACHYON_OUTBOUND_METADATA_DIR="$WORK_DIR/runtime/run/outbound-metadata" \
|
|
TACHYON_SECTION_CACHE_DIR="$WORK_DIR/runtime/run/section-cache" \
|
|
TACHYON_RUNTIME_CACHE_FORMAT_FILE="$WORK_DIR/runtime/run/cache-format" \
|
|
TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_DIR="$WORK_DIR/runtime/persistent" \
|
|
TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT_FILE="$WORK_DIR/runtime/persistent/cache-format" \
|
|
TACHYON_PERSISTENT_SUBSCRIPTION_CACHE_FORMAT="7" \
|
|
TACHYON_SUBSCRIPTION_BOOTSTRAP_RETRY_PID_FILE="$WORK_DIR/runtime/run/bootstrap.pid" \
|
|
"$@"
|
|
}
|
|
|
|
runtime_env cache_ucode ensure-runtime-dirs
|
|
for dir in \
|
|
"$WORK_DIR/runtime/tmp-sing-box" \
|
|
"$WORK_DIR/runtime/tmp-sing-box/rulesets" \
|
|
"$WORK_DIR/runtime/tmp-sing-box/subscriptions" \
|
|
"$WORK_DIR/runtime/run" \
|
|
"$WORK_DIR/runtime/run/subscription-update" \
|
|
"$WORK_DIR/runtime/run/subscription-metadata" \
|
|
"$WORK_DIR/runtime/run/outbound-metadata" \
|
|
"$WORK_DIR/runtime/run/section-cache"; do
|
|
[ -d "$dir" ] || fail "ensure-runtime-dirs should create $dir"
|
|
done
|
|
|
|
if [ -d "$WORK_DIR/runtime/run/subscription-links" ]; then
|
|
fail "ensure-runtime-dirs should not recreate the retired subscription-links directory"
|
|
fi
|
|
|
|
mkdir -p "$WORK_DIR/runtime/run/subscription-links" "$WORK_DIR/runtime/persistent"
|
|
printf '7\n' >"$WORK_DIR/runtime/run/cache-format"
|
|
printf 'stale\n' >"$WORK_DIR/runtime/run/section-cache/stale.json"
|
|
printf 'stale\n' >"$WORK_DIR/runtime/run/subscription-links/stale.json"
|
|
printf '7\n' >"$WORK_DIR/runtime/persistent/cache-format"
|
|
cat >"$WORK_DIR/runtime/persistent/proxy-subscription-1.json" <<'JSON'
|
|
{
|
|
"version": 1,
|
|
"format": "sing-box-json",
|
|
"outbounds": [
|
|
{
|
|
"type": "socks",
|
|
"tag": "preserved-node",
|
|
"server": "127.0.0.1",
|
|
"server_port": 1080
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
runtime_env cache_ucode ensure-runtime-cache-format
|
|
assert_eq "8" \
|
|
"$(sed -n '1p' "$WORK_DIR/runtime/run/cache-format")" \
|
|
"runtime cache format"
|
|
[ ! -e "$WORK_DIR/runtime/run/section-cache/stale.json" ] ||
|
|
fail "ensure-runtime-cache-format should clear old runtime section cache"
|
|
[ ! -e "$WORK_DIR/runtime/run/subscription-links" ] ||
|
|
fail "runtime format migration should remove the retired subscription-links directory"
|
|
assert_eq "7" \
|
|
"$(sed -n '1p' "$WORK_DIR/runtime/persistent/cache-format")" \
|
|
"persistent cache format"
|
|
[ -f "$WORK_DIR/runtime/persistent/proxy-subscription-1.json" ] ||
|
|
fail "runtime format migration should preserve the compatible persistent subscription cache"
|
|
node - "$WORK_DIR/runtime/persistent/proxy-subscription-1.json" <<'NODE'
|
|
const fs = require('fs');
|
|
const subscription = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
const outbound = subscription.outbounds[0];
|
|
if (outbound.server !== '127.0.0.1' || outbound.share_link !== 'socks5://127.0.0.1:1080#preserved-node') {
|
|
console.error('runtime format migration should preserve the outbound and backfill its direct link');
|
|
process.exit(1);
|
|
}
|
|
NODE
|
|
runtime_env cache_ucode run-deferred-bootstrap ""
|
|
runtime_env cache_ucode stop-deferred-bootstrap-worker
|
|
|
|
mkdir -p \
|
|
"$WORK_DIR/tmp" \
|
|
"$WORK_DIR/persistent" \
|
|
"$WORK_DIR/sections" \
|
|
"$WORK_DIR/links" \
|
|
"$WORK_DIR/metadata" \
|
|
"$WORK_DIR/outbound"
|
|
|
|
cat >"$WORK_DIR/cache-maintenance.json" <<'JSON'
|
|
{
|
|
"section": [
|
|
{
|
|
".name": "proxy",
|
|
"enabled": "1",
|
|
"action": "proxy",
|
|
"subscription_urls": [
|
|
"https://example.com/one.txt",
|
|
"https://example.com/two.txt"
|
|
]
|
|
},
|
|
{
|
|
".name": "direct",
|
|
"enabled": "1",
|
|
"action": "bypass"
|
|
},
|
|
{
|
|
".name": "disabled",
|
|
"enabled": "0",
|
|
"action": "proxy",
|
|
"subscription_urls": [
|
|
"https://example.com/disabled.txt"
|
|
]
|
|
},
|
|
{
|
|
".name": "bad/name",
|
|
"enabled": "1",
|
|
"action": "proxy",
|
|
"subscription_urls": [
|
|
"https://example.com/bad.txt"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
|
|
assert_eq $'sections\tproxy direct disabled\nsources\tproxy-subscription-1 proxy-subscription-2 disabled-subscription-1\nall\tproxy proxy-subscription-1 proxy-subscription-2 direct disabled disabled-subscription-1\nmissing\t1' \
|
|
"$(cache_ucode maintenance-plan-fixture "$WORK_DIR/cache-maintenance.json" "$WORK_DIR/sections")" \
|
|
"cache maintenance plan with missing proxy section cache"
|
|
|
|
touch "$WORK_DIR/sections/proxy.json"
|
|
assert_eq $'sections\tproxy direct disabled\nsources\tproxy-subscription-1 proxy-subscription-2 disabled-subscription-1\nall\tproxy proxy-subscription-1 proxy-subscription-2 direct disabled disabled-subscription-1\nmissing\t0' \
|
|
"$(cache_ucode maintenance-plan-fixture "$WORK_DIR/cache-maintenance.json" "$WORK_DIR/sections")" \
|
|
"cache maintenance plan with available proxy section cache"
|
|
|
|
cache_ucode runtime-cache-needs-rebuild-fixture "$WORK_DIR/cache-maintenance.json" "$WORK_DIR/sections" >/dev/null &&
|
|
fail "runtime cache should not need rebuild after proxy cache exists"
|
|
cache_ucode prepared-runtime-cache-should-skip-fixture "$WORK_DIR/cache-maintenance.json" "$WORK_DIR/sections" runtime 1 >/dev/null ||
|
|
fail "prepared runtime cache should skip only when section cache exists"
|
|
rm -f "$WORK_DIR/sections/proxy.json"
|
|
cache_ucode runtime-cache-needs-rebuild-fixture "$WORK_DIR/cache-maintenance.json" "$WORK_DIR/sections" >/dev/null ||
|
|
fail "runtime cache should need rebuild when proxy cache is missing"
|
|
if cache_ucode prepared-runtime-cache-should-skip-fixture "$WORK_DIR/cache-maintenance.json" "$WORK_DIR/sections" runtime 1 >/dev/null 2>&1; then
|
|
fail "prepared runtime cache must not skip when section cache is missing"
|
|
fi
|
|
|
|
touch \
|
|
"$WORK_DIR/tmp/proxy-subscription-1.json" \
|
|
"$WORK_DIR/tmp/stale-subscription-1.json" \
|
|
"$WORK_DIR/persistent/proxy-subscription-2.url" \
|
|
"$WORK_DIR/persistent/stale-subscription-2.user_agent" \
|
|
"$WORK_DIR/sections/proxy.json" \
|
|
"$WORK_DIR/sections/proxy-subscription-1.json" \
|
|
"$WORK_DIR/sections/stale.json" \
|
|
"$WORK_DIR/links/proxy.json" \
|
|
"$WORK_DIR/links/proxy-subscription-1.json" \
|
|
"$WORK_DIR/metadata/direct.metadata.json" \
|
|
"$WORK_DIR/outbound/stale.json" \
|
|
"$WORK_DIR/outbound/cache-format"
|
|
|
|
candidate_paths="$(
|
|
printf '%s\n' \
|
|
"$WORK_DIR/tmp/proxy-subscription-1.json" \
|
|
"$WORK_DIR/tmp/stale-subscription-1.json" \
|
|
"$WORK_DIR/persistent/proxy-subscription-2.url" \
|
|
"$WORK_DIR/persistent/stale-subscription-2.user_agent" \
|
|
"$WORK_DIR/sections/proxy.json" \
|
|
"$WORK_DIR/sections/proxy-subscription-1.json" \
|
|
"$WORK_DIR/sections/stale.json" \
|
|
"$WORK_DIR/links/proxy.json" \
|
|
"$WORK_DIR/links/proxy-subscription-1.json" \
|
|
"$WORK_DIR/metadata/direct.metadata.json" \
|
|
"$WORK_DIR/outbound/stale.json" \
|
|
"$WORK_DIR/outbound/cache-format"
|
|
)"
|
|
|
|
assert_eq "$(
|
|
printf '%s\n' \
|
|
"$WORK_DIR/tmp/stale-subscription-1.json" \
|
|
"$WORK_DIR/persistent/stale-subscription-2.user_agent" \
|
|
"$WORK_DIR/sections/stale.json" \
|
|
"$WORK_DIR/links/proxy-subscription-1.json" \
|
|
"$WORK_DIR/outbound/stale.json"
|
|
)" \
|
|
"$(printf '%s\n' "$candidate_paths" | cache_ucode stale-cache-delete-paths-fixture \
|
|
"$WORK_DIR/cache-maintenance.json" \
|
|
"$WORK_DIR/tmp" \
|
|
"$WORK_DIR/persistent" \
|
|
"$WORK_DIR/sections" \
|
|
"$WORK_DIR/links" \
|
|
"$WORK_DIR/metadata" \
|
|
"$WORK_DIR/outbound")" \
|
|
"stale cache delete path plan"
|
|
|
|
mkdir -p "$WORK_DIR/runtime-cache" "$WORK_DIR/persistent-cache"
|
|
cat >"$WORK_DIR/cache-current.json" <<'JSON'
|
|
{
|
|
"section": [
|
|
{
|
|
".name": "proxy",
|
|
"subscription_urls": [
|
|
"https://example.com/one.txt",
|
|
"https://example.com/two.txt"
|
|
],
|
|
"subscription_url_settings": "{\"https://example.com/one.txt\":{\"user_agent\":\"v2rayN\"}}"
|
|
},
|
|
{
|
|
".name": "manual",
|
|
"selector_proxy_links": "vless://manual"
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
|
|
cat >"$WORK_DIR/runtime-cache/proxy-subscription-1.json" <<'JSON'
|
|
{"outbounds":[{"type":"direct","tag":"one"}]}
|
|
JSON
|
|
printf '%s' 'https://example.com/one.txt' >"$WORK_DIR/runtime-cache/proxy-subscription-1.url"
|
|
printf '%s' 'v2rayN' >"$WORK_DIR/runtime-cache/proxy-subscription-1.user_agent"
|
|
|
|
cache_ucode section-current-usable-cache-fixture \
|
|
"$WORK_DIR/cache-current.json" proxy "$WORK_DIR/runtime-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null ||
|
|
fail "runtime cache should be current and usable"
|
|
|
|
printf '%s' 'stale-hwid' >"$WORK_DIR/runtime-cache/proxy-subscription-1.hwid"
|
|
if cache_ucode section-current-usable-cache-fixture \
|
|
"$WORK_DIR/cache-current.json" proxy "$WORK_DIR/runtime-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >"$WORK_DIR/hwid.out" 2>"$WORK_DIR/hwid.err"; then
|
|
fail "mismatched generated HWID cache should not be current"
|
|
fi
|
|
if grep -Fq "left-hand side is not a function" "$WORK_DIR/hwid.err"; then
|
|
fail "mismatched generated HWID cache must not crash"
|
|
fi
|
|
rm -f "$WORK_DIR/runtime-cache/proxy-subscription-1.hwid"
|
|
|
|
mkdir -p "$WORK_DIR/custom-header-cache"
|
|
cat >"$WORK_DIR/cache-custom-headers.json" <<'JSON'
|
|
{
|
|
"section": [
|
|
{
|
|
".name": "custom",
|
|
"subscription_urls": [
|
|
"https://example.com/custom.txt"
|
|
],
|
|
"subscription_url_settings": "{\"https://example.com/custom.txt\":{\"user_agent\":\"v2rayN\",\"custom_device_headers\":\"1\",\"device_os\":\"iOS\",\"ver_os\":\"26.4\",\"device_model\":\"iPhone 17 Pro Max\",\"device_locale\":\"ru\",\"app_version\":\"4.6.0\",\"accept_language\":\"ru-RU,ru;q=0.9\"}}"
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
cat >"$WORK_DIR/custom-header-cache/custom-subscription-1.json" <<'JSON'
|
|
{"outbounds":[{"type":"direct","tag":"custom"}]}
|
|
JSON
|
|
printf '%s' 'https://example.com/custom.txt' >"$WORK_DIR/custom-header-cache/custom-subscription-1.url"
|
|
printf '%s' 'v2rayN' >"$WORK_DIR/custom-header-cache/custom-subscription-1.user_agent"
|
|
|
|
if cache_ucode section-current-usable-cache-fixture \
|
|
"$WORK_DIR/cache-custom-headers.json" custom "$WORK_DIR/custom-header-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null 2>&1; then
|
|
fail "custom device headers should invalidate a cache without their signature"
|
|
fi
|
|
|
|
cat >"$WORK_DIR/custom-header-cache/custom-subscription-1.json" <<'JSON'
|
|
{"outbounds":[{"type":"direct","tag":"custom"}]}
|
|
JSON
|
|
printf '%s' 'https://example.com/custom.txt' >"$WORK_DIR/custom-header-cache/custom-subscription-1.url"
|
|
printf '%s' 'v2rayN' >"$WORK_DIR/custom-header-cache/custom-subscription-1.user_agent"
|
|
printf '%s' '{ "enabled": "1", "device_os": "iOS", "ver_os": "26.4", "device_model": "iPhone 17 Pro Max", "device_locale": "ru", "app_version": "4.6.0", "accept_language": "ru-RU,ru;q=0.9" }' >"$WORK_DIR/custom-header-cache/custom-subscription-1.device_headers"
|
|
|
|
cache_ucode section-current-usable-cache-fixture \
|
|
"$WORK_DIR/cache-custom-headers.json" custom "$WORK_DIR/custom-header-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null ||
|
|
fail "matching custom device headers signature should keep the cache current"
|
|
|
|
sed 's/\\"device_os\\":\\"iOS\\"/\\"device_os\\":\\"Android\\"/' \
|
|
"$WORK_DIR/cache-custom-headers.json" >"$WORK_DIR/cache-custom-headers-changed.json"
|
|
if cache_ucode section-current-usable-cache-fixture \
|
|
"$WORK_DIR/cache-custom-headers-changed.json" custom "$WORK_DIR/custom-header-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null 2>&1; then
|
|
fail "changed custom device headers should invalidate cache"
|
|
fi
|
|
|
|
printf '%s' 'https://example.com/stale.txt' >"$WORK_DIR/runtime-cache/proxy-subscription-1.url"
|
|
if cache_ucode section-current-usable-cache-fixture \
|
|
"$WORK_DIR/cache-current.json" proxy "$WORK_DIR/runtime-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null 2>&1; then
|
|
fail "stale runtime cache URL should not be current"
|
|
fi
|
|
|
|
rm -f "$WORK_DIR/runtime-cache/proxy-subscription-1.json" "$WORK_DIR/runtime-cache/proxy-subscription-1.url" "$WORK_DIR/runtime-cache/proxy-subscription-1.user_agent"
|
|
cat >"$WORK_DIR/persistent-cache/proxy-subscription-2.json" <<'JSON'
|
|
{"outbounds":[{"type":"direct","tag":"two"}]}
|
|
JSON
|
|
printf '%s' 'https://example.com/two.txt' >"$WORK_DIR/persistent-cache/proxy-subscription-2.url"
|
|
printf '%s' 'sing-box/default' >"$WORK_DIR/persistent-cache/proxy-subscription-2.user_agent"
|
|
|
|
cache_ucode section-current-usable-cache-fixture \
|
|
"$WORK_DIR/cache-current.json" proxy "$WORK_DIR/runtime-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null ||
|
|
fail "persistent cache should restore and satisfy current cache"
|
|
test -s "$WORK_DIR/runtime-cache/proxy-subscription-2.json" ||
|
|
fail "persistent cache restore should write runtime json"
|
|
assert_eq 'https://example.com/two.txt' \
|
|
"$(cat "$WORK_DIR/runtime-cache/proxy-subscription-2.url")" \
|
|
"restored subscription URL"
|
|
|
|
if cache_ucode section-current-usable-cache-fixture \
|
|
"$WORK_DIR/cache-current.json" manual "$WORK_DIR/runtime-cache" "$WORK_DIR/persistent-cache" "sing-box/default" >/dev/null 2>&1; then
|
|
fail "section without subscription URLs should not have usable subscription cache"
|
|
fi
|
|
|
|
mkdir -p "$WORK_DIR/link-subscriptions"
|
|
cat >"$WORK_DIR/link-subscriptions/proxy-subscription-1.json" <<'JSON'
|
|
{
|
|
"outbounds": [
|
|
{
|
|
"type": "vless",
|
|
"tag": "vless-encrypted",
|
|
"server": "example.com",
|
|
"server_port": 443,
|
|
"uuid": "00000000-0000-4000-8000-000000000001",
|
|
"encryption": "mlkem768x25519plus.native.test"
|
|
},
|
|
{
|
|
"type": "vless",
|
|
"tag": "vless-none",
|
|
"server": "example.com",
|
|
"server_port": 443,
|
|
"uuid": "00000000-0000-4000-8000-000000000001",
|
|
"encryption": "none"
|
|
}
|
|
]
|
|
}
|
|
JSON
|
|
serialize_outbound_link() {
|
|
ucode -L "$TACHYON_LIB" -e '
|
|
let fs = require("fs");
|
|
let share_link = require("subscription.share_link");
|
|
let source = json(fs.readfile(ARGV[0]));
|
|
print(share_link.serialize_outbound_link(source.outbounds[int(ARGV[1])]), "\n");
|
|
' "$1" "$2"
|
|
}
|
|
|
|
encrypted_link="$(serialize_outbound_link "$WORK_DIR/link-subscriptions/proxy-subscription-1.json" 0)"
|
|
case "$encrypted_link" in
|
|
*encryption=mlkem768x25519plus.native.test*) ;;
|
|
*) fail "expected VLESS encryption in serialized link, got $encrypted_link" ;;
|
|
esac
|
|
none_link="$(serialize_outbound_link "$WORK_DIR/link-subscriptions/proxy-subscription-1.json" 1)"
|
|
case "$none_link" in
|
|
*encryption=none*) fail "did not expect encryption=none in serialized link, got $none_link" ;;
|
|
esac
|
|
|
|
mkdir -p "$WORK_DIR/persist-runtime" "$WORK_DIR/persist-empty" "$WORK_DIR/persist-fresh"
|
|
cat >"$WORK_DIR/persist-runtime/proxy-subscription-1.json" <<'JSON'
|
|
{"outbounds":[{"type":"direct","tag":"one"}]}
|
|
JSON
|
|
cat >"$WORK_DIR/runtime/persistent/proxy-subscription-1.metadata.json" <<'JSON'
|
|
{"version":1,"title":"WolfPN"}
|
|
JSON
|
|
printf '%s' 'https://example.com/one.txt' >"$WORK_DIR/runtime/persistent/proxy-subscription-1.url"
|
|
printf '%s' 'v2rayN' >"$WORK_DIR/runtime/persistent/proxy-subscription-1.user_agent"
|
|
cat >"$WORK_DIR/persist-empty/empty.metadata.json" <<'JSON'
|
|
{}
|
|
JSON
|
|
|
|
runtime_env cache_ucode persist-source-cache \
|
|
proxy-subscription-1 \
|
|
"$WORK_DIR/persist-runtime/proxy-subscription-1.json" \
|
|
'https://example.com/one.txt' \
|
|
'v2rayN' \
|
|
"$WORK_DIR/persist-empty/empty.metadata.json"
|
|
test -s "$WORK_DIR/runtime/persistent/proxy-subscription-1.metadata.json" ||
|
|
fail "same subscription source with empty fresh metadata must keep previous metadata"
|
|
node - "$WORK_DIR/runtime/persistent/proxy-subscription-1.metadata.json" <<'JS'
|
|
const fs = require("fs");
|
|
const value = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
if (value.title !== "WolfPN") {
|
|
console.error("previous metadata was not preserved");
|
|
process.exit(1);
|
|
}
|
|
JS
|
|
|
|
runtime_env cache_ucode persist-source-cache \
|
|
proxy-subscription-1 \
|
|
"$WORK_DIR/persist-runtime/proxy-subscription-1.json" \
|
|
'https://example.com/changed.txt' \
|
|
'v2rayN' \
|
|
"$WORK_DIR/persist-empty/empty.metadata.json"
|
|
[ ! -e "$WORK_DIR/runtime/persistent/proxy-subscription-1.metadata.json" ] ||
|
|
fail "changed subscription URL with empty metadata must drop stale metadata"
|
|
|
|
printf 'subscription cache state checks passed\n'
|