fix: revert TUNNEL_LOCAL_SSH_PORT to 2223, fix registry, add incident docs
- wizard.sh + connect.conf: TUNNEL_LOCAL_SSH_PORT back to 2223 (Synology SSH port) - port-registry: renamed x6340 → x4662 (matches QuickConnect ID) - port-registry: added MR2200ac mesh satellite note for x4662 - deployment checklist: added kit-connect section, post-deployment rules, incident log, and complete fleet port registry table - memory: added fleet-router-inventory.md with full fleet state - hub README: updated with current status and locations Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
setup() { load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-score.sh'; }
|
||||
|
||||
@test "composite weights sum to a 0-100 score" {
|
||||
# latency35 dl25 up10 jit10 loss10 sig10 ; all metrics perfect -> 100
|
||||
run score_composite 5 25 12.5 1 0 -65
|
||||
[ "$output" -eq 100 ]
|
||||
}
|
||||
@test "composite all-worst -> 0" {
|
||||
run score_composite 200 0.5 0.5 50 40 -95
|
||||
[ "$output" -eq 0 ]
|
||||
}
|
||||
@test "rolling average of 3 samples" {
|
||||
run rolling_avg 90 60 30 # -> 60
|
||||
[ "$output" -eq 60 ]
|
||||
}
|
||||
@test "rolling average rounds half-up" {
|
||||
run rolling_avg 100 100 99 # 299/3 = 99.67 -> 100
|
||||
[ "$output" -eq 100 ]
|
||||
}
|
||||
@test "signal weight redistributes when no modem (signal='')" {
|
||||
run score_composite 5 25 12.5 1 0 "" # still 100, sig weight moved to latency/throughput
|
||||
[ "$output" -eq 100 ]
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
LIBDIR="$(cd "$BATS_TEST_DIRNAME/../.."/package/kit-busrouter/files/usr/lib/busrouter && pwd)"
|
||||
|
||||
setup() {
|
||||
export LIB_DIR="$LIBDIR"
|
||||
export STATE_DIR="$BATS_TMPDIR/daemon_state"
|
||||
rm -rf "$STATE_DIR"
|
||||
mkdir -p "$STATE_DIR"
|
||||
export SCORE_SAMPLES=3
|
||||
export CELL_IFACE=rmnet_mhi0 CELL_MBR=modem_0001
|
||||
export ETH_IFACE=eth0 ETH_MBR=wan
|
||||
export INTERVAL=30
|
||||
# Load all libs (daemon.sh's while loop is guarded by case "${0##*/}" so safe to load)
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-decide.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-score.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/metrics-net.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/speedtest.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/modem-sim.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/starlink.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/wan-mwan.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/telemetry.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/daemon.sh'
|
||||
}
|
||||
|
||||
@test "daemon.sh loads without error and does not start loop" {
|
||||
run true
|
||||
[ "$status" -eq 0 ]
|
||||
# If we reach here, the load in setup() did not hang -> loop guard works
|
||||
}
|
||||
|
||||
@test "_score_history_update: primes from default 50 50 50 and appends new score" {
|
||||
run _score_history_update "modem_0001" 70
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -qE '^50 50 70$'
|
||||
}
|
||||
|
||||
@test "_score_history_update: keeps last SCORE_SAMPLES scores only" {
|
||||
export SCORE_SAMPLES=3
|
||||
_score_history_update "modem_0001" 60 >/dev/null
|
||||
_score_history_update "modem_0001" 70 >/dev/null
|
||||
_score_history_update "modem_0001" 80 >/dev/null
|
||||
run _score_history_update "modem_0001" 90
|
||||
[ "$status" -eq 0 ]
|
||||
# Should be "70 80 90" (last 3 of: 50 50 50 60 70 80 90)
|
||||
echo "$output" | grep -qE '^70 80 90$'
|
||||
}
|
||||
|
||||
@test "_collect_wan: writes metric and score files" {
|
||||
net_probe_iface() { echo "20 5 0"; }
|
||||
speed_test_due() { return 0; }
|
||||
speed_test_iface() { echo "50.0 10.0"; }
|
||||
score_composite() { echo "75"; }
|
||||
rolling_avg() { echo "72"; }
|
||||
|
||||
run _collect_wan "eth0" "wan"
|
||||
[ "$status" -eq 0 ]
|
||||
[ -f "${STATE_DIR}/metric_wan" ]
|
||||
[ -f "${STATE_DIR}/score_wan" ]
|
||||
score=$(cat "${STATE_DIR}/score_wan")
|
||||
[ "$score" -eq 72 ]
|
||||
}
|
||||
|
||||
@test "_collect_wan: fail-open when net_probe_iface returns error" {
|
||||
net_probe_iface() { return 1; }
|
||||
speed_test_due() { return 1; }
|
||||
score_composite() { echo "0"; }
|
||||
rolling_avg() { echo "0"; }
|
||||
|
||||
run _collect_wan "rmnet_mhi0" "modem_0001" "-99"
|
||||
[ "$status" -eq 0 ]
|
||||
[ -f "${STATE_DIR}/metric_modem_0001" ]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
setup(){ load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-decide.sh'; }
|
||||
|
||||
@test "equal scores -> 50/50" { run wan_weight 70 70; [ "$output" -eq 50 ]; }
|
||||
# 50 + 2*(80-60) = 90 -> clamp to 80 (max). So 80 is the true formula result.
|
||||
@test "weight = 50 + 2*diff, rounded to 10" { run wan_weight 80 60; [ "$output" -eq 80 ]; }
|
||||
@test "clamped to 20..80 (high)" { run wan_weight 100 0; [ "$output" -eq 80 ]; }
|
||||
@test "clamped to 20..80 (low)" { run wan_weight 0 100; [ "$output" -eq 20 ]; }
|
||||
# 50 + 2*(58-55) = 56 -> round to nearest 10 -> 60
|
||||
@test "rounds to nearest 10" { run wan_weight 58 55; [ "$output" -eq 60 ]; }
|
||||
@test "no change if within hysteresis band" { run weight_changed 50 54; [ "$status" -eq 1 ]; }
|
||||
@test "change if crosses a 10% step" { run weight_changed 50 65; [ "$status" -eq 0 ]; }
|
||||
@test "change is symmetric (decrease)" { run weight_changed 60 50; [ "$status" -eq 0 ]; }
|
||||
@test "sim switch only after N bad cycles" { run sim_should_switch 3 3; [ "$status" -eq 0 ]; }
|
||||
@test "sim no switch before threshold" { run sim_should_switch 2 3; [ "$status" -eq 1 ]; }
|
||||
|
||||
@test "gw_verdict: 3/3 -> alive" { run gw_verdict 1 1 1; [ "$status" -eq 0 ]; }
|
||||
@test "gw_verdict: 2/3 no http -> alive" { run gw_verdict 1 1 0; [ "$status" -eq 0 ]; }
|
||||
@test "gw_verdict: 2/3 no ping -> alive" { run gw_verdict 1 0 1; [ "$status" -eq 0 ]; }
|
||||
@test "gw_verdict: 2/3 no dns -> alive" { run gw_verdict 0 1 1; [ "$status" -eq 0 ]; }
|
||||
@test "gw_verdict: 1/3 -> dead" { run gw_verdict 1 0 0; [ "$status" -eq 1 ]; }
|
||||
@test "gw_verdict: 0/3 -> dead" { run gw_verdict 0 0 0; [ "$status" -eq 1 ]; }
|
||||
@@ -0,0 +1,125 @@
|
||||
FIXTURES="$BATS_TEST_DIRNAME/../fixtures"
|
||||
|
||||
LIBDIR="$(cd "$BATS_TEST_DIRNAME/../.."/package/kit-busrouter/files/usr/lib/busrouter && pwd)"
|
||||
|
||||
setup() {
|
||||
export LIB_DIR="$LIBDIR"
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-decide.sh'
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/modem-sim.sh'
|
||||
}
|
||||
|
||||
@test "modem_signal ubus parse: rsrp=-96 rsrq=-10 sinr=13" {
|
||||
run _modem_parse_ubus < "$FIXTURES/modem-signal-ubus.json"
|
||||
[ "$status" -eq 0 ]
|
||||
rsrp=$(echo "$output" | awk '{print $1}')
|
||||
rsrq=$(echo "$output" | awk '{print $2}')
|
||||
sinr=$(echo "$output" | awk '{print $3}')
|
||||
[ "$rsrp" -eq -96 ]
|
||||
[ "$rsrq" -eq -10 ]
|
||||
[ "$sinr" -eq 13 ]
|
||||
}
|
||||
|
||||
@test "modem_signal AT parse: rsrp=-97 rsrq=-10 sinr=14 (rsrp rsrq sinr order)" {
|
||||
run _modem_parse_at < "$FIXTURES/modem-signal-at.txt"
|
||||
[ "$status" -eq 0 ]
|
||||
rsrp=$(echo "$output" | awk '{print $1}')
|
||||
rsrq=$(echo "$output" | awk '{print $2}')
|
||||
sinr=$(echo "$output" | awk '{print $3}')
|
||||
[ "$rsrp" -eq -97 ]
|
||||
[ "$rsrq" -eq -10 ]
|
||||
[ "$sinr" -eq 14 ]
|
||||
}
|
||||
|
||||
@test "modem_signal ubus parse: empty input exits 1" {
|
||||
run _modem_parse_ubus < /dev/null
|
||||
[ "$status" -ne 0 ]
|
||||
[ -z "$output" ]
|
||||
}
|
||||
|
||||
@test "modem_signal AT parse: empty input exits 1" {
|
||||
run _modem_parse_at < /dev/null
|
||||
[ "$status" -ne 0 ]
|
||||
[ -z "$output" ]
|
||||
}
|
||||
|
||||
@test "modem_sim_switch: rejects invalid slot" {
|
||||
export MODEM_AT_CMD=/bin/true MODEM_BUS=1-1.2
|
||||
run modem_sim_switch 3
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "modem_sim_switch: accepts slot 1" {
|
||||
# Stub gl_modem to emit 'OK' and stub ifdown/ifup
|
||||
mkdir -p "$BATS_TMPDIR/stub"
|
||||
printf '#!/bin/sh
|
||||
echo OK
|
||||
' > "$BATS_TMPDIR/stub/gl_modem"
|
||||
printf '#!/bin/sh
|
||||
exit 0
|
||||
' > "$BATS_TMPDIR/stub/ifdown"
|
||||
printf '#!/bin/sh
|
||||
exit 0
|
||||
' > "$BATS_TMPDIR/stub/ifup"
|
||||
chmod +x "$BATS_TMPDIR/stub/gl_modem" "$BATS_TMPDIR/stub/ifdown" "$BATS_TMPDIR/stub/ifup"
|
||||
export PATH="$BATS_TMPDIR/stub:$PATH"
|
||||
export MODEM_AT_CMD=gl_modem MODEM_BUS=1-1.2
|
||||
run modem_sim_switch 1
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
# --- Task 4.2: SIM auto-switch ---
|
||||
|
||||
_sim_make_stub() {
|
||||
mkdir -p "$BATS_TMPDIR/sim_stub"
|
||||
printf '#!/bin/sh
|
||||
case "$*" in *QUIMSLOT\?*) echo "+QUIMSLOT: 1"; echo "OK" ;; *QUIMSLOT=*) echo "OK" ;; *) echo "OK" ;; esac
|
||||
' > "$BATS_TMPDIR/sim_stub/gl_modem"
|
||||
printf '#!/bin/sh
|
||||
exit 0
|
||||
' > "$BATS_TMPDIR/sim_stub/ifdown"
|
||||
printf '#!/bin/sh
|
||||
exit 0
|
||||
' > "$BATS_TMPDIR/sim_stub/ifup"
|
||||
chmod +x "$BATS_TMPDIR/sim_stub/gl_modem" "$BATS_TMPDIR/sim_stub/ifdown" "$BATS_TMPDIR/sim_stub/ifup"
|
||||
export PATH="$BATS_TMPDIR/sim_stub:$PATH"
|
||||
export MODEM_AT_CMD=gl_modem MODEM_BUS=1-1.2
|
||||
export SIM_STATE_DIR="$BATS_TMPDIR/simstate"
|
||||
export SIM_SWITCH_RSRP=-80 # threshold above actual -99 bench value -> easy to trigger
|
||||
export SIM_SWITCH_CYCLES=3
|
||||
export SIM_SWITCH_COOLDOWN=3600
|
||||
rm -rf "$BATS_TMPDIR/simstate"
|
||||
mkdir -p "$BATS_TMPDIR/simstate"
|
||||
}
|
||||
|
||||
@test "sim_check: good signal resets counter and does not switch" {
|
||||
_sim_make_stub
|
||||
# First call with bad signal to set counter to 1
|
||||
sim_check_and_switch -90 || true
|
||||
bad=$(cat "$SIM_STATE_DIR/sim_bad_cycles" 2>/dev/null || echo 0)
|
||||
[ "$bad" -eq 1 ]
|
||||
# Now good signal resets
|
||||
sim_check_and_switch -70 || true
|
||||
bad=$(cat "$SIM_STATE_DIR/sim_bad_cycles" 2>/dev/null || echo 0)
|
||||
[ "$bad" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "sim_check: triggers switch after SIM_SWITCH_CYCLES bad cycles" {
|
||||
_sim_make_stub
|
||||
# 2 bad cycles -> no switch yet
|
||||
sim_check_and_switch -90 || true
|
||||
sim_check_and_switch -90 || true
|
||||
[ ! -f "$SIM_STATE_DIR/sim_last_switch" ]
|
||||
# 3rd bad cycle -> switch triggered
|
||||
run sim_check_and_switch -90
|
||||
[ "$status" -eq 0 ]
|
||||
[ -f "$SIM_STATE_DIR/sim_last_switch" ]
|
||||
}
|
||||
|
||||
@test "sim_check: cooldown prevents switch within SIM_SWITCH_COOLDOWN" {
|
||||
_sim_make_stub
|
||||
export SIM_SWITCH_COOLDOWN=3600
|
||||
# Pre-seed a recent switch time
|
||||
date +%s > "$SIM_STATE_DIR/sim_last_switch"
|
||||
run sim_check_and_switch -90
|
||||
[ "$status" -eq 1 ] # blocked by cooldown
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
FIXTURES="$BATS_TEST_DIRNAME/../../tests/fixtures"
|
||||
setup() { load '../../package/kit-busrouter/files/usr/lib/busrouter/metrics-net.sh'; }
|
||||
|
||||
@test "net: zero-jitter no-loss" {
|
||||
run _net_parse_ping < "$FIXTURES/ping-ok.txt"
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "15.0 0.0 0" ]
|
||||
}
|
||||
@test "net: jitter and packet loss" {
|
||||
run _net_parse_ping < "$FIXTURES/ping-loss.txt"
|
||||
[ "$output" = "15.0 5.0 20" ]
|
||||
}
|
||||
@test "net: 100pct loss gives 0 0 100" {
|
||||
run _net_parse_ping < "$FIXTURES/ping-timeout.txt"
|
||||
[ "$output" = "0 0 100" ]
|
||||
}
|
||||
@test "net: partial output defaults loss to 100" {
|
||||
run _net_parse_ping < "$FIXTURES/ping-partial.txt"
|
||||
[ "$output" = "20.0 0.0 100" ]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
setup() { load '../../package/kit-busrouter/files/usr/lib/busrouter/lib-score.sh'; }
|
||||
|
||||
@test "latency: 5ms scores 100" { run score_latency 5; [ "$output" -eq 100 ]; }
|
||||
@test "latency: 200ms scores 0" { run score_latency 200; [ "$output" -eq 0 ]; }
|
||||
@test "latency clamps below 0" { run score_latency 500; [ "$output" -eq 0 ]; }
|
||||
@test "download: 25MB/s scores 100" { run score_download 25; [ "$output" -eq 100 ]; }
|
||||
@test "download: 0.5MB/s scores 0" { run score_download 0.5; [ "$output" -eq 0 ]; }
|
||||
@test "upload: 12.5MB/s scores 100" { run score_upload 12.5; [ "$output" -eq 100 ]; }
|
||||
@test "upload: 0.5MB/s scores 0" { run score_upload 0.5; [ "$output" -eq 0 ]; }
|
||||
@test "jitter: 1ms scores 100" { run score_jitter 1; [ "$output" -eq 100 ]; }
|
||||
@test "jitter: 50ms scores 0" { run score_jitter 50; [ "$output" -eq 0 ]; }
|
||||
@test "loss: 0% scores 100" { run score_loss 0; [ "$output" -eq 100 ]; }
|
||||
@test "loss: 40% scores 0" { run score_loss 40; [ "$output" -eq 0 ]; }
|
||||
@test "signal: -65dBm scores 100" { run score_signal -65; [ "$output" -eq 100 ]; }
|
||||
@test "signal: -95dBm scores 0" { run score_signal -95; [ "$output" -eq 0 ]; }
|
||||
@test "signal clamps above 100" { run score_signal -50; [ "$output" -eq 100 ]; }
|
||||
@@ -0,0 +1 @@
|
||||
@test "bats runs" { run true; [ "$status" -eq 0 ]; }
|
||||
@@ -0,0 +1,39 @@
|
||||
SPEED_STATE_DIR="$BATS_TMPDIR/busrouter"
|
||||
setup() {
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/speedtest.sh'
|
||||
mkdir -p "$SPEED_STATE_DIR"
|
||||
export SPEED_STATE_DIR
|
||||
}
|
||||
teardown() { rm -rf "$SPEED_STATE_DIR"; }
|
||||
|
||||
@test "speed: CELL_METERED=1 skips cellular, emits 0 0" {
|
||||
export CELL_METERED=1 CELLULAR_IFACE=rmnet_mhi0
|
||||
run speed_test_iface rmnet_mhi0
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "0 0" ]
|
||||
}
|
||||
|
||||
@test "speed: CELL_METERED=0 does not skip cellular (returns non 0 0)" {
|
||||
export CELL_METERED=0 CELLULAR_IFACE=rmnet_mhi0
|
||||
run speed_test_iface rmnet_mhi0
|
||||
[ "$output" != "0 0" ] || [ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "speed_test_due: no state file -> due (exit 0)" {
|
||||
run speed_test_due eth0
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "speed_test_due: fresh state file -> not due (exit 1)" {
|
||||
date +%s > "$SPEED_STATE_DIR/speed_last_eth0"
|
||||
export SPEED_INTERVAL=3600
|
||||
run speed_test_due eth0
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "speed_test_due: old state file -> due (exit 0)" {
|
||||
echo "0" > "$SPEED_STATE_DIR/speed_last_eth0"
|
||||
export SPEED_INTERVAL=300
|
||||
run speed_test_due eth0
|
||||
[ "$status" -eq 0 ]
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
FIXTURES="$BATS_TEST_DIRNAME/../fixtures"
|
||||
|
||||
setup() {
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/starlink.sh'
|
||||
}
|
||||
|
||||
# Helper: run _starlink_parse on a fixture file
|
||||
_parse_fixture() {
|
||||
_starlink_parse < "$FIXTURES/$1"
|
||||
}
|
||||
|
||||
@test "starlink: online dish: lat=28 dl=200000000 obstructed=0 outage=0" {
|
||||
run _parse_fixture starlink-online.json
|
||||
[ "$status" -eq 0 ]
|
||||
lat=$(echo "$output" | awk '{print $1}')
|
||||
dl=$(echo "$output" | awk '{print $2}')
|
||||
obs=$(echo "$output" | awk '{print $3}')
|
||||
outage=$(echo "$output" | awk '{print $4}')
|
||||
[ "$lat" -eq 28 ]
|
||||
[ "$dl" -gt 100000000 ]
|
||||
[ "$obs" -eq 0 ]
|
||||
[ "$outage" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "starlink: obstructed dish: obstructed=1" {
|
||||
run _parse_fixture starlink-obstructed.json
|
||||
[ "$status" -eq 0 ]
|
||||
obs=$(echo "$output" | awk '{print $3}')
|
||||
[ "$obs" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "starlink: obstructed dish: fractionObstructed>0.01 sets obstructed=1" {
|
||||
run _parse_fixture starlink-obstructed.json
|
||||
[ "$status" -eq 0 ]
|
||||
obs=$(echo "$output" | awk '{print $3}')
|
||||
[ "$obs" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "starlink: outage dish: outage=1" {
|
||||
run _parse_fixture starlink-outage.json
|
||||
[ "$status" -eq 0 ]
|
||||
outage=$(echo "$output" | awk '{print $4}')
|
||||
[ "$outage" -eq 1 ]
|
||||
}
|
||||
|
||||
@test "starlink: outage dish: dl=0 lat=0" {
|
||||
run _parse_fixture starlink-outage.json
|
||||
[ "$status" -eq 0 ]
|
||||
lat=$(echo "$output" | awk '{print $1}')
|
||||
dl=$(echo "$output" | awk '{print $2}')
|
||||
[ "$lat" -eq 0 ]
|
||||
[ "$dl" -eq 0 ]
|
||||
}
|
||||
|
||||
@test "starlink: grpcurl absent returns exit 1" {
|
||||
export GRPCURL=/nonexistent/grpcurl
|
||||
run starlink_status
|
||||
[ "$status" -eq 1 ]
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
# telemetry-synology.bats — Tests for Synology telemetry agent
|
||||
|
||||
LIBDIR="$(cd "$BATS_TEST_DIRNAME/../.."/package/kit-busrouter/files/usr/lib/busrouter && pwd)"
|
||||
|
||||
setup() {
|
||||
export LIB_DIR="$LIBDIR"
|
||||
export AIWANBAL_STATE_DIR="$BATS_TMPDIR/aiwanbal"
|
||||
export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/telemetry-buf"
|
||||
export TELEMETRY_HUB="http://10.88.0.1:8080/api/telemetry"
|
||||
rm -rf "$AIWANBAL_STATE_DIR" "$TELEMETRY_BUFFER_DIR"
|
||||
mkdir -p "$AIWANBAL_STATE_DIR"
|
||||
|
||||
# Seed minimal aiwanbal state for collection tests
|
||||
printf '85\n' > "$AIWANBAL_STATE_DIR/wan1_score_avg"
|
||||
printf '15\n' > "$AIWANBAL_STATE_DIR/wan1_latency"
|
||||
printf '50000000\n' > "$AIWANBAL_STATE_DIR/wan1_throughput" # 50 Mbps
|
||||
printf '10000000\n' > "$AIWANBAL_STATE_DIR/wan1_upload"
|
||||
printf '2\n' > "$AIWANBAL_STATE_DIR/wan1_jitter"
|
||||
printf '0\n' > "$AIWANBAL_STATE_DIR/wan1_packet_loss"
|
||||
printf -- '-80\n' > "$AIWANBAL_STATE_DIR/wan1_rsrp"
|
||||
printf -- '-12\n' > "$AIWANBAL_STATE_DIR/wan1_rsrq"
|
||||
printf '8\n' > "$AIWANBAL_STATE_DIR/wan1_sinr"
|
||||
printf '5G-NR\n' > "$AIWANBAL_STATE_DIR/wan1_technology"
|
||||
printf 'eyeride\n' > "$AIWANBAL_STATE_DIR/wan1_modem_type"
|
||||
|
||||
printf '70\n' > "$AIWANBAL_STATE_DIR/wan2_score_avg"
|
||||
printf '25\n' > "$AIWANBAL_STATE_DIR/wan2_latency"
|
||||
printf '30000000\n' > "$AIWANBAL_STATE_DIR/wan2_throughput"
|
||||
printf '5000000\n' > "$AIWANBAL_STATE_DIR/wan2_upload"
|
||||
printf '5\n' > "$AIWANBAL_STATE_DIR/wan2_jitter"
|
||||
printf '1\n' > "$AIWANBAL_STATE_DIR/wan2_packet_loss"
|
||||
printf -- '-90\n' > "$AIWANBAL_STATE_DIR/wan2_rsrp"
|
||||
printf 'generic\n' > "$AIWANBAL_STATE_DIR/wan2_modem_type"
|
||||
|
||||
printf 'active\n' > "$AIWANBAL_STATE_DIR/wan0_mode"
|
||||
|
||||
# Fake device identity
|
||||
mkdir -p /etc/busrouter
|
||||
printf 'B0042\n' > /etc/busrouter/device-id
|
||||
printf '1.0\n' > /etc/busrouter/version
|
||||
|
||||
# Load the library under test
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/telemetry-synology.sh'
|
||||
}
|
||||
|
||||
teardown() {
|
||||
rm -rf "$AIWANBAL_STATE_DIR" "$TELEMETRY_BUFFER_DIR"
|
||||
rm -f /etc/busrouter/device-id /etc/busrouter/version
|
||||
}
|
||||
|
||||
# ── Data collection ──────────────────────────────────────────────────────────────
|
||||
|
||||
@test "telemetry_synology_collect: output contains required JSON keys" {
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"device_id"'
|
||||
echo "$output" | grep -q '"timestamp"'
|
||||
echo "$output" | grep -q '"uptime"'
|
||||
echo "$output" | grep -q '"version"'
|
||||
echo "$output" | grep -q '"platform":"synology"'
|
||||
echo "$output" | grep -q '"gps"'
|
||||
echo "$output" | grep -q '"wan"'
|
||||
echo "$output" | grep -q '"wan1"'
|
||||
echo "$output" | grep -q '"wan2"'
|
||||
echo "$output" | grep -q '"starlink"'
|
||||
echo "$output" | grep -q '"sel_primary"'
|
||||
}
|
||||
|
||||
@test "telemetry_synology_collect: device_id from identity file" {
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"device_id":"B0042"'
|
||||
}
|
||||
|
||||
@test "telemetry_synology_collect: falls back to hostname when identity file missing" {
|
||||
rm -f /etc/busrouter/device-id
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
# Should contain a device_id (whatever hostname returns)
|
||||
echo "$output" | grep -q '"device_id"'
|
||||
}
|
||||
|
||||
@test "telemetry_synology_collect: handles missing state files gracefully" {
|
||||
rm -rf "$AIWANBAL_STATE_DIR"
|
||||
mkdir -p "$AIWANBAL_STATE_DIR"
|
||||
# No state files at all — should still produce valid JSON
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"wan1"'
|
||||
echo "$output" | grep -q '"wan2"'
|
||||
}
|
||||
|
||||
@test "telemetry_synology_collect: sel_primary is wan1 in active mode" {
|
||||
printf 'active\n' > "$AIWANBAL_STATE_DIR/wan0_mode"
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"sel_primary":"wan1"'
|
||||
}
|
||||
|
||||
@test "telemetry_synology_collect: sel_primary is wan2 in failover when wan2 up" {
|
||||
printf 'failover\n' > "$AIWANBAL_STATE_DIR/wan0_mode"
|
||||
printf 'up\n' > "$AIWANBAL_STATE_DIR/wan2_state"
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"sel_primary":"wan2"'
|
||||
}
|
||||
|
||||
@test "telemetry_synology_collect: wan JSON includes cellular metadata" {
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"rsrp_dbm"'
|
||||
echo "$output" | grep -q '"technology"'
|
||||
echo "$output" | grep -q '"carrier"'
|
||||
echo "$output" | grep -q '"5G-NR"'
|
||||
}
|
||||
|
||||
@test "telemetry_synology_collect: throughput converted from B/s to Mbps" {
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
# 50000000 B/s = ~400 Mbps → should appear as "dl_mbps":400.0 or similar
|
||||
echo "$output" | grep -q '"dl_mbps"'
|
||||
}
|
||||
|
||||
@test "telemetry_synology_collect: gps is null when Eyeride unavailable" {
|
||||
# Without EYERIDE_PASSWORD, eyeride_gps returns 1 → gps fields are null
|
||||
unset EYERIDE_PASSWORD
|
||||
run telemetry_synology_collect
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"lat":null'
|
||||
echo "$output" | grep -q '"lon":null'
|
||||
echo "$output" | grep -q '"fix":null'
|
||||
}
|
||||
|
||||
# ── Buffering + POST ─────────────────────────────────────────────────────────────
|
||||
|
||||
@test "telemetry_synology_send: buffers payload when curl fails" {
|
||||
mkdir -p "$BATS_TMPDIR/tel_stub"
|
||||
printf '#!/bin/sh\nexit 7\n' > "$BATS_TMPDIR/tel_stub/curl"
|
||||
chmod +x "$BATS_TMPDIR/tel_stub/curl"
|
||||
export PATH="$BATS_TMPDIR/tel_stub:$PATH"
|
||||
export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/buf_test"
|
||||
rm -rf "$TELEMETRY_BUFFER_DIR"
|
||||
|
||||
run telemetry_synology_send
|
||||
[ -d "$TELEMETRY_BUFFER_DIR" ]
|
||||
buf_count=$(ls "$TELEMETRY_BUFFER_DIR"/*.json 2>/dev/null | wc -l)
|
||||
[ "$buf_count" -ge 1 ]
|
||||
}
|
||||
|
||||
@test "telemetry_synology_flush: posts and removes buffered file on success" {
|
||||
mkdir -p "$BATS_TMPDIR/tel_stub2"
|
||||
printf '#!/bin/sh\necho 200\n' > "$BATS_TMPDIR/tel_stub2/curl"
|
||||
chmod +x "$BATS_TMPDIR/tel_stub2/curl"
|
||||
export PATH="$BATS_TMPDIR/tel_stub2:$PATH"
|
||||
export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/flush_test"
|
||||
mkdir -p "$TELEMETRY_BUFFER_DIR"
|
||||
echo '{"test":1}' > "$TELEMETRY_BUFFER_DIR/1234567890_test.json"
|
||||
|
||||
run telemetry_synology_flush
|
||||
[ "$status" -eq 0 ]
|
||||
[ ! -f "$TELEMETRY_BUFFER_DIR/1234567890_test.json" ]
|
||||
}
|
||||
|
||||
@test "telemetry_synology_flush: stops on first failure" {
|
||||
mkdir -p "$BATS_TMPDIR/tel_stub3"
|
||||
# curl that fails on second call
|
||||
printf '#!/bin/sh\nif [ -f /tmp/first_done ]; then exit 7; fi\ntouch /tmp/first_done; echo 200\n' > "$BATS_TMPDIR/tel_stub3/curl"
|
||||
chmod +x "$BATS_TMPDIR/tel_stub3/curl"
|
||||
export PATH="$BATS_TMPDIR/tel_stub3:$PATH"
|
||||
export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/flush_fail"
|
||||
rm -f /tmp/first_done
|
||||
mkdir -p "$TELEMETRY_BUFFER_DIR"
|
||||
echo '{"test":1}' > "$TELEMETRY_BUFFER_DIR/1111111111_a.json"
|
||||
echo '{"test":2}' > "$TELEMETRY_BUFFER_DIR/2222222222_b.json"
|
||||
|
||||
run telemetry_synology_flush
|
||||
[ "$status" -ne 0 ]
|
||||
# First file should be deleted, second should remain
|
||||
[ ! -f "$TELEMETRY_BUFFER_DIR/1111111111_a.json" ]
|
||||
[ -f "$TELEMETRY_BUFFER_DIR/2222222222_b.json" ]
|
||||
rm -f /tmp/first_done
|
||||
}
|
||||
|
||||
# ── WAN JSON fragment ────────────────────────────────────────────────────────────
|
||||
|
||||
@test "_synology_wan_json: produces valid JSON fragment" {
|
||||
run _synology_wan_json 1
|
||||
[ "$status" -eq 0 ]
|
||||
# Should be a valid JSON object (starts with {, ends with })
|
||||
echo "$output" | grep -q '^{'
|
||||
echo "$output" | grep -q '}$'
|
||||
echo "$output" | grep -q '"score"'
|
||||
echo "$output" | grep -q '"latency_ms"'
|
||||
echo "$output" | grep -q '"carrier"'
|
||||
}
|
||||
|
||||
@test "_synology_wan_json: defaults score to 0 when file missing" {
|
||||
rm -f "$AIWANBAL_STATE_DIR/wan1_score_avg"
|
||||
run _synology_wan_json 1
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"score":0'
|
||||
}
|
||||
|
||||
@test "_synology_wan_json: modem type maps to carrier name" {
|
||||
printf 'peplink\n' > "$AIWANBAL_STATE_DIR/wan1_modem_type"
|
||||
run _synology_wan_json 1
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"carrier":"Peplink"'
|
||||
}
|
||||
|
||||
# ── eyeride_gps ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@test "eyeride_gps: returns 1 when no password set" {
|
||||
unset EYERIDE_PASSWORD
|
||||
run eyeride_gps
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "eyeride_gps: returns 1 when eyeride unreachable" {
|
||||
export EYERIDE_PASSWORD="test"
|
||||
export EYERIDE_IP="192.0.2.1" # TEST-NET-1 — guaranteed unreachable
|
||||
export EYERIDE_PORT="9999"
|
||||
run eyeride_gps
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
FIXTURES="$BATS_TEST_DIRNAME/../fixtures"
|
||||
|
||||
LIBDIR="$(cd "$BATS_TEST_DIRNAME/../.."/package/kit-busrouter/files/usr/lib/busrouter && pwd)"
|
||||
|
||||
setup() {
|
||||
export LIB_DIR="$LIBDIR"
|
||||
load '../../package/kit-busrouter/files/usr/lib/busrouter/telemetry.sh'
|
||||
}
|
||||
|
||||
@test "gps_fix parse: 3D fix returns lat lon fix_quality=3" {
|
||||
run _gps_parse < "$FIXTURES/gps-fix.txt"
|
||||
[ "$status" -eq 0 ]
|
||||
lat=$(echo "$output" | awk '{print $1}')
|
||||
lon=$(echo "$output" | awk '{print $2}')
|
||||
fix=$(echo "$output" | awk '{print $3}')
|
||||
[ "$lat" = "40.712776" ]
|
||||
[ "$lon" = "-74.005974" ]
|
||||
[ "$fix" -eq 3 ]
|
||||
}
|
||||
|
||||
@test "gps_fix parse: no-fix response exits 1" {
|
||||
run _gps_parse < "$FIXTURES/gps-nofix.txt"
|
||||
[ "$status" -ne 0 ]
|
||||
[ -z "$output" ]
|
||||
}
|
||||
|
||||
@test "gps_fix parse: empty input exits 1" {
|
||||
run _gps_parse < /dev/null
|
||||
[ "$status" -ne 0 ]
|
||||
[ -z "$output" ]
|
||||
}
|
||||
|
||||
# --- Task 5.2: telemetry collect + POST ---
|
||||
|
||||
@test "telemetry_collect: output contains required JSON keys" {
|
||||
run telemetry_collect
|
||||
[ "$status" -eq 0 ]
|
||||
echo "$output" | grep -q '"device_id"'
|
||||
echo "$output" | grep -q '"timestamp"'
|
||||
echo "$output" | grep -q '"gps"'
|
||||
echo "$output" | grep -q '"wan"'
|
||||
echo "$output" | grep -q '"starlink"'
|
||||
echo "$output" | grep -q '"sim_slot"'
|
||||
echo "$output" | grep -q '"uptime"'
|
||||
}
|
||||
|
||||
@test "telemetry_send: buffers payload when curl fails" {
|
||||
mkdir -p "$BATS_TMPDIR/tel_stub"
|
||||
printf '#!/bin/sh\nexit 7\n' > "$BATS_TMPDIR/tel_stub/curl"
|
||||
chmod +x "$BATS_TMPDIR/tel_stub/curl"
|
||||
export PATH="$BATS_TMPDIR/tel_stub:$PATH"
|
||||
export TELEMETRY_HUB="http://10.88.0.1:8080/api/telemetry"
|
||||
export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/tel_buf"
|
||||
rm -rf "$TELEMETRY_BUFFER_DIR"
|
||||
run telemetry_send
|
||||
[ -d "$TELEMETRY_BUFFER_DIR" ]
|
||||
buf_count=$(ls "$TELEMETRY_BUFFER_DIR"/*.json 2>/dev/null | wc -l)
|
||||
[ "$buf_count" -ge 1 ]
|
||||
}
|
||||
|
||||
@test "telemetry_flush: posts and removes buffered file on success" {
|
||||
mkdir -p "$BATS_TMPDIR/tel_stub2"
|
||||
printf '#!/bin/sh\necho 200\n' > "$BATS_TMPDIR/tel_stub2/curl"
|
||||
chmod +x "$BATS_TMPDIR/tel_stub2/curl"
|
||||
export PATH="$BATS_TMPDIR/tel_stub2:$PATH"
|
||||
export TELEMETRY_HUB="http://10.88.0.1:8080/api/telemetry"
|
||||
export TELEMETRY_BUFFER_DIR="$BATS_TMPDIR/tel_flush_buf"
|
||||
mkdir -p "$TELEMETRY_BUFFER_DIR"
|
||||
echo '{"test":1}' > "$TELEMETRY_BUFFER_DIR/1234567890_test.json"
|
||||
run telemetry_flush
|
||||
[ "$status" -eq 0 ]
|
||||
[ ! -f "$TELEMETRY_BUFFER_DIR/1234567890_test.json" ]
|
||||
}
|
||||
Reference in New Issue
Block a user