@@ -1,7 +1,8 @@
#!/usr/bin/env bash
# @tag requirements: [REQ-DEP-001, REQ-DEP-002]
# @tag requirements: [REQ-DEP-001, REQ-DEP-002, REQ-DEP-004]
# Tests for packaging/linux/qemu: the systemd units, the libvirt hook (with a fake systemctl), the
# VM wait helper (with fake gdbus and virsh) and the port allocation helper. Nothing is installed and no
# VM wait helper (with fake gdbus and virsh), the port allocation helper and the pairing helper (with a
# fake curl). Nothing is installed and no
# real systemd or libvirt service is started.
#
# Usage: test_sunshine_qemu.sh [test-name...] (default: every test_* function)
@@ -372,13 +373,197 @@
}
# @tag requirements: [REQ-DEP-002]
# a fake curl that records its arguments and answers from ${tmp}/curl.<method><path with / as _>
# e.g. ${tmp}/curl.GET_pin for `GET /api/pin`. Missing files answer with an empty object.
# shellcheck disable=SC2016 # the fake's body is a script; its $... must reach it unexpanded
fake_curl() {
fake_bin curl '
method=""; url=""; body=""
while [[ $# -gt 0 ]]; do
case "$1" in
-X) method="$2"; shift 2 ;;
-d) body="$2"; shift 2 ;;
-u|-H|--cacert|--max-time) echo "$1 $2" >> "'"${tmp}"'/curl.args"; shift 2 ;;
--insecure) echo "insecure" >> "'"${tmp}"'/curl.args"; shift ;;
-*) shift ;;
*) url="$1"; shift ;;
esac
done
echo "${method} ${url} ${body}" >> "'"${tmp}"'/curl.log"
key="${url#*/api}"
file="'"${tmp}"'/curl.${method}${key//\//_}"
if [[ -r "${file}" ]]; then cat "${file}"; else echo "{}"; fi
'
}
run_pair() {
PATH="${tmp}/bin:${PATH}" SUNSHINE_QEMU_CONF_DIR="${tmp}/etc" SUNSHINE_QEMU_API_PASSWORD=secret \
"${pkg}/sunshine-qemu-pair" "$@"
}
# The API sorts its JSON keys, so the fixtures are in that order, not the order the fields are printed in.
pending_json() {
printf '{"pairings":[%s]}' "$1" > "${tmp}/curl.GET_pin"
}
# @tag requirements: [REQ-DEP-004]
test_pair_lists_the_waiting_requests() {
fake_curl
pending_json '{"address":"192.168.1.5","id":"0123456789abcdef0123456789abcdef","name":"Living room"},{"address":"127.0.0.1","id":"fedcba9876543210fedcba9876543210","name":"Laptop"}'
local output
output="$(run_pair --port 47989 list)" || fail "list failed: ${output}" || return 1
grep -q "^0123456789abcdef0123456789abcdef Living room 192.168.1.5$" <<< "${output}" || fail "first request missing from: ${output}"
grep -q "^fedcba9876543210fedcba9876543210 Laptop 127.0.0.1$" <<< "${output}" || fail "second request missing from: ${output}"
}
# @tag requirements: [REQ-DEP-004]
test_pair_says_when_nothing_is_waiting() {
fake_curl
pending_json ''
local output
output="$(run_pair --port 47989 list)" || fail "list failed: ${output}" || return 1
expect_eq "${output}" "no pairing requests are waiting" "empty list"
}
# @tag requirements: [REQ-DEP-004]
test_pair_approves_the_only_waiting_request() {
fake_curl
pending_json '{"address":"127.0.0.1","id":"0123456789abcdef0123456789abcdef","name":"Laptop"}'
printf '{"status":true}' > "${tmp}/curl.POST_pin"
local output
output="$(run_pair --port 47989 accept 1234 2>/dev/null)" || fail "accept failed: ${output}" || return 1
expect_eq "${output}" "paired 'Laptop'" "accept output" || return 1
local post
post="$(grep '^POST ' "${tmp}/curl.log")"
grep -q 'https://127.0.0.1:47990/api/pin' <<< "${post}" || fail "wrong URL: ${post}" || return 1
grep -q '"pairing_id":"0123456789abcdef0123456789abcdef"' <<< "${post}" || fail "wrong pairing id: ${post}" || return 1
grep -q '"pin":"1234"' <<< "${post}" || fail "wrong pin: ${post}" || return 1
grep -q '"name":"Laptop"' <<< "${post}" || fail "wrong name: ${post}"
}
# @tag requirements: [REQ-DEP-004]
test_pair_refuses_to_guess_between_requests() {
fake_curl
pending_json '{"address":"127.0.0.1","id":"0123456789abcdef0123456789abcdef","name":"Laptop"},{"address":"192.168.1.5","id":"fedcba9876543210fedcba9876543210","name":"Phone"}'
local output
if output="$(run_pair --port 47989 accept 1234 2>&1)"; then
fail "accept must not pick one of two requests"
return 1
fi
grep -q "more than one request is waiting" <<< "${output}" || fail "no explanation: ${output}" || return 1
[[ ! -f "${tmp}/curl.log" ]] || ! grep -q '^POST ' "${tmp}/curl.log" || fail "it approved one anyway"
}
# @tag requirements: [REQ-DEP-004]
test_pair_picks_the_named_request() {
fake_curl
pending_json '{"address":"127.0.0.1","id":"0123456789abcdef0123456789abcdef","name":"Laptop"},{"address":"192.168.1.5","id":"fedcba9876543210fedcba9876543210","name":"Phone"}'
printf '{"status":true}' > "${tmp}/curl.POST_pin"
run_pair --port 47989 accept 4321 --name Phone > /dev/null 2>&1 || fail "accept --name failed" || return 1
grep -q '"pairing_id":"fedcba9876543210fedcba9876543210"' "${tmp}/curl.log" || fail "approved the wrong request: $(grep '^POST ' "${tmp}/curl.log")" || return 1
grep -q '"name":"Phone"' "${tmp}/curl.log" || fail "stored the wrong name"
}
# @tag requirements: [REQ-DEP-004]
test_pair_stores_the_label_when_given_one() {
fake_curl
pending_json '{"address":"127.0.0.1","id":"0123456789abcdef0123456789abcdef","name":"Laptop"}'
printf '{"status":true}' > "${tmp}/curl.POST_pin"
run_pair --port 47989 accept 1234 --label "Cole's laptop" > /dev/null 2>&1 || fail "accept --label failed" || return 1
grep -q "\"name\":\"Cole's laptop\"" "${tmp}/curl.log" || fail "label not stored: $(grep '^POST ' "${tmp}/curl.log")"
}
# @tag requirements: [REQ-DEP-004]
test_pair_fails_when_sunshine_rejects_the_pin() {
fake_curl
pending_json '{"address":"127.0.0.1","id":"0123456789abcdef0123456789abcdef","name":"Laptop"}'
printf '{"status":false}' > "${tmp}/curl.POST_pin"
local output
if output="$(run_pair --port 47989 accept 9999 2>&1)"; then
fail "a rejected PIN must fail"
return 1
fi
grep -q "pairing failed" <<< "${output}" || fail "unclear message: ${output}"
}
# @tag requirements: [REQ-DEP-004]
test_pair_reads_the_port_and_certificate_from_a_vm_config() {
fake_curl
mkdir -p "${tmp}/etc"
printf 'port = 48189\ncert = %s/etc/win11.crt\n' "${tmp}" > "${tmp}/etc/win11.conf"
: > "${tmp}/etc/win11.crt"
pending_json ''
run_pair --vm win11 list > /dev/null || fail "list through --vm failed" || return 1
grep -q 'https://127.0.0.1:48190/api/pin' "${tmp}/curl.log" || fail "wrong port: $(cat "${tmp}/curl.log")" || return 1
grep -q -- "--cacert ${tmp}/etc/win11.crt" "${tmp}/curl.args" || fail "didn't verify with the instance's certificate: $(cat "${tmp}/curl.args")"
}
# @tag requirements: [REQ-DEP-004]
test_pair_waits_for_a_request_to_appear() {
fake_curl
pending_json ''
printf '{"status":true}' > "${tmp}/curl.POST_pin"
# the request shows up only on the third poll; the fake's body is a script, so its $(...) stays unexpanded
# shellcheck disable=SC2016
fake_bin curl '
method=""; url=""; body=""
while [[ $# -gt 0 ]]; do
case "$1" in
-X) method="$2"; shift 2 ;;
-d) body="$2"; shift 2 ;;
-u|-H|--cacert|--max-time) shift 2 ;;
-*) shift ;;
*) url="$1"; shift ;;
esac
done
echo "${method} ${url} ${body}" >> "'"${tmp}"'/curl.log"
if [[ "${method}" == POST ]]; then echo "{\"status\":true}"; exit 0; fi
count=$(( $(cat "'"${tmp}"'/polls" 2> /dev/null || echo 0) + 1 ))
echo "${count}" > "'"${tmp}"'/polls"
if (( count < 3 )); then
echo "{\"pairings\":[]}"
else
echo "{\"pairings\":[{\"address\":\"127.0.0.1\",\"id\":\"0123456789abcdef0123456789abcdef\",\"name\":\"Laptop\"}]}"
fi
'
local output
output="$(run_pair --port 47989 accept 1234 --wait 30 2>/dev/null)" || fail "accept --wait failed: ${output}" || return 1
expect_eq "${output}" "paired 'Laptop'" "accept --wait output" || return 1
expect_eq "$(cat "${tmp}/polls")" "3" "number of polls"
}
# @tag requirements: [REQ-DEP-004]
test_pair_lists_and_removes_paired_clients() {
fake_curl
printf '{"named_certs":[{"enabled":true,"name":"Laptop","uuid":"AAAA-1"},{"enabled":false,"name":"Phone","uuid":"BBBB-2"}],"status":true}' > "${tmp}/curl.GET_clients_list"
local output
output="$(run_pair --port 47989 clients)" || fail "clients failed: ${output}" || return 1
grep -q "^AAAA-1 Laptop enabled$" <<< "${output}" || fail "enabled client missing: ${output}" || return 1
grep -q "^BBBB-2 Phone disabled$" <<< "${output}" || fail "disabled client missing: ${output}" || return 1
run_pair --port 47989 unpair AAAA-1 > /dev/null || fail "unpair failed" || return 1
grep -q 'POST https://127.0.0.1:47990/api/clients/unpair {"uuid":"AAAA-1"}' "${tmp}/curl.log" || fail "wrong unpair request: $(cat "${tmp}/curl.log")" || return 1
run_pair --port 47989 unpair --all > /dev/null || fail "unpair --all failed" || return 1
grep -q 'POST https://127.0.0.1:47990/api/clients/unpair-all' "${tmp}/curl.log" || fail "no unpair-all request"
}
# @tag requirements: [REQ-DEP-004]
test_pair_needs_a_password() {
fake_curl
local output
if output="$(PATH="${tmp}/bin:${PATH}" "${pkg}/sunshine-qemu-pair" --port 47989 list < /dev/null 2>&1)"; then
fail "it must not run without a password"
return 1
fi
grep -q "no password" <<< "${output}" || fail "unclear message: ${output}"
}
test_scripts_are_shellcheck_clean() {
[[ -x "${shellcheck_bin}" ]] || {
failure="skip: shellcheck isn't installed"
return 2
}
local output
output="$("${shellcheck_bin}" "${pkg}/libvirt-hook-qemu" "${pkg}/sunshine-qemu-wait-vm" "${pkg}/sunshine-qemu-port" "${BASH_SOURCE[0]}" 2>&1)" || fail "${output}"
output="$("${shellcheck_bin}" "${pkg}/libvirt-hook-qemu" "${pkg}/sunshine-qemu-wait-vm" "${pkg}/sunshine-qemu-port" "${pkg}/sunshine-qemu-pair" "${BASH_SOURCE[0]}" 2>&1)" || fail "${output}"
}
names=("$@")