mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 00:35:13 +08:00
🎉 Sync 2026-09-27 21:03:26
This commit is contained in:
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 };
|
||||
}
|
||||
};
|
||||
@@ -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; }
|
||||
};
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
@@ -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
|
||||
}
|
||||
@@ -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');
|
||||
@@ -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');
|
||||
Reference in new issue
Block a user