💋 Sync 2026-10-11 00:16:14
Merge-upstream / merge (push) Canceled after 0s

This commit is contained in:
github-actions[bot] committed 2026-10-11 00:16:14 +08:00
1 parent c7bafc635d
commit c71da84035
264 files changed
+8169 -3944

No files matched your search

+7
View File
@@ -50,6 +50,13 @@ Describe 'SafeShield core shell behavior'
The error should equal ''
End
It 'removes preserved legacy license keys during upgrade'
When call ss_case_legacy_license_cleanup
The status should be success
The output should equal ''
The error should equal ''
End
It 'keeps package and runtime versions synchronized'
When call ss_case_status_version
The status should be success
+17
View File
@@ -0,0 +1,17 @@
# shellcheck shell=sh
Describe 'SafeShield device API integration'
It 'uses the generic device sync endpoint with device authentication'
When call ss_case_device_api_contract
The status should be success
The output should equal ''
The error should equal ''
End
It 'waits cleanly when the SmartSafeHub device credential is unavailable'
When call ss_case_device_registration_pending
The status should be success
The output should equal ''
The error should equal ''
End
End
+2
View File
@@ -18,6 +18,8 @@ export SS_SPEC_ROOT
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/spec/support/blocklist_cases.sh"
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/spec/support/device_api_cases.sh"
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/spec/support/runtime_cases.sh"
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/spec/support/statistics_cases.sh"
+6 -4
View File
@@ -150,7 +150,6 @@ ss_case_multi_artifact() (
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
@@ -159,11 +158,13 @@ ss_case_multi_artifact() (
ss_status_set() { :; }
ss_status_add_error() { :; }
log_info() { :; }
log_warn() { :; }
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_device_credential_token() { printf '%s\n' 'test-device-credential'; }
ss_write_resolve_payload() { printf '{}\n' >"$1"; }
ss_http_post_json() { printf '{}\n' >"$3"; }
ss_json_get_file() {
@@ -184,8 +185,8 @@ ss_case_multi_artifact() (
'@.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' ;;
'@.entitlement.plan') printf '%s\n' 'pro' ;;
'@.entitlement.status') printf '%s\n' 'active' ;;
'@.device.profile') printf '%s\n' 'test-profile' ;;
*) : ;;
esac
@@ -277,7 +278,6 @@ ss_case_upgrade_required() (
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
@@ -287,12 +287,14 @@ ss_case_upgrade_required() (
ss_status_add_error() { :; }
ss_status_add_warning() { :; }
log_info() { :; }
log_warn() { :; }
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_device_credential_token() { printf '%s\n' 'test-device-credential'; }
ss_write_resolve_payload() { printf '{}\n' >"$1"; }
printf '%s\n' 'HTTP error 426' >"$TMP_DIR/uclient.log"
+33 -8
View File
@@ -10,9 +10,6 @@ ss_case_utils() (
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
@@ -303,7 +300,6 @@ ss_case_resolve_payload() (
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"
@@ -311,7 +307,8 @@ ss_case_resolve_payload() (
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"'
! grep -Fq '"credential"' "$payload"
! grep -Fq '"license_key"' "$payload"
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"'
@@ -348,15 +345,20 @@ ss_case_rpcd_contract() (
[ -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
for module in status config refresh rules 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
for method in status config statistics config_update set_enabled refresh rules_list rule_add rule_delete; 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'
[ ! -e "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/license.uc" ]
ss_spec_assert_file_not_contains "$ENTRY" 'license_get'
ss_spec_assert_file_not_contains "$ENTRY" 'license_update'
ss_spec_assert_file_not_contains "$ACL" 'license_get'
ss_spec_assert_file_not_contains "$ACL" 'license_update'
ss_spec_assert_file_contains "$STATISTICS_MODULE" 'effective_snapshot_interval_s: to_int(data.snapshot_interval_s, snapshot_interval_s)'
)
@@ -413,7 +415,7 @@ ss_case_status_state() (
done
: >"$CALLS"
ss_status_reset_artifact_fields
for key in license_plan artifact_tier artifact_source_count artifact_allow_source_count; do
for key in entitlement_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"
@@ -429,6 +431,29 @@ ss_case_status_state() (
ss_spec_assert_eq "$(cat "$SS_BLOCKLIST_FILE")" 'address=/ads.example/#'
)
ss_case_legacy_license_cleanup() (
set -eu
TMP_DIR="$(ss_spec_tmpdir)"
trap 'rm -rf "$TMP_DIR"' EXIT HUP INT TERM
mkdir -p "$TMP_DIR/bin"
CALLS="$TMP_DIR/uci.calls"
export CALLS
cat >"$TMP_DIR/bin/uci" <<'EOF_UCI'
#!/bin/sh
printf '%s\n' "$*" >>"$CALLS"
case "$*" in
'-q get safeshield.config.license_key') exit 0 ;;
esac
exit 0
EOF_UCI
chmod 755 "$TMP_DIR/bin/uci"
PATH="$TMP_DIR/bin:$PATH" sh "$SS_SPEC_ROOT/files/etc/uci-defaults/91_safeshield_remove_legacy_license"
ss_spec_assert_file_contains "$CALLS" '-q get safeshield.config.license_key'
ss_spec_assert_file_contains "$CALLS" '-q delete safeshield.config.license_key'
ss_spec_assert_file_contains "$CALLS" '-q commit safeshield'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/Makefile" './files/etc/uci-defaults/91_safeshield_remove_legacy_license'
)
ss_case_status_version() (
set -eu
make_version="$(sed -n 's/^PKG_VERSION:=//p' "$SS_SPEC_ROOT/Makefile")"
+57
View File
@@ -0,0 +1,57 @@
#!/bin/sh
# shellcheck shell=sh
ss_case_device_api_contract() (
set -eu
BLOCKLIST="$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
STATISTICS="$SS_SPEC_ROOT/files/usr/lib/safeshield/statistics.sh"
ss_spec_assert_file_not_contains "$BLOCKLIST" '/api/v1/devices/bootstrap'
ss_spec_assert_file_contains "$BLOCKLIST" '/api/v1/devices/sync'
ss_spec_assert_file_contains "$STATISTICS" '/api/v1/devices/sync'
ss_spec_assert_file_contains "$BLOCKLIST" 'Authorization: ${authorization}'
ss_spec_assert_file_contains "$BLOCKLIST" 'Device ${device_credential}'
ss_spec_assert_file_not_contains "$BLOCKLIST" "url='https://www.smartsafehub.com/api/v1/licenses/resolve'"
ss_spec_assert_file_not_contains "$STATISTICS" "resolve_url='https://www.smartsafehub.com/api/v1/licenses/resolve'"
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/config.sh" 'ss_license_key'
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/libexec/safeshield-statistics-uploader" 'ss_license_key'
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield.uc" 'license_update'
ss_spec_assert_file_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/status.uc" 'entitlement: {'
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/share/rpcd/ucode/safeshield/status.uc" 'license: {'
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/status.sh" 'license_plan'
ss_spec_assert_file_not_contains "$SS_SPEC_ROOT/files/usr/lib/safeshield/status.sh" 'license_status'
)
ss_case_device_registration_pending() (
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_download_retry=1
ss_download_timeout=10
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() { :; }
log_info() { :; }
log_warn() { :; }
log_error() { :; }
log_ok() { :; }
command_exists() { return 1; }
# shellcheck disable=SC1091
. "$SS_SPEC_ROOT/files/usr/lib/safeshield/blocklist.sh"
ss_write_resolve_payload() { printf '{}\n' >"$1"; }
ss_device_credential_token() { return 1; }
if ss_resolve_artifact; then
return 1
fi
ss_spec_assert_eq "$SS_RESOLVE_ERROR_CODE" 'device_registration_pending'
ss_spec_assert_file_contains "$TMP_DIR/status" 'device_registration_status=pending'
ss_spec_assert_file_contains "$TMP_DIR/status" 'health_api_resolve=0'
)
+7 -10
View File
@@ -529,7 +529,6 @@ EOF_CORE
MOCK_REFRESH_RESULT='allowed'
MOCK_DENIED_RECHECK_DUE='0'
SS_STATISTICS_UPLOAD_TOKEN=''
ss_license_key='paid-license-key'
ss_should_terminate=0
: >"$REFRESH_CALLS"
upload_sleep() {
@@ -625,14 +624,12 @@ EOF_CORE
upload_state_load
ss_spec_assert_eq "$upload_state_needs_full" '1'
# An unlicensed device is blocked locally without asking the Hub for a
# statistics credential. The long-lived uploader can remain idle so a later
# license update can re-enable it without restarting the collector.
MOCK_UPLOAD_ENTITLEMENT='allowed'
# Statistics eligibility comes only from device sync. A denied account/plan
# is rechecked on the normal entitlement cadence without any local license key.
MOCK_UPLOAD_ENTITLEMENT='denied'
MOCK_REFRESH_RESULT='allowed'
MOCK_DENIED_RECHECK_DUE='1'
SS_STATISTICS_UPLOAD_TOKEN='token-old'
ss_license_key=''
SS_STATISTICS_UPLOAD_TOKEN=''
ss_should_terminate=0
: >"$REFRESH_CALLS"
upload_sleep() {
@@ -640,9 +637,9 @@ EOF_CORE
return 1
}
main
ss_spec_assert_eq "$MOCK_UPLOAD_ENTITLEMENT" 'denied'
ss_spec_assert_eq "$(wc -l <"$REFRESH_CALLS" | tr -d ' ')" '0'
ss_spec_assert_eq "$SS_STATISTICS_UPLOAD_TOKEN" ''
ss_spec_assert_eq "$MOCK_UPLOAD_ENTITLEMENT" 'allowed'
ss_spec_assert_eq "$(wc -l <"$REFRESH_CALLS" | tr -d ' ')" '1'
ss_spec_assert_eq "$SS_STATISTICS_UPLOAD_TOKEN" 'token-new'
)
ss_case_statistics_upload_credentials() (
+3
View File
@@ -64,6 +64,9 @@ ss_case_dead_code_contract() (
! grep -Eq '^ss_detect_primary_mac\\(\\)[[:space:]]*\\{' "$BLOCKLIST"
! grep -F 'ss_debug=' "$CONFIG"
! grep -F '/lib/functions/network.sh' "$INIT"
! grep -F 'ss_license_key' "$CONFIG"
! grep -F 'license_key:' "$INIT"
! grep -Eq '^ss_mask_secret\(\)[[:space:]]*\{' "$UTILS"
# Keep the replacement/runtime contracts that made the retired helpers obsolete.
ss_spec_assert_file_contains "$BLOCKLIST" 'ss_identity_ensure "$model" "$arch"'
+1 -1
View File
@@ -34,7 +34,7 @@ ss_case_ucode() (
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
for name in core config refresh rules statistics status runtime; do
run_ucode_test "$name"
done