💋 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

@@ -5,7 +5,6 @@ let values = {
debug: '0',
download_retry: '3',
statistics_enabled: '1',
license_key: 'abcd1234wxyz',
device_vendor: 'Junatum',
device_model: 'Test Router',
device_arch: 'aarch64',
@@ -33,12 +32,6 @@ function to_int(v, def) {
return type(n) == 'int' ? n : def;
}
function mask_secret(v) {
let s = sprintf('%s', v || '');
if (!length(s)) return '';
if (length(s) <= 8) return '********';
return sprintf('%s...%s', substr(s, 0, 4), substr(s, length(s) - 4));
}
function api_error(code, message, field) {
let error = { code: code, message: message };
@@ -71,7 +64,6 @@ return {
cfg: function(name, def) { return values[name] == null ? def : values[name]; },
to_bool: to_bool,
to_int: to_int,
mask_secret: mask_secret,
api_error: api_error,
uci_commit_option: uci_commit_option,
bool_uci: function(v) { return v ? '1' : '0'; }
@@ -1,43 +0,0 @@
'use strict';
let state = {
license_key: 'existing-license-key',
commits: 0,
deletes: 0
};
function trim(s) {
return replace(s, /^[\r\n\t ]+|[\r\n\t ]+$/g, '');
}
function mask_secret(v) {
let s = sprintf('%s', v || '');
if (!length(s)) return '';
if (length(s) <= 8) return '********';
return sprintf('%s...%s', substr(s, 0, 4), substr(s, length(s) - 4));
}
function api_error(code, message, field) {
let error = { code: code, message: message };
if (field) error.field = field;
return { ok: false, error: error };
}
return {
state: state,
reload_uci: function() { return true; },
cfg: function(name, def) { return name == 'license_key' ? state.license_key : def; },
trim: trim,
mask_secret: mask_secret,
api_error: api_error,
uci_commit_option: function(name, value) {
state.license_key = value;
state.commits++;
return true;
},
uci_delete_option: function(name) {
state.license_key = '';
state.deletes++;
return true;
}
};
@@ -1,14 +0,0 @@
'use strict';
let state = { refresh_count: 0, statistics_reset_count: 0, accepted: true, reason: '' };
return {
state: state,
reset_statistics_upload_state: function() {
state.statistics_reset_count++;
},
start_refresh_async: function() {
state.refresh_count++;
return { accepted: state.accepted, reason: state.reason };
}
};
@@ -0,0 +1,24 @@
'use strict';
let ubus = require('ubus');
let state = { calls: [], timeout: false };
function popen(command, mode) {
push(state.calls, command);
if (state.timeout) return null;
let name = match(command, /\"name\":\"([^\"]+)\"/);
let service_name = name ? name[1] : null;
let instance = service_name ? ubus.state.services[service_name] : null;
let response = {};
if (instance != null) response[service_name] = instance;
return {
read: function() { return sprintf('%J', response); },
close: function() { return true; }
};
}
return {
state: state,
popen: popen,
unlink: function() { return true; }
};
@@ -1,6 +1,8 @@
'use strict';
return {
service_status: function() {
return { state: 'running', running: true, lookup_ok: true, instances: { statistics: { running: true } } };
},
service_instance_running: function(name, instance) {
return name == 'safeshield' && instance == 'statistics';
}
+3 -11
View File
@@ -6,7 +6,6 @@ let config = {
require_wan: '1',
refresh_interval_s: '28800',
boot_start_delay_s: '30',
license_key: 'abcd1234wxyz',
apply_local_overrides: '1',
device_vendor: 'Junatum',
device_model: 'Test Router',
@@ -41,8 +40,8 @@ let state = {
health_dns_runtime: '1',
blocklist_installed: '1',
blocklist_file_size_kb: '2048',
license_plan: 'pro',
license_status: 'active'
entitlement_plan: 'pro',
entitlement_status: 'active'
},
warnings: [],
errors: []
@@ -67,12 +66,6 @@ function to_int(v, def) {
return type(n) == 'int' ? n : def;
}
function mask_secret(v) {
let s = sprintf('%s', v || '');
if (!length(s)) return '';
if (length(s) <= 8) return '********';
return sprintf('%s...%s', substr(s, 0, 4), substr(s, length(s) - 4));
}
return {
PKG_NAME: 'safeshield',
@@ -93,6 +86,5 @@ return {
file_size_kb: function() { return 0; },
to_bool: to_bool,
to_optional_bool: to_optional_bool,
to_int: to_int,
mask_secret: mask_secret
to_int: to_int
};
@@ -1,9 +1,11 @@
'use strict';
let state = { service_running: true, dnsmasq_running: true };
let state = { service_running: true, dnsmasq_running: true, lookup_ok: true };
return {
state: state,
service_status: function(name) {
let running = name == 'dnsmasq' ? state.dnsmasq_running : state.service_running;
return { running: running && state.lookup_ok, state: !state.lookup_ok ? 'unknown' : (running ? 'running' : 'stopped'), lookup_ok: state.lookup_ok };
},
service_running: function() { return state.service_running; },
dnsmasq_running: function() { return state.dnsmasq_running; }
};
+2 -4
View File
@@ -8,8 +8,6 @@ let built = config.build();
assert(built.schema.name == 'safeshield.config', 'config schema name is preserved');
assert(built.values.enabled == false, 'disabled state is normalized to boolean');
assert(built.values.download_retry == 3, 'integer options are normalized');
assert(built.license.configured == true, 'configured license is reported');
assert(built.license.key_masked == 'abcd...wxyz', 'license key is masked');
assert(built.device.memory_mb == 256, 'device memory is normalized to integer');
let invalid_values = config.update({ args: { values: 'not-an-object' } });
@@ -27,8 +25,8 @@ assert(unknown.ok == false && unknown.error.code == 'unknown_option', 'unknown o
let dedicated = config.update({ args: { values: { enabled: true } } });
assert(dedicated.ok == false && dedicated.error.code == 'dedicated_method_required', 'enabled requires dedicated method');
let dedicated_license = config.update({ args: { values: { license_key: 'new-key' } } });
assert(dedicated_license.ok == false && dedicated_license.error.code == 'dedicated_method_required', 'license_key requires dedicated method');
let retired_license_key = config.update({ args: { values: { license_key: 'new-key' } } });
assert(retired_license_key.ok == false && retired_license_key.error.code == 'unknown_option', 'retired license_key is not a writable option');
runtime.state.last_action = '';
let statistics = config.update({ args: { values: { statistics_enabled: false } } });
-3
View File
@@ -22,9 +22,6 @@ assert(core.to_int('42', 7) == 42, 'to_int converts numeric strings');
assert(core.to_int('', 7) == 7, 'to_int uses default for empty values');
assert(core.to_int(null, 7) == 7, 'to_int uses default for null');
assert(core.mask_secret('') == '', 'mask_secret keeps empty secrets empty');
assert(core.mask_secret('short') == '********', 'mask_secret fully masks short secrets');
assert(core.mask_secret('abcd1234wxyz') == 'abcd...wxyz', 'mask_secret partially masks long secrets');
let error = core.api_error('invalid_value', 'Invalid value', 'field_name');
assert(error.ok == false, 'api_error marks response as failed');
-44
View File
@@ -1,44 +0,0 @@
'use strict';
let core = require('core');
let runtime = require('runtime');
let license = require('license');
let current = license.get();
assert(current.ok == true && current.license.key == 'existing-license-key', 'license_get returns current key');
let missing = license.update({ args: {} });
assert(missing.ok == false && missing.error.code == 'missing_argument', 'license_update requires license_key');
let multiline = license.update({ args: { license_key: 'line1\nline2' } });
assert(multiline.ok == false && multiline.error.code == 'invalid_license_key', 'license_update rejects line breaks');
let oversized_key = '';
for (let i = 0; i < 513; i++) oversized_key += 'x';
let too_long = license.update({ args: { license_key: oversized_key } });
assert(too_long.ok == false && too_long.error.code == 'invalid_license_key', 'license_update rejects keys longer than 512 characters');
runtime.state.refresh_count = 0;
runtime.state.statistics_reset_count = 0;
let updated = license.update({ args: { license_key: ' new-license-key ' } });
assert(updated.ok == true && updated.changed == true, 'license_update accepts a new key');
assert(core.state.license_key == 'new-license-key', 'license_update trims key before saving');
assert(updated.license.key_masked == 'new-...-key', 'license_update masks saved key');
assert(runtime.state.refresh_count == 1, 'license_update requests refresh after change');
assert(runtime.state.statistics_reset_count == 1, 'license_update resets statistics upload entitlement after change');
runtime.state.refresh_count = 0;
runtime.state.statistics_reset_count = 0;
let unchanged = license.update({ args: { license_key: 'new-license-key' } });
assert(unchanged.ok == true && unchanged.changed == false, 'unchanged license does not write');
assert(runtime.state.refresh_count == 0, 'unchanged license does not refresh');
assert(runtime.state.statistics_reset_count == 0, 'unchanged license keeps statistics upload entitlement');
runtime.state.statistics_reset_count = 0;
let cleared = license.update({ args: { license_key: '' } });
assert(cleared.ok == true && cleared.changed == true, 'empty license clears configured key');
assert(core.state.license_key == '' && core.state.deletes == 1, 'empty license uses delete helper');
assert(cleared.license.configured == false, 'cleared license is reported unconfigured');
assert(runtime.state.statistics_reset_count == 1, 'clearing the license resets statistics upload entitlement');
print('ucode license tests: ok\n');
+14
View File
@@ -2,6 +2,7 @@
let core = require('core');
let ubus = require('ubus');
let fs = require('fs');
let runtime = require('runtime');
ubus.state.services.safeshield = {
@@ -10,6 +11,7 @@ ubus.state.services.safeshield = {
stopped: { running: false }
}
};
assert(runtime.service_status('safeshield', false).state == 'running', 'runtime distinguishes running services');
assert(runtime.service_running('safeshield') == true, 'service_running accepts any running instance');
assert(runtime.service_instance_running('safeshield', 'refreshd') == true, 'service_instance_running finds a running instance');
assert(runtime.service_instance_running('safeshield', 'stopped') == false, 'service_instance_running preserves stopped state');
@@ -20,6 +22,18 @@ ubus.state.services.dnsmasq = { instances: { main: { running: true } } };
assert(runtime.dnsmasq_running() == true, 'dnsmasq_running accepts a running dnsmasq instance');
ubus.state.services.dnsmasq.instances.main.running = false;
assert(runtime.dnsmasq_running() == false, 'dnsmasq_running rejects stopped dnsmasq instances');
assert(length(fs.state.calls) == 6, 'supported service checks use a separate bounded ubus client while unsupported names are rejected locally');
for (let call in fs.state.calls) {
assert(index(call, 'ubus -t 2 call service list') >= 0, 'service lookup uses ubus built-in timeout without an external timeout binary');
}
fs.state.timeout = true;
assert(runtime.service_running('safeshield') == false, 'failed or timed out service lookup fails closed');
assert(runtime.service_status('safeshield', true).state == 'unknown', 'lookup failure is exposed as unknown');
fs.state.timeout = false;
let calls_before = length(fs.state.calls);
assert(runtime.service_status('safeshield', true).lookup_ok == true, 'failed lookups are not cached');
assert(runtime.service_status('safeshield', true).lookup_ok == true, 'successful lookup can be reused briefly');
assert(length(fs.state.calls) == calls_before + 1, 'repeated successful lookups share the cached result');
let action = runtime.run_service_action('status', 1000);
assert(action.ok == true && action.rc == 0, 'run_service_action reports successful service commands');
@@ -5,6 +5,7 @@ let statistics = require('statistics');
let result = statistics.build();
assert(result.schema.name == 'safeshield.statistics' && result.schema.version == 3, 'statistics schema is preserved');
assert(result.enabled == true && result.effective_enabled == true && result.available == true, 'configured and effective statistics availability are reported');
assert(result.collector_state == 'running' && result.collector_lookup_ok == true, 'collector lookup detail is exposed');
assert(result.collector_running == true, 'statistics collector state is reported');
assert(result.snapshot_interval_s == 30 && result.effective_snapshot_interval_s == 30 && result.retention_hours == 24, 'statistics config values are normalized');
assert(result.generation_id == 'generation-test', 'statistics generation is preserved');
+8 -1
View File
@@ -1,9 +1,15 @@
'use strict';
let core = require('core');
let runtime = require('runtime');
let status = require('status');
let ready = status.build();
assert(ready.runtime.refreshd_state == 'running' && ready.runtime.dnsmasq_state == 'running' && ready.runtime.refreshd_lookup_ok == true, 'healthy status includes successful procd lookup details');
runtime.state.lookup_ok = false;
let unknown = status.build();
assert(unknown.runtime.refreshd_state == 'unknown' && unknown.runtime.refreshd_lookup_ok == false && unknown.active == false, 'unknown service lookups do not claim running');
runtime.state.lookup_ok = true;
assert(ready.status == 'ready', 'status preserves ready state');
assert(ready.summary.label == 'Ready' && ready.summary.severity == 'info', 'ready summary is generated');
assert(index(ready.summary.message, '42000 rules active') >= 0, 'ready summary includes active rule count');
@@ -11,7 +17,8 @@ assert(ready.artifact.source_count == 2, 'status reports artifact source count')
assert(ready.artifact.block_source_count == 1 && ready.artifact.allow_source_count == 1, 'status reports artifact action counts');
assert(ready.sources.items[0].last_result == 'ok', 'healthy API source is reported ok');
assert(ready.blocklist.installed == true && ready.blocklist.file_size_kb == 2048, 'blocklist state is normalized');
assert(ready.license.configured == true && ready.license.key_masked == 'abcd...wxyz', 'license state is masked');
assert(ready.entitlement.plan == 'pro' && ready.entitlement.status == 'active', 'Hub entitlement is exposed under the entitlement field');
assert(ready.license == null, 'status does not expose the retired license status field');
assert(ready.device.device_code == 'xiaomi-ax3000t', 'status exposes the canonical device code');
assert(ready.device.device_code_source == 'smartsafehub_firmware', 'status exposes the device code source');
assert(ready.health.overall == 'ok', 'healthy ready state reports overall ok');