Files
coredesktop/tests/network.bats
T
david 448c8b43f1
shellcheck / shellcheck (push) Failing after 17s
fix agent networking
2026-11-02 08:57:45 -07:00

219 lines
8.9 KiB
Bash

#!/usr/bin/env bats
load test_helper
# The mDNS helper and the configure items built on it (ai-servers, storage),
# with fake avahi-browse, curl and smbclient. Nothing here touches the network.
setup() {
common_setup
export COREDESKTOP="$REPO"
AVAHI="$BATS_TEST_TMPDIR/avahi"
# What avahi-browse --parsable prints, including this machine (basement),
# an IPv6 copy of a service, and an unresolved (+) line
cat > "$AVAHI" << 'EOF'
+;enp5s0;IPv4;llm;_ssh._tcp;local
=;enp5s0;IPv4;llm;_ssh._tcp;local;llm.local;192.168.0.5;22;
=;enp5s0;IPv6;llm;_ssh._tcp;local;llm.local;fe80::1;22;
=;enp5s0;IPv4;llm;_sftp-ssh._tcp;local;llm.local;192.168.0.5;22;
=;enp5s0;IPv4;basement;_ssh._tcp;local;basement.local;192.168.0.106;22;
=;enp5s0;IPv4;PI-STAR;_smb._tcp;local;pi-star.local;192.168.0.102;445;
=;enp5s0;IPv4;HP\032LaserJet\032MFP\032M234dw\032\0403BE90A\041\032\064\032lab;_ipp._tcp;local;lab.local;192.168.0.22;631;"rp=printers/HP"
EOF
export AVAHI
stub avahi-browse 'cat "$AVAHI"'
stub hostnamectl 'echo basement'
confirm_yes() { return 0; }
confirm() { return 0; }
unsandboxed() { echo "unsandboxed $*" >> "$STUB_LOG"; }
export -f confirm_yes confirm unsandboxed
}
# --- lib/mdns.sh --------------------------------------------------------------------
@test "mdns_unescape decodes Avahi's decimal escapes" {
source "$REPO/configure.d/lib/mdns.sh"
[ "$(mdns_unescape 'HP\032LaserJet\032\0403BE90A\041\032\064\032lab')" = "HP LaserJet (3BE90A) @ lab" ]
[ "$(mdns_unescape 'a\.b')" = "a.b" ]
[ "$(mdns_unescape plain)" = "plain" ]
}
@test "mdns_services lists each resolved IPv4 service once, with its name decoded" {
source "$REPO/configure.d/lib/mdns.sh"
run mdns_services
[ "$status" -eq 0 ]
[ "${#lines[@]}" -eq 5 ]
[[ "$output" == *"HP LaserJet MFP M234dw (3BE90A) @ lab|_ipp._tcp|lab.local|192.168.0.22|631|\"rp=printers/HP\""* ]]
[[ "$output" != *"fe80"* ]]
}
@test "mdns_hosts lists the other machines, not this one" {
source "$REPO/configure.d/lib/mdns.sh"
run mdns_hosts
[ "$status" -eq 0 ]
[ "$output" = "$(printf 'lab.local\nllm.local\npi-star.local')" ]
}
# --- configure ai-servers --------------------------------------------------------------
setup_ai() {
source "$REPO/configure.d/50-ai-servers.sh"
# llm.local:11434 is Ollama with two models; nothing else answers
stub curl 'case "$*" in
*http://llm.local:11434/v1/models*) echo "{\"data\": [{\"id\": \"qwen3-coder:30b\"}, {\"id\": \"gemma4:12b\"}]}" ;;
*http://llm.local:11434/api/version*) echo "{\"version\": \"0.14.0\"}" ;;
*) exit 7 ;;
esac'
}
@test "find_servers asks each machine on the LLM ports and lists the ones that answer" {
setup_ai
run find_servers
[ "$status" -eq 0 ]
[ "$output" = "llm.local:11434|2" ]
[ "$(server_kind llm.local:11434)" = Ollama ]
}
@test "sandbox_allow adds a server once, keeping what's there" {
setup_ai
mkdir -p "$(dirname "$SANDBOX_CONF")"
echo '{"network": {"allowedDomains": ["docs.example.com"]}, "filesystem": {"allowWrite": ["~/notes"]}}' > "$SANDBOX_CONF"
sandbox_allow llm.local:11434
sandbox_allow llm.local:11434
[ "$(jq -c '.network.allowedDomains' "$SANDBOX_CONF")" = '["docs.example.com","llm.local:11434"]' ]
[ "$(jq -c '.filesystem.allowWrite' "$SANDBOX_CONF")" = '["~/notes"]' ]
sandbox_disallow llm.local:11434
[ "$(jq -c '.network.allowedDomains' "$SANDBOX_CONF")" = '["docs.example.com"]' ]
}
@test "sandbox_allow leaves a file that isn't valid JSON alone" {
setup_ai
mkdir -p "$(dirname "$SANDBOX_CONF")"
echo 'not json' > "$SANDBOX_CONF"
run sandbox_allow llm.local:11434
[ "$status" -ne 0 ]
[ "$(cat "$SANDBOX_CONF")" = "not json" ]
}
@test "claude_allow starts from the default domains when Claude has no list, and keeps their order" {
setup_ai
mkdir -p "$HOME/.claude"
echo '{"model": "opus"}' > "$CLAUDE_SETTINGS"
claude_allow llm.local:11434
[ "$(jq -c '.sandbox.network.allowedDomains[0:2]' "$CLAUDE_SETTINGS")" = '["anthropic.com","*.anthropic.com"]' ]
[ "$(jq -r '.sandbox.network.allowedDomains[-1]' "$CLAUDE_SETTINGS")" = llm.local:11434 ]
[ "$(jq -r '.model' "$CLAUDE_SETTINGS")" = opus ]
claude_disallow llm.local:11434
run jq -e '.sandbox.network.allowedDomains | index("llm.local:11434")' "$CLAUDE_SETTINGS"
[ "$status" -ne 0 ]
}
@test "claude_allow does nothing without Claude's settings" {
setup_ai
claude_allow llm.local:11434
[ ! -e "$CLAUDE_SETTINGS" ]
}
@test "opencode_add adds the server's models as a provider, and opencode_remove takes it out" {
setup_ai
opencode_add llm.local:11434 Ollama gemma4:12b > /dev/null
[ "$(opencode_provider llm.local:11434)" = llm-11434 ]
[ "$(jq -r '.provider["llm-11434"].options.baseURL' "$OPENCODE_CONF")" = "http://llm.local:11434/v1" ]
[ "$(jq -r '.provider["llm-11434"].name' "$OPENCODE_CONF")" = "Ollama on llm.local" ]
[ "$(jq -c '.provider["llm-11434"].models | keys' "$OPENCODE_CONF")" = '["gemma4:12b","qwen3-coder:30b"]' ]
[ "$(jq -r '.model' "$OPENCODE_CONF")" = "llm-11434/gemma4:12b" ]
opencode_remove llm.local:11434
[ "$(jq -c '.provider' "$OPENCODE_CONF")" = '{}' ]
[ "$(jq -r '.model // "none"' "$OPENCODE_CONF")" = none ]
}
@test "hermes_use points Hermes at the server's OpenAI API" {
setup_ai
hermes_use llm.local:11434 gemma4:12b
called unsandboxed hermes config set model.provider custom
called unsandboxed hermes config set model.base_url http://llm.local:11434/v1
called unsandboxed hermes config set model.default gemma4:12b
}
@test "add_server allows the server and remembers it; remove_server undoes it" {
setup_ai
mkdir -p "$(dirname "$AI_SERVERS_CONF")"
touch "$AI_SERVERS_CONF"
stub systemctl 'exit 3'
run add_server llm.local:11434 <<< "2"
[ "$status" -eq 0 ]
[ "$(cat "$AI_SERVERS_CONF")" = llm.local:11434 ]
jq -e '.network.allowedDomains | index("llm.local:11434")' "$SANDBOX_CONF"
run remove_server llm.local:11434
[ "$status" -eq 0 ]
[ ! -s "$AI_SERVERS_CONF" ]
run jq -e '.network.allowedDomains | index("llm.local:11434")' "$SANDBOX_CONF"
[ "$status" -ne 0 ]
}
@test "add_server refuses an address that isn't an LLM server" {
setup_ai
run add_server lab.local:11434 <<< "1"
[ "$status" -eq 1 ]
[[ "$output" == *"doesn't answer like an LLM server"* ]]
[ ! -e "$SANDBOX_CONF" ]
}
# --- configure storage -------------------------------------------------------------------
@test "find_storage lists SMB and SFTP servers, not this machine" {
source "$REPO/configure.d/60-storage.sh"
run find_storage
[ "$status" -eq 0 ]
[ "$output" = "$(printf 'sftp|llm.local|llm\nsmb|pi-star.local|PI-STAR')" ]
}
@test "smb_shares lists disk shares, without admin shares, and passes the password outside the arguments" {
source "$REPO/configure.d/60-storage.sh"
stub smbclient 'echo "PASSWD=$PASSWD" >> "$STUB_LOG"
printf "Disk|files|Shared files\nDisk|C\$|Default share\nIPC|IPC\$|IPC Service\nDisk|My Music|\n"'
run smb_shares nas.local me secret
[ "$output" = "$(printf 'files\nMy Music')" ]
called smbclient -L //nas.local -U me -g
called PASSWD=secret
}
@test "smb_fstab_line mounts as you on first use, with \\040 for spaces" {
source "$REPO/configure.d/60-storage.sh"
run smb_fstab_line "//nas.local/My Files" /mnt/nas-my-files /etc/coredesktop/smb/nas-my-files
[[ "$output" == "//nas.local/My\\040Files /mnt/nas-my-files cifs credentials=/etc/coredesktop/smb/nas-my-files,uid=$(id -u),gid=$(id -g),"* ]]
[[ "$output" == *",x-systemd.automount,"*" 0 0" ]]
run smb_fstab_line //nas.local/pub /mnt/nas-pub ""
[[ "$output" == *" cifs guest,"* ]]
}
@test "mount_point names the folder after the host and share, avoiding ones in use" {
source "$REPO/configure.d/60-storage.sh"
[ "$(mount_point Bats-Test-NAS.local 'My Files')" = /mnt/bats-test-nas-my-files ]
[ "$(mount_point bats-test-host.local '')" = /mnt/bats-test-host ]
mkdir -p "$(dirname "$STORAGE_CONF")"
echo "sftp|me@bats-test-host.local:|/mnt/bats-test-host" > "$STORAGE_CONF"
[ "$(mount_point bats-test-host.local '')" = /mnt/bats-test-host-2 ]
}
@test "sftp_unit runs sftp-mount as a user service" {
source "$REPO/configure.d/60-storage.sh"
run sftp_unit "me@brain.local:/srv/my data" /mnt/brain-my-data
[[ "$output" == *'ExecStart=%h/.local/share/coredesktop/system/sftp-mount "me@brain.local:/srv/my data" "/mnt/brain-my-data"'* ]]
[[ "$output" == *"WantedBy=default.target"* ]]
[ "$(sftp_unit_name /mnt/brain-my-data)" = coredesktop-sftp-brain-my-data.service ]
}
@test "enter_and_add reads smb:// and sftp:// addresses" {
source "$REPO/configure.d/60-storage.sh"
add_smb() { echo "smb [$1] [$2]"; }
add_sftp() { echo "sftp [$1] [$2] [$3]"; }
[ "$(enter_and_add <<< 'smb://nas.local/My Files' | tail -n1)" = "smb [nas.local] [My Files]" ]
[ "$(enter_and_add <<< 'sftp://brain.local' | tail -n1)" = "sftp [brain.local] [] []" ]
[ "$(enter_and_add <<< 'sftp://me@brain.local/srv/data' | tail -n1)" = "sftp [brain.local] [me] [/srv/data]" ]
run enter_and_add <<< 'smb://nas.local'
[ "$status" -eq 1 ]
}