// 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!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/install/server.sh"
));

#[test]
fn server_install_infers_checkout_owner_when_root_wrapper_hides_sudo_user() {
    for expected in [
        "resolve_install_user()",
        "if [[ -n ${SUDO_USER:-} && ${SUDO_USER} != root ]]",
        "owner_uid=$(stat -c '%u' \"$SCRIPT_REPO_ROOT\"",
        "ORIG_USER=$(resolve_install_user)",
    ] {
        assert!(
            SERVER_INSTALL.contains(expected),
            "install script should include root-wrapper install user inference: {expected}"
        );
    }
}

#[test]
fn server_install_pins_hdmi_camera_and_display_defaults() {
    for expected in [
        "LESAVKA_CAM_OUTPUT=%s",
        "LESAVKA_CAM_HDMI_MIRROR=%s",
        "LESAVKA_CAM_CODEC=%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",
        "LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS=%s",
        "LESAVKA_UPSTREAM_MAX_LIVE_LAG_MS=%s",
        "LESAVKA_UPSTREAM_STARTUP_TIMEOUT_MS=%s",
        "LESAVKA_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s",
        "LESAVKA_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s",
        "LESAVKA_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=%s",
        "LESAVKA_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=%s",
        "LESAVKA_UPSTREAM_PAIR_SLACK_US=%s",
        "LESAVKA_UPSTREAM_AUDIO_MASTER_WAIT_GRACE_MS=%s",
        "LESAVKA_UPSTREAM_STALE_DROP_MS=%s",
        "LESAVKA_UVC_HEVC_SPOOL_PULL_TIMEOUT_MS=%s",
        "LESAVKA_UVC_HEVC_FRESHNESS_QUEUE_BUFFERS=%s",
        "LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT=%s",
        "LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE=%s",
        "LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY=%s",
        "LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS=%s",
        "LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT=%s",
        "LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB=%s",
        "LESAVKA_SERVER_BIND_ADDR=%s",
        "/etc/lesavka/uvc.env",
        "LESAVKA_UVC_MAXPACKET=",
        "LESAVKA_UVC_INTERVAL=",
        "LESAVKA_UVC_WIDTH=",
        "LESAVKA_UVC_HEIGHT=",
        "LESAVKA_UVC_CODEC=",
        "LESAVKA_UVC_FRAME_META=%s",
        "LESAVKA_UVC_FRAME_META_LOG_PATH=%s",
        "LESAVKA_UVC_CONTROL_READ_ONLY=",
        "LESAVKA_UAC_HOST_MIXER_CONTROLS=%s",
        "LESAVKA_UAC_APP_LEAKY_TYPE=%s",
        "LESAVKA_REQUIRE_TLS=%s",
        "LESAVKA_TLS_CERT=%s",
        "LESAVKA_TLS_KEY=%s",
        "LESAVKA_TLS_CLIENT_CA=%s",
        "LESAVKA_SYNTHETIC_PKI_DIR",
        "LESAVKA_PASTE_KEY_FILE=%s",
    ] {
        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}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_INSTALL_CAM_OUTPUT:-uvc}"));
    assert!(
        SERVER_INSTALL
            .contains("${LESAVKA_INSTALL_CAM_HDMI_MIRROR:-${LESAVKA_CAM_HDMI_MIRROR:-auto}}")
    );
    assert!(SERVER_INSTALL.contains("normalize_cam_codec()"));
    assert!(
        SERVER_INSTALL.contains(
            "REQUESTED_CAM_CODEC=${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-mjpeg}}"
        )
    );
    assert!(
        SERVER_INSTALL
            .contains("INSTALL_CAM_CODEC=$(normalize_cam_codec \"${REQUESTED_CAM_CODEC}\")")
    );
    assert!(SERVER_INSTALL.contains("INSTALL_CAM_CODEC_EXPLICIT=0"));
    assert!(
        SERVER_INSTALL
            .contains("${LESAVKA_INSTALL_UPLINK_AUDIO_CODEC:-${LESAVKA_UPLINK_AUDIO_CODEC:-pcm}}")
    );
    assert!(
        SERVER_INSTALL
            .contains("PERSISTED_UVC_CODEC=$(persisted_uvc_value LESAVKA_UVC_CODEC || true)")
            && SERVER_INSTALL.contains(
                "REQUESTED_UVC_CODEC=${LESAVKA_INSTALL_UVC_CODEC:-${PERSISTED_UVC_CODEC:-mjpeg}}"
            )
            && SERVER_INSTALL
                .contains("INSTALL_UVC_CODEC=$(normalize_uvc_codec \"$REQUESTED_UVC_CODEC\")"),
        "plain upgrade installs should normalize persisted UVC output to the supported MJPEG helper path"
    );
    assert!(
        SERVER_INSTALL.contains("${LESAVKA_INSTALL_UVC_FRAME_META:-${LESAVKA_UVC_FRAME_META:-0}}")
    );
    assert!(SERVER_INSTALL.contains(
        "${LESAVKA_INSTALL_UVC_FRAME_META_LOG_PATH:-${LESAVKA_UVC_FRAME_META_LOG_PATH:-/tmp/lesavka-uvc-frame-meta.jsonl}}"
    ));
    assert!(SERVER_INSTALL.contains("${LESAVKA_HDMI_WIDTH:-1920}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_HDMI_HEIGHT:-1080}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_HDMI_SINK:-fbdevsink}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UAC_HOST_MIXER_CONTROLS:-0}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UAC_APP_LEAKY_TYPE:-downstream}"));
    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"));
    assert!(SERVER_INSTALL.contains("DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=135090"));
    assert!(SERVER_INSTALL.contains(
        "DEFAULT_MJPEG_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=0,1280x720@30=0,1920x1080@20=0,1920x1080@30=0"
    ));
    assert!(SERVER_INSTALL.contains(
        "DEFAULT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=162659,1280x720@30=135090,1920x1080@20=160045,1920x1080@30=127952"
    ));
    assert!(SERVER_INSTALL.contains(
        "DEFAULT_MJPEG_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=-34199,1280x720@30=-34199,1920x1080@20=-34199,1920x1080@30=-34199"
    ));
    assert!(SERVER_INSTALL.contains(
        "DEFAULT_MJPEG_OPUS_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=541419,1280x720@30=513850,1920x1080@20=538805,1920x1080@30=506712"
    ));
    assert!(SERVER_INSTALL.contains("DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=0"));
    assert!(SERVER_INSTALL.contains("DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=110000"));
    assert!(SERVER_INSTALL.contains(
        "DEFAULT_HEVC_UPSTREAM_VIDEO_PLAYOUT_MODE_OFFSETS_US=1280x720@20=173852,1280x720@30=110000,1920x1080@20=160045,1920x1080@30=127952"
    ));
    assert!(SERVER_INSTALL.contains(
        "DEFAULT_HEVC_OPUS_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US=$DEFAULT_HEVC_UPSTREAM_AUDIO_PLAYOUT_MODE_OFFSETS_US"
    ));
    assert!(SERVER_INSTALL.contains("LESAVKA_UPLINK_AUDIO_CODEC=%s"));
    assert!(
        SERVER_INSTALL.contains("LESAVKA_UPSTREAM_MJPEG_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s")
    );
    assert!(
        SERVER_INSTALL.contains("LESAVKA_UPSTREAM_MJPEG_OPUS_VIDEO_PLAYOUT_MODE_OFFSETS_US=%s")
    );
    assert!(SERVER_INSTALL.contains("LESAVKA_UPSTREAM_HEVC_OPUS_AUDIO_PLAYOUT_MODE_OFFSETS_US=%s"));
    assert!(
        SERVER_INSTALL.contains("resolve_upstream_video_playout_offset_us"),
        "video offset should be resolved through stale-baseline migration logic"
    );
    assert!(SERVER_INSTALL.contains("LEGACY_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=-45000"));
    assert!(
        SERVER_INSTALL.contains("PREVIOUS_TUNED_MJPEG_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US=1260000")
    );
    assert!(SERVER_INSTALL.contains("PREVIOUS_ZERO_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=0"));
    assert!(
        SERVER_INSTALL.contains("PREVIOUS_DELAYED_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=350000")
    );
    assert!(
        SERVER_INSTALL.contains("PREVIOUS_BROWSER_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=130000")
    );
    assert!(
        SERVER_INSTALL.contains("PREVIOUS_SCALAR_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=170000")
    );
    assert!(
        SERVER_INSTALL.contains("PREVIOUS_OVERSHOT_MJPEG_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US=1090000")
    );
    assert!(
        SERVER_INSTALL.contains("LESAVKA_INSTALL_UPSTREAM_AUDIO_PLAYOUT_OFFSET_US"),
        "install-specific offset override should bypass stale ambient runtime env"
    );
    assert!(
        SERVER_INSTALL.contains("LESAVKA_INSTALL_UPSTREAM_VIDEO_PLAYOUT_OFFSET_US"),
        "install-specific video offset override should bypass stale ambient runtime env"
    );
    assert!(SERVER_INSTALL.contains("PERSISTED_CAM_HDMI_MIRROR="));
    assert!(SERVER_INSTALL.contains("HDMI mirror:"));
    assert!(
        SERVER_INSTALL.contains(
            "migrating stale upstream audio playout offset to the per-mode MJPEG/UAC default"
        ),
        "installer should not preserve old MJPEG/UVC sync baselines accidentally"
    );
    assert!(
        SERVER_INSTALL.contains(
            "migrating stale upstream video playout offset to the per-mode direct UVC/UAC MJPEG sync center"
        ),
        "installer should not preserve old video delay baselines accidentally"
    );
    assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_PAIR_SLACK_US:-80000}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_AUDIO_MASTER_WAIT_GRACE_MS:-350}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_STALE_DROP_MS:-80}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEVC_SPOOL_PULL_TIMEOUT_MS:-20}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEVC_FRESHNESS_QUEUE_BUFFERS:-2}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEVC_DECODE_MISS_LIMIT:-15}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE:-1}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_JPEG_QUALITY:-60}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_PULL_TIMEOUT_MS:-0}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_MISS_LIMIT:-30}"));
    assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_DIRECT_MJPEG_NORMALIZE_RSS_LIMIT_MB:-384}"));
    assert!(
        SERVER_INSTALL.contains("lesavka_server::video=info"),
        "server installs should not leave the hot webcam frame path at debug logging by default"
    );
    assert!(
        SERVER_INSTALL.contains("LESAVKA_UPSTREAM_MJPEG_VIDEO_PLAYOUT_MODE_OFFSETS_US"),
        "installer should persist MJPEG-specific calibration maps"
    );
    assert!(
        SERVER_INSTALL.contains("LESAVKA_UPSTREAM_HEVC_VIDEO_PLAYOUT_MODE_OFFSETS_US"),
        "installer should persist HEVC-specific calibration maps"
    );
    assert!(SERVER_INSTALL.contains("${LESAVKA_INSTALL_SERVER_BIND_ADDR:-0.0.0.0:50051}"));
    assert!(
        SERVER_INSTALL
            .contains("LESAVKA_PASTE_KEY_FILE=${LESAVKA_PASTE_KEY_FILE:-/etc/lesavka/paste-key}")
    );
    assert!(SERVER_INSTALL.contains("openssl rand -hex 32"));
    assert!(SERVER_INSTALL.contains("paste-key"));
    assert!(SERVER_INSTALL.contains("ca.crt client.crt client.key paste-key"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_MAXPACKET 1024"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FPS 20"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_INTERVAL 500000"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_WIDTH 1280"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_HEIGHT 720"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_CONTROL_READ_ONLY 0"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_BUFFER_COUNT 4"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_QUEUE_PACING 0"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_BULK 1"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FRAME_SIZE_GUARD 1"));
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_FRAME_MAX_BYTES 0"));
    assert!(SERVER_INSTALL.contains(
        "LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC=${LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC:-4500000}"
    ));
    assert!(
        !SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_MJPEG_BUDGET_BYTES_PER_SEC"),
        "installer should refresh the safer MJPEG byte-budget default instead of preserving an old field-tuned budget forever"
    );
    assert!(SERVER_INSTALL.contains("uvc_env_value LESAVKA_UVC_ISOCHRONOUS_LIMIT_PCT 85"));
    assert!(
        SERVER_INSTALL
            .contains("uvc_env_value LESAVKA_UVC_STATS_PATH /run/lesavka-uvc-video-stats.json")
    );
    assert!(
        !SERVER_INSTALL.contains("LESAVKA_UVC_CODEC=${LESAVKA_UVC_CODEC:-mjpeg}"),
        "install script should not let ambient LESAVKA_UVC_CODEC leak into persisted defaults"
    );
    assert!(
        SERVER_INSTALL
            .contains("UVC gadget output codec '${REQUESTED_UVC_CODEC}' is not supported"),
        "install script should not keep creating YUYV descriptors under the misleading HEVC UVC label"
    );
    assert!(
        SERVER_INSTALL.contains("ensure_hevc_decode_support"),
        "install script should prepare HEVC decode dependencies when HEVC is selected"
    );
    assert!(
        SERVER_INSTALL.contains("rpi_hevc_dec"),
        "install script should try the Raspberry Pi HEVC decoder before requiring another hardware decoder"
    );
    assert!(
        SERVER_INSTALL.contains("Software HEVC decoder passed:")
            && SERVER_INSTALL.contains("LESAVKA_HEVC_DECODER")
            && SERVER_INSTALL.contains("LESAVKA_ALLOW_SOFTWARE_VIDEO"),
        "install script should document software HEVC fallback only as an explicit follow-up"
    );
    assert!(
        SERVER_INSTALL.contains("hardware HEVC decoder passed a real 1280x720 decode smoke")
            && SERVER_INSTALL.contains("videotestsrc num-buffers=30")
            && SERVER_INSTALL.contains("x265enc speed-preset=ultrafast tune=zerolatency")
            && SERVER_INSTALL.contains("h265parse disable-passthrough=true config-interval=-1")
            && SERVER_INSTALL
                .contains("decoder_chain=\"$hevc_decoder discard-corrupted-frames=true automatic-request-sync-points=true\""),
        "install script should prove HEVC hardware decode with a real frame smoke, not only gst-inspect"
    );
    assert!(
        SERVER_INSTALL.contains("synthetic 1280x720 decode smoke failed")
            && SERVER_INSTALL
                .contains("Continuing because this install is not selecting HEVC upstream")
            && SERVER_INSTALL
                .contains("Explicit HEVC installs require a passing hardware decode smoke")
            && SERVER_INSTALL.contains("scripts/manual/run_hardware_media_smoke.sh"),
        "install script should keep non-HEVC installs safe while warning about a broken HEVC stack"
    );
    assert!(
        SERVER_INSTALL.contains("Refusing HEVC upstream install because production video decode must be hardware-accelerated and proven")
            && SERVER_INSTALL.contains("Use LESAVKA_INSTALL_CAM_CODEC=mjpeg while the HEVC decoder stack is repaired")
            && SERVER_INSTALL.contains("Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress."),
        "explicit HEVC installs should fail loud while defensive default paths fall back to MJPEG"
    );
    assert!(
        !SERVER_INSTALL
            .contains("LESAVKA_SERVER_BIND_ADDR=${LESAVKA_SERVER_BIND_ADDR:-0.0.0.0:50051}"),
        "install script should not let ambient LESAVKA_SERVER_BIND_ADDR leak into persisted defaults"
    );
    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"
    );
    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"
    );
    assert!(
        SERVER_INSTALL.contains("uvc_gadget_present"),
        "install script should detect when the live gadget is missing the expected UVC function"
    );
    assert!(
        SERVER_INSTALL.contains(
            "UVC function is missing from the live gadget; forcing a rebuild before server start."
        ),
        "install script should force a rebuild when the live gadget is attached but missing UVC"
    );
    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"
    );
    assert!(
        SERVER_INSTALL.contains("LESAVKA_FORCE_GADGET_REBUILD"),
        "install script should require an explicit force knob for attached-host gadget rebuilds"
    );
    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("ATTACHED_UVC_CHANGE_DEFERRED=1"),
        "attached UVC descriptor/runtime changes should be deferred instead of half-applied"
    );
    assert!(
        SERVER_INSTALL.contains("pause_recovery_ladder_timer"),
        "server installs should stop any stale recovery timer before a long build/install cycle"
    );
    assert!(
        SERVER_INSTALL.contains("lesavka_server_has_active_clients")
            && SERVER_INSTALL.contains("list_server_established_client_lines")
            && SERVER_INSTALL.contains("LESAVKA_INSTALL_RESTART_WITH_CLIENTS"),
        "attached-gadget installs should distinguish idle safe updates from active client sessions"
    );
    assert!(
        SERVER_INSTALL
            .contains("Active gRPC clients are connected; leaving the running server untouched"),
        "active clients should keep the old server process alive instead of forcing a disruptive update"
    );
    assert!(
        SERVER_INSTALL
            .contains("Leaving /etc/lesavka/server.env and /etc/lesavka/uvc.env unchanged"),
        "active-client deferral should preserve the last known live runtime env"
    );
    assert!(
        SERVER_INSTALL
            .contains("No active gRPC clients detected; applying runtime env and restarting lesavka-server only")
            && SERVER_INSTALL
                .contains("Preserving lesavka-core and lesavka-uvc so the attached USB gadget is not cycled")
            && SERVER_INSTALL.contains("restart_lesavka_server_only"),
        "idle attached-gadget installs should leave the newest server running without cycling USB gadget services"
    );
    assert!(
        SERVER_INSTALL.contains("server-only restart completed")
            && SERVER_INSTALL.contains("Live UVC descriptors may still differ"),
        "idle attached-gadget installs should be explicit about server readiness and deferred descriptor rebuilds"
    );
    assert!(
        SERVER_INSTALL.contains("ATTACHED_UVC_RESTART_DEFERRED")
            && SERVER_INSTALL.contains(
                "updated UVC env matches the live descriptor, restarting lesavka-server only"
            )
            && SERVER_INSTALL.contains("Live UVC descriptor already matches"),
        "attached installs with env-only changes should restart lesavka-server without cycling the UVC gadget"
    );
    assert!(
        SERVER_INSTALL
            .contains("Recovery ladder: installed, enabled, and started in server-only mode"),
        "deferred attached-gadget installs should leave a safe recovery ladder in place"
    );
    assert!(
        SERVER_INSTALL.contains("LIVE_UVC_DESCRIPTOR_MISMATCH"),
        "installer should catch partial previous runs where env already changed but live descriptors did not"
    );
    assert!(
        SERVER_INSTALL.contains("live_uac_descriptor_matches_request")
            && SERVER_INSTALL.contains("LIVE_UAC_DESCRIPTOR_MISMATCH")
            && SERVER_INSTALL.contains("p_volume_present")
            && SERVER_INSTALL.contains("c_volume_present")
            && SERVER_INSTALL.contains("LESAVKA_UAC_HOST_MIXER_CONTROLS:-0"),
        "installer should catch live UAC descriptor drift, including stale host mixer controls"
    );
    assert!(
        SERVER_INSTALL.contains(
            "UVC/UAC runtime settings or live descriptors differ while the host is attached"
        ) && SERVER_INSTALL.contains(
            "live UAC descriptor does not match requested host mixer/default audio settings"
        ),
        "installer should explain when UAC descriptor drift requires a maintenance-window gadget rebuild"
    );
    assert!(
        SERVER_INSTALL.contains("LIVE_UVC_FUNCTION_MISSING"),
        "installer should not rebuild a missing attached UVC function without explicit maintenance-window force"
    );
    assert!(
        SERVER_INSTALL.contains("live_gadget_bound")
            && SERVER_INSTALL.contains("HOST_GADGET_PROTECTED=1")
            && SERVER_INSTALL.contains("bound=${LIVE_GADGET_BOUND}"),
        "installer should protect a bound gadget even when the UDC state is ambiguous"
    );
    assert!(
        SERVER_INSTALL.contains("ATTACHED_UVC_RESTART_DEFERRED=1"),
        "attached UVC env changes that match the live descriptor should still defer gadget restarts"
    );
    assert!(
        SERVER_INSTALL.contains(
            "updated UVC env matches the live descriptor, restarting lesavka-server only"
        ) && SERVER_INSTALL
            .contains("Preserving lesavka-core so the attached USB gadget is not cycled")
            && SERVER_INSTALL.contains(
                "set LESAVKA_INSTALL_RESTART_UVC_HELPER=1 when a helper-only refresh is required"
            ),
        "safe env repairs should preserve the gadget and require an explicit helper-only refresh"
    );
    assert!(
        SERVER_INSTALL.contains("dwDefaultFrameInterval"),
        "live descriptor matching should include frame interval, not only codec labels"
    );
    assert!(
        SERVER_INSTALL.contains("1920x1080) frame_root=\"$function_root/streaming/mjpeg/m/1080p\"")
            && SERVER_INSTALL
                .contains("1280x720) frame_root=\"$function_root/streaming/mjpeg/m/720p\"")
            && SERVER_INSTALL.contains(
                "grep -qx \"${LESAVKA_UVC_INTERVAL:-500000}\" \"$frame_root/dwFrameInterval\""
            ),
        "live descriptor matching should recognize all supported MJPEG UVC profiles instead of collapsing to one 720p frame"
    );
    assert!(
        SERVER_INSTALL.contains("streaming/header/h/mjpeg")
            && SERVER_INSTALL.contains("streaming/header/h/yuyv"),
        "live descriptor codec detection should prefer the active UVC header links"
    );
    assert!(
        SERVER_INSTALL
            .find("sudo install -m 0644 \"$UVC_ENV_TMP\" /etc/lesavka/uvc.env")
            .unwrap()
            < SERVER_INSTALL
                .find("if [[ \"$ATTACHED_UVC_RESTART_DEFERRED\" == \"1\" ]]")
                .unwrap(),
        "safe env repair should install env files before exiting without service restarts"
    );
    assert!(
        SERVER_INSTALL
            .find("if [[ \"$ATTACHED_UVC_RESTART_DEFERRED\" == \"1\" ]]")
            .unwrap()
            < SERVER_INSTALL
                .rfind("restart_lesavka_uvc_helper_only")
                .unwrap(),
        "safe env repair must remain separate from the later version-update helper refresh path"
    );
    assert!(
        SERVER_INSTALL.contains("install_uvc_kernel_watch_unit")
            && SERVER_INSTALL
                .find("\ninstall_uvc_kernel_watch_unit\n")
                .unwrap()
                < SERVER_INSTALL
                    .find("if [[ \"$ATTACHED_UVC_RESTART_DEFERRED\" == \"1\" ]]")
                    .unwrap(),
        "kernel UVC telemetry must be installed even when an attached-gadget branch exits early"
    );
    assert!(
        SERVER_INSTALL
            .find("ATTACHED_UVC_CHANGE_DEFERRED=1")
            .unwrap()
            < SERVER_INSTALL
                .find("sudo install -m 0644 \"$SERVER_ENV_TMP\" /etc/lesavka/server.env")
                .unwrap(),
        "attached UVC deferral must run before runtime env files are rewritten"
    );
    assert!(
        SERVER_INSTALL
            .find("ATTACHED_UVC_CHANGE_DEFERRED=1")
            .unwrap()
            < SERVER_INSTALL
                .rfind("restart_lesavka_uvc_helper_only")
                .unwrap(),
        "attached UVC deferral must run before the later safe helper refresh call site"
    );
    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"
    );
    assert!(
        SERVER_INSTALL.contains("no hard gadget rebuild is needed"),
        "LESAVKA_ALLOW_GADGET_RESET should permit recovery without causing unconditional hard rebuilds"
    );
    assert!(
        !SERVER_INSTALL.contains(
            "[[ -n ${LESAVKA_ALLOW_GADGET_RESET:-} ]] || [[ \"$FORCE_GADGET_REBUILD\" == \"1\" ]]"
        ),
        "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"
    );
    assert!(
        SERVER_INSTALL.contains("sudo systemctl stop lesavka-uvc"),
        "install script should stop the UVC helper before directly rebuilding the gadget underneath it"
    );
    assert!(
        !SERVER_INSTALL.contains("sudo systemctl restart lesavka-core"),
        "install script should not immediately rerun lesavka-core after a successful direct forced rebuild"
    );
    assert!(
        SERVER_INSTALL.contains("video-output node did not appear after rebuild"),
        "install script should explain why it refuses a half-applied UVC install"
    );
    assert!(
        !SERVER_INSTALL.contains("RefuseManualStop=yes"),
        "install script should not generate a UVC unit that blocks legitimate refreshes"
    );
    assert!(
        SERVER_INSTALL.contains("KillMode=control-group"),
        "install script should stop the whole UVC helper cgroup instead of leaving child processes behind"
    );
    assert!(
        !SERVER_INSTALL.contains("KillMode=process"),
        "install script should not leave the server or helper units in process-only kill mode"
    );
    assert!(
        SERVER_INSTALL.contains("lesavka-uvc already active; runtime settings unchanged."),
        "install script should avoid unnecessary UVC restarts when nothing changed"
    );
    assert!(
        SERVER_INSTALL.contains("CAN_TOUCH_UVC_SERVICE=0")
            && SERVER_INSTALL.contains("preserving it during attached-gadget version update")
            && SERVER_INSTALL.contains("LESAVKA_INSTALL_RESTART_UVC_HELPER")
            && SERVER_INSTALL.contains("not starting it during a version-only install"),
        "ordinary attached-gadget version updates should preserve the UVC helper unless an operator explicitly opts into helper refresh"
    );
    assert!(
        SERVER_INSTALL.contains("restart_lesavka_uvc_helper_only")
            && SERVER_INSTALL.contains("systemctl restart lesavka-uvc")
            && SERVER_INSTALL
                .contains("lesavka-uvc helper restarted in-place by explicit request; lesavka-core and the attached USB gadget were preserved"),
        "the optional helper refresh must be explicit and must not cycle lesavka-core or rebuild the gadget"
    );
    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_INSTALL.contains("ExecStartPre=/usr/local/bin/lesavka-core.sh --attach"),
        "server restarts during ordinary installs must not implicitly start UVC or touch the gadget"
    );
    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"
    );
    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"
    );
    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"
    );
    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"
    );
    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"
    );
    assert!(
        SERVER_INSTALL.contains("unexpected process"),
        "install script should fail loud instead of killing an unrelated process on the server port"
    );
    assert!(
        SERVER_INSTALL.contains("kernel dropped the hidden TCP"),
        "install script should report when kernel-level stale-socket cleanup succeeds"
    );
    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"
    );
    assert!(
        SERVER_INSTALL.contains("validate_server_ready"),
        "install script should verify that lesavka-server reaches a running state"
    );
    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"
    );
    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"
    );
    assert!(
        SERVER_INSTALL.contains("failed to reach active/running state"),
        "install script should explain server startup failures instead of claiming success"
    );
}

#[test]
fn server_install_provisions_non_rebooting_recovery_ladder() {
    for expected in [
        "install_verified_executable \"$SRC_DIR/scripts/daemon/lesavka-recovery-ladder.sh\" /usr/local/bin/lesavka-recovery-ladder",
        "lesavka-recovery-ladder.service",
        "lesavka-recovery-ladder.timer",
        "ExecStart=/usr/local/bin/lesavka-recovery-ladder recover",
        "Environment=LESAVKA_RECOVERY_ALLOW_UVC_RESTART=0",
        "Environment=LESAVKA_RECOVERY_ALLOW_CORE_RESTART=0",
        "Environment=LESAVKA_RECOVERY_ALLOW_REBOOT=0",
        "OnUnitActiveSec=60s",
        "sudo /usr/local/bin/lesavka-recovery-ladder snapshot || true",
        "sudo systemctl start lesavka-recovery-ladder.timer",
    ] {
        assert!(
            SERVER_INSTALL.contains(expected),
            "server installer should preserve recovery ladder marker {expected}"
        );
    }
    assert!(
        SERVER_INSTALL.find("validate_server_ready").unwrap()
            < SERVER_INSTALL
                .find("sudo /usr/local/bin/lesavka-recovery-ladder snapshot || true")
                .unwrap(),
        "last-known-good snapshots should only be refreshed after the installed server is verified"
    );
    assert!(
        SERVER_INSTALL
            .find("install_recovery_ladder_units")
            .unwrap()
            < SERVER_INSTALL
                .find("ATTACHED_UVC_CHANGE_DEFERRED=0")
                .unwrap(),
        "recovery ladder units should be installed before any attached-gadget deferral can exit"
    );
}

#[test]
fn server_install_treats_pipewire_as_one_coherent_transaction() {
    for expected in [
        "pacman_install()",
        "pipewire_package_set()",
        "libpipewire pipewire pipewire-audio pipewire-pulse",
        "for package in pipewire-alsa pipewire-jack",
        "pacman -Q \"$package\"",
        "\"${PIPEWIRE_PACKAGES[@]}\"",
        "breaks dependency '.*pipewire",
        "PipeWire packages are at mixed exact versions",
        "Lesavka does not require JACK",
        "do not remove jack2 just for Lesavka",
        "failed retrieving file",
        "sudo pacman -Syu --disable-download-timeout",
    ] {
        assert!(
            SERVER_INSTALL.contains(expected),
            "server installer should preserve PipeWire transaction marker {expected}"
        );
    }
}

#[test]
fn server_install_prefers_invoked_checkout_for_development_installs() {
    for expected in [
        "INSTALL_SOURCE=${LESAVKA_INSTALL_SOURCE:-auto}",
        "resolve_source_checkout",
        "Using local source checkout",
        "set LESAVKA_INSTALL_SOURCE=ref",
        "LESAVKA_INSTALL_SOURCE=local requested",
        "unsupported LESAVKA_INSTALL_SOURCE",
        "source_revision",
        "+dirty",
    ] {
        assert!(
            SERVER_INSTALL.contains(expected),
            "server installer should preserve local-source install marker {expected}"
        );
    }
    assert!(
        SERVER_INSTALL.find("resolve_source_checkout").unwrap()
            < SERVER_INSTALL
                .find("cargo clean && cargo build --release --bins")
                .unwrap(),
        "server installer should resolve source before building"
    );
}

#[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",
        "Client TLS bundle:",
        "Client install can use:",
        "sudo chown \"$ORIG_USER\":\"$ORIG_USER\" \"$LESAVKA_CLIENT_BUNDLE\"",
        "local synthetic probe TLS identity",
        "$USER_HOME/.config/lesavka/pki",
        "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"
    );
}

#[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!(
        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"
    );
    assert!(
        SERVER_INSTALL.contains("git -C \"$SCRIPT_REPO_ROOT\" rev-parse --short HEAD"),
        "install script should print the installed git revision for operator clarity"
    );
    assert!(
        SERVER_INSTALL.contains("Camera output:"),
        "install script should print the persisted camera output in the footer"
    );
    assert!(
        SERVER_INSTALL.contains("Server bind:"),
        "install script should print the persisted server bind address in the footer"
    );
    assert!(
        SERVER_INSTALL.contains("UVC codec:"),
        "install script should print the persisted UVC codec in the footer"
    );
}

#[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"
    );
}

#[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"
    );
}

#[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"
    );
    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"
    );
    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"
    );
    assert!(
        SERVER_INSTALL.contains("[ \"$LEFT_TAG\" = \"$RIGHT_TAG\" ]"),
        "install script should refuse duplicated path tags instead of assigning both eyes to one card"
    );
}
