mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-04 07:34:25 +02:00
Addresses all four blockers from the consolidated review on #6168. 1. Both installers probed and printed the wrong port. scripts/install.sh:402 and scripts/install.ps1 start Compose inheriting the current environment, and Compose lets that environment outrank .env — but the installers then derived the probe port and the summary URL from .env alone. Measured on this branch before the fix, every port variable diverged: ambient PORT=9100 -> compose 9100, installer 8080 ambient BACKEND_PORT=9200 -> compose 9200, installer 8080 ambient API_PORT=9300 -> compose 9300, installer 8080 ambient SERVER_PORT=9400 -> compose 9400, installer 8080 ambient FRONTEND_PORT=3100 -> compose 3100, installer 3000 Both now read the ports back once with `docker compose port` after `up -d` and reuse that single result for the health check and the summary. If the query fails they fail loudly instead of claiming success. The .env-derived helpers are deleted rather than left beside the new path. 2. The Make preflight is removed entirely, as the review recommended. It modelled `origin=environment` and `origin=file` but not `command line`, so `make selfhost BACKEND_PORT=9000` over a .env with API_PORT=7000 announced 7000 while Compose published 9000. That was its fourth wrong report in four rounds, because it was a second parser of precedence rules — the very thing this PR removes elsewhere. `docker compose port` is the runtime truth, so the script, the Makefile captures and the macro are gone. 3. Tests now cover the installer paths that were unguarded. scripts/install.test.sh gains a `--with-server` matrix (defaults, .env PORT, all four backend aliases, FRONTEND_PORT, ambient overriding .env for all five, and explicit-empty fallback) plus a case proving an unresolvable port fails loudly. A new scripts/install.ps1.test.ps1 drives the same matrix through Start-LocalInstall. Every case asserts Compose's published port == the probed URL == the printed URL. Ambient variables are explicitly cleared per case so a runner's own PORT cannot leak. The Makefile matrix gains the command-line origin cases. CI runs the PowerShell suite on windows-latest in the always-on installer job, and the frontend filter now includes both installers. 4. Docs state the real contract: the alias order is shared, but which *source* wins is per entry point — Compose prefers the environment, make prefers the included env file, and a make command-line assignment outranks both. The removed preflight's promise is gone from SELF_HOSTING_AI.md, and the compose header no longer claims the installer derives its own health-check port. Verified failing without the fixes: the Bash matrix reports `[ambient PORT beats .env] compose published 9500 / probed 9100 / printed 9100` against the old installer, and the PowerShell matrix reports `printed backend=8080` against an injected summary divergence. Co-authored-by: multica-agent <github@multica.ai>
519 lines
15 KiB
Bash
519 lines
15 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
|
# Build a self-contained sandbox with stub `curl` and a tarball that the
|
|
# release-binary fallback path will download. Each test supplies its own
|
|
# `brew` stub to model a specific Homebrew failure mode.
|
|
_setup_sandbox() {
|
|
local tmp="$1"
|
|
local stub_bin="$tmp/stub-bin"
|
|
local install_bin="$tmp/install-bin"
|
|
local payload_dir="$tmp/payload"
|
|
mkdir -p "$stub_bin" "$install_bin" "$payload_dir"
|
|
|
|
cat >"$payload_dir/multica" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
echo "multica v0.3.2 (commit: test)"
|
|
STUB
|
|
chmod +x "$payload_dir/multica"
|
|
tar -czf "$tmp/multica.tar.gz" -C "$payload_dir" multica
|
|
|
|
cat >"$stub_bin/curl" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
if [[ "$*" == *"-sI"* ]]; then
|
|
printf 'HTTP/2 302\r\nlocation: https://github.com/multica-ai/multica/releases/tag/v0.3.2\r\n'
|
|
exit 0
|
|
fi
|
|
|
|
out=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-o)
|
|
out="$2"
|
|
shift 2
|
|
;;
|
|
*)
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$out" ]]; then
|
|
echo "stub curl expected -o" >&2
|
|
exit 2
|
|
fi
|
|
cp "$MULTICA_TEST_ARCHIVE" "$out"
|
|
STUB
|
|
chmod +x "$stub_bin/curl"
|
|
}
|
|
|
|
_run_installer() {
|
|
local tmp="$1"
|
|
local out="$tmp/install.out"
|
|
local err="$tmp/install.err"
|
|
if ! PATH="$tmp/stub-bin:$tmp/install-bin:/usr/bin:/bin" \
|
|
MULTICA_BIN_DIR="$tmp/install-bin" \
|
|
MULTICA_TEST_ARCHIVE="$tmp/multica.tar.gz" \
|
|
bash "$ROOT_DIR/scripts/install.sh" >"$out" 2>"$err"; then
|
|
echo "install.sh exited non-zero" >&2
|
|
cat "$out" >&2 || true
|
|
cat "$err" >&2 || true
|
|
return 1
|
|
fi
|
|
|
|
if [[ ! -x "$tmp/install-bin/multica" ]]; then
|
|
echo "expected fallback binary at $tmp/install-bin/multica" >&2
|
|
cat "$out" >&2 || true
|
|
cat "$err" >&2 || true
|
|
return 1
|
|
fi
|
|
|
|
if ! grep -q "Homebrew output (last 80 lines):" "$err"; then
|
|
echo "expected diagnostic tail in stderr" >&2
|
|
cat "$err" >&2 || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
test_brew_install_failure_falls_back_to_release_binary() {
|
|
local tmp
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp"' RETURN
|
|
|
|
_setup_sandbox "$tmp"
|
|
cat >"$tmp/stub-bin/brew" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
case "${1:-}" in
|
|
tap)
|
|
exit 0
|
|
;;
|
|
install)
|
|
echo "simulated brew install failure" >&2
|
|
exit 42
|
|
;;
|
|
list)
|
|
exit 1
|
|
;;
|
|
*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
STUB
|
|
chmod +x "$tmp/stub-bin/brew"
|
|
|
|
_run_installer "$tmp"
|
|
}
|
|
|
|
test_brew_tap_failure_falls_back_to_release_binary() {
|
|
local tmp
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp"' RETURN
|
|
|
|
_setup_sandbox "$tmp"
|
|
cat >"$tmp/stub-bin/brew" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
case "${1:-}" in
|
|
tap)
|
|
echo "simulated brew tap failure" >&2
|
|
exit 17
|
|
;;
|
|
*)
|
|
echo "brew $* should not be reached after tap failure" >&2
|
|
exit 99
|
|
;;
|
|
esac
|
|
STUB
|
|
chmod +x "$tmp/stub-bin/brew"
|
|
|
|
_run_installer "$tmp"
|
|
}
|
|
|
|
test_remote_ssh_install_prints_token_login_hint() {
|
|
local tmp
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp"' RETURN
|
|
|
|
_setup_sandbox "$tmp"
|
|
cat >"$tmp/stub-bin/brew" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
case "${1:-}" in
|
|
tap)
|
|
exit 0
|
|
;;
|
|
install)
|
|
echo "simulated brew install failure" >&2
|
|
exit 42
|
|
;;
|
|
list)
|
|
exit 1
|
|
;;
|
|
*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
STUB
|
|
chmod +x "$tmp/stub-bin/brew"
|
|
|
|
(
|
|
export SSH_CONNECTION="192.0.2.10 54321 198.51.100.20 22"
|
|
_run_installer "$tmp"
|
|
)
|
|
|
|
if ! grep -q "Looks like a remote/SSH session" "$tmp/install.out"; then
|
|
echo "expected remote/SSH token-login hint in installer output" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
if ! grep -q "https://multica.ai/settings?tab=tokens" "$tmp/install.out"; then
|
|
echo "expected direct API Tokens settings URL in installer output" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
if ! grep -q "Settings > API Tokens" "$tmp/install.out"; then
|
|
echo "expected API Tokens tab name in installer output" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
if ! grep -q "multica login --token <YOUR_TOKEN>" "$tmp/install.out"; then
|
|
echo "expected token login command in installer output" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
if grep -q "multica config set server_url" "$tmp/install.out"; then
|
|
echo "did not expect default cloud server config command in installer output" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
if grep -q "multica config set app_url" "$tmp/install.out"; then
|
|
echo "did not expect default cloud app config command in installer output" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
test_local_install_does_not_print_token_login_hint() {
|
|
local tmp
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp"' RETURN
|
|
|
|
_setup_sandbox "$tmp"
|
|
cat >"$tmp/stub-bin/brew" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
case "${1:-}" in
|
|
tap)
|
|
exit 0
|
|
;;
|
|
install)
|
|
echo "simulated brew install failure" >&2
|
|
exit 42
|
|
;;
|
|
list)
|
|
exit 1
|
|
;;
|
|
*)
|
|
exit 0
|
|
;;
|
|
esac
|
|
STUB
|
|
chmod +x "$tmp/stub-bin/brew"
|
|
|
|
(
|
|
unset SSH_CONNECTION SSH_CLIENT SSH_TTY
|
|
_run_installer "$tmp"
|
|
)
|
|
|
|
if grep -q "Looks like a remote/SSH session" "$tmp/install.out"; then
|
|
echo "did not expect remote/SSH token-login hint in local installer output" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
if grep -q "multica login --token <YOUR_TOKEN>" "$tmp/install.out"; then
|
|
echo "did not expect token login command in local installer output" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# --with-server: the probed port and the printed port must both be the port
|
|
# Docker Compose reported (#6145)
|
|
#
|
|
# The installer used to derive the port from .env with its own copy of the alias
|
|
# chain. Compose gives the *calling environment* precedence over .env, so any
|
|
# ambient PORT / BACKEND_PORT / API_PORT / SERVER_PORT / FRONTEND_PORT moved the
|
|
# published port while the installer kept probing and printing the file value.
|
|
#
|
|
# Here the docker stub plays Compose: it answers `port` from the same resolution
|
|
# Compose performs, environment first, then .env. The installer must take that
|
|
# answer as given for both the health check and the summary — which is exactly
|
|
# what a .env-only derivation cannot do, because the two disagree in every case
|
|
# below. That real Compose resolves this way is proven separately, against real
|
|
# `docker compose config`, in scripts/selfhost-config.test.sh; that test needs a
|
|
# Docker CLI, which this job deliberately does not require.
|
|
# ---------------------------------------------------------------------------
|
|
_setup_server_sandbox() {
|
|
local tmp="$1"
|
|
local stub_bin="$tmp/stub-bin"
|
|
local server_dir="$tmp/server"
|
|
mkdir -p "$stub_bin" "$server_dir/.git"
|
|
|
|
# Minimal self-host assets: only the port mapping matters here.
|
|
cat >"$server_dir/.env.example" <<'ENVFILE'
|
|
PORT=8080
|
|
# BACKEND_PORT=8080
|
|
# API_PORT=8080
|
|
# SERVER_PORT=8080
|
|
FRONTEND_PORT=3000
|
|
JWT_SECRET=change-me-in-production
|
|
POSTGRES_PASSWORD=multica
|
|
DATABASE_URL=postgres://multica:multica@localhost:5432/multica?sslmode=disable
|
|
ENVFILE
|
|
touch "$server_dir/docker-compose.selfhost.yml"
|
|
|
|
# Compose stand-in. Resolves the published host port the way Compose does:
|
|
# the process environment wins over .env, then the alias chain decides.
|
|
cat >"$stub_bin/docker" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
_env_file_value() {
|
|
local key="$1" line
|
|
line="$(grep -E "^${key}=" .env 2>/dev/null | tail -n 1 || true)"
|
|
[ -n "$line" ] || return 1
|
|
printf '%s' "${line#*=}"
|
|
}
|
|
|
|
# Environment first (Compose interpolation), then the env file.
|
|
_resolve() {
|
|
local key="$1" from_env
|
|
eval "from_env=\${$key-__unset__}"
|
|
if [ "$from_env" != "__unset__" ]; then
|
|
printf '%s' "$from_env"
|
|
return 0
|
|
fi
|
|
_env_file_value "$key"
|
|
}
|
|
|
|
_published_backend_port() {
|
|
local value
|
|
for key in BACKEND_PORT API_PORT SERVER_PORT PORT; do
|
|
if value="$(_resolve "$key")" && [ -n "$value" ]; then
|
|
printf '%s' "$value"
|
|
return
|
|
fi
|
|
done
|
|
printf '8080'
|
|
}
|
|
|
|
_published_frontend_port() {
|
|
local value
|
|
if value="$(_resolve FRONTEND_PORT)" && [ -n "$value" ]; then
|
|
printf '%s' "$value"
|
|
return
|
|
fi
|
|
printf '3000'
|
|
}
|
|
|
|
case "${1:-}" in
|
|
info) exit 0 ;;
|
|
compose)
|
|
shift
|
|
subcommand=""
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
pull | up | port | version | ps | logs | down) subcommand="$arg"; break ;;
|
|
esac
|
|
done
|
|
case "$subcommand" in
|
|
port)
|
|
service=""
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
backend | frontend) service="$arg"; break ;;
|
|
esac
|
|
done
|
|
case "$service" in
|
|
backend) printf '127.0.0.1:%s\n' "$(_published_backend_port)" ;;
|
|
frontend) printf '127.0.0.1:%s\n' "$(_published_frontend_port)" ;;
|
|
*) exit 1 ;;
|
|
esac
|
|
;;
|
|
version) echo "2.30.0" ;;
|
|
esac
|
|
exit 0
|
|
;;
|
|
esac
|
|
exit 0
|
|
STUB
|
|
chmod +x "$stub_bin/docker"
|
|
|
|
# git: the installer takes the "existing installation" path, so only the
|
|
# fetch/checkout calls run and they are all tolerant of failure.
|
|
printf '#!/usr/bin/env bash\nexit 0\n' >"$stub_bin/git"
|
|
chmod +x "$stub_bin/git"
|
|
|
|
# brew: pretend the CLI installs cleanly so the run reaches the summary.
|
|
printf '#!/usr/bin/env bash\nexit 0\n' >"$stub_bin/brew"
|
|
chmod +x "$stub_bin/brew"
|
|
|
|
printf '#!/usr/bin/env bash\necho "multica v0.3.2 (commit: test)"\n' >"$stub_bin/multica"
|
|
chmod +x "$stub_bin/multica"
|
|
|
|
# curl records every probed URL so the health-check port can be asserted.
|
|
cat >"$stub_bin/curl" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
http*) printf '%s\n' "$arg" >>"$MULTICA_TEST_CURL_LOG" ;;
|
|
esac
|
|
done
|
|
exit 0
|
|
STUB
|
|
chmod +x "$stub_bin/curl"
|
|
|
|
printf '#!/usr/bin/env bash\nhead -c 32 /dev/zero | od -An -tx1 | tr -d " \\n"\n' >"$stub_bin/openssl"
|
|
chmod +x "$stub_bin/openssl"
|
|
}
|
|
|
|
# Runs `install.sh --with-server` with the sandbox stubs. Remaining arguments are
|
|
# ambient environment assignments, so each case controls the environment
|
|
# explicitly instead of inheriting a CI runner's PORT.
|
|
_run_with_server() {
|
|
local tmp="$1"
|
|
shift
|
|
|
|
: >"$tmp/curl.log"
|
|
if ! env -i \
|
|
PATH="$tmp/stub-bin:/usr/bin:/bin" \
|
|
HOME="$tmp" \
|
|
MULTICA_INSTALL_DIR="$tmp/server" \
|
|
MULTICA_SELFHOST_REF="main" \
|
|
MULTICA_TEST_CURL_LOG="$tmp/curl.log" \
|
|
"$@" \
|
|
bash "$ROOT_DIR/scripts/install.sh" --with-server \
|
|
>"$tmp/install.out" 2>"$tmp/install.err"; then
|
|
echo "install.sh --with-server exited non-zero" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
cat "$tmp/install.err" >&2 || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Asserts the probed port and the printed ports all match the stub's answer.
|
|
_require_server_ports() {
|
|
local tmp="$1" label="$2" expected_backend="$3" expected_frontend="$4"
|
|
local probed printed_backend printed_frontend
|
|
|
|
probed="$(sed -n '1s#.*localhost:\([0-9]*\)/health#\1#p' "$tmp/curl.log")"
|
|
printed_backend="$(sed -n 's#.*Backend:[^0-9]*http://localhost:\([0-9]*\).*#\1#p' "$tmp/install.out" | head -n 1)"
|
|
printed_frontend="$(sed -n 's#.*Frontend:[^0-9]*http://localhost:\([0-9]*\).*#\1#p' "$tmp/install.out" | head -n 1)"
|
|
|
|
if [ "$probed" != "$expected_backend" ] ||
|
|
[ "$printed_backend" != "$expected_backend" ] ||
|
|
[ "$printed_frontend" != "$expected_frontend" ]; then
|
|
echo "[$label] installer ports disagree with the port Compose published" >&2
|
|
echo " compose published: backend=$expected_backend frontend=$expected_frontend" >&2
|
|
echo " health check probed: ${probed:-<none>}" >&2
|
|
echo " printed: backend=${printed_backend:-<none>} frontend=${printed_frontend:-<none>}" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
test_with_server_uses_compose_published_ports() {
|
|
local tmp
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp"' RETURN
|
|
|
|
# label | .env mutation (sed) | ambient env | expected backend | expected frontend
|
|
local cases='defaults|||8080|3000
|
|
env-file PORT|s/^PORT=8080/PORT=9100/||9100|3000
|
|
env-file BACKEND_PORT|s/^# BACKEND_PORT=8080/BACKEND_PORT=9200/||9200|3000
|
|
env-file API_PORT|s/^# API_PORT=8080/API_PORT=9300/||9300|3000
|
|
env-file SERVER_PORT|s/^# SERVER_PORT=8080/SERVER_PORT=9400/||9400|3000
|
|
env-file FRONTEND_PORT|s/^FRONTEND_PORT=3000/FRONTEND_PORT=3100/||8080|3100
|
|
ambient PORT beats .env|s/^PORT=8080/PORT=9100/|PORT=9500|9500|3000
|
|
ambient BACKEND_PORT beats .env|s/^PORT=8080/PORT=9100/|BACKEND_PORT=9600|9600|3000
|
|
ambient API_PORT beats .env|s/^PORT=8080/PORT=9100/|API_PORT=9700|9700|3000
|
|
ambient SERVER_PORT beats .env|s/^PORT=8080/PORT=9100/|SERVER_PORT=9800|9800|3000
|
|
ambient FRONTEND_PORT beats .env|s/^FRONTEND_PORT=3000/FRONTEND_PORT=3100/|FRONTEND_PORT=3200|8080|3200
|
|
empty ambient BACKEND_PORT falls through|s/^PORT=8080/PORT=9100/|BACKEND_PORT=|9100|3000
|
|
empty env-file BACKEND_PORT falls through|s/^PORT=8080/PORT=9100/;s/^# BACKEND_PORT=8080/BACKEND_PORT=/||9100|3000'
|
|
|
|
local label mutation ambient expect_backend expect_frontend
|
|
while IFS='|' read -r label mutation ambient expect_backend expect_frontend; do
|
|
[ -n "$label" ] || continue
|
|
|
|
rm -rf "$tmp/server" "$tmp/stub-bin"
|
|
_setup_server_sandbox "$tmp"
|
|
cp "$tmp/server/.env.example" "$tmp/server/.env"
|
|
if [ -n "$mutation" ]; then
|
|
sed "$mutation" "$tmp/server/.env" >"$tmp/server/.env.new"
|
|
mv "$tmp/server/.env.new" "$tmp/server/.env"
|
|
fi
|
|
|
|
if [ -n "$ambient" ]; then
|
|
_run_with_server "$tmp" "$ambient" || return 1
|
|
else
|
|
_run_with_server "$tmp" || return 1
|
|
fi
|
|
_require_server_ports "$tmp" "$label" "$expect_backend" "$expect_frontend" || return 1
|
|
done <<<"$cases"
|
|
}
|
|
|
|
test_with_server_fails_when_compose_port_is_unavailable() {
|
|
local tmp
|
|
tmp="$(mktemp -d)"
|
|
trap 'rm -rf "$tmp"' RETURN
|
|
|
|
_setup_server_sandbox "$tmp"
|
|
cp "$tmp/server/.env.example" "$tmp/server/.env"
|
|
|
|
# Compose cannot report a port, e.g. the container never came up.
|
|
cat >"$tmp/stub-bin/docker" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
case "${1:-}" in
|
|
info) exit 0 ;;
|
|
compose)
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
port) exit 1 ;;
|
|
version) echo "2.30.0"; exit 0 ;;
|
|
esac
|
|
done
|
|
exit 0
|
|
;;
|
|
esac
|
|
exit 0
|
|
STUB
|
|
chmod +x "$tmp/stub-bin/docker"
|
|
|
|
if _run_with_server "$tmp" >/dev/null 2>&1; then
|
|
echo "installer must not report success when Compose cannot report the port" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
if ! grep -q "could not read the backend host port" "$tmp/install.err"; then
|
|
echo "expected an explicit failure about the backend host port" >&2
|
|
cat "$tmp/install.err" >&2 || true
|
|
return 1
|
|
fi
|
|
if grep -q "server is running and CLI is ready" "$tmp/install.out"; then
|
|
echo "installer claimed success despite an unresolved port" >&2
|
|
cat "$tmp/install.out" >&2 || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
test_brew_install_failure_falls_back_to_release_binary
|
|
test_brew_tap_failure_falls_back_to_release_binary
|
|
test_remote_ssh_install_prints_token_login_hint
|
|
test_local_install_does_not_print_token_login_hint
|
|
test_with_server_uses_compose_published_ports
|
|
test_with_server_fails_when_compose_port_is_unavailable
|
|
echo "install.sh tests passed"
|