🎉 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

@@ -0,0 +1,78 @@
'use strict';
let values = {
enabled: '0',
debug: '0',
download_retry: '3',
statistics_enabled: '1',
license_key: 'abcd1234wxyz',
device_vendor: 'Junatum',
device_model: 'Test Router',
device_arch: 'aarch64',
device_memory_mb: '256'
};
const CONFIG_SPEC = {
debug: { kind: 'bool', def: false },
download_retry: { kind: 'int', def: 3, min: 1, max: 100 },
statistics_enabled: { kind: 'bool', def: true }
};
function to_bool(v, def) {
if (v == null) return def;
let s = sprintf('%s', v);
if (s == '1' || s == 'true' || s == 'yes' || s == 'on') return true;
if (s == '0' || s == 'false' || s == 'no' || s == 'off') return false;
return def;
}
function to_int(v, def) {
if (v == null || v == '') return def;
let n = int(v);
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 };
if (field) error.field = field;
return { ok: false, error: error };
}
let uci = {
set: function(pkg, section, name, value) {
values[name] = value;
return true;
},
commit: function() { return true; },
revert: function() { return true; }
};
function uci_commit_option(name, value) {
values[name] = sprintf('%s', value);
return true;
}
return {
uci: uci,
PKG_NAME: 'safeshield',
CONFIG_SCHEMA_NAME: 'safeshield.config',
CONFIG_SCHEMA_VERSION: 1,
CONFIG_SPEC: CONFIG_SPEC,
values: values,
reload_uci: function() { return true; },
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'; }
};
@@ -0,0 +1,22 @@
'use strict';
let state = {
last_action: '',
refresh_count: 0,
service_ok: true,
service_rc: 0,
refresh_accepted: true,
refresh_reason: ''
};
return {
state: state,
run_service_action: function(action) {
state.last_action = action;
return { ok: state.service_ok, rc: state.service_rc };
},
start_refresh_async: function() {
state.refresh_count++;
return { accepted: state.refresh_accepted, reason: state.refresh_reason };
}
};
+16
View File
@@ -0,0 +1,16 @@
'use strict';
function cursor() {
return {
load: function() { return true; },
get: function() { return null; },
set: function() { return true; },
delete: function() { return true; },
commit: function() { return true; },
revert: function() { return true; }
};
}
return {
cursor: cursor
};
@@ -0,0 +1,43 @@
'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;
}
};
@@ -0,0 +1,14 @@
'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,10 @@
'use strict';
let state = { accepted: true, reason: '' };
return {
state: state,
start_refresh_async: function() {
return { accepted: state.accepted, reason: state.reason };
}
};
@@ -0,0 +1,8 @@
'use strict';
let state = { value: 'test-status' };
return {
state: state,
build: function() { return { marker: state.value }; }
};
@@ -0,0 +1,32 @@
'use strict';
function trim(s) {
return replace(s, /^[\r\n\t ]+|[\r\n\t ]+$/g, '');
}
function to_bool(v, def) {
if (v == null) return def;
let s = sprintf('%s', v);
if (s == '1' || s == 'true' || s == 'yes' || s == 'on') return true;
if (s == '0' || s == 'false' || s == 'no' || s == 'off') return false;
return def;
}
function api_error(code, message, field) {
let error = { code: code, message: message };
if (field) error.field = field;
return { ok: false, error: error };
}
return {
LOCAL_ALLOWLIST_FILE: sprintf('%s/allowlist', TEST_TMP),
LOCAL_BLOCKLIST_FILE: sprintf('%s/blocklist', TEST_TMP),
RULES_SCHEMA_NAME: 'safeshield.rules',
RULES_SCHEMA_VERSION: 1,
RULES_DIR: TEST_TMP,
trim: trim,
reload_uci: function() { return true; },
cfg: function(name, def) { return name == 'apply_local_overrides' ? '1' : def; },
to_bool: to_bool,
api_error: api_error
};
@@ -0,0 +1,11 @@
'use strict';
let state = { apply_count: 0, accepted: true, reason: '' };
return {
state: state,
start_local_apply_async: function() {
state.apply_count++;
return { accepted: state.accepted, reason: state.reason };
}
};
@@ -0,0 +1,31 @@
'use strict';
let values = {
enabled: '1',
apply_local_overrides: '1'
};
let state = {
status: { data: { status: 'idle' } },
reloads: 0
};
function to_bool(v, def) {
if (v == null) return def;
let s = sprintf('%s', v);
if (s == '1' || s == 'true' || s == 'yes' || s == 'on') return true;
if (s == '0' || s == 'false' || s == 'no' || s == 'off') return false;
return def;
}
return {
PKG_NAME: 'safeshield',
STATUS_FILE: sprintf('%s/status.json', TEST_TMP),
SERVICE_INIT: '/bin/true',
values: values,
state: state,
read_json_file: function() { return state.status; },
to_bool: to_bool,
reload_uci: function() { state.reloads++; return true; },
cfg: function(name, def) { return values[name] == null ? def : values[name]; }
};
@@ -0,0 +1,27 @@
'use strict';
let state = {
services: {}
};
function connect() {
return {
call: function(object, method, args) {
if (object != 'service' || method != 'list' || !args || !args.name) {
return null;
}
let result = {};
let service = state.services[args.name];
if (service != null) {
result[args.name] = service;
}
return result;
}
};
}
return {
state: state,
connect: connect
};
@@ -0,0 +1,80 @@
'use strict';
let config = {
statistics_enabled: '1',
statistics_retention_hours: '24',
statistics_snapshot_interval_s: '30'
};
let data = {
schema: { name: 'collector' },
generation_id: 'generation-test',
snapshot_seq: '42',
volatile: '0',
storage: 'tmpfs+flash',
persistent: '1',
persistence_enabled: '1',
persistence_healthy: '1',
persistence_mode: 'journal',
persistent_error_count: '2',
persistent_last_error_at: '190',
persistent_updated_at: '195',
persistent_checkpoint_interval_s: '3600',
persistent_compacted_at: '180',
persistent_compact_interval_s: '604800',
started_at: '100',
session_started_at: '150',
updated_at: '200',
totals: { queries: '120', blocked: '12' },
source: {
backend: 'dnsmasq_ubus',
available: '1',
instance_id: 'epoch-test',
transport_scope: 'udp',
client_capacity: '128',
tracked_clients: '2',
untracked_queries: '3',
untracked_blocked: '2',
poll_error_count: '4',
last_error_at: '199'
},
hourly: [
{ bucket_start: '10', queries: '20', blocked: '2' },
'invalid',
{ bucket_start: 20, queries: 30, blocked: 3 }
],
device_limit: '128',
devices_truncated: '1',
devices: [
{ id: 'aa:bb', mac: 'AA:BB', ip: '192.0.2.10', hostname: 'phone', identified: '1', queries: '50', blocked: '5' },
{ id: '', ip: '192.0.2.11', queries: 10 },
'invalid'
]
};
function to_bool(v, def) {
if (v == null) return def;
let s = sprintf('%s', v);
if (s == '1' || s == 'true' || s == 'yes' || s == 'on') return true;
if (s == '0' || s == 'false' || s == 'no' || s == 'off') return false;
return def;
}
function to_int(v, def) {
if (v == null || v == '') return def;
let n = int(v);
return type(n) == 'int' ? n : def;
}
return {
PKG_NAME: 'safeshield',
STATISTICS_SCHEMA_NAME: 'safeshield.statistics',
STATISTICS_SCHEMA_VERSION: 3,
STATISTICS_FILE: '/tmp/statistics.json',
data: data,
reload_uci: function() { return true; },
cfg: function(name, def) { return config[name] == null ? def : config[name]; },
read_json_file: function() { return data; },
to_bool: to_bool,
to_int: to_int
};
@@ -0,0 +1,7 @@
'use strict';
return {
service_instance_running: function(name, instance) {
return name == 'safeshield' && instance == 'statistics';
}
};
@@ -0,0 +1,98 @@
'use strict';
let config = {
enabled: '1',
refresh_on_boot: '1',
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',
device_arch: 'aarch64',
device_memory_mb: '256'
};
let identity = {
physical_fingerprint: 'fingerprint',
fingerprint_version: '1',
identity_provider: 'factory-mac',
identity_source: 'eth0',
identity_strength: 'strong',
identity_profile: 'test-profile',
device_code: 'xiaomi-ax3000t',
device_code_source: 'smartsafehub_firmware',
installation_id: 'install-id'
};
let state = {
data: {
status: 'ready',
valid_line_count: '42000',
artifact_tier: 'pro',
artifact_version: '20260830T000000Z',
artifact_unique_domains: '41000',
artifact_source_count: '2',
artifact_block_source_count: '1',
artifact_allow_source_count: '1',
health_api_resolve: '1',
health_artifact_download: '1',
health_dns_runtime: '1',
blocklist_installed: '1',
blocklist_file_size_kb: '2048',
license_plan: 'pro',
license_status: 'active'
},
warnings: [],
errors: []
};
function to_bool(v, def) {
if (v == null) return def;
let s = sprintf('%s', v);
if (s == '1' || s == 'true' || s == 'yes' || s == 'on') return true;
if (s == '0' || s == 'false' || s == 'no' || s == 'off') return false;
return def;
}
function to_optional_bool(v) {
if (v == null || v == '') return null;
return to_bool(v, null);
}
function to_int(v, def) {
if (v == null || v == '') return def;
let n = int(v);
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',
PKG_VERSION: '0.3.16-r4',
STATUS_FILE: '/tmp/status.json',
BLOCKLIST_FILE: '/tmp/blocklist',
LOCAL_ALLOWLIST_FILE: '/etc/safeshield/allowlist',
LOCAL_BLOCKLIST_FILE: '/etc/safeshield/blocklist',
SCHEMA_NAME: 'safeshield.status',
SCHEMA_VERSION: 1,
state: state,
config: config,
reload_uci: function() { return true; },
cfg: function(name, def) { return config[name] == null ? def : config[name]; },
identity_cfg: function(name, def) { return identity[name] == null ? def : identity[name]; },
read_json_file: function() { return state; },
file_exists: function() { return false; },
file_size_kb: function() { return 0; },
to_bool: to_bool,
to_optional_bool: to_optional_bool,
to_int: to_int,
mask_secret: mask_secret
};
@@ -0,0 +1,9 @@
'use strict';
let state = { service_running: true, dnsmasq_running: true };
return {
state: state,
service_running: function() { return state.service_running; },
dnsmasq_running: function() { return state.dnsmasq_running; }
};
+83
View File
@@ -0,0 +1,83 @@
'use strict';
let core = require('core');
let runtime = require('runtime');
let config = require('config');
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' } });
assert(invalid_values.ok == false && invalid_values.error.code == 'invalid_type', 'config_update requires an object');
let invalid_type = config.update({ args: { values: { debug: 'true' } } });
assert(invalid_type.ok == false && invalid_type.error.code == 'invalid_type', 'boolean options reject strings');
let out_of_range = config.update({ args: { values: { download_retry: 101 } } });
assert(out_of_range.ok == false && out_of_range.error.code == 'out_of_range', 'integer options enforce ranges');
let unknown = config.update({ args: { values: { unknown_option: true } } });
assert(unknown.ok == false && unknown.error.code == 'unknown_option', 'unknown options are rejected');
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');
runtime.state.last_action = '';
let statistics = config.update({ args: { values: { statistics_enabled: false } } });
assert(statistics.ok == true, 'statistics update succeeds');
assert(statistics.reconciled == true && statistics.restarted == false, 'statistics-only update reconciles without restart');
assert(runtime.state.last_action == 'reconcile_statistics', 'statistics update requests reconciliation');
assert(core.values.statistics_enabled == '0', 'statistics update commits UCI value');
runtime.state.last_action = '';
runtime.state.refresh_count = 0;
let debug = config.update({ args: { values: { debug: true } } });
assert(debug.ok == true && debug.restarted == true, 'regular config update restarts service');
assert(runtime.state.last_action == 'restart', 'regular config update requests restart');
assert(runtime.state.refresh_count == 1, 'regular config update requests refresh');
assert(core.values.debug == '1', 'regular config update commits UCI value');
runtime.state.last_action = '';
runtime.state.refresh_count = 0;
let unchanged = config.update({ args: { values: { debug: true } } });
assert(unchanged.ok == true && length(unchanged.changed) == 0 && unchanged.restarted == false, 'unchanged config is a no-op');
assert(runtime.state.last_action == '' && runtime.state.refresh_count == 0, 'unchanged config does not touch runtime');
runtime.state.service_ok = false;
runtime.state.service_rc = 9;
let statistics_failure = config.update({ args: { values: { statistics_enabled: true } } });
assert(statistics_failure.ok == false && statistics_failure.committed == true, 'statistics reconcile failure reports committed configuration');
assert(statistics_failure.error.code == 'statistics_reconcile_failed' && statistics_failure.service_rc == 9, 'statistics reconcile failure preserves runtime error');
runtime.state.service_rc = 11;
let restart_failure = config.update({ args: { values: { download_retry: 4 } } });
assert(restart_failure.ok == false && restart_failure.committed == true, 'restart failure reports committed configuration');
assert(restart_failure.error.code == 'service_restart_failed' && restart_failure.service_rc == 11, 'restart failure preserves runtime error');
runtime.state.service_ok = true;
runtime.state.service_rc = 0;
let missing_enabled = config.set_enabled({ args: {} });
assert(missing_enabled.ok == false && missing_enabled.error.code == 'missing_argument', 'set_enabled requires enabled argument');
let invalid_enabled = config.set_enabled({ args: { enabled: 1 } });
assert(invalid_enabled.ok == false && invalid_enabled.error.code == 'invalid_type', 'set_enabled requires boolean');
let enabled = config.set_enabled({ args: { enabled: true } });
assert(enabled.ok == true && enabled.changed == true, 'set_enabled updates changed state');
assert(core.values.enabled == '1', 'set_enabled commits enabled state');
runtime.state.service_ok = false;
runtime.state.service_rc = 13;
let disable_failure = config.set_enabled({ args: { enabled: false } });
assert(disable_failure.ok == false && disable_failure.committed == true, 'set_enabled reports a committed change when runtime reconciliation fails');
assert(disable_failure.error.code == 'service_restart_failed' && disable_failure.service_rc == 13, 'set_enabled failure preserves runtime error');
print('ucode config tests: ok\n');
+36
View File
@@ -0,0 +1,36 @@
'use strict';
let core = require('core');
assert(core.trim(' example.com\n') == 'example.com', 'trim removes surrounding whitespace');
assert(core.trim('\t value \r\n') == 'value', 'trim removes tabs and line breaks');
assert(core.trim('value ') == 'value', 'trim removes trailing whitespace');
assert(core.trim(' value') == 'value', 'trim removes leading whitespace');
assert(core.to_bool('1', false) == true, 'to_bool accepts 1');
assert(core.to_bool('yes', false) == true, 'to_bool accepts yes');
assert(core.to_bool('off', true) == false, 'to_bool accepts off');
assert(core.to_bool('invalid', true) == true, 'to_bool falls back to default');
assert(core.to_bool(null, false) == false, 'to_bool handles null');
assert(core.to_optional_bool('true') == true, 'to_optional_bool accepts true');
assert(core.to_optional_bool('0') == false, 'to_optional_bool accepts 0');
assert(core.to_optional_bool('') == null, 'to_optional_bool treats empty as null');
assert(core.to_optional_bool('invalid') == null, 'to_optional_bool rejects invalid values');
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');
assert(error.error.code == 'invalid_value', 'api_error preserves error code');
assert(error.error.field == 'field_name', 'api_error preserves error field');
assert(core.bool_uci(true) == '1', 'bool_uci serializes true');
assert(core.bool_uci(false) == '0', 'bool_uci serializes false');
print('ucode core tests: ok\n');
+44
View File
@@ -0,0 +1,44 @@
'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');
+21
View File
@@ -0,0 +1,21 @@
'use strict';
let runtime = require('runtime');
let refresh = require('refresh');
let accepted = refresh.call();
assert(accepted.ok == true && accepted.accepted == true, 'accepted refresh succeeds');
assert(accepted.status.marker == 'test-status', 'refresh returns current status');
runtime.state.accepted = false;
runtime.state.reason = 'already_running';
let running = refresh.call();
assert(running.ok == true && running.accepted == false, 'already-running refresh is idempotent');
assert(running.reason == 'already_running', 'already-running reason is preserved');
runtime.state.reason = 'disabled';
let disabled = refresh.call();
assert(disabled.ok == false && disabled.accepted == false, 'disabled refresh is rejected');
assert(disabled.reason == 'disabled', 'disabled reason is preserved');
print('ucode refresh tests: ok\n');
+43
View File
@@ -0,0 +1,43 @@
'use strict';
let fs = require('fs');
let runtime = require('runtime');
let rules = require('rules');
let invalid_action = rules.add({ args: { action: 'deny', domain: 'example.com' } });
assert(invalid_action.ok == false && invalid_action.error.code == 'invalid_action', 'rules reject invalid actions');
let invalid_domain = rules.add({ args: { action: 'block', domain: 'bad..example.com' } });
assert(invalid_domain.ok == false && invalid_domain.error.code == 'invalid_domain', 'rules reject invalid domains');
let allow = rules.add({ args: { action: 'allow', domain: ' Example.COM ', refresh: false } });
assert(allow.ok == true && allow.added == true, 'allow rule is added');
assert(allow.domain == 'example.com', 'rule domain is normalized to lowercase');
assert(allow.refresh.reason == 'not_requested', 'refresh=false skips local apply');
let duplicate = rules.add({ args: { action: 'allow', domain: 'example.com' } });
assert(duplicate.ok == true && duplicate.added == false, 'duplicate rule is idempotent');
assert(duplicate.refresh.reason == 'unchanged', 'duplicate rule does not refresh');
runtime.state.apply_count = 0;
let block = rules.add({ args: { action: 'block', domain: '0.0.0.0 Ads.Example.COM' } });
assert(block.ok == true && block.domain == 'ads.example.com', 'hosts-format rule is normalized');
assert(runtime.state.apply_count == 1, 'changed rule requests local apply');
let listed = rules.list({ args: {} });
assert(listed.ok == true, 'rules_list succeeds');
assert(listed.counts.allow == 1 && listed.counts.block == 1, 'rules_list reports counts');
assert(listed.allow[0] == 'example.com', 'rules_list returns allow entry');
assert(listed.block[0] == 'ads.example.com', 'rules_list returns block entry');
let allow_only = rules.list({ args: { action: 'allow' } });
assert(length(allow_only.block) == 0 && length(allow_only.allow) == 1, 'rules_list filters by action');
let deleted = rules.delete({ args: { action: 'allow', domain: 'EXAMPLE.COM', refresh: false } });
assert(deleted.ok == true && deleted.deleted == true, 'rule_delete removes normalized rule');
assert(deleted.rules.counts.allow == 0, 'rule_delete returns updated rule count');
let content = fs.readfile(sprintf('%s/blocklist', TEST_TMP));
assert(content == 'ads.example.com\n', 'block rule file stores normalized domain');
print('ucode rules tests: ok\n');
+74
View File
@@ -0,0 +1,74 @@
'use strict';
let core = require('core');
let ubus = require('ubus');
let runtime = require('runtime');
ubus.state.services.safeshield = {
instances: {
refreshd: { running: true },
stopped: { running: false }
}
};
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');
assert(runtime.service_running('missing') == false, 'service_running rejects missing services');
assert(runtime.service_instance_running('missing', 'refreshd') == false, 'service_instance_running rejects missing services');
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');
let action = runtime.run_service_action('status', 1000);
assert(action.ok == true && action.rc == 0, 'run_service_action reports successful service commands');
core.state.status = { data: { status: 'running' } };
assert(runtime.refresh_running() == true, 'refresh_running recognizes running status');
core.state.status = { data: { status: 'ready' } };
assert(runtime.refresh_running() == false, 'refresh_running rejects non-running status');
core.values.enabled = '0';
let refresh_disabled = runtime.start_refresh_async();
assert(refresh_disabled.accepted == false && refresh_disabled.reason == 'disabled', 'start_refresh_async rejects disabled SafeShield');
core.values.enabled = '1';
ubus.state.services.safeshield = null;
let refresh_stopped = runtime.start_refresh_async();
assert(refresh_stopped.accepted == false && refresh_stopped.reason == 'service_stopped', 'start_refresh_async rejects a stopped service');
ubus.state.services.safeshield = { instances: { refreshd: { running: true } } };
core.state.status = { data: { status: 'running' } };
let refresh_running = runtime.start_refresh_async();
assert(refresh_running.accepted == false && refresh_running.reason == 'already_running', 'start_refresh_async is idempotent during a refresh');
core.state.status = { data: { status: 'ready' } };
let refresh_spawned = runtime.start_refresh_async();
assert(refresh_spawned.accepted == true && refresh_spawned.reason == '' && refresh_spawned.rc == 0, 'start_refresh_async launches the detached refresh worker');
core.values.enabled = '0';
let local_disabled = runtime.start_local_apply_async();
assert(local_disabled.accepted == false && local_disabled.reason == 'disabled', 'start_local_apply_async rejects disabled SafeShield');
core.values.enabled = '1';
core.values.apply_local_overrides = '0';
let local_overrides_disabled = runtime.start_local_apply_async();
assert(local_overrides_disabled.accepted == false && local_overrides_disabled.reason == 'local_overrides_disabled', 'start_local_apply_async requires local overrides');
core.values.apply_local_overrides = '1';
ubus.state.services.safeshield = null;
let local_stopped = runtime.start_local_apply_async();
assert(local_stopped.accepted == false && local_stopped.reason == 'service_stopped', 'start_local_apply_async rejects a stopped service');
ubus.state.services.safeshield = { instances: { refreshd: { running: true } } };
let local_spawned = runtime.start_local_apply_async();
assert(local_spawned.accepted == true && local_spawned.reason == '' && local_spawned.rc == 0, 'start_local_apply_async launches the detached local-rule worker');
assert(core.state.reloads >= 8, 'async entrypoints reload UCI before checking runtime state');
print('ucode runtime tests: ok\n');
reload_service() {
restart
}
+29
View File
@@ -0,0 +1,29 @@
'use strict';
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_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');
assert(result.snapshot_seq == 42, 'statistics snapshot sequence is preserved');
assert(result.started_at == 100 && result.session_started_at == 150 && result.updated_at == 200, 'statistics timestamps are normalized');
assert(result.volatile == false && result.storage == 'tmpfs+flash' && result.persistent == true, 'statistics storage metadata is normalized');
assert(result.persistence_enabled == true && result.persistence_healthy == true && result.persistence_mode == 'journal', 'statistics persistence state is normalized');
assert(result.persistent_error_count == 2 && result.persistent_last_error_at == 190, 'statistics persistence errors are normalized');
assert(result.persistent_updated_at == 195 && result.persistent_checkpoint_interval_s == 3600, 'statistics persistence checkpoint metadata is normalized');
assert(result.totals.queries == 120 && result.totals.blocked == 12, 'statistics totals are normalized');
assert(result.source.backend == 'dnsmasq_ubus' && result.source.available == true, 'statistics source backend is normalized');
assert(result.source.instance_id == 'epoch-test' && result.source.transport_scope == 'udp', 'statistics source epoch and transport scope are preserved');
assert(result.source.client_capacity == 128 && result.source.tracked_clients == 2 && result.source.untracked_queries == 3 && result.source.untracked_blocked == 2, 'statistics source client metadata is normalized');
assert(result.source.poll_error_count == 4 && result.source.last_error_at == 199, 'statistics source errors are normalized');
assert(length(result.hourly) == 2, 'invalid hourly entries are discarded');
assert(result.hourly[0].queries == 20 && result.hourly[1].blocked == 3, 'hourly values are normalized');
assert(result.device_limit == 128 && result.devices_truncated == true, 'device metadata is normalized');
assert(length(result.devices) == 1, 'invalid devices and empty ids are discarded');
assert(result.devices[0].identified == true, 'device identified flag is normalized');
assert(result.devices[0].queries == 50 && result.devices[0].blocked == 5, 'device counters are normalized');
print('ucode statistics tests: ok\n');
+53
View File
@@ -0,0 +1,53 @@
'use strict';
let core = require('core');
let status = require('status');
let ready = status.build();
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');
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.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');
core.state.data.status = 'error';
core.state.errors = [ { code: 'artifact_download_failed' } ];
let error = status.build();
assert(error.summary.label == 'Error' && error.summary.severity == 'error', 'error summary is generated');
assert(error.summary.message == 'artifact_download_failed', 'error summary uses first error code');
assert(error.health.overall == 'error', 'error status reports overall error');
core.state.data.status = 'ready';
core.state.errors = [];
core.state.warnings = [ { code: 'dnsmasq_warning' } ];
let warning = status.build();
assert(warning.summary.severity == 'warning', 'warnings elevate informational summary severity');
assert(warning.health.overall == 'warning', 'warnings report overall warning health');
core.config.enabled = '0';
core.state.data = {};
core.state.warnings = [];
let disabled = status.build();
assert(disabled.status == 'disabled', 'disabled config falls back to disabled status');
assert(disabled.summary.label == 'Disabled' && disabled.summary.severity == 'warning', 'disabled summary is generated');
assert(disabled.sources.items[0].last_result == 'disabled', 'disabled API source is reported disabled');
core.config.enabled = '1';
core.state.data = {
status: 'error',
health_api_resolve: '0',
last_error_code: 'safeshield_upgrade_required'
};
core.state.errors = [ { code: 'safeshield_upgrade_required' } ];
core.state.warnings = [];
let upgrade_required = status.build();
assert(upgrade_required.sources.items[0].last_result == 'safeshield_upgrade_required', 'upgrade-required source result is preserved');
assert(upgrade_required.summary.message == 'safeshield_upgrade_required', 'upgrade-required summary uses the specific error code');
print('ucode status tests: ok\n');