#[test] fn quiet_update_keeps_prebuilt_enabled_by_default() { let install = include_str!("../install.sh"); assert!(install.contains("use_prebuilt=\"${DOSH_USE_PREBUILT:-1}\"")); assert!( !install.contains("use_prebuilt=0"), "quiet updates must not force source builds" ); assert!(install.contains("[ \"$quiet\" != \"1\" ] && echo \"Trying Dosh prebuilt")); assert!(install.contains("[ \"$quiet\" != \"1\" ] && echo \"Building Dosh $role\"")); assert!( !install.contains("[ \"$quiet\" = \"1\" ] && echo"), "quiet updates must suppress informational installer output" ); } #[test] fn release_scripts_skip_stale_artifacts_when_auto_discovering() { let upload = include_str!("../scripts/upload-gitea-release.sh"); assert!(upload.contains("skipping $artifact: version")); assert!(upload.contains("expected_version")); let publish = include_str!("../scripts/publish-gitea-release.sh"); assert!(publish.contains("skipping stale artifact")); assert!(publish.contains("actual=\"$(artifact_version \"$artifact\" || true)\"")); } #[test] fn release_upload_refuses_partial_platform_sets_by_default() { let upload = include_str!("../scripts/upload-gitea-release.sh"); assert!(upload.contains("require_complete_release_artifacts \"$@\"")); assert!(upload.contains("DOSH_RELEASE_ALLOW_PARTIAL=1")); assert!(upload.contains("refusing partial release upload")); for name in [ "dosh-linux-x86_64.tar.gz", "dosh-macos-aarch64.tar.gz", "dosh-macos-x86_64.tar.gz", "dosh-windows-x86_64.zip", "dosh-windows-aarch64.zip", ] { assert!( upload.contains(name), "release upload completeness check must require {name}" ); } } #[test] fn release_upload_marks_prerelease_tags_as_prereleases() { let upload = include_str!("../scripts/upload-gitea-release.sh"); assert!(upload.contains("release_prerelease=false")); assert!(upload.contains("*-*) release_prerelease=true")); assert!(upload.contains("\"prerelease\":%s")); assert!(upload.contains("metadata_payload=")); assert!( upload.contains("-X PATCH"), "existing Gitea releases must be corrected when prerelease metadata changes" ); } #[test] fn release_gates_test_all_targets() { let makefile = include_str!("../Makefile"); let ci = include_str!("../.github/workflows/ci.yml"); assert!(makefile.contains("test:\n\tcargo test --all-targets")); assert!(makefile.contains("cargo test --all-targets\n\t$(MAKE) tui-harness")); assert!(ci.contains("run: sh -n install.sh scripts/*.sh")); assert!(ci.contains("run: cargo clippy --all-targets -- -D warnings")); assert!(ci.contains("run: cargo test --all-targets")); assert!(ci.contains("run: sh scripts/tui-harness.sh")); let macos_client = ci .split("macos-client:") .nth(1) .and_then(|tail| tail.split("\n windows-client:").next()) .expect("macos-client job"); assert!(macos_client.contains("name: macos-aarch64")); assert!(macos_client.contains("name: macos-x86_64")); assert!(macos_client.contains("os: macos-14")); assert!(macos_client.contains("os: macos-13")); assert!(macos_client.contains("name: Test macOS client")); assert!( macos_client.contains("run: cargo test --all-targets"), "macOS CI must run Rust tests, not only release packaging" ); assert!( macos_client.contains("run: node --test vscode-extension/extension.test.js"), "macOS CI must exercise VS Code helper quoting" ); let windows_client = ci .split("windows-client:") .nth(1) .and_then(|tail| tail.split("\n package-release:").next()) .expect("windows-client job"); assert!(windows_client.contains("runs-on: windows-latest")); assert!(windows_client.contains("name: Test Windows client")); assert!(windows_client.contains("name: PowerShell syntax check")); assert!(windows_client.contains("shell: pwsh")); assert!( windows_client .contains("[System.Management.Automation.Language.Parser]::ParseFile(\"install.ps1\""), "Windows CI must parse the installer with PowerShell" ); assert!( windows_client.contains("name: cmd installer smoke check") && windows_client.contains("DOSH_INSTALL_PS1=") && windows_client.contains("call install.cmd ok") && windows_client.contains("find /I \";{1}\\dosh-smoke-bin;\""), "Windows CI must execute the cmd wrapper against a local installer stub" ); assert!( windows_client.contains("run: cargo test --all-targets"), "Windows CI must run Rust tests, not only build/package" ); assert!( windows_client.contains("run: node --test vscode-extension/extension.test.js"), "Windows CI must exercise VS Code helper quoting" ); } #[test] fn one_dot_zero_gates_default_to_full_length_soaks() { let makefile = include_str!("../Makefile"); let reconnect_check = makefile .split("reconnect-check:") .nth(1) .and_then(|tail| tail.split("\nhostile-network-check:").next()) .expect("reconnect-check target"); assert!(reconnect_check.contains("DOSH_SOAK_SECONDS=$${DOSH_SOAK_SECONDS:-1800}")); assert!(!reconnect_check.contains("DOSH_SOAK_SECONDS=$${DOSH_SOAK_SECONDS:-300}")); let hostile_check = makefile .split("hostile-network-check:") .nth(1) .and_then(|tail| tail.split("\npersistence-check:").next()) .expect("hostile-network-check target"); assert!(hostile_check.contains("DOSH_BADNET_SOAK_SECONDS=$${DOSH_BADNET_SOAK_SECONDS:-1800}")); assert!(!hostile_check.contains("DOSH_BADNET_SOAK_SECONDS=$${DOSH_BADNET_SOAK_SECONDS:-300}")); } #[test] fn installers_skip_stale_latest_release_prebuilts() { let install = include_str!("../install.sh"); assert!(install.contains("latest_release_is_stale")); assert!(install.contains("current_source_version")); assert!(install.contains("latest release $latest is older than source $current")); assert!(install.contains("version_prerelease")); assert!(install.contains("prerelease_less_than")); assert!( install.contains("if latest_release_is_stale; then"), "unix installer must skip stale latest before downloading a prebuilt" ); let ps1 = include_str!("../install.ps1"); assert!(ps1.contains("Latest-ReleaseIsStale")); assert!(ps1.contains("Current-SourceVersion")); assert!(ps1.contains("latest release $latestVersion is older than source $current")); assert!(ps1.contains("Version-Prerelease")); assert!(ps1.contains("Compare-Prerelease")); assert!( ps1.contains("if (Latest-ReleaseIsStale)"), "windows installer must skip stale latest before downloading a prebuilt" ); } #[test] fn installers_refuse_prebuilts_without_checksums() { let install = include_str!("../install.sh"); assert!(install.contains("verify_archive_checksum")); assert!(install.contains("refusing unverified binary for $url")); assert!( !install.contains("continuing without sidecar verification"), "unix installer must not silently install an unverifiable prebuilt" ); let ps1 = include_str!("../install.ps1"); assert!(ps1.contains("Verify-ArchiveChecksum")); assert!(ps1.contains("refusing unverified binary for $Url")); assert!( !ps1.contains("continuing without sidecar verification"), "windows installer must not silently install an unverifiable prebuilt" ); } #[test] fn windows_installer_reuses_persistent_source_update_cache() { let ps1 = include_str!("../install.ps1"); assert!(ps1.contains("DOSH_UPDATE_CACHE")); assert!(ps1.contains("elseif ($env:LOCALAPPDATA)")); assert!(ps1.contains("Join-Path $env:LOCALAPPDATA \"dosh\\source\"")); assert!(ps1.contains("Join-Path $HOME \".cache\\dosh\\source\"")); assert!(ps1.contains("Assert-NoRelativePathSegments($Path)")); assert!(ps1.contains("Assert-NoRelativePathSegments $Path")); assert!(ps1.contains("$segment -eq \".\" -or $segment -eq \"..\"")); assert!(ps1.contains("$localAppData = Normalize-PathForCompare $env:LOCALAPPDATA")); assert!(ps1.contains( "$localAppDataDosh = Normalize-PathForCompare (Join-Path $env:LOCALAPPDATA \"dosh\")" )); assert!(ps1.contains("$full -eq $localAppData -or $full -eq $localAppDataDosh")); assert!(ps1.contains("$sourceCache = Assert-SafeUpdateCache $UpdateCache")); assert!(ps1.contains( "$fetchArgs = @(\"-C\", $sourceCache, \"fetch\", \"--depth\", \"1\", \"origin\", \"main\")" )); assert!(ps1.contains("$fetchArgs = @(\"-C\", $sourceCache, \"fetch\", \"-q\", \"--depth\", \"1\", \"origin\", \"main\")")); assert!(ps1.contains("git @fetchArgs")); assert!(ps1.contains("git -C $sourceCache checkout -q -B main FETCH_HEAD")); assert!(ps1.contains( "$cloneArgs = @(\"clone\", \"--depth\", \"1\", \"--branch\", \"main\", $Repo, $sourceCache)" )); assert!(ps1.contains("$cloneArgs = @(\"clone\", \"-q\", \"--depth\", \"1\", \"--branch\", \"main\", $Repo, $sourceCache)")); assert!(ps1.contains("git @cloneArgs | Out-Null")); assert!( !ps1.contains("git clone --depth 1 $Repo $tmp"), "Windows source updates should reuse a persistent cache instead of recloning into temp" ); } #[test] fn windows_quiet_source_updates_silence_git_like_unix() { let install = include_str!("../install.sh"); let ps1 = include_str!("../install.ps1"); assert!(install.contains("git -C \"$update_cache\" fetch -q --depth 1 origin main")); assert!(install.contains("git clone -q --depth 1 --branch main \"$repo\" \"$update_cache\"")); assert!(ps1.contains("if ($Quiet)")); assert!(ps1.contains("\"fetch\", \"-q\", \"--depth\"")); assert!(ps1.contains("\"clone\", \"-q\", \"--depth\"")); } #[test] fn windows_installer_bootstraps_rust_for_source_fallback_like_unix() { let install = include_str!("../install.sh"); let ps1 = include_str!("../install.ps1"); assert!(install.contains("ensure_cargo()")); assert!(install.contains("cargo not found; installing Rust toolchain with rustup")); assert!(ps1.contains("function Ensure-Cargo")); assert!(ps1.contains("cargo not found; installing Rust toolchain with winget/rustup")); assert!(ps1.contains("winget install --id Rustlang.Rustup")); assert!(ps1.contains("--accept-package-agreements --accept-source-agreements")); assert!(ps1.contains("PathList-Contains $env:Path $cargoBin")); assert!(ps1.contains("$env:Path = \"$cargoBin;$env:Path\"")); assert!(ps1.contains("Ensure-Cargo")); assert!(!ps1.contains("Require-Command cargo")); } #[test] fn windows_installer_bootstraps_git_for_source_fallback_like_unix() { let ps1 = include_str!("../install.ps1"); assert!(ps1.contains("function Ensure-Git")); assert!(ps1.contains("git not found; installing Git with winget")); assert!(ps1.contains("winget install --id Git.Git")); assert!(ps1.contains("--accept-package-agreements --accept-source-agreements")); assert!(ps1.contains("Join-Path $env:ProgramFiles \"Git\\cmd\"")); assert!(ps1.contains("Join-Path ${env:ProgramFiles(x86)} \"Git\\cmd\"")); assert!(ps1.contains("Join-Path $env:LOCALAPPDATA \"Programs\\Git\\cmd\"")); assert!(ps1.contains("PathList-Contains $env:Path $gitBin")); assert!(ps1.contains("$env:Path = \"$gitBin;$env:Path\"")); assert!(ps1.contains("Ensure-Git")); assert!(!ps1.contains("Require-Command git")); } #[test] fn windows_cmd_installer_wraps_native_powershell_installer() { let cmd = include_str!("../install.cmd"); let readme = include_str!("../README.md"); assert!(cmd.contains("@echo off")); assert!(cmd.contains("https://git.palav.dev/Palav/dosh/raw/branch/main/install.ps1")); assert!(cmd.contains("for %%P in (powershell.exe pwsh.exe powershell pwsh) do")); assert!(cmd.contains("set \"TMP_BINDIR=%TEMP%\\dosh-install-%RANDOM%-%RANDOM%.bindir\"")); assert!(cmd.contains("set \"DOSH_INSTALL_BINDIR_FILE=%TMP_BINDIR%\"")); assert!(cmd.contains("if exist \"%SCRIPT_URL%\" (")); assert!(cmd.contains("copy /Y \"%SCRIPT_URL%\" \"%TMP_PS%\"")); assert!(cmd.contains("Invoke-WebRequest -UseBasicParsing")); assert!(cmd.contains("-ExecutionPolicy Bypass -File \"%TMP_PS%\" %*")); assert!(cmd.contains( "for /f \"usebackq delims=\" %%D in (\"%TMP_BINDIR%\") do if not defined DOSH_CMD_BINDIR set \"DOSH_CMD_BINDIR=%%D\"" )); assert!(cmd.contains("if not defined DOSH_CMD_BINDIR (")); assert!( cmd.contains( "if \"%DOSH_CMD_PREFIX%\"==\"\" set \"DOSH_CMD_PREFIX=%USERPROFILE%\\.local\"" ) ); assert!(cmd.contains("set \"DOSH_CMD_BINDIR=%DOSH_CMD_PREFIX%\\bin\"")); assert!(cmd.contains("echo ;%PATH%; | find /I \";%DOSH_CMD_BINDIR%;\"")); assert!(cmd.contains("set \"DOSH_NEXT_PATH=%DOSH_CMD_BINDIR%;%PATH%\"")); assert!(cmd.contains("endlocal & set \"PATH=%DOSH_NEXT_PATH%\" & exit /b %RC%")); assert!(readme.contains("Windows client from `cmd.exe`:")); assert!(readme.contains("install.cmd")); } #[test] fn windows_installer_honors_quiet_update_mode() { let ps1 = include_str!("../install.ps1"); assert!(ps1.contains("$Quiet = $env:DOSH_UPDATE_QUIET")); assert!(ps1.contains("function Write-Info($Message)")); assert!(ps1.contains("if (-not $Quiet)")); assert!(ps1.contains("Write-Info \"Trying Dosh prebuilt")); assert!(ps1.contains("Write-Info \"Falling back to source build\"")); assert!(ps1.contains("Write-Info \"Installed Dosh client to $bindir\"")); assert!( !ps1.contains("Write-Host \"Trying Dosh prebuilt"), "Windows quiet update must not print prebuilt progress directly" ); assert!( !ps1.contains("Write-Host \"Installed Dosh client"), "Windows quiet update must not print install summary directly" ); } #[test] fn windows_source_fallback_builds_client_only_like_unix() { let install = include_str!("../install.sh"); let ps1 = include_str!("../install.ps1"); assert!(install.contains("cargo build -q --release --bin dosh-client")); assert!(install.contains("cargo build --release --bin dosh-client")); assert!(ps1.contains("$buildArgs = @(\"build\", \"--release\", \"--bin\", \"dosh-client\")")); assert!( ps1.contains( "$buildArgs = @(\"build\", \"-q\", \"--release\", \"--bin\", \"dosh-client\")" ), "Windows quiet source fallback should use cargo -q like Unix" ); assert!(ps1.contains("cargo @buildArgs")); assert!( !ps1.contains("cargo build --release --bin dosh-client --bin dosh-bench"), "Windows client source fallback must not compile dosh-bench on every update" ); assert!(ps1.contains("if (Test-Path \"target\\release\\dosh-bench.exe\")")); } #[test] fn windows_prebuilt_install_requires_client_but_not_bench() { let install = include_str!("../install.sh"); let ps1 = include_str!("../install.ps1"); assert!(install.contains("install_extracted_binary \"$tmpdir/extract\" dosh-client")); assert!(install.contains("if found_bench=\"$(find_extracted_binary")); assert!(ps1.contains("throw \"prebuilt archive missing dosh-client.exe\"")); assert!(ps1.contains("-Filter \"dosh-bench.exe\"")); assert!(ps1.contains("if ($bench)")); assert!(ps1.contains("Install-Binary $client.FullName (Join-Path $bindir \"dosh.exe\")")); assert!(!ps1.contains("Install-Binary (Join-Path $bindir \"dosh-client.exe\")")); assert!( !ps1.contains("throw \"prebuilt archive missing dosh-bench.exe\""), "Windows prebuilt install should not fall back to source just because dosh-bench is absent" ); } #[test] fn windows_installer_stages_locked_binary_replacements() { let ps1 = include_str!("../install.ps1"); assert!(ps1.contains("function Start-DeferredBinaryReplacement")); assert!(ps1.contains("function Apply-PendingBinaryReplacements")); assert!(ps1.contains("$pending = \"$Destination.pending.$PID\"")); assert!(ps1.contains("Move-Item -LiteralPath $tmp -Destination $pending -Force")); assert!(ps1.contains("Start-DeferredBinaryReplacement $pending $Destination")); assert!(ps1.contains("binary is in use; staged replacement")); assert!(ps1.contains("for (`$i = 0; `$i -lt 3600; `$i++)")); assert!(ps1.contains( "Move-Item -LiteralPath `$payload.Pending -Destination `$payload.Destination -Force" )); assert!(ps1.contains("[System.Text.Encoding]::Unicode.GetBytes($script)")); assert!(ps1.contains("Start-Process -FilePath $ps")); assert!(ps1.contains("Get-ChildItem -LiteralPath $Dir -File -Filter \"*.pending.*\"")); assert!(ps1.contains("$marker = \".pending.\"")); assert!(ps1.contains("$destinationName = $name.Substring(0, $index)")); assert!(ps1.contains("Move-Item -LiteralPath $pending -Destination $destination -Force")); assert!(ps1.contains("Apply-PendingBinaryReplacements $bindir")); let recovery = ps1.find("Apply-PendingBinaryReplacements $bindir").unwrap(); let install = ps1.find("if ($UsePrebuilt)").unwrap(); assert!( recovery < install, "pending binary replacements must be recovered before installing new binaries" ); } #[test] fn windows_installer_adds_user_path_idempotently() { let ps1 = include_str!("../install.ps1"); assert!(ps1.contains("function Add-UserPath($PathToAdd)")); assert!(ps1.contains("function PathList-Contains($PathValue, $PathToFind)")); assert!(ps1.contains("function Write-PathStatus($PathToCheck)")); assert!(ps1.contains("$bindir = Normalize-PathForCompare (Join-Path $Prefix \"bin\")")); assert!(ps1.contains("if ($env:DOSH_INSTALL_BINDIR_FILE)")); assert!(ps1.contains("Write-Utf8NoBom $env:DOSH_INSTALL_BINDIR_FILE $bindir")); assert!(ps1.contains("$wanted = Normalize-PathForCompare $PathToFind")); assert!(ps1.contains("foreach ($entry in ($PathValue -split ';' | Where-Object { $_ }))")); assert!(ps1.contains("$wanted = Normalize-PathForCompare $PathToAdd")); assert!(ps1.contains("-ieq $wanted")); assert!(ps1.contains("(@($entries) + $PathToAdd) -join ';'")); assert!(ps1.contains("PathList-Contains $env:Path $PathToAdd")); assert!(ps1.contains("$env:Path = \"$PathToAdd;$env:Path\"")); assert!( ps1.find("PathList-Contains $env:Path $PathToAdd").unwrap() < ps1.find("foreach ($entry in $entries)").unwrap(), "Windows installer must refresh the current shell PATH even when user PATH already contains Dosh" ); assert!( !ps1.contains("\"$userPath;$bindir\""), "Windows installer must not write a leading semicolon when user PATH is empty" ); assert!(ps1.contains("Add-UserPath $bindir")); assert!(ps1.contains("Write-PathStatus $bindir")); assert!(ps1.contains("Current terminal PATH includes Dosh.")); assert!(ps1.contains("Open a new terminal if dosh is not found on PATH.")); assert!( !ps1.contains("Open a new terminal for PATH changes to apply."), "Windows installer should not always claim a new terminal is required" ); } #[test] fn windows_installer_prefers_native_wow64_arch_for_prebuilts() { let ps1 = include_str!("../install.ps1"); assert!(ps1.contains("PROCESSOR_ARCHITEW6432")); assert!(ps1.contains("$arch = if ($env:PROCESSOR_ARCHITEW6432)")); assert!(ps1.contains("\"ARM64\" { \"aarch64\"; break }")); assert!(ps1.contains("\"dosh-windows-$(Normalize-Arch).zip\"")); } #[test] fn unix_installer_rejects_unsafe_source_update_cache_paths() { let install = include_str!("../install.sh"); assert!(install.contains("normalize_update_cache_path()")); assert!(install.contains("safe_update_cache_path()")); assert!(install.contains("update_cache=\"$(safe_update_cache_path \"$update_cache\")\"")); for pattern in [ "\"\"|\"/\"|\"$home_norm\"|\"$home_cache_norm\"", "\".\"|\"..\"|./*|../*|*/.|*/..|*/./*|*/../*", ] { assert!( install.contains(pattern), "Unix installer must reject unsafe update cache pattern {pattern}" ); } assert!( install.contains("rm -rf \"$update_cache\""), "source cache replacement remains the destructive operation guarded by safe_update_cache_path" ); } #[test] fn installers_generate_same_native_auth_client_defaults() { let install = include_str!("../install.sh"); let ps1 = include_str!("../install.ps1"); for setting in [ "auth_preference = \"native,ssh\"", "trust_on_first_use = false", "native_auth_timeout_ms = 700", "known_hosts = \"~/.config/dosh/known_hosts\"", "identity_files = [\"~/.ssh/id_ed25519\"]", "use_ssh_agent = true", "forward_agent = false", ] { assert!( install.contains(setting), "Unix installer missing client default {setting}" ); assert!( ps1.contains(setting), "Windows installer missing client default {setting}" ); } } #[test] fn windows_installer_uses_native_credential_cache_path() { let install = include_str!("../install.sh"); let ps1 = include_str!("../install.ps1"); assert!(install.contains("credential_cache = \"~/.local/share/dosh/credentials\"")); assert!(ps1.contains("$credentialCache = if ($env:LOCALAPPDATA)")); assert!(ps1.contains("Join-Path $env:LOCALAPPDATA \"dosh\\credentials\"")); assert!(ps1.contains(".Replace('\\', '/')")); assert!(ps1.contains("credential_cache = \"$credentialCache\"")); } #[test] fn windows_installer_generates_hosts_config_like_unix() { let install = include_str!("../install.sh"); let ps1 = include_str!("../install.ps1"); for setting in [ "hosts.toml", "# default_command = \"tm\"", "[default]", "predict = true", ] { assert!( install.contains(setting), "Unix installer missing host config setting {setting}" ); assert!( ps1.contains(setting), "Windows installer missing host config setting {setting}" ); } assert!(install.contains("host_udp_line=\"# dosh_host = \\\"server.example.com\\\"\"")); assert!( ps1.contains("$hostUdpLine = if ($DoshHost)") && ps1.contains("# dosh_host = `\"server.example.com`\""), "Windows installer should not bake an SSH target into dosh_host" ); assert!( ps1.contains("Write-Utf8NoBom $hostsConfig $hostsToml"), "Windows installer should write TOML as UTF-8 without BOM" ); assert!( !ps1.contains("Set-Content -NoNewline -Encoding utf8 $hostsConfig"), "PowerShell Set-Content -Encoding utf8 can write a BOM on older hosts" ); } #[test] fn package_check_verifies_only_artifacts_it_builds() { let makefile = include_str!("../Makefile"); let verify = include_str!("../scripts/verify-release-artifacts.sh"); assert!( verify.contains("if [ \"$#\" -gt 0 ]; then"), "release verifier must accept an explicit artifact list" ); let package_check = makefile .split("package-check:") .nth(1) .and_then(|tail| tail.split("\ninstall:").next()) .expect("package-check target"); assert!(package_check.contains("package-release-linux")); assert!(package_check.contains("package-release-windows")); assert!(package_check.contains("package-release-windows-arm64")); assert!(package_check.contains("aarch64-w64-mingw32-clang")); assert!(package_check.contains("aarch64-pc-windows-gnullvm")); assert!(package_check.contains("dosh-linux-x86_64.tar.gz")); assert!(package_check.contains("dosh-windows-x86_64.zip")); assert!( !package_check.contains("dosh-macos-aarch64.tar.gz"), "Linux package-check does not build macOS artifacts, so it must not depend on one" ); } #[test] fn complete_release_verifier_requires_all_desktop_architectures() { let verify = include_str!("../scripts/verify-release-artifacts.sh"); assert!(verify.contains("dosh-linux-x86_64.tar.gz")); assert!(verify.contains("dosh-macos-aarch64.tar.gz")); assert!(verify.contains("dosh-macos-x86_64.tar.gz")); assert!(verify.contains("dosh-windows-x86_64.zip")); assert!(verify.contains("dosh-windows-aarch64.zip")); } #[test] fn package_release_workflow_builds_both_windows_architectures() { let ci = include_str!("../.github/workflows/ci.yml"); assert!(ci.contains("name: windows-x86_64")); assert!(ci.contains("name: windows-aarch64")); assert!(ci.contains("DOSH_PACKAGE_ARCH: ${{ matrix.package_arch }}")); assert!(ci.contains("aarch64-pc-windows-msvc")); } #[test] fn release_verifier_checks_checksums_and_expected_binaries() { let verify = include_str!("../scripts/verify-release-artifacts.sh"); assert!(verify.contains("verify_checksum()")); assert!(verify.contains("release checksum mismatch")); for member in [ "dosh/bin/dosh", "dosh/bin/dosh-client", "dosh/bin/dosh-server", "dosh/bin/dosh-auth", "dosh/bin/dosh-bench", "dosh/bin/dosh.exe", "dosh/bin/dosh-client.exe", "dosh/bin/dosh-bench.exe", ] { assert!( verify.contains(member), "release verifier must require archive member {member}" ); } assert!(verify.contains("artifact missing expected member")); } #[test] fn package_release_stamps_git_build_info() { let package = include_str!("../scripts/package-release.sh"); let build = include_str!("../build.rs"); for name in [ "DOSH_BUILD_GIT_HASH", "DOSH_BUILD_COMMIT_DATE", "DOSH_BUILD_GIT_DIRTY", ] { assert!(package.contains(name), "package script must export {name}"); assert!(build.contains(name), "build.rs must consume {name}"); } assert!(package.contains("git rev-parse --short=12 HEAD")); assert!(package.contains("git show -s --format=%cs HEAD")); assert!(build.contains("cargo:rerun-if-env-changed=DOSH_BUILD_GIT_HASH")); } #[test] fn package_release_maps_windows_cross_arch_targets() { let package = include_str!("../scripts/package-release.sh"); assert!(package.contains("host_arch=\"$(normalize_arch)\"")); assert!(package.contains("elif [ \"$arch\" != \"$host_arch\" ]; then")); assert!(package.contains("x86_64-pc-windows-msvc")); assert!(package.contains("aarch64-pc-windows-msvc")); } #[test] fn package_release_windows_zip_tries_classic_and_core_powershell() { let package = include_str!("../scripts/package-release.sh"); assert!(package.contains("powershell_compress_archive()")); assert!(package.contains("powershell_word()")); assert!(package.contains("sed \"s/'/''/g\"")); assert!(package.contains("for ps in powershell.exe powershell pwsh.exe pwsh; do")); assert!(package.contains("\"$ps\" -NoProfile -Command")); assert!(package.contains("Compress-Archive -Force -LiteralPath $(powershell_word \"$stage\")")); assert!(package.contains("-DestinationPath $(powershell_word \"$out_dir/$artifact\")")); assert!(package.contains("windows packaging requires PowerShell or zip")); } #[test] fn systemd_service_does_not_sandbox_remote_shells() { let service = include_str!("../packaging/systemd/dosh-server.service"); let install = include_str!("../install.sh"); for raw in [service, install] { assert!(raw.contains("KillMode=process")); assert!( raw.contains("Restart=always"), "dosh-server should come back after accidental SIGTERM while preserving child shells" ); for directive in [ "NoNewPrivileges=", "PrivateTmp=", "ProtectSystem=", "ProtectHome=", "RestrictAddressFamilies=", "RestrictRealtime=", "LockPersonality=", "MemoryDenyWriteExecute=", ] { assert!( !raw.contains(directive), "dosh sessions are user shells; systemd sandbox directive {directive} changes terminal/app behavior" ); } assert!( !raw.contains("ReadWritePaths="), "remote shells must not be restricted to dosh config/data paths" ); } }