2026-04-22 22:10:39 -03:00
|
|
|
//! Contract tests for server install-time operational defaults.
|
|
|
|
|
//!
|
|
|
|
|
//! Scope: statically guard the generated `/etc/lesavka/server.env` values.
|
|
|
|
|
//! Targets: `scripts/install/server.sh`.
|
|
|
|
|
//! Why: HDMI capture adapter settings should be reproducible after reboot or
|
|
|
|
|
//! reinstall instead of living as one-off shell state.
|
|
|
|
|
|
|
|
|
|
const SERVER_INSTALL: &str = include_str!("../../scripts/install/server.sh");
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn server_install_pins_hdmi_camera_and_display_defaults() {
|
|
|
|
|
for expected in [
|
|
|
|
|
"LESAVKA_CAM_OUTPUT=%s",
|
|
|
|
|
"LESAVKA_CAM_WIDTH=%s",
|
|
|
|
|
"LESAVKA_CAM_HEIGHT=%s",
|
|
|
|
|
"LESAVKA_CAM_FPS=%s",
|
|
|
|
|
"LESAVKA_HDMI_WIDTH=%s",
|
|
|
|
|
"LESAVKA_HDMI_HEIGHT=%s",
|
|
|
|
|
"LESAVKA_HDMI_SINK=%s",
|
|
|
|
|
"LESAVKA_HDMI_FBDEV=%s",
|
2026-04-25 16:48:20 -03:00
|
|
|
"LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS=%s",
|
2026-05-01 19:16:40 -03:00
|
|
|
"LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS=%s",
|
|
|
|
|
"LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS=%s",
|
2026-04-25 16:48:20 -03:00
|
|
|
"LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=%s",
|
|
|
|
|
"LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=%s",
|
2026-04-25 22:25:24 -03:00
|
|
|
"LESAVKA_UPSTREAM_PAIR_SLACK_US=%s",
|
2026-05-02 11:33:49 -03:00
|
|
|
"LESAVKA_UPSTREAM_AUDIO_MASTER_WAIT_GRACE_MS=%s",
|
2026-04-25 22:25:24 -03:00
|
|
|
"LESAVKA_UPSTREAM_STALE_DROP_MS=%s",
|
2026-04-28 02:29:32 -03:00
|
|
|
"LESAVKA_SERVER_BIND_ADDR=%s",
|
2026-04-27 14:34:50 -03:00
|
|
|
"/etc/lesavka/uvc.env",
|
2026-04-27 15:50:29 -03:00
|
|
|
"LESAVKA_UVC_MAXPACKET=",
|
|
|
|
|
"LESAVKA_UVC_INTERVAL=",
|
|
|
|
|
"LESAVKA_UVC_WIDTH=",
|
|
|
|
|
"LESAVKA_UVC_HEIGHT=",
|
|
|
|
|
"LESAVKA_UVC_CODEC=",
|
2026-04-28 21:00:56 -03:00
|
|
|
"LESAVKA_UVC_CONTROL_READ_ONLY=",
|
2026-04-30 08:16:57 -03:00
|
|
|
"LESAVKA_REQUIRE_TLS=%s",
|
|
|
|
|
"LESAVKA_TLS_CERT=%s",
|
|
|
|
|
"LESAVKA_TLS_KEY=%s",
|
|
|
|
|
"LESAVKA_TLS_CLIENT_CA=%s",
|
2026-04-22 22:10:39 -03:00
|
|
|
] {
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains(expected),
|
|
|
|
|
"install script should emit {expected}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_CAM_WIDTH:-1920}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_CAM_HEIGHT:-1080}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_CAM_FPS:-30}"));
|
2026-04-27 16:42:02 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_INSTALL_CAM_OUTPUT:-uvc}"));
|
2026-04-27 20:36:15 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_INSTALL_UVC_CODEC:-mjpeg}"));
|
2026-04-22 22:10:39 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_HDMI_WIDTH:-1920}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_HDMI_HEIGHT:-1080}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_HDMI_SINK:-fbdevsink}"));
|
2026-05-01 19:16:40 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS:-350}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS:-1000}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS:-60000}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0"));
|
2026-05-02 12:49:38 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=1090000"));
|
2026-05-01 19:48:00 -03:00
|
|
|
assert!(
|
2026-05-02 01:00:57 -03:00
|
|
|
SERVER_INSTALL.contains("resolve_upstream_video_playout_offset_us"),
|
|
|
|
|
"video offset should be resolved through stale-baseline migration logic"
|
2026-05-01 19:48:00 -03:00
|
|
|
);
|
2026-05-01 13:52:36 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("LEGACY_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-45000"));
|
2026-05-01 19:16:40 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("PREVIOUS_TUNED_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=1260000")
|
|
|
|
|
);
|
2026-05-02 10:51:49 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("PREVIOUS_ZERO_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=0"));
|
2026-05-02 01:00:57 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("PREVIOUS_DELAYED_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=350000")
|
|
|
|
|
);
|
2026-05-02 12:49:38 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("PREVIOUS_BROWSER_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=130000")
|
|
|
|
|
);
|
2026-05-01 13:52:36 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"),
|
|
|
|
|
"install-specific offset override should bypass stale ambient runtime env"
|
|
|
|
|
);
|
2026-05-02 01:00:57 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("LESAVKA_INSTALL_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"),
|
|
|
|
|
"install-specific video offset override should bypass stale ambient runtime env"
|
|
|
|
|
);
|
2026-05-01 13:52:36 -03:00
|
|
|
assert!(
|
2026-05-01 19:16:40 -03:00
|
|
|
SERVER_INSTALL.contains("migrating stale upstream audio playout offset to the 0.17 freshness-first planner default"),
|
|
|
|
|
"installer should not preserve old MJPEG/UVC sync baselines accidentally"
|
2026-05-01 13:52:36 -03:00
|
|
|
);
|
2026-05-02 01:00:57 -03:00
|
|
|
assert!(
|
2026-05-02 12:49:38 -03:00
|
|
|
SERVER_INSTALL.contains("migrating stale upstream video playout offset to the 0.17 browser-visible MJPEG/UVC sync baseline"),
|
2026-05-02 01:00:57 -03:00
|
|
|
"installer should not preserve old video delay baselines accidentally"
|
|
|
|
|
);
|
2026-04-28 22:03:51 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_PAIR_SLACK_US:-80000}"));
|
2026-05-02 11:33:49 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_AUDIO_MASTER_WAIT_GRACE_MS:-350}"));
|
2026-04-25 22:25:24 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_STALE_DROP_MS:-80}"));
|
2026-04-28 02:29:32 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_INSTALL_SERVER_BIND_ADDR:-0.0.0.0:50051}"));
|
2026-04-27 21:36:30 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_MAXPACKET:-1024}"));
|
2026-05-02 22:59:18 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_INTERVAL:-333333}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_WIDTH:-1280}"));
|
|
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEIGHT:-720}"));
|
2026-04-28 22:03:51 -03:00
|
|
|
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_CONTROL_READ_ONLY:-0}"));
|
2026-04-27 20:36:15 -03:00
|
|
|
assert!(
|
|
|
|
|
!SERVER_INSTALL.contains("LESAVKA_UVC_CODEC=${LESAVKA_UVC_CODEC:-mjpeg}"),
|
|
|
|
|
"install script should not let ambient LESAVKA_UVC_CODEC leak into persisted defaults"
|
|
|
|
|
);
|
2026-04-28 02:29:32 -03:00
|
|
|
assert!(
|
2026-04-28 14:23:59 -03:00
|
|
|
!SERVER_INSTALL
|
|
|
|
|
.contains("LESAVKA_SERVER_BIND_ADDR=${LESAVKA_SERVER_BIND_ADDR:-0.0.0.0:50051}"),
|
2026-04-28 02:29:32 -03:00
|
|
|
"install script should not let ambient LESAVKA_SERVER_BIND_ADDR leak into persisted defaults"
|
|
|
|
|
);
|
2026-04-27 15:50:29 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("EnvironmentFile=-/etc/lesavka/uvc.env"),
|
|
|
|
|
"install script should feed live UVC runtime settings into services"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
!SERVER_INSTALL.contains("Environment=LESAVKA_UVC_CODEC=mjpeg"),
|
|
|
|
|
"install script should not pin UVC codec to mjpeg in the systemd units"
|
|
|
|
|
);
|
2026-04-27 16:05:42 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("LESAVKA_UVC_FALLBACK=0"),
|
|
|
|
|
"forced gadget rebuilds should fail loud instead of silently dropping UVC"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("validate_uvc_gadget_ready"),
|
|
|
|
|
"install script should verify that the UVC gadget comes back before declaring success"
|
|
|
|
|
);
|
2026-04-27 21:02:59 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("uvc_gadget_present"),
|
|
|
|
|
"install script should detect when the live gadget is missing the expected UVC function"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
2026-04-28 14:23:59 -03:00
|
|
|
SERVER_INSTALL.contains(
|
|
|
|
|
"UVC function is missing from the live gadget; forcing a rebuild before server start."
|
|
|
|
|
),
|
2026-04-27 21:02:59 -03:00
|
|
|
"install script should force a rebuild when the live gadget is attached but missing UVC"
|
|
|
|
|
);
|
2026-04-27 23:04:18 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("lesavka-core gadget rebuilt directly."),
|
|
|
|
|
"install script should trust the direct forced rebuild instead of immediately rerunning the oneshot core unit"
|
|
|
|
|
);
|
2026-04-30 15:47:07 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("LESAVKA_FORCE_GADGET_REBUILD"),
|
|
|
|
|
"install script should require an explicit force knob for attached-host gadget rebuilds"
|
|
|
|
|
);
|
2026-04-30 16:13:42 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("EXPLICIT_GADGET_REBUILD=1"),
|
|
|
|
|
"installer should distinguish organic rebuild needs from an explicit operator hard-reset request"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("[[ \"$EXPLICIT_GADGET_REBUILD\" != \"1\" ]] || [[ -z ${LESAVKA_ALLOW_GADGET_RESET:-} ]]"),
|
|
|
|
|
"attached-host hard rebuilds should require both allow and explicit force flags"
|
|
|
|
|
);
|
2026-04-30 15:47:07 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("no hard gadget rebuild is needed"),
|
|
|
|
|
"LESAVKA_ALLOW_GADGET_RESET should permit recovery without causing unconditional hard rebuilds"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
2026-04-30 18:38:34 -03:00
|
|
|
!SERVER_INSTALL.contains(
|
|
|
|
|
"[[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]] || [[ \"$FORCE_GADGET_REBUILD\" == \"1\" ]]"
|
|
|
|
|
),
|
2026-04-30 15:47:07 -03:00
|
|
|
"LESAVKA_ALLOW_GADGET_RESET must not itself trigger a hard UDC detach/rebind"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
!SERVER_INSTALL.contains("LESAVKA_RELOAD_UVCVIDEO=1"),
|
|
|
|
|
"install script should not unload/reload uvcvideo during ordinary server installs"
|
|
|
|
|
);
|
2026-04-28 14:23:59 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("sudo systemctl stop lesavka-uvc"),
|
|
|
|
|
"install script should stop the UVC helper before directly rebuilding the gadget underneath it"
|
|
|
|
|
);
|
2026-04-27 23:04:18 -03:00
|
|
|
assert!(
|
|
|
|
|
!SERVER_INSTALL.contains("sudo systemctl restart lesavka-core"),
|
|
|
|
|
"install script should not immediately rerun lesavka-core after a successful direct forced rebuild"
|
|
|
|
|
);
|
2026-04-27 16:05:42 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("video-output node did not appear after rebuild"),
|
|
|
|
|
"install script should explain why it refuses a half-applied UVC install"
|
|
|
|
|
);
|
2026-04-27 16:12:51 -03:00
|
|
|
assert!(
|
|
|
|
|
!SERVER_INSTALL.contains("RefuseManualStop=yes"),
|
|
|
|
|
"install script should not generate a UVC unit that blocks legitimate refreshes"
|
|
|
|
|
);
|
2026-04-27 21:02:59 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("KillMode=control-group"),
|
|
|
|
|
"install script should stop the whole UVC helper cgroup instead of leaving child processes behind"
|
|
|
|
|
);
|
2026-04-27 22:17:34 -03:00
|
|
|
assert!(
|
|
|
|
|
!SERVER_INSTALL.contains("KillMode=process"),
|
|
|
|
|
"install script should not leave the server or helper units in process-only kill mode"
|
|
|
|
|
);
|
2026-04-27 16:12:51 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("lesavka-uvc already active; runtime settings unchanged."),
|
|
|
|
|
"install script should avoid unnecessary UVC restarts when nothing changed"
|
|
|
|
|
);
|
2026-04-28 14:23:59 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("sudo systemctl start lesavka-uvc"),
|
|
|
|
|
"install script should start the UVC helper so the host enumerates the UVC function"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("lesavka-uvc started to attach the UVC gadget to the host."),
|
|
|
|
|
"install script should report when it starts the UVC helper for enumeration"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("Wants=lesavka-uvc.service"),
|
|
|
|
|
"server unit should pull in the external UVC helper on UVC installs"
|
|
|
|
|
);
|
2026-04-28 20:28:36 -03:00
|
|
|
assert!(
|
|
|
|
|
!SERVER_INSTALL.contains("Environment=LESAVKA_ALLOW_GADGET_CYCLE=1"),
|
|
|
|
|
"server unit should not auto-cycle the gadget while the external UVC helper owns it"
|
|
|
|
|
);
|
2026-04-28 14:23:59 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("/var/log/lesavka/server.log"),
|
|
|
|
|
"install script should keep server logs out of sticky /tmp"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("LESAVKA_SERVER_LOG_PATH=/var/log/lesavka/server.log"),
|
|
|
|
|
"server unit should point tracing at the non-sticky log path"
|
|
|
|
|
);
|
2026-04-27 22:34:46 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("clear_stale_server_listener"),
|
|
|
|
|
"install script should clear stale server listeners before restart"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("lsof -tiTCP:"),
|
|
|
|
|
"install script should inspect the bind port before starting a fresh server"
|
|
|
|
|
);
|
2026-04-28 01:33:17 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("/proc/net/tcp"),
|
|
|
|
|
"install script should fall back to procfs listener detection when lsof misses the owner"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("socket:["),
|
|
|
|
|
"install script should map listening socket inodes back to owning PIDs"
|
|
|
|
|
);
|
2026-04-28 02:04:29 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("ss -K state connected"),
|
|
|
|
|
"install script should clear hidden connected socket state before retrying the server port"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("ss -K state listening"),
|
|
|
|
|
"install script should ask the kernel to drop a hidden listening socket when no PID is visible"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("list_server_bound_inactive_lines"),
|
|
|
|
|
"install script should check for sockets that remain bound after listener teardown"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("bound-inactive tcp sockets"),
|
|
|
|
|
"install script should explain when a hidden socket stops listening but still blocks the port"
|
|
|
|
|
);
|
2026-04-27 22:34:46 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("unexpected process"),
|
|
|
|
|
"install script should fail loud instead of killing an unrelated process on the server port"
|
|
|
|
|
);
|
2026-04-28 02:04:29 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("kernel dropped the hidden TCP"),
|
|
|
|
|
"install script should report when kernel-level stale-socket cleanup succeeds"
|
|
|
|
|
);
|
2026-04-28 01:33:17 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("no owning PID could be identified"),
|
|
|
|
|
"install script should fail loud when a port is listening but procfs cannot identify the owner"
|
|
|
|
|
);
|
2026-04-27 22:34:46 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("validate_server_ready"),
|
|
|
|
|
"install script should verify that lesavka-server reaches a running state"
|
|
|
|
|
);
|
2026-04-30 15:25:56 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("edid_bytes=$(wc -c <\"$dev/edid\""),
|
|
|
|
|
"HDMI auto-detection should count real EDID bytes instead of trusting sysfs file size"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("(( edid_bytes > 0 ))"),
|
|
|
|
|
"HDMI auto-detection should prefer a connector with real EDID over a boot-forced phantom connection"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("preserving existing LESAVKA_HDMI_CONNECTOR"),
|
|
|
|
|
"HDMI auto-detection should preserve the prior connector when physical evidence is still tied"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("edid=%sB"),
|
|
|
|
|
"ambiguous HDMI diagnostics should show EDID evidence for operator debugging"
|
|
|
|
|
);
|
2026-04-28 20:28:36 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("active and listening"),
|
|
|
|
|
"install script should require the TCP listener before declaring server readiness"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("did not open TCP"),
|
|
|
|
|
"install script should explain active-but-not-listening server failures"
|
|
|
|
|
);
|
2026-04-27 22:34:46 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("failed to reach active/running state"),
|
|
|
|
|
"install script should explain server startup failures instead of claiming success"
|
|
|
|
|
);
|
2026-04-25 16:48:20 -03:00
|
|
|
}
|
|
|
|
|
|
2026-04-30 08:16:57 -03:00
|
|
|
#[test]
|
|
|
|
|
fn server_install_generates_mtls_identity_and_client_bundle() {
|
|
|
|
|
for expected in [
|
|
|
|
|
"ensure_server_tls_pki",
|
|
|
|
|
"openssl genrsa",
|
|
|
|
|
"extendedKeyUsage = serverAuth",
|
|
|
|
|
"extendedKeyUsage = clientAuth",
|
|
|
|
|
"LESAVKA_CLIENT_BUNDLE",
|
|
|
|
|
"lesavka-client-pki.tar.gz",
|
2026-04-30 11:38:16 -03:00
|
|
|
"Client TLS bundle:",
|
|
|
|
|
"Client install can use:",
|
|
|
|
|
"sudo chown \"$ORIG_USER\":\"$ORIG_USER\" \"$LESAVKA_CLIENT_BUNDLE\"",
|
2026-04-30 08:16:57 -03:00
|
|
|
"LESAVKA_REQUIRE_TLS=%s",
|
|
|
|
|
"LESAVKA_TLS_CLIENT_CA=%s",
|
|
|
|
|
] {
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains(expected),
|
|
|
|
|
"server install script should include TLS/mTLS contract fragment {expected}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("LESAVKA_REQUIRE_TLS:-1"),
|
|
|
|
|
"installed servers should require TLS by default"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("38.28.125.112"),
|
|
|
|
|
"server cert SAN defaults should include the current public relay endpoint"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-25 16:48:20 -03:00
|
|
|
#[test]
|
|
|
|
|
fn server_install_reports_installed_version_and_revision() {
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("Installed:"),
|
|
|
|
|
"install script should print the installed build identity at the end"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
2026-04-26 00:27:43 -03:00
|
|
|
SERVER_INSTALL.contains("manifest_package_version"),
|
|
|
|
|
"install script should read the package version from the synced source manifest"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("\"$SRC_DIR/server/Cargo.toml\""),
|
|
|
|
|
"install script should report the server package semver from the server manifest"
|
2026-04-25 16:48:20 -03:00
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("git -C \"$SCRIPT_REPO_ROOT\" rev-parse --short HEAD"),
|
|
|
|
|
"install script should print the installed git revision for operator clarity"
|
|
|
|
|
);
|
2026-04-27 20:39:31 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("Camera output:"),
|
|
|
|
|
"install script should print the persisted camera output in the footer"
|
|
|
|
|
);
|
2026-04-28 02:29:32 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("Server bind:"),
|
|
|
|
|
"install script should print the persisted server bind address in the footer"
|
|
|
|
|
);
|
2026-04-27 20:39:31 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("UVC codec:"),
|
|
|
|
|
"install script should print the persisted UVC codec in the footer"
|
|
|
|
|
);
|
2026-04-22 22:10:39 -03:00
|
|
|
}
|
2026-04-24 21:27:19 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn server_install_keeps_uac_sanity_helper_available() {
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("run_uac_output_sanity.sh"),
|
|
|
|
|
"install script should ship the root UAC sanity helper"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("/usr/local/bin/lesavka-uac-sanity"),
|
|
|
|
|
"install script should install the UAC sanity helper to a stable path"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-24 22:43:25 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn server_install_grants_operator_audio_group_access() {
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("usermod -aG audio"),
|
|
|
|
|
"install script should grant the invoking operator access to ALSA devices"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("SUDO_USER"),
|
|
|
|
|
"install script should target the sudo-invoking operator instead of a hard-coded account"
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-04-25 05:57:29 -03:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn server_install_builds_eye_links_from_capture_nodes_only() {
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("/sys/class/video4linux/$(basename \"$dev\")/index"),
|
|
|
|
|
"install script should inspect the kernel video index so it only links real capture nodes"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("GC_CAPTURE_PAIRS"),
|
|
|
|
|
"install script should assemble eye-link candidates from udev-tagged capture devices"
|
|
|
|
|
);
|
2026-04-25 06:18:27 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("discover_gc_capture_pairs"),
|
|
|
|
|
"install script should retry capture-node discovery instead of trusting a single early snapshot"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("for _ in {1..20}; do"),
|
|
|
|
|
"install script should wait briefly for both GC311 capture nodes to finish enumerating"
|
|
|
|
|
);
|
2026-04-25 11:14:44 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("borrowing relay GPIO power for capture discovery"),
|
|
|
|
|
"install script should temporarily power capture cards for discovery when relay GPIO is available"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("returning relay GPIO power control to Lesavka auto mode"),
|
|
|
|
|
"install script should restore relay power control after capture discovery"
|
|
|
|
|
);
|
|
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("trap restore_capture_power_after_discovery EXIT"),
|
|
|
|
|
"install script should restore borrowed capture power even if discovery exits early"
|
|
|
|
|
);
|
2026-04-25 05:57:29 -03:00
|
|
|
assert!(
|
|
|
|
|
SERVER_INSTALL.contains("[ \"$LEFT_TAG\" = \"$RIGHT_TAG\" ]"),
|
|
|
|
|
"install script should refuse duplicated path tags instead of assigning both eyes to one card"
|
|
|
|
|
);
|
|
|
|
|
}
|