🎉 Sync 2026-09-27 21:03:26

This commit is contained in:
github-actions[bot] committed 2026-09-27 21:03:26 +08:00
1 parent f4559bbed1
commit 065763f7fd
673 files changed
+261285 -34160

No files matched your search

+445
View File
@@ -0,0 +1,445 @@
#!/bin/sh
# shellcheck shell=sh
ss_case_blocklist_format() (
set -eu
TMP_DIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
SS_TMP_DIR="$TMP_DIR/tmp"
SS_DNSMASQ_DIR="$TMP_DIR/dnsmasq.d"
SS_BLOCKLIST_FILE="$SS_DNSMASQ_DIR/safeshield.blocklist"
ss_min_valid_line_count=0
ss_max_blocklist_file_size_kb=1024
ss_dnsmasq_sanity_check=0
ss_valid_line_count=0
mkdir -p "$SS_TMP_DIR" "$SS_DNSMASQ_DIR"
ss_should_stop() { return 1; }
ss_status_set() { :; }
ss_status_add_error() { :; }
log_info() { :; }
log_error() { :; }
log_warn() { :; }
ss_sync_path() { :; }
ss_blocklist_tmp_path() { printf '%s/.safeshield.blocklist.tmp.%s\n' "$SS_DNSMASQ_DIR" "$$"; }
ss_install_blocklist_atomic() { mv -f "$1" "$2"; }
DNSMASQ_PATCHED=1
ss_dnsmasq_safeshield_block_supported() { [ "$DNSMASQ_PATCHED" = '1' ]; }
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh" 'smartsafehub-block='
normalized="$(printf '%s\n' \
'address=/ads.example/#' \
'address=/legacy-v4.example/0.0.0.0' \
'address=/legacy-v6.example/::' \
'safeshield-block=/native.example/' | ss_normalize_domains)"
expected='ads.example
legacy-v4.example
legacy-v6.example
native.example'
ss_spec_assert_eq "$normalized" "$expected"
cat >"$SS_TMP_DIR/api.0.block.txt" <<'DATA'
ads.example
tracker.example
ads.example
DATA
cat >"$SS_TMP_DIR/api.1.block.txt" <<'DATA'
phishing.example
tracker.example
DATA
cat >"$SS_TMP_DIR/api.2.allow.txt" <<'DATA'
tracker.example
remote-allow.example
DATA
cat >"$SS_TMP_DIR/local.block.txt" <<'DATA'
remote-allow.example
local-block.example
DATA
cat >"$SS_TMP_DIR/local.allow.txt" <<'DATA'
local-block.example
DATA
ss_merge_lists
expected='safeshield-block=/ads.example/
safeshield-block=/phishing.example/
safeshield-block=/remote-allow.example/
server=/local-block.example/#
server=/tracker.example/#'
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" "$expected"
ss_spec_assert_eq "$(grep -c . "$SS_BLOCKLIST_FILE")" '5'
check_blocklist_rule_present 'ads.example'
ss_spec_assert_eq "$(find_test_domains 1)" 'ads.example'
rm -f "$SS_TMP_DIR"/api.*.block.txt "$SS_TMP_DIR"/api.*.allow.txt
printf '%s\n' 'example.com' >"$SS_TMP_DIR/api.0.block.txt"
printf '%s\n' 'good.example.com' >"$SS_TMP_DIR/api.1.allow.txt"
printf '%s\n' 'blocked.good.example.com' >"$SS_TMP_DIR/local.block.txt"
printf '%s\n' 'allowed.blocked.good.example.com' >"$SS_TMP_DIR/local.allow.txt"
ss_merge_lists
expected='safeshield-block=/blocked.good.example.com/
safeshield-block=/example.com/
server=/allowed.blocked.good.example.com/#
server=/good.example.com/#'
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" "$expected"
cat >"$SS_BLOCKLIST_FILE" <<'DATA'
address=/migrate-one.example/#
address=/migrate-two.example/0.0.0.0
server=/allowed.example/#
DATA
ss_reconcile_blocklist_file_for_dnsmasq "$SS_BLOCKLIST_FILE"
expected='safeshield-block=/migrate-one.example/
safeshield-block=/migrate-two.example/
server=/allowed.example/#'
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" "$expected"
DNSMASQ_PATCHED=0
ss_reconcile_blocklist_file_for_dnsmasq "$SS_BLOCKLIST_FILE"
expected='address=/migrate-one.example/#
address=/migrate-two.example/#
server=/allowed.example/#'
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" "$expected"
rm -f \
"$SS_TMP_DIR"/api.*.block.txt \
"$SS_TMP_DIR"/api.*.allow.txt \
"$SS_TMP_DIR"/local.block.txt \
"$SS_TMP_DIR"/local.allow.txt
printf '%s\n' 'fallback.example' >"$SS_TMP_DIR/api.0.block.txt"
ss_merge_lists
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" 'address=/fallback.example/#'
DNSMASQ_PATCHED=1
cat >"$SS_BLOCKLIST_FILE" <<'DATA'
address=/legacy.example/0.0.0.0
address=/legacy.example/::
DATA
check_blocklist_rule_present 'legacy.example'
cat >"$SS_BLOCKLIST_FILE" <<'DATA'
address=/sample-one.example/#
address=/sample-two.example/#
address=/sample-three.example/#
DATA
sampled="$(find_test_domains 2)"
expected_sample='sample-one.example
sample-two.example'
ss_spec_assert_eq "$sampled" "$expected_sample"
RULE_CHECK_COUNT=0
DNS_CHECK_COUNT=0
check_blocklist_rule_present() {
RULE_CHECK_COUNT=$((RULE_CHECK_COUNT + 1))
return 1
}
check_domain_blocked() {
DNS_CHECK_COUNT=$((DNS_CHECK_COUNT + 1))
return 0
}
check_blocklist_applied_multi_with_stats 2 2
ss_spec_assert_eq "$RULE_CHECK_COUNT" '0'
ss_spec_assert_eq "$DNS_CHECK_COUNT" '2'
)
ss_case_multi_artifact() (
set -eu
TMP_DIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
SS_TMP_DIR="$TMP_DIR/tmp"
SS_API_PAYLOAD="$SS_TMP_DIR/resolve-request.json"
SS_API_RESPONSE="$SS_TMP_DIR/resolve-response.json"
SS_RESOLVED_SOURCES="$SS_TMP_DIR/resolved-sources.tsv"
SS_ARTIFACT_CACHE_STATE="$SS_TMP_DIR/artifact-sources.state"
SS_MAX_ARTIFACT_SOURCES=16
ss_license_key='test-license'
ss_download_retry=1
ss_download_timeout=10
ss_max_blocklist_file_size_kb=1024
mkdir -p "$SS_TMP_DIR"
ss_should_stop() { return 1; }
ss_status_set() { :; }
ss_status_add_error() { :; }
log_info() { :; }
log_error() { :; }
log_ok() { :; }
command_exists() { command -v "$1" >/dev/null 2>&1; }
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
ss_write_resolve_payload() { printf '{}\n' >"$1"; }
ss_http_post_json() { printf '{}\n' >"$3"; }
ss_json_get_file() {
case "$2" in
'@.artifact.sources[0].download_url') printf '%s\n' 'https://example.invalid/hagezi.txt' ;;
'@.artifact.sources[0].action') printf '%s\n' 'block' ;;
'@.artifact.sources[0].id') printf '%s\n' 'hagezi-pro' ;;
'@.artifact.sources[0].sha256') printf '%s\n' "$HAGEZI_SHA" ;;
'@.artifact.sources[1].download_url') printf '%s\n' 'https://example.invalid/smartsafehub-block.txt' ;;
'@.artifact.sources[1].action') printf '%s\n' 'block' ;;
'@.artifact.sources[1].id') printf '%s\n' 'smartsafehub-pro' ;;
'@.artifact.sources[1].sha256') printf '%s\n' "$SMART_BLOCK_SHA" ;;
'@.artifact.sources[2].download_url') printf '%s\n' 'https://example.invalid/smartsafehub-allow.txt' ;;
'@.artifact.sources[2].action') printf '%s\n' 'allow' ;;
'@.artifact.sources[2].id') printf '%s\n' 'smartsafehub-allow' ;;
'@.artifact.sources[2].sha256') printf '%s\n' "$SMART_ALLOW_SHA" ;;
'@.artifact.sources[3].download_url' | '@.artifact.sources[16].download_url' | '@.artifact.sha256') : ;;
'@.artifact.tier') printf '%s\n' 'pro' ;;
'@.artifact.version') printf '%s\n' '20260830T120000Z' ;;
'@.artifact.unique_domains' | '@.artifact.rules') printf '%s\n' '4' ;;
'@.license.plan') printf '%s\n' 'pro' ;;
'@.license.status') printf '%s\n' 'active' ;;
'@.device.profile') printf '%s\n' 'test-profile' ;;
*) : ;;
esac
}
cat >"$TMP_DIR/hagezi.txt" <<'DATA'
address=/ads.example/#
address=/shared.example/#
DATA
cat >"$TMP_DIR/smartsafehub-block.txt" <<'DATA'
phishing.example
shared.example
DATA
printf '%s\n' 'shared.example' >"$TMP_DIR/smartsafehub-allow.txt"
HAGEZI_SHA="$(sha256sum "$TMP_DIR/hagezi.txt" | awk '{print $1}')"
SMART_BLOCK_SHA="$(sha256sum "$TMP_DIR/smartsafehub-block.txt" | awk '{print $1}')"
SMART_ALLOW_SHA="$(sha256sum "$TMP_DIR/smartsafehub-allow.txt" | awk '{print $1}')"
export HAGEZI_SHA SMART_BLOCK_SHA SMART_ALLOW_SHA
ss_resolve_artifact
expected_sources="0|block|hagezi-pro|https://example.invalid/hagezi.txt|$HAGEZI_SHA
1|block|smartsafehub-pro|https://example.invalid/smartsafehub-block.txt|$SMART_BLOCK_SHA
2|allow|smartsafehub-allow|https://example.invalid/smartsafehub-allow.txt|$SMART_ALLOW_SHA"
ss_spec_assert_eq "$(cat "$SS_RESOLVED_SOURCES")" "$expected_sources"
ss_http_get_file() {
case "$1" in
'https://example.invalid/hagezi.txt') cp "$TMP_DIR/hagezi.txt" "$2" ;;
'https://example.invalid/smartsafehub-block.txt') cp "$TMP_DIR/smartsafehub-block.txt" "$2" ;;
'https://example.invalid/smartsafehub-allow.txt') cp "$TMP_DIR/smartsafehub-allow.txt" "$2" ;;
*) return 1 ;;
esac
}
ss_download_api_artifacts
ss_cached_api_sources_available
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.0.block.txt")" 'ads.example
shared.example'
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.1.block.txt")" 'phishing.example
shared.example'
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.2.allow.txt")" 'shared.example'
ss_json_get_file() {
case "$2" in
'@.artifact.sources[0].download_url') : ;;
'@.artifact.download_url') printf '%s\n' 'https://example.invalid/legacy.txt' ;;
'@.artifact.sha256') printf '%s\n' 'legacy-sha' ;;
*) : ;;
esac
}
ss_resolve_artifact_sources "$SS_API_RESPONSE"
ss_spec_assert_eq "$(cat "$SS_RESOLVED_SOURCES")" '0|block|legacy|https://example.invalid/legacy.txt|legacy-sha'
)
ss_case_performance_paths() (
set -eu
TMP="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
SS_TMP_DIR="$TMP"
export SS_TMP_DIR
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
cat >"$TMP/api.0.block.txt" <<'EOF_BLOCK_0'
alpha.example
shared.example
EOF_BLOCK_0
cat >"$TMP/api.1.block.txt" <<'EOF_BLOCK_1'
beta.example
shared.example
EOF_BLOCK_1
ss_merge_sorted_api_sources block "$TMP/merged-blocks.txt"
expected='alpha.example
beta.example
shared.example'
ss_spec_assert_eq "$(cat "$TMP/merged-blocks.txt")" "$expected"
rm -f "$TMP"/api.*.block.txt
ss_merge_sorted_api_sources block "$TMP/empty-blocks.txt"
[ ! -s "$TMP/empty-blocks.txt" ]
BLOCKLIST="$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
STATISTICS_AGGREGATE="$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics/30-aggregate.awk"
STATISTICS_OUTPUT="$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics/50-output.awk"
ss_spec_assert_file_contains "$BLOCKLIST" 'sort -m -u "$@" >"$out"'
ss_spec_assert_file_contains "$STATISTICS_AGGREGATE" 'delete device_first_bucket[key]'
ss_spec_assert_file_contains "$STATISTICS_OUTPUT" 'device_first_hour = device_first_bucket[key] + 0'
ss_spec_assert_file_contains "$STATISTICS_OUTPUT" 'for (bucket = device_first_hour; bucket <= device_last_hour; bucket += 3600)'
)
ss_case_upgrade_required() (
set -eu
TMP_DIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
SS_TMP_DIR="$TMP_DIR/tmp"
SS_API_PAYLOAD="$SS_TMP_DIR/resolve-request.json"
SS_API_RESPONSE="$SS_TMP_DIR/resolve-response.json"
SS_RESOLVED_SOURCES="$SS_TMP_DIR/resolved-sources.tsv"
SS_ARTIFACT_CACHE_STATE="$SS_TMP_DIR/artifact-sources.state"
ss_license_key='test-license'
ss_download_retry=3
ss_download_timeout=10
ss_max_blocklist_file_size_kb=1024
mkdir -p "$SS_TMP_DIR"
ss_should_stop() { return 1; }
ss_status_set() { printf '%s=%s\n' "$1" "$2" >>"$TMP_DIR/status"; }
ss_status_add_error() { :; }
ss_status_add_warning() { :; }
log_info() { :; }
log_error() { :; }
log_ok() { :; }
command_exists() { return 1; }
sleep() { printf 'sleep\n' >>"$TMP_DIR/sleep"; }
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
ss_write_resolve_payload() { printf '{}\n' >"$1"; }
printf '%s\n' 'HTTP error 426' >"$TMP_DIR/uclient.log"
ss_spec_assert_eq "$(ss_http_status_from_uclient_log "$TMP_DIR/uclient.log")" '426'
command_exists() { [ "$1" = 'curl' ]; }
curl() {
out=''
while [ "$#" -gt 0 ]; do
if [ "$1" = '-o' ]; then
shift
out="$1"
fi
shift
done
[ -z "$out" ] || printf '%s\n' '{"code":"safeshield_upgrade_required"}' >"$out"
printf '%s' '426'
return 0
}
if ss_http_post_json 'https://example.invalid/resolve' "$SS_API_PAYLOAD" "$SS_API_RESPONSE"; then
return 1
fi
ss_spec_assert_eq "$SS_HTTP_STATUS" '426'
if ss_http_get_file 'https://example.invalid/artifact' "$SS_TMP_DIR/artifact.raw"; then
return 1
fi
ss_spec_assert_eq "$SS_HTTP_STATUS" '426'
command_exists() { return 1; }
RESOLVE_CALLS=0
ss_http_post_json() {
RESOLVE_CALLS=$((RESOLVE_CALLS + 1))
SS_HTTP_STATUS='426'
return 1
}
if ss_resolve_artifact; then
return 1
fi
ss_spec_assert_eq "$RESOLVE_CALLS" '1'
ss_spec_assert_eq "$SS_RESOLVE_ERROR_CODE" 'safeshield_upgrade_required'
ss_spec_assert_file_contains "$TMP_DIR/status" 'health_api_resolve=0'
ss_spec_assert_not_exists "$TMP_DIR/sleep"
printf '%s\n' '0|block|test-source|https://example.invalid/artifact.txt|' >"$SS_RESOLVED_SOURCES"
DOWNLOAD_CALLS=0
ss_http_get_file() {
DOWNLOAD_CALLS=$((DOWNLOAD_CALLS + 1))
SS_HTTP_STATUS='426'
return 1
}
if ss_download_api_artifacts; then
return 1
fi
ss_spec_assert_eq "$DOWNLOAD_CALLS" '1'
ss_spec_assert_eq "$SS_ARTIFACT_DOWNLOAD_ERROR_CODE" 'safeshield_upgrade_required'
ss_spec_assert_file_contains "$TMP_DIR/status" 'health_artifact_download=0'
ss_spec_assert_not_exists "$TMP_DIR/sleep"
)
ss_case_artifact_integrity() (
set -eu
TMP="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
SS_TMP_DIR="$TMP/runtime"
SS_RESOLVED_SOURCES="$SS_TMP_DIR/resolved-sources.tsv"
SS_ARTIFACT_CACHE_STATE="$SS_TMP_DIR/artifact-sources.state"
ss_download_retry=2
ss_download_timeout=10
ss_max_blocklist_file_size_kb=1024
mkdir -p "$SS_TMP_DIR"
STATUS="$TMP/status"
: >"$STATUS"
ss_should_stop() { return 1; }
ss_status_set() { printf 'set %s %s\n' "$1" "${2:-}" >>"$STATUS"; }
ss_status_add_error() { printf 'error %s\n' "$1" >>"$STATUS"; }
ss_status_add_warning() { printf 'warning %s\n' "$1" >>"$STATUS"; }
log_info() { :; }
log_error() { :; }
log_ok() { :; }
sleep() { :; }
command_exists() { command -v "$1" >/dev/null 2>&1; }
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
printf '%s\n' 'ads.example' 'shared.example' >"$TMP/source0.txt"
printf '%s\n' 'shared.example' >"$TMP/source1.txt"
SOURCE0_SHA="$(sha256sum "$TMP/source0.txt" | awk '{print $1}')"
printf '0|block|source-0|https://example.invalid/source0|%s\n' "$SOURCE0_SHA" >"$SS_RESOLVED_SOURCES"
printf '%s\n' '1|allow|source-1|https://example.invalid/source1|' >>"$SS_RESOLVED_SOURCES"
GET_CALLS=0
ss_http_get_file() {
GET_CALLS=$((GET_CALLS + 1))
SS_HTTP_STATUS=''
case "$1" in
https://example.invalid/source0)
if [ "$GET_CALLS" -eq 1 ]; then
return 1
fi
cp "$TMP/source0.txt" "$2"
;;
https://example.invalid/source1)
cp "$TMP/source1.txt" "$2"
;;
*) return 1 ;;
esac
}
ss_download_api_artifacts
ss_spec_assert_eq "$GET_CALLS" '3'
ss_cached_api_sources_available
ss_spec_assert_file_line "$STATUS" 'set health_artifact_download 1'
ss_spec_assert_file_line "$STATUS" 'set health_artifact_sha256 '
ss_spec_assert_file_line "$STATUS" 'warning artifact_sha256_partial'
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.0.block.txt")" 'ads.example
shared.example'
ss_spec_assert_eq "$(cat "$SS_TMP_DIR/api.1.allow.txt")" 'shared.example'
: >"$STATUS"
printf '%s\n' '0|block|bad-sha|https://example.invalid/source0|0000000000000000000000000000000000000000000000000000000000000000' >"$SS_RESOLVED_SOURCES"
GET_CALLS=0
ss_http_get_file() {
GET_CALLS=$((GET_CALLS + 1))
SS_HTTP_STATUS=''
cp "$TMP/source0.txt" "$2"
}
! ss_download_api_artifacts
ss_spec_assert_file_line "$STATUS" 'set health_artifact_sha256 0'
ss_spec_assert_file_line "$STATUS" 'error artifact_sha256_mismatch'
ss_spec_assert_not_exists "$SS_ARTIFACT_CACHE_STATE"
: >"$STATUS"
command_exists() { return 1; }
! ss_verify_artifact_sha256 "$TMP/source0.txt" "$SOURCE0_SHA"
ss_spec_assert_file_line "$STATUS" 'set health_artifact_sha256 0'
ss_spec_assert_file_line "$STATUS" 'error sha256sum_not_found'
command_exists() { command -v "$1" >/dev/null 2>&1; }
: >"$STATUS"
printf '%s\n' '0|block|too-large|https://example.invalid/source0|' >"$SS_RESOLVED_SOURCES"
ss_max_blocklist_file_size_kb=1
ss_http_get_file() {
SS_HTTP_STATUS=''
cp "$TMP/source0.txt" "$2"
}
du() { printf '2\t%s\n' "$2"; }
! ss_download_api_artifacts
ss_spec_assert_file_line "$STATUS" 'set health_artifact_download 0'
ss_spec_assert_file_line "$STATUS" 'error artifact_too_large'
ss_spec_assert_not_exists "$SS_ARTIFACT_CACHE_STATE"
)
+40
View File
@@ -0,0 +1,40 @@
#!/bin/sh
# shellcheck shell=sh
SS_SPEC_ROOT="${SS_SPEC_ROOT:-$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)}"
ss_spec_tmpdir() {
mktemp -d
}
ss_spec_assert_eq() {
[ "$1" = "$2" ]
}
ss_spec_assert_ne() {
[ "$1" != "$2" ]
}
ss_spec_assert_file_contains() {
grep -Fq -- "$2" "$1"
}
ss_spec_assert_file_not_contains() {
! grep -Fq -- "$2" "$1"
}
ss_spec_assert_file_line() {
grep -Fxq -- "$2" "$1"
}
ss_spec_assert_exists() {
[ -e "$1" ]
}
ss_spec_assert_not_exists() {
[ ! -e "$1" ]
}
ss_spec_assert_nonempty() {
[ -s "$1" ]
}
+449
View File
@@ -0,0 +1,449 @@
#!/bin/sh
# shellcheck shell=sh
ss_case_utils() (
set -eu
PKG_NAME='safeshield-test'
export PKG_NAME
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/utils.sh"
command_exists sh
! command_exists safeshield-command-that-does-not-exist
ss_spec_assert_eq "$(ss_mask_secret '')" ''
ss_spec_assert_eq "$(ss_mask_secret '12345678')" '********'
ss_spec_assert_eq "$(ss_mask_secret 'abcd1234wxyz')" 'abcd****wxyz'
is_valid_integer 0
is_valid_integer 42
for value in '' -1 1.5 12x; do
! is_valid_integer "$value"
done
is_greater_equal 2.90 2.90
is_greater_equal 2.91 2.90
! is_greater_equal 2.79 2.80
)
ss_case_config_validation() (
set -eu
PKG_NAME='safeshield-test'
export PKG_NAME
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/utils.sh"
WARNINGS=''
CONFIG_GET_VALUE=''
log_warn() { :; }
ss_status_add_warning() {
WARNINGS="${WARNINGS}${WARNINGS:+ }$1"
}
config_get() {
eval "$1='${CONFIG_GET_VALUE}'"
}
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/config.sh"
CONFIG_GET_VALUE='configured'
ss_spec_assert_eq "$(ss_config_get config enabled default)" 'configured'
CONFIG_GET_VALUE=''
ss_spec_assert_eq "$(ss_config_get config enabled default)" 'default'
ss_download_timeout='invalid'
ss_download_retry='-1'
ss_max_blocklist_file_size_kb='1.5'
ss_min_valid_line_count=''
ss_pause_timeout='invalid'
ss_boot_start_delay_s='-5'
ss_statistics_snapshot_interval_s='9'
ss_statistics_retention_hours='169'
ss_compress_blocklist='2'
ss_initial_dnsmasq_restart='yes'
ss_dnsmasq_sanity_check='no'
ss_apply_local_overrides='2'
ss_statistics_enabled='off'
WARNINGS=''
ss_validate_config
ss_spec_assert_eq "$ss_download_timeout" '10'
ss_spec_assert_eq "$ss_download_retry" '3'
ss_spec_assert_eq "$ss_max_blocklist_file_size_kb" '30000'
ss_spec_assert_eq "$ss_min_valid_line_count" '3000'
ss_spec_assert_eq "$ss_pause_timeout" '20'
ss_spec_assert_eq "$ss_boot_start_delay_s" '30'
ss_spec_assert_eq "$ss_statistics_snapshot_interval_s" '60'
ss_spec_assert_eq "$ss_statistics_retention_hours" '168'
ss_spec_assert_eq "$ss_compress_blocklist" '0'
ss_spec_assert_eq "$ss_initial_dnsmasq_restart" '0'
ss_spec_assert_eq "$ss_dnsmasq_sanity_check" '1'
ss_spec_assert_eq "$ss_apply_local_overrides" '1'
ss_spec_assert_eq "$ss_statistics_enabled" '1'
for code in \
invalid_download_timeout \
invalid_download_retry \
invalid_max_blocklist_file_size_kb \
invalid_min_valid_line_count \
invalid_pause_timeout \
invalid_boot_start_delay_s \
invalid_statistics_snapshot_interval_s \
invalid_statistics_retention_hours \
invalid_compress_blocklist \
invalid_initial_dnsmasq_restart \
invalid_dnsmasq_sanity_check \
invalid_apply_local_overrides \
invalid_statistics_enabled; do
case " $WARNINGS " in
*" $code "*) ;;
*) return 1 ;;
esac
done
ss_download_timeout='1'
ss_download_retry='0'
ss_max_blocklist_file_size_kb='1'
ss_min_valid_line_count='0'
ss_pause_timeout='0'
ss_boot_start_delay_s='0'
ss_statistics_snapshot_interval_s='10'
ss_statistics_retention_hours='168'
ss_compress_blocklist='1'
ss_initial_dnsmasq_restart='1'
ss_dnsmasq_sanity_check='0'
ss_apply_local_overrides='0'
ss_statistics_enabled='0'
WARNINGS=''
ss_validate_config
[ -z "$WARNINGS" ]
ss_spec_assert_eq "$ss_statistics_snapshot_interval_s" '10'
ss_spec_assert_eq "$ss_statistics_retention_hours" '168'
)
ss_case_dnsmasq_version() (
set -eu
TMP_DIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
PKG_NAME='safeshield'
export PKG_NAME
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/utils.sh"
DNSMASQ_FEATURE_HEALTH=''
ss_status_set() {
[ "$1" = 'health_dnsmasq_features' ] && DNSMASQ_FEATURE_HEALTH="$2"
return 0
}
log_error() { :; }
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/dns.sh"
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/dns.sh" 'smartsafehub'
make_dnsmasq() {
version="$1"
feature_rc="${2:-0}"
cat >"$TMP_DIR/dnsmasq" <<SCRIPT
#!/bin/sh
case "\${1:-}" in
--version)
printf '%s\n' 'Dnsmasq version ${version} Copyright (c) 2000-2026 Simon Kelley'
exit 0
;;
--test)
exit ${feature_rc}
;;
esac
exit 0
SCRIPT
chmod +x "$TMP_DIR/dnsmasq"
}
PATH="$TMP_DIR:$PATH"
export PATH
make_dnsmasq '2.93'
ss_spec_assert_eq "$(ss_dnsmasq_version)" '2.93'
ss_require_supported_dnsmasq
make_dnsmasq '2.94'
ss_require_supported_dnsmasq
make_dnsmasq '2.93rc1'
ss_spec_assert_eq "$(ss_dnsmasq_version)" '2.93'
ss_require_supported_dnsmasq
make_dnsmasq '2.92'
! ss_require_supported_dnsmasq
ss_spec_assert_eq "$(ss_dnsmasq_check_error)" 'dnsmasq_version_unsupported'
make_dnsmasq '2.93' 1
ss_require_supported_dnsmasq
ss_spec_assert_eq "$(ss_dnsmasq_check_error)" ''
ss_spec_assert_eq "$DNSMASQ_FEATURE_HEALTH" '0'
cat >"$TMP_DIR/dnsmasq" <<'SCRIPT'
#!/bin/sh
printf '%s\n' 'unexpected version output'
SCRIPT
chmod +x "$TMP_DIR/dnsmasq"
! ss_require_supported_dnsmasq
ss_spec_assert_eq "$(ss_dnsmasq_check_error)" 'dnsmasq_version_unknown'
rm -f "$TMP_DIR/dnsmasq"
ORIGINAL_PATH="$PATH"
PATH="$TMP_DIR"
! ss_require_supported_dnsmasq
PATH="$ORIGINAL_PATH"
ss_spec_assert_eq "$(ss_dnsmasq_check_error)" 'dnsmasq_binary_not_found'
)
ss_case_identity() (
set -eu
TMP_DIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
SS_IDENTITY_DIR="$TMP_DIR/identity"
SS_IDENTITY_FILE="$SS_IDENTITY_DIR/identity.env"
export SS_IDENTITY_DIR SS_IDENTITY_FILE
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/identity.sh"
ss_spec_assert_eq "$(ss_identity_normalize_mac 'MAC=AA:BB:CC:DD:EE:FF')" 'aa:bb:cc:dd:ee:ff'
ss_identity_valid_mac 'aa:bb:cc:dd:ee:ff'
for mac in '' '00:00:00:00:00:00' 'ff:ff:ff:ff:ff:ff' 'aa:bb:cc:dd:ee'; do
! ss_identity_valid_mac "$mac"
done
ss_spec_assert_eq "$(ss_identity_hex_to_mac 'AABBCCDDEEFF')" 'aa:bb:cc:dd:ee:ff'
ss_spec_assert_eq "$(ss_identity_profile_code 'iptime,ax3000sm')" 'iptime_ax3000sm'
ss_spec_assert_eq "$(ss_identity_profile_code 'glinet,gl-mt300n-v2')" 'gl_mt300n_v2'
ss_spec_assert_eq "$(ss_identity_profile_code 'xiaomi,mi-router-ax3000t')" 'xiaomi_mi_router_ax3000t'
ss_spec_assert_eq "$(ss_identity_profile_code 'smartsafehub,router')" 'smartsafehub'
ss_spec_assert_eq "$(ss_identity_device_code_from_board 'iptime,ax3000sm')" 'iptime-ax3000sm'
ss_spec_assert_eq "$(ss_identity_device_code_from_board 'glinet,gl-mt300n-v2')" 'gl-mt300n-v2'
ss_spec_assert_eq "$(ss_identity_device_code_from_board 'xiaomi,mi-router-ax3000t')" 'xiaomi-ax3000t'
ss_spec_assert_eq "$(ss_identity_device_code_from_board 'xiaomi,mi-router-ax3000t-ubootmod')" 'xiaomi-ax3000t'
! ss_identity_device_code_from_board 'vendor,unsupported-router'
ss_identity_valid_device_code 'xiaomi-ax3000t'
! ss_identity_valid_device_code 'Xiaomi AX3000T'
SS_SMARTSAFEHUB_FIRMWARE_JSON="$TMP_DIR/firmware.json"
export SS_SMARTSAFEHUB_FIRMWARE_JSON
printf '%s\n' '{"device_code":"xiaomi-ax3000t"}' >"$SS_SMARTSAFEHUB_FIRMWARE_JSON"
MOCK_DEVICE_CODE='xiaomi-ax3000t'
jsonfilter() { printf '%s\n' "$MOCK_DEVICE_CODE"; }
ss_identity_board_name() { printf '%s' 'iptime,ax3000sm'; }
ss_identity_refresh_device_code
ss_spec_assert_eq "$SS_DEVICE_CODE" 'xiaomi-ax3000t'
ss_spec_assert_eq "$SS_DEVICE_CODE_SOURCE" 'smartsafehub_firmware'
MOCK_DEVICE_CODE='invalid device code'
ss_identity_refresh_device_code
ss_spec_assert_eq "$SS_DEVICE_CODE" 'iptime-ax3000sm'
ss_spec_assert_eq "$SS_DEVICE_CODE_SOURCE" 'board'
rm -f "$SS_SMARTSAFEHUB_FIRMWARE_JSON"
ss_identity_board_name() { printf '%s' 'vendor,unsupported-router'; }
ss_identity_refresh_device_code
ss_spec_assert_eq "$SS_DEVICE_CODE" ''
ss_spec_assert_eq "$SS_DEVICE_CODE_SOURCE" 'unknown'
ss_identity_board_name() { printf '%s' 'iptime,ax3000sm'; }
ss_identity_try_mtd_factory_mac() { printf '%s' 'aa:bb:cc:dd:ee:ff|mtd:Factory:0x4:mac'; }
ss_identity_detect_primary_mac() { return 1; }
ss_identity_compute_physical 'ipTIME AX3000SM' 'aarch64'
ss_spec_assert_eq "$SS_IDENTITY_PROVIDER" 'factory_mac'
ss_spec_assert_eq "$SS_IDENTITY_SOURCE" 'mtd:Factory:0x4:mac'
ss_spec_assert_eq "$SS_IDENTITY_STRENGTH" 'hardware_soft'
ss_spec_assert_eq "$SS_IDENTITY_PROFILE" 'iptime_ax3000sm'
ss_identity_valid_sha256 "$SS_IDENTITY_VALUE_SHA256"
ss_identity_valid_sha256 "$SS_PHYSICAL_FINGERPRINT"
SS_INSTALLATION_ID='11111111-2222-3333-4444-555555555555'
SS_INSTALLATION_SECRET='0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
SS_IDENTITY_CREATED_AT='1788090000'
SS_IDENTITY_UPDATED_AT='1788090001'
ss_identity_write_env
ss_spec_assert_nonempty "$SS_IDENTITY_FILE"
identity_mode="$(LC_ALL=C ls -ld "$SS_IDENTITY_FILE" | awk '{ print substr($1, 2, 9) }')"
ss_spec_assert_eq "$identity_mode" 'rw-------'
expected_fingerprint="$SS_PHYSICAL_FINGERPRINT"
SS_PHYSICAL_FINGERPRINT=''
SS_IDENTITY_PROVIDER=''
SS_IDENTITY_SOURCE=''
SS_IDENTITY_STRENGTH=''
SS_IDENTITY_PROFILE=''
SS_INSTALLATION_ID=''
ss_identity_load
ss_spec_assert_eq "$SS_PHYSICAL_FINGERPRINT" "$expected_fingerprint"
ss_spec_assert_eq "$SS_IDENTITY_PROVIDER" 'factory_mac'
ss_spec_assert_eq "$SS_INSTALLATION_ID" '11111111-2222-3333-4444-555555555555'
SYNCED=0
STATUS_SYNCED=0
ss_identity_sync_uci() { SYNCED=1; }
ss_identity_status_set() { STATUS_SYNCED=1; }
ss_identity_create() { return 97; }
ss_identity_ensure 'ipTIME AX3000SM' 'aarch64'
ss_spec_assert_eq "$SYNCED" '1'
ss_spec_assert_eq "$STATUS_SYNCED" '1'
)
ss_case_resolve_payload() (
set -eu
TMP_DIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
ss_detect_device_model() { printf '%s' 'Test Router'; }
ss_detect_device_vendor() { printf '%s' 'TestVendor'; }
ss_detect_device_arch() { printf '%s' 'test_arch'; }
ss_detect_device_memory_mb() { printf '%s' '128'; }
SS_PHYSICAL_FINGERPRINT='test-fingerprint'
SS_FINGERPRINT_VERSION='1'
SS_IDENTITY_PROVIDER='factory_mac'
SS_IDENTITY_SOURCE='test-source'
SS_IDENTITY_STRENGTH='hardware_soft'
SS_IDENTITY_PROFILE='test-profile'
SS_DEVICE_CODE='test-router'
SS_DEVICE_CODE_SOURCE='board'
SS_INSTALLATION_ID='test-installation'
ss_identity_ensure() { return 0; }
is_valid_integer() {
case "$1" in
'' | *[!0-9]*) return 1 ;;
*) return 0 ;;
esac
}
ss_status_set() { :; }
ss_license_key='test-license'
SS_VERSION_FILE="$TMP_DIR/version"
export SS_VERSION_FILE
printf '%s\n' '0.3.18-r1' >"$SS_VERSION_FILE"
payload="$TMP_DIR/resolve-request.json"
ss_write_resolve_payload "$payload"
ss_spec_assert_file_contains "$payload" ' "safeshield_version": "0.3.18-r1"'
! grep -Eq '^ "safeshield_version"' "$payload"
ss_spec_assert_file_contains "$payload" '"license_key": "test-license"'
ss_spec_assert_file_contains "$payload" '"physical_fingerprint": "test-fingerprint"'
ss_spec_assert_file_contains "$payload" '"fingerprint_version": 1'
ss_spec_assert_file_contains "$payload" '"identity_provider": "factory_mac"'
ss_spec_assert_file_contains "$payload" '"identity_source": "test-source"'
ss_spec_assert_file_contains "$payload" '"identity_strength": "hardware_soft"'
ss_spec_assert_file_contains "$payload" '"identity_profile": "test-profile"'
ss_spec_assert_file_contains "$payload" '"device_code": "test-router"'
ss_spec_assert_file_contains "$payload" '"device_code_source": "board"'
ss_spec_assert_file_contains "$payload" '"installation_id": "test-installation"'
ss_spec_assert_file_contains "$payload" '"vendor": "TestVendor"'
ss_spec_assert_file_contains "$payload" '"model": "Test Router"'
ss_spec_assert_file_contains "$payload" '"arch": "test_arch"'
ss_spec_assert_file_contains "$payload" '"memory_mb": 128'
ss_detect_device_memory_mb() { printf '%s' 'invalid-memory'; }
invalid_memory_payload="$TMP_DIR/resolve-request-invalid-memory.json"
ss_write_resolve_payload "$invalid_memory_payload"
ss_spec_assert_file_contains "$invalid_memory_payload" '"memory_mb": 0'
SS_VERSION_FILE="$TMP_DIR/missing-version"
export SS_VERSION_FILE
fallback_payload="$TMP_DIR/resolve-request-fallback.json"
ss_write_resolve_payload "$fallback_payload"
ss_spec_assert_file_contains "$fallback_payload" ' "safeshield_version": "unknown"'
! grep -Eq '^ "safeshield_version"' "$fallback_payload"
)
ss_case_rpcd_contract() (
set -eu
ENTRY="$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield.uc"
ACL="$SS_SPEC_ROOT/files/usr/share/rpcd/acl.d/safeshield.json"
MAKEFILE="$SS_SPEC_ROOT/Makefile"
STATISTICS_MODULE="$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/statistics.uc"
[ -f "$ENTRY" ]
[ -f "$ACL" ]
ss_spec_assert_file_contains "$ENTRY" "unshift(REQUIRE_SEARCH_PATH, '/usr/share/rpcd/ucode/safeshield/*.uc');"
for module in status config refresh rules license statistics; do
ss_spec_assert_file_contains "$ENTRY" "let $module = require('$module');"
done
for method in status config statistics config_update set_enabled refresh rules_list rule_add rule_delete license_get license_update; do
ss_spec_assert_file_contains "$ENTRY" " $method: {"
ss_spec_assert_file_contains "$ACL" "\"$method\""
done
ss_spec_assert_file_contains "$MAKEFILE" './files/usr/share/rpcd/ucode/safeshield/*.uc'
ss_spec_assert_file_contains "$MAKEFILE" './files/usr/share/rpcd/acl.d/safeshield.json'
ss_spec_assert_file_contains "$STATISTICS_MODULE" 'effective_snapshot_interval_s: to_int(data.snapshot_interval_s, snapshot_interval_s)'
)
ss_case_status_state() (
set -eu
TMP_DIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
RUNNING_STATUS_FILE="$TMP_DIR/status.json"
SS_DNSMASQ_DIR="$TMP_DIR/dnsmasq.d"
SS_BLOCKLIST_FILE="$SS_DNSMASQ_DIR/safeshield.blocklist"
SS_PREV_BLOCKLIST_GZ="$TMP_DIR/previous.gz"
export RUNNING_STATUS_FILE SS_DNSMASQ_DIR SS_BLOCKLIST_FILE SS_PREV_BLOCKLIST_GZ
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/status.sh"
CALLS="$TMP_DIR/calls"
: >"$CALLS"
record() { printf '%s\t%s\t%s\n' "$1" "${2:-}" "${3:-}" >>"$CALLS"; }
ss_status_set() { record set "$1" "$2"; }
ss_status_add_error() { record error "$1"; }
ss_status_apply() {
record apply "$1" "${2:-}"
return 0
}
ss_now() { printf '%s' '1000'; }
is_valid_integer() {
case "$1" in '' | *[!0-9]*) return 1 ;; esac
[ "$1" -ge 0 ] 2>/dev/null
}
ss_status_mark_failure 'artifact_download_failed'
grep -F "$(printf 'set\tstatus\terror')" "$CALLS" >/dev/null
grep -F "$(printf 'set\tlast_failure\t1000')" "$CALLS" >/dev/null
grep -F "$(printf 'set\tlast_result\terror')" "$CALLS" >/dev/null
grep -F "$(printf 'set\tlast_error_code\tartifact_download_failed')" "$CALLS" >/dev/null
grep -F "$(printf 'error\tartifact_download_failed')" "$CALLS" >/dev/null
: >"$CALLS"
ss_status_mark_success 300
grep -F "$(printf 'apply\tclear_messages')" "$CALLS" >/dev/null
grep -F "$(printf 'set\tstatus\tready')" "$CALLS" >/dev/null
grep -F "$(printf 'set\tstage\tdone')" "$CALLS" >/dev/null
grep -F "$(printf 'set\tlast_success\t1000')" "$CALLS" >/dev/null
grep -F "$(printf 'set\tnext_refresh_at\t1300')" "$CALLS" >/dev/null
: >"$CALLS"
ss_status_mark_success invalid
! grep -F "$(printf 'set\tnext_refresh_at')" "$CALLS" >/dev/null
: >"$CALLS"
ss_status_reset_health_fields
for key in health_dnsmasq_binary health_dns_runtime health_artifact_download dnsmasq_min_version; do
grep -F "$(printf 'set\t%s\t' "$key")" "$CALLS" >/dev/null
done
: >"$CALLS"
ss_status_reset_blocklist_fields
for key in blocklist_installed blocklist_file_size_kb blocklist_verification_ok blocklist_backup_available; do
grep -F "$(printf 'set\t%s\t' "$key")" "$CALLS" >/dev/null
done
: >"$CALLS"
ss_status_reset_artifact_fields
for key in license_plan artifact_tier artifact_source_count artifact_allow_source_count; do
grep -F "$(printf 'set\t%s\t' "$key")" "$CALLS" >/dev/null
done
mkdir -p "$SS_DNSMASQ_DIR"
printf '%s\n' 'address=/ads.example/#' >"$TMP_DIR/new.blocklist"
ss_sync_path() { :; }
ss_install_blocklist_atomic "$TMP_DIR/new.blocklist" "$SS_BLOCKLIST_FILE"
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" 'address=/ads.example/#'
command_exists() { command -v "$1" >/dev/null 2>&1; }
ss_export_existing_blocklist
ss_spec_assert_nonempty "$SS_PREV_BLOCKLIST_GZ"
rm -f "$SS_BLOCKLIST_FILE"
ss_restore_previous_blocklist
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" 'address=/ads.example/#'
)
ss_case_status_version() (
set -eu
make_version="$(sed -n 's/^PKG_VERSION:=//p' "$SS_SPEC_ROOT/Makefile")"
make_release="$(sed -n 's/^PKG_RELEASE:=//p' "$SS_SPEC_ROOT/Makefile")"
init_version="$(sed -n "s/^readonly PKG_VERSION='\([^']*\)'/\1/p" "$SS_SPEC_ROOT/files/etc/init.d/safeshield")"
[ -n "$make_version" ]
[ -n "$make_release" ]
[ -n "$init_version" ]
ss_spec_assert_eq "$init_version" "${make_version}-r${make_release}"
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/etc/init.d/safeshield" "printf '%s\\n' \"\$PKG_VERSION\""
ss_spec_assert_file_contains "$SS_SPEC_ROOT/Makefile" "echo '\$(PKG_VERSION)-r\$(PKG_RELEASE)' > \$(1)/usr/lib/safeshield/version"
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/core.uc" 'fs.readfile(`/usr/lib/${PKG_NAME}/version`)'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/status.uc" 'version: PKG_VERSION,'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh" 'safeshield_upgrade_required'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh" 'failure_code="$(ss_resolve_error_code)"'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh" 'failure_code="$(ss_artifact_download_error_code)"'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh" 'if [ "$failure_code" != "safeshield_upgrade_required" ]; then'
)
+535
View File
@@ -0,0 +1,535 @@
#!/bin/sh
# shellcheck shell=sh
ss_case_refresh_state_machine() (
set -eu
TMP="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
CORE="$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh"
HARNESS="$TMP/core-state-machine.sh"
sed -n '/^safeshield_apply_local_rules() {/,${p;}' "$CORE" >"$HARNESS"
# shellcheck disable=SC1090
. "$HARNESS"
SS_TMP_DIR="$TMP/runtime"
SS_DNSMASQ_DIR="$TMP/dnsmasq.d"
SS_BLOCKLIST_FILE="$SS_DNSMASQ_DIR/safeshield.blocklist"
SS_INACTIVE_BLOCKLIST_FILE="$SS_TMP_DIR/inactive.blocklist"
SS_PREV_BLOCKLIST_GZ="$TMP/previous.gz"
mkdir -p "$SS_TMP_DIR" "$SS_DNSMASQ_DIR"
CALLS="$TMP/calls"
: >"$CALLS"
record() { printf '%s\n' "$*" >>"$CALLS"; }
log_info() { :; }
log_warn() { :; }
log_error() { :; }
log_ok() { :; }
ss_status_prepare_refresh() { record status_prepare_refresh; }
ss_status_set() { record "status_set $1 ${2:-}"; }
ss_status_set_now() { record "status_set_now $1"; }
ss_status_add_error() { record "status_error $1"; }
ss_status_mark_failure() { record "failure $1"; }
ss_status_mark_success() { record "success $1"; }
ss_load_config() {
ss_enabled=1
export ss_initial_dnsmasq_restart=0
ss_apply_local_overrides=1
return 0
}
ss_config_get() {
case "$2" in
refresh_interval_s) printf '%s\n' '300' ;;
*) printf '%s\n' "${3:-}" ;;
esac
}
is_valid_integer() { case "$1" in '' | *[!0-9]*) return 1 ;; *) return 0 ;; esac }
ss_sync_detected_device_config() { record sync_device_config; }
ss_identity_ensure() {
record identity_ensure
[ "${FAIL_STAGE:-}" != 'identity' ]
}
ss_detect_device_model() { printf '%s\n' 'Test Router'; }
ss_detect_device_arch() { printf '%s\n' 'test-arch'; }
ss_should_stop() { return 1; }
ss_mkdirs() {
record mkdirs
[ "${FAIL_STAGE:-}" != 'mkdir' ]
}
ss_clean_tmp() { record clean_tmp; }
ss_require_supported_dnsmasq() {
record require_dnsmasq
[ "${FAIL_STAGE:-}" != 'dnsmasq_version' ]
}
ss_dnsmasq_check_error() { printf '%s\n' 'dnsmasq_version_unsupported'; }
ss_ensure_dnsmasq_confdir() {
record ensure_confdir
[ "${FAIL_STAGE:-}" != 'confdir' ]
}
ss_export_existing_blocklist() {
record export_blocklist
printf '%s\n' 'backup' >"$SS_PREV_BLOCKLIST_GZ"
return 0
}
DNSMASQ_CALL=0
dnsmasq_restart() {
DNSMASQ_CALL=$((DNSMASQ_CALL + 1))
record "dnsmasq_restart $DNSMASQ_CALL"
[ "${FAIL_STAGE:-}" != 'restart' ]
}
ss_resolve_artifact() {
record resolve_artifact
[ "${FAIL_STAGE:-}" != 'resolve' ]
}
ss_resolve_error_code() { printf '%s\n' "${RESOLVE_ERROR_CODE:-artifact_resolve_failed}"; }
ss_download_api_artifacts() {
record download_artifacts
[ "${FAIL_STAGE:-}" != 'download' ]
}
ss_artifact_download_error_code() { printf '%s\n' "${DOWNLOAD_ERROR_CODE:-artifact_download_failed}"; }
ss_prepare_local_rule_files() {
record prepare_local_rules
[ "${FAIL_STAGE:-}" != 'local_rules' ]
}
ss_merge_lists() {
record merge_lists
[ "${FAIL_STAGE:-}" != 'merge' ]
}
ss_install_blocklist() {
record install_blocklist
[ "${FAIL_STAGE:-}" != 'install' ]
}
check_dns_runtime() {
record check_dns_runtime
[ "${FAIL_STAGE:-}" != 'runtime' ]
}
check_blocklist_applied_multi_with_stats() {
record "verify_blocklist $*"
[ "${FAIL_STAGE:-}" != 'verify' ]
}
ss_restore_and_restart() { record restore_and_restart; }
ss_refresh_lock_open() {
record lock_open
[ "${LOCK_AVAILABLE:-1}" = '1' ]
}
ss_refresh_lock_close() { record lock_close; }
ss_local_rules_fingerprint_from_tmp() { printf '%s\n' 'fingerprint-new'; }
ss_local_rules_state_write() { record "local_state_write $1"; }
ss_abort_refresh() {
record abort_refresh
return 130
}
assert_refresh_failure() {
stage="$1"
code="$2"
expect_restore="$3"
FAIL_STAGE="$stage"
RESOLVE_ERROR_CODE='artifact_resolve_failed'
DOWNLOAD_ERROR_CODE='artifact_download_failed'
DNSMASQ_CALL=0
: >"$CALLS"
if safeshield_force_download; then
return 1
fi
ss_spec_assert_file_line "$CALLS" "failure $code"
ss_spec_assert_file_line "$CALLS" 'lock_close'
if [ "$expect_restore" = '1' ]; then
ss_spec_assert_file_line "$CALLS" 'restore_and_restart'
else
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
fi
}
FAIL_STAGE=''
LOCK_AVAILABLE=1
: >"$CALLS"
safeshield_force_download
ss_spec_assert_file_line "$CALLS" 'status_set stage resolve_api'
ss_spec_assert_file_line "$CALLS" 'status_set stage download_artifact'
ss_spec_assert_file_line "$CALLS" 'status_set stage merge'
ss_spec_assert_file_line "$CALLS" 'status_set stage install'
ss_spec_assert_file_line "$CALLS" 'status_set stage restart_dnsmasq'
ss_spec_assert_file_line "$CALLS" 'status_set stage runtime_check'
ss_spec_assert_file_line "$CALLS" 'status_set stage blocklist_verify'
ss_spec_assert_file_line "$CALLS" 'success 300'
ss_spec_assert_file_line "$CALLS" 'local_state_write fingerprint-new'
ss_spec_assert_file_line "$CALLS" 'lock_close'
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
assert_refresh_failure merge merge_failed 1
assert_refresh_failure install install_failed 1
assert_refresh_failure restart dnsmasq_restart_failed 1
assert_refresh_failure runtime dns_runtime_check_failed 1
assert_refresh_failure verify blocklist_verification_failed 1
FAIL_STAGE='resolve'
RESOLVE_ERROR_CODE='safeshield_upgrade_required'
: >"$CALLS"
! safeshield_force_download
ss_spec_assert_file_line "$CALLS" 'failure safeshield_upgrade_required'
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
FAIL_STAGE='download'
DOWNLOAD_ERROR_CODE='safeshield_upgrade_required'
: >"$CALLS"
! safeshield_force_download
ss_spec_assert_file_line "$CALLS" 'failure safeshield_upgrade_required'
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
FAIL_STAGE=''
LOCK_AVAILABLE=0
: >"$CALLS"
safeshield_force_download
ss_spec_assert_file_line "$CALLS" 'lock_open'
! grep -F 'status_prepare_refresh' "$CALLS" >/dev/null
)
ss_case_local_apply_state_machine() (
set -eu
TMP="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
CORE="$SS_SPEC_ROOT/files/usr/lib/safeshield/core.sh"
HARNESS="$TMP/local-apply.sh"
sed -n '/^safeshield_apply_local_rules() {/,/^safeshield_force_download() {/p' "$CORE" | sed '$d' >"$HARNESS"
# shellcheck disable=SC1090
. "$HARNESS"
SS_TMP_DIR="$TMP/runtime"
SS_DNSMASQ_DIR="$TMP/dnsmasq.d"
SS_BLOCKLIST_FILE="$SS_DNSMASQ_DIR/safeshield.blocklist"
SS_PREV_BLOCKLIST_GZ="$TMP/previous.gz"
SS_INACTIVE_BLOCKLIST_FILE="$SS_TMP_DIR/inactive.blocklist"
mkdir -p "$SS_TMP_DIR" "$SS_DNSMASQ_DIR"
CALLS="$TMP/calls"
: >"$CALLS"
record() { printf '%s\n' "$*" >>"$CALLS"; }
log_info() { :; }
log_warn() { :; }
log_error() { :; }
log_ok() { :; }
sleep() { :; }
ss_load_config() {
ss_enabled="${MOCK_ENABLED:-1}"
export ss_apply_local_overrides="${MOCK_OVERRIDES:-1}"
return 0
}
ss_mkdirs() { return 0; }
ss_refresh_lock_open_wait() {
record "lock_wait $1"
return "${LOCK_RC:-0}"
}
ss_refresh_lock_close() { record lock_close; }
ss_cached_api_sources_available() { [ "${CACHE_AVAILABLE:-1}" = '1' ]; }
ss_status_set() { record "status_set $1 ${2:-}"; }
ss_status_set_now() { record "status_set_now $1"; }
ss_status_add_error() { record "status_error $1"; }
ss_prepare_local_rule_files() {
record prepare_local_rules
[ "${LOCAL_FAIL:-}" != 'local_rules' ]
}
ss_local_rules_fingerprint_from_tmp() { printf '%s\n' "${NEW_FINGERPRINT:-fingerprint-new}"; }
ss_local_rules_state_read() { [ -n "${APPLIED_FINGERPRINT:-}" ] && printf '%s\n' "$APPLIED_FINGERPRINT"; }
ss_local_rules_state_write() { record "local_state_write $1"; }
ss_require_supported_dnsmasq() { [ "${LOCAL_FAIL:-}" != 'dnsmasq_version' ]; }
ss_dnsmasq_check_error() { printf '%s\n' 'dnsmasq_version_unsupported'; }
ss_ensure_dnsmasq_confdir() {
record ensure_confdir
[ "${LOCAL_FAIL:-}" != 'confdir' ]
}
ss_export_existing_blocklist() {
printf '%s\n' backup >"$SS_PREV_BLOCKLIST_GZ"
return 0
}
ss_merge_lists() {
record merge_lists
[ "${LOCAL_FAIL:-}" != 'merge' ]
}
dnsmasq_restart() {
record dnsmasq_restart
[ "${LOCAL_FAIL:-}" != 'restart' ]
}
check_dns_runtime() {
record check_dns_runtime
[ "${LOCAL_FAIL:-}" != 'runtime' ]
}
check_blocklist_applied_multi_with_stats() {
record "verify_blocklist $*"
[ "${LOCAL_FAIL:-}" != 'verify' ]
}
ss_restore_and_restart() { record restore_and_restart; }
ss_mark_local_apply_failure() { record "local_failure $1"; }
ss_mark_local_apply_success() { record local_success; }
FALLBACK_RC=0
safeshield_force_download() {
record fallback_refresh
return "$FALLBACK_RC"
}
CACHE_AVAILABLE=0
LOCK_RC=0
LOCAL_FAIL=''
: >"$CALLS"
safeshield_apply_local_rules
ss_spec_assert_file_line "$CALLS" 'fallback_refresh'
ss_spec_assert_file_line "$CALLS" 'lock_close'
CACHE_AVAILABLE=1
LOCK_RC=1
: >"$CALLS"
! safeshield_apply_local_rules
ss_spec_assert_file_line "$CALLS" 'local_failure local_apply_lock_timeout'
LOCK_RC=130
: >"$CALLS"
set +e
safeshield_apply_local_rules
rc=$?
set -e
ss_spec_assert_eq "$rc" '130'
! grep -F 'local_failure' "$CALLS" >/dev/null
LOCK_RC=0
APPLIED_FINGERPRINT='fingerprint-new'
NEW_FINGERPRINT='fingerprint-new'
printf '%s\n' 'safeshield-block=/cached.example/' >"$SS_BLOCKLIST_FILE"
: >"$CALLS"
safeshield_apply_local_rules
ss_spec_assert_file_line "$CALLS" 'status_set stage done'
! grep -Fx 'merge_lists' "$CALLS" >/dev/null
! grep -Fx 'dnsmasq_restart' "$CALLS" >/dev/null
APPLIED_FINGERPRINT='fingerprint-old'
NEW_FINGERPRINT='fingerprint-new'
for scenario in merge restart runtime verify; do
LOCAL_FAIL="$scenario"
: >"$CALLS"
! safeshield_apply_local_rules
case "$scenario" in
merge) code='local_merge_failed' ;;
restart) code='local_dnsmasq_restart_failed' ;;
runtime) code='local_dns_runtime_check_failed' ;;
verify) code='local_blocklist_verification_failed' ;;
esac
ss_spec_assert_file_line "$CALLS" "local_failure $code"
ss_spec_assert_file_line "$CALLS" 'restore_and_restart'
ss_spec_assert_file_line "$CALLS" 'lock_close'
done
LOCAL_FAIL=''
: >"$CALLS"
safeshield_apply_local_rules
ss_spec_assert_file_line "$CALLS" 'merge_lists'
ss_spec_assert_file_line "$CALLS" 'dnsmasq_restart'
ss_spec_assert_file_line "$CALLS" 'check_dns_runtime'
ss_spec_assert_file_line "$CALLS" 'verify_blocklist 5 3'
ss_spec_assert_file_line "$CALLS" 'local_state_write fingerprint-new'
ss_spec_assert_file_line "$CALLS" 'local_success'
! grep -Fx 'restore_and_restart' "$CALLS" >/dev/null
)
ss_case_refresh_locking() (
set -eu
TMP="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
RUNNING_STATUS_FILE="$TMP/status.json"
SS_REFRESH_LOCK="$TMP/safeshield-refresh.lock"
SS_REFRESH_LOCK_FD=9
MOCK_FLOCK_DIR="$TMP/mock-flock.lock"
# Keep this regression test portable on development hosts such as macOS,
# where flock(1) is not installed by default. The production code still
# invokes flock -n/-u; this shim emulates those operations with atomic
# mkdir/rmdir so contention, release, timeout, and termination paths are
# exercised without depending on a host-specific flock binary.
flock() {
case "${1:-}" in
-n)
mkdir "$MOCK_FLOCK_DIR" 2>/dev/null
;;
-u)
rmdir "$MOCK_FLOCK_DIR" 2>/dev/null || true
return 0
;;
*)
return 2
;;
esac
}
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/status.sh"
is_valid_integer() { case "$1" in '' | *[!0-9]*) return 1 ;; *) return 0 ;; esac }
ss_should_stop() { return 1; }
mkdir "$MOCK_FLOCK_DIR"
! ss_refresh_lock_open
! ss_refresh_lock_open_wait 0
rmdir "$MOCK_FLOCK_DIR"
ss_refresh_lock_open
ss_spec_assert_exists "$MOCK_FLOCK_DIR"
ss_refresh_lock_close
ss_spec_assert_not_exists "$MOCK_FLOCK_DIR"
mkdir "$MOCK_FLOCK_DIR"
ss_should_stop() { return 0; }
set +e
ss_refresh_lock_open_wait 10
rc=$?
set -e
rmdir "$MOCK_FLOCK_DIR"
ss_spec_assert_eq "$rc" '130'
)
ss_case_service_lifecycle() (
set -eu
TMP="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP"' EXIT HUP INT TERM
INIT="$SS_SPEC_ROOT/files/etc/init.d/safeshield"
HARNESS="$TMP/init-lifecycle.sh"
sed -n '/^start_service() {/,/^reconcile_statistics() {/p' "$INIT" | sed '$d' >"$HARNESS"
# shellcheck disable=SC1090
. "$HARNESS"
export PKG_NAME='safeshield'
SS_BLOCKLIST_FILE="$TMP/dnsmasq.d/safeshield.blocklist"
export SS_INACTIVE_BLOCKLIST_FILE="$TMP/inactive.blocklist"
mkdir -p "${SS_BLOCKLIST_FILE%/*}"
CALLS="$TMP/calls"
: >"$CALLS"
record() { printf '%s\n' "$*" >>"$CALLS"; }
log_error() { :; }
log_warn() { :; }
ss_load_config() {
export ss_enabled="${MOCK_ENABLED:-1}"
export ss_statistics_enabled="${MOCK_STATISTICS:-1}"
return "${LOAD_RC:-0}"
}
ss_identity_ensure() {
record identity_ensure
return 0
}
ss_detect_device_model() { printf '%s\n' 'Test Router'; }
ss_detect_device_arch() { printf '%s\n' 'test-arch'; }
ss_mkdirs() {
record mkdirs
return 0
}
ss_statistics_request_rebaseline() {
record request_rebaseline
return 0
}
ss_statistics_configure_dnsmasq() {
record "statistics_configure $1"
printf '%s\n' "${STATISTICS_CHANGED:-0}"
}
ss_runtime_deactivate_blocklist() {
record deactivate_blocklist
return "${DEACTIVATE_RC:-0}"
}
ss_runtime_activate_blocklist() {
record activate_blocklist
return "${ACTIVATE_RC:-0}"
}
ss_status_set() { record "status_set $1 ${2:-}"; }
ss_status_mark_failure() { record "failure $1"; }
ss_require_supported_dnsmasq() {
record require_dnsmasq
return 0
}
ss_dnsmasq_check_error() { printf '%s\n' 'dnsmasq_version_unsupported'; }
ss_statistics_disable_if_dnsmasq_unpatched() {
record statistics_guard
return 0
}
ss_reconcile_installed_blocklists_for_dnsmasq() {
record reconcile_blocklists
return "${MIGRATION_RC:-0}"
}
ss_ensure_dnsmasq_confdir() {
record ensure_confdir
return 0
}
dnsmasq_restart() {
record dnsmasq_restart
return "${DNSMASQ_RC:-0}"
}
procd_open_instance() { record procd_open_instance; }
procd_set_param() { record "procd_set_param $*"; }
procd_close_instance() { record procd_close_instance; }
ss_statistics_add_procd_instance() { record add_statistics_instance; }
ss_statistics_add_upload_procd_instance() { record add_statistics_upload_instance; }
MOCK_ENABLED=0
MOCK_STATISTICS=1
STATISTICS_CHANGED=1
MIGRATION_RC=0
DNSMASQ_RC=0
: >"$CALLS"
start_service
ss_spec_assert_file_line "$CALLS" 'request_rebaseline'
ss_spec_assert_file_line "$CALLS" 'statistics_configure 0'
ss_spec_assert_file_line "$CALLS" 'deactivate_blocklist'
ss_spec_assert_file_line "$CALLS" 'dnsmasq_restart'
ss_spec_assert_file_line "$CALLS" 'status_set stage disabled'
! grep -Fx 'procd_open_instance' "$CALLS" >/dev/null
printf '%s\n' 'safeshield-block=/active.example/' >"$SS_BLOCKLIST_FILE"
: >"$CALLS"
start_service
ss_spec_assert_file_line "$CALLS" 'deactivate_blocklist'
! grep -Fx 'dnsmasq_restart' "$CALLS" >/dev/null
rm -f "$SS_BLOCKLIST_FILE"
MOCK_ENABLED=1
MOCK_STATISTICS=1
STATISTICS_CHANGED=0
MIGRATION_RC=0
: >"$CALLS"
start_service
ss_spec_assert_file_line "$CALLS" 'require_dnsmasq'
ss_spec_assert_file_line "$CALLS" 'statistics_guard'
ss_spec_assert_file_line "$CALLS" 'reconcile_blocklists'
ss_spec_assert_file_line "$CALLS" 'statistics_configure 1'
ss_spec_assert_file_line "$CALLS" 'activate_blocklist'
ss_spec_assert_file_line "$CALLS" 'procd_set_param command /usr/libexec/safeshield-refreshd'
ss_spec_assert_file_line "$CALLS" 'add_statistics_instance'
ss_spec_assert_file_line "$CALLS" 'add_statistics_upload_instance'
MIGRATION_RC=1
: >"$CALLS"
! start_service
ss_spec_assert_file_line "$CALLS" 'failure blocklist_directive_migration_failed'
! grep -Fx 'activate_blocklist' "$CALLS" >/dev/null
MIGRATION_RC=0
MOCK_ENABLED=1
STATISTICS_CHANGED=1
rm -f "$SS_BLOCKLIST_FILE"
: >"$CALLS"
stop_service
ss_spec_assert_file_line "$CALLS" 'statistics_configure 0'
ss_spec_assert_file_line "$CALLS" 'deactivate_blocklist'
ss_spec_assert_file_line "$CALLS" 'dnsmasq_restart'
ss_spec_assert_file_line "$CALLS" 'status_set stage stopped'
printf '%s\n' 'safeshield-block=/active.example/' >"$SS_BLOCKLIST_FILE"
: >"$CALLS"
stop_service
ss_spec_assert_file_line "$CALLS" 'deactivate_blocklist'
! grep -Fx 'dnsmasq_restart' "$CALLS" >/dev/null
rm -f "$SS_BLOCKLIST_FILE"
MOCK_ENABLED=0
STATISTICS_CHANGED=0
: >"$CALLS"
stop_service
ss_spec_assert_file_line "$CALLS" 'status_set status disabled'
ss_spec_assert_file_line "$CALLS" 'status_set last_result disabled'
)
reload_service() {
restart
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+72
View File
@@ -0,0 +1,72 @@
#!/bin/sh
# shellcheck shell=sh
ss_case_tooling() (
set -eu
[ -f "$SS_SPEC_ROOT/.pre-commit-config.yaml" ]
[ -f "$SS_SPEC_ROOT/.shellspec" ]
[ -f "$SS_SPEC_ROOT/spec/spec_helper.sh" ]
[ -f "$SS_SPEC_ROOT/spec/core_spec.sh" ]
[ -f "$SS_SPEC_ROOT/spec/blocklist_spec.sh" ]
[ -f "$SS_SPEC_ROOT/spec/runtime_spec.sh" ]
[ -f "$SS_SPEC_ROOT/spec/statistics_spec.sh" ]
[ -f "$SS_SPEC_ROOT/spec/ucode_spec.sh" ]
[ -f "$SS_SPEC_ROOT/spec/tooling_spec.sh" ]
[ -f "$SS_SPEC_ROOT/scripts/lint.sh" ]
[ -f "$SS_SPEC_ROOT/.github/workflows/lint-shell.yml" ]
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.pre-commit-config.yaml" 'entry: sh scripts/lint.sh'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.pre-commit-config.yaml" 'always_run: true'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.github/workflows/lint-shell.yml" 'sh scripts/lint.sh'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.github/workflows/lint-shell.yml" 'SHELLSPEC_VERSION: 0.28.1'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/.github/workflows/lint-shell.yml" 'run: REQUIRE_UCODE=1 shellspec'
[ ! -e "$SS_SPEC_ROOT/tests/run.sh" ]
ss_spec_assert_file_contains "$SS_SPEC_ROOT/scripts/lint.sh" 'spec/*_spec.sh)'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/scripts/lint.sh" 'shfmt -d -ci'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/scripts/lint.sh" 'shellcheck -x -S warning'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/scripts/lint.sh" 'sh -n "$file"'
[ ! -e "$SS_SPEC_ROOT/spec/legacy_tests_spec.sh" ]
)
ss_case_dead_code_contract() (
set -eu
UTILS="$SS_SPEC_ROOT/files/usr/lib/safeshield/utils.sh"
STATUS="$SS_SPEC_ROOT/files/usr/lib/safeshield/status.sh"
LOG="$SS_SPEC_ROOT/files/usr/lib/safeshield/log.sh"
DNS="$SS_SPEC_ROOT/files/usr/lib/safeshield/dns.sh"
BLOCKLIST="$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
CONFIG="$SS_SPEC_ROOT/files/usr/lib/safeshield/config.sh"
INIT="$SS_SPEC_ROOT/files/etc/init.d/safeshield"
for symbol in \
str_contains \
str_contains_word \
str_first_word \
str_to_lower \
str_to_upper \
str_replace \
is_greater; do
! grep -Eq "^${symbol}\\(\\)[[:space:]]*\\{" "$UTILS"
done
for symbol in is_enabled is_active; do
! grep -Eq "^${symbol}\\(\\)[[:space:]]*\\{" "$STATUS"
done
for symbol in log_fail log_debug; do
! grep -Eq "^${symbol}\\(\\)[[:space:]]*\\{" "$LOG"
done
for symbol in ss_clear_active_blocklist dnsmasq_kill; do
! grep -Eq "^${symbol}\\(\\)[[:space:]]*\\{" "$DNS"
done
! grep -Eq '^ss_detect_primary_mac\\(\\)[[:space:]]*\\{' "$BLOCKLIST"
! grep -F 'ss_debug=' "$CONFIG"
! grep -F '/lib/functions/network.sh' "$INIT"
# Keep the replacement/runtime contracts that made the retired helpers obsolete.
ss_spec_assert_file_contains "$BLOCKLIST" 'ss_identity_ensure "$model" "$arch"'
ss_spec_assert_file_contains "$DNS" '/etc/init.d/dnsmasq restart'
ss_spec_assert_file_contains "$INIT" "procd_add_reload_interface_trigger 'wan'"
)
+68
View File
@@ -0,0 +1,68 @@
#!/bin/sh
# shellcheck shell=sh
ss_case_ucode() (
set -eu
MODULE_DIR="$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield"
ENTRY="$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield.uc"
STATUS_STORE="$SS_SPEC_ROOT/files/usr/lib/safeshield/status-store.uc"
UCODE_BIN="${UCODE:-ucode}"
if ! command -v "$UCODE_BIN" >/dev/null 2>&1; then
[ "${REQUIRE_UCODE:-0}" != '1' ]
return 0
fi
TMPDIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMPDIR"' EXIT HUP INT TERM
compile_ucode() {
file="$1"
output="$TMPDIR/$(basename -- "$file").ucb"
"$UCODE_BIN" -c -o "$output" "$file" >/dev/null
}
compile_ucode "$ENTRY"
compile_ucode "$STATUS_STORE"
for file in "$MODULE_DIR"/*.uc; do
compile_ucode "$file"
done
run_ucode_test() {
name="$1"
mock_dir="$SS_SPEC_ROOT/tests/ucode/mocks/$name"
test_file="$SS_SPEC_ROOT/tests/ucode/test_$name.uc"
test_tmp="$TMPDIR/$name"
test_modules="$TMPDIR/modules/$name"
mkdir -p "$test_tmp" "$test_modules"
cp "$MODULE_DIR/$name.uc" "$test_modules/$name.uc"
cp "$mock_dir"/*.uc "$test_modules/"
"$UCODE_BIN" -L "$test_modules" -D "TEST_TMP=\"$test_tmp\"" "$test_file" >/dev/null
}
for name in core config license refresh rules statistics status runtime; do
run_ucode_test "$name"
done
status_file="$TMPDIR/status-store.json"
printf '%s\n' 'not-json' >"$status_file"
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" dump)"
printf '%s\n' "$out" | grep -F '"data":{}' >/dev/null
printf '%s\n' "$out" | grep -F '"warnings":[]' >/dev/null
printf '%s\n' "$out" | grep -F '"errors":[]' >/dev/null
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" set stage running)"
printf '%s\n' "$out" >"$status_file"
printf '%s\n' "$out" | grep -F '"stage":"running"' >/dev/null
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" add_warning dnsmasq_warning)"
printf '%s\n' "$out" >"$status_file"
printf '%s\n' "$out" | grep -F '"code":"dnsmasq_warning"' >/dev/null
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" add_error artifact_download_failed)"
printf '%s\n' "$out" >"$status_file"
printf '%s\n' "$out" | grep -F '"code":"artifact_download_failed"' >/dev/null
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" clear_messages)"
printf '%s\n' "$out" | grep -F '"warnings":[]' >/dev/null
printf '%s\n' "$out" | grep -F '"errors":[]' >/dev/null
out="$("$UCODE_BIN" "$STATUS_STORE" "$status_file" reset)"
printf '%s\n' "$out" | grep -F '"data":{}' >/dev/null
if "$UCODE_BIN" "$STATUS_STORE" "$status_file" set >/dev/null 2>&1; then
return 1
fi
if "$UCODE_BIN" "$STATUS_STORE" "$status_file" unknown >/dev/null 2>&1; then
return 1
fi
)