pi-hole/test/test_any_automated_install.py
Adam Warner df9c60e351
Reduce code duplication in FTL arch detect tests.
Use parametrize to run the same test with different parameters for each arch we need to test.
Also include detection of unusupported in this test.
Create FTL_BRANCH constant to be used in functions (less places to remember to change it)

Signed-off-by: Adam Warner <me@adamwarner.co.uk>
2024-01-08 18:26:59 +00:00

563 lines
18 KiB
Python

import pytest
from textwrap import dedent
import re
from .conftest import (
tick_box,
info_box,
cross_box,
mock_command,
mock_command_run,
mock_command_2,
mock_command_passthrough,
run_script,
)
FTL_BRANCH = "development-v6"
def test_supported_package_manager(host):
"""
confirm installer exits when no supported package manager found
"""
# break supported package managers
host.run("rm -rf /usr/bin/apt-get")
host.run("rm -rf /usr/bin/rpm")
package_manager_detect = host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
"""
)
expected_stdout = cross_box + " No supported package manager found"
assert expected_stdout in package_manager_detect.stdout
# assert package_manager_detect.rc == 1
def test_selinux_not_detected(host):
"""
confirms installer continues when SELinux configuration file does not exist
"""
check_selinux = host.run(
"""
rm -f /etc/selinux/config
source /opt/pihole/basic-install.sh
checkSelinux
"""
)
expected_stdout = info_box + " SELinux not detected"
assert expected_stdout in check_selinux.stdout
assert check_selinux.rc == 0
def get_directories_recursive(host, directory):
if directory is None:
return directory
# returns all non-hidden subdirs of 'directory'
dirs_raw = host.run("find {} -type d -not -path '*/.*'".format(directory))
dirs = list(filter(bool, dirs_raw.stdout.splitlines()))
return dirs
def test_installPihole_fresh_install_readableFiles(host):
"""
confirms all necessary files are readable by pihole user
"""
# dialog returns Cancel for user prompt
mock_command("dialog", {"*": ("", "0")}, host)
# mock git pull
mock_command_passthrough("git", {"pull": ("", "0")}, host)
# mock systemctl to not start FTL
mock_command_2(
"systemctl",
{
"enable pihole-FTL": ("", "0"),
"restart pihole-FTL": ("", "0"),
"start pihole-FTL": ("", "0"),
"*": ('echo "systemctl call with $@"', "0"),
},
host,
)
# try to install man
host.run("command -v apt-get > /dev/null && apt-get install -qq man")
host.run("command -v dnf > /dev/null && dnf install -y man")
host.run("command -v yum > /dev/null && yum install -y man")
# Workaround to get FTLv6 installed until it reaches master branch
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
install = host.run(
"""
export TERM=xterm
export DEBIAN_FRONTEND=noninteractive
umask 0027
runUnattended=true
useUpdateVars=true
source /opt/pihole/basic-install.sh > /dev/null
runUnattended=true
useUpdateVars=true
main
/opt/pihole/pihole-FTL-prestart.sh
"""
)
assert 0 == install.rc
maninstalled = True
if (info_box + " man not installed") in install.stdout:
maninstalled = False
if (info_box + " man pages not installed") in install.stdout:
maninstalled = False
piholeuser = "pihole"
exit_status_success = 0
test_cmd = 'su --shell /bin/bash --command "test -{0} {1}" -p {2}'
# check files in /etc/pihole for read, write and execute permission
check_etc = test_cmd.format("r", "/etc/pihole", piholeuser)
actual_rc = host.run(check_etc).rc
assert exit_status_success == actual_rc
check_etc = test_cmd.format("x", "/etc/pihole", piholeuser)
actual_rc = host.run(check_etc).rc
assert exit_status_success == actual_rc
# readable and writable dhcp.leases
check_leases = test_cmd.format("r", "/etc/pihole/dhcp.leases", piholeuser)
actual_rc = host.run(check_leases).rc
assert exit_status_success == actual_rc
check_leases = test_cmd.format("w", "/etc/pihole/dhcp.leases", piholeuser)
actual_rc = host.run(check_leases).rc
# readable dns-servers.conf
assert exit_status_success == actual_rc
check_servers = test_cmd.format("r", "/etc/pihole/dns-servers.conf", piholeuser)
actual_rc = host.run(check_servers).rc
assert exit_status_success == actual_rc
# readable install.log
check_install = test_cmd.format("r", "/etc/pihole/install.log", piholeuser)
actual_rc = host.run(check_install).rc
assert exit_status_success == actual_rc
# readable versions
check_localversion = test_cmd.format("r", "/etc/pihole/versions", piholeuser)
actual_rc = host.run(check_localversion).rc
assert exit_status_success == actual_rc
# readable logrotate
check_logrotate = test_cmd.format("r", "/etc/pihole/logrotate", piholeuser)
actual_rc = host.run(check_logrotate).rc
assert exit_status_success == actual_rc
# readable macvendor.db
check_macvendor = test_cmd.format("r", "/etc/pihole/macvendor.db", piholeuser)
actual_rc = host.run(check_macvendor).rc
assert exit_status_success == actual_rc
# check readable and executable /etc/init.d/pihole-FTL
check_init = test_cmd.format("x", "/etc/init.d/pihole-FTL", piholeuser)
actual_rc = host.run(check_init).rc
assert exit_status_success == actual_rc
check_init = test_cmd.format("r", "/etc/init.d/pihole-FTL", piholeuser)
actual_rc = host.run(check_init).rc
assert exit_status_success == actual_rc
# check readable and executable manpages
if maninstalled is True:
check_man = test_cmd.format("x", "/usr/local/share/man", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format("r", "/usr/local/share/man", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format("x", "/usr/local/share/man/man8", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format("r", "/usr/local/share/man/man8", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format("x", "/usr/local/share/man/man5", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format("r", "/usr/local/share/man/man5", piholeuser)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
check_man = test_cmd.format(
"r", "/usr/local/share/man/man8/pihole.8", piholeuser
)
actual_rc = host.run(check_man).rc
assert exit_status_success == actual_rc
# check not readable cron file
check_sudo = test_cmd.format("x", "/etc/cron.d/", piholeuser)
actual_rc = host.run(check_sudo).rc
assert exit_status_success == actual_rc
check_sudo = test_cmd.format("r", "/etc/cron.d/", piholeuser)
actual_rc = host.run(check_sudo).rc
assert exit_status_success == actual_rc
check_sudo = test_cmd.format("r", "/etc/cron.d/pihole", piholeuser)
actual_rc = host.run(check_sudo).rc
assert exit_status_success == actual_rc
directories = get_directories_recursive(host, "/etc/.pihole/")
for directory in directories:
check_pihole = test_cmd.format("r", directory, piholeuser)
actual_rc = host.run(check_pihole).rc
check_pihole = test_cmd.format("x", directory, piholeuser)
actual_rc = host.run(check_pihole).rc
findfiles = 'find "{}" -maxdepth 1 -type f -exec echo {{}} \\;;'
filelist = host.run(findfiles.format(directory))
files = list(filter(bool, filelist.stdout.splitlines()))
for file in files:
check_pihole = test_cmd.format("r", file, piholeuser)
actual_rc = host.run(check_pihole).rc
def test_update_package_cache_success_no_errors(host):
"""
confirms package cache was updated without any errors
"""
updateCache = host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
"""
)
expected_stdout = tick_box + " Update local cache of available packages"
assert expected_stdout in updateCache.stdout
assert "error" not in updateCache.stdout.lower()
def test_update_package_cache_failure_no_errors(host):
"""
confirms package cache was not updated
"""
mock_command("apt-get", {"update": ("", "1")}, host)
updateCache = host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
update_package_cache
"""
)
expected_stdout = cross_box + " Update local cache of available packages"
assert expected_stdout in updateCache.stdout
assert "Error: Unable to update package cache." in updateCache.stdout
@pytest.mark.parametrize(
"arch,detected_string,supported",
[
("aarch64", "AArch64 (64 Bit ARM)", True),
("armv6", "ARMv6", True),
("armv7l", "ARMv7 (or newer)", True),
("armv7", "ARMv7 (or newer)", True),
("armv8a", "ARMv7 (or newer)", True),
("x86_64", "x86_64", True),
("riscv64", "riscv64", True),
("mips", "mips", False),
],
)
def test_FTL_detect_no_errors(host, arch, detected_string, supported):
"""
confirms only correct package is downloaded for FTL engine
"""
# mock uname to return passed platform
mock_command("uname", {"-m": (arch, "0")}, host)
# mock readelf to respond with passed CPU architecture
mock_command_2(
"readelf",
{
"-A /bin/sh": ("Tag_CPU_arch: " + arch, "0"),
"-A /usr/bin/sh": ("Tag_CPU_arch: " + arch, "0"),
},
host,
)
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
"""
)
if supported:
expected_stdout = info_box + " FTL Checks..."
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Detected " + detected_string + " architecture"
assert expected_stdout in detectPlatform.stdout
expected_stdout = tick_box + " Downloading and Installing FTL"
assert expected_stdout in detectPlatform.stdout
else:
expected_stdout = (
"Not able to detect architecture (unknown: " + detected_string + ")"
)
assert expected_stdout in detectPlatform.stdout
def test_FTL_development_binary_installed_and_responsive_no_errors(host):
"""
confirms FTL development binary is copied and functional in installed location
"""
host.run('echo "' + FTL_BRANCH + '" > /etc/pihole/ftlbranch')
host.run(
"""
source /opt/pihole/basic-install.sh
create_pihole_user
funcOutput=$(get_binary_name)
binary="pihole-FTL${funcOutput##*pihole-FTL}"
theRest="${funcOutput%pihole-FTL*}"
FTLdetect "${binary}" "${theRest}"
"""
)
version_check = host.run(
"""
VERSION=$(pihole-FTL version)
echo ${VERSION:0:1}
"""
)
expected_stdout = "v"
assert expected_stdout in version_check.stdout
def test_IPv6_only_link_local(host):
"""
confirms IPv6 blocking is disabled for Link-local address
"""
# mock ip -6 address to return Link-local address
mock_command_2(
"ip",
{"-6 address": ("inet6 fe80::d210:52fa:fe00:7ad7/64 scope link", "0")},
host,
)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
find_IPv6_information
"""
)
expected_stdout = "Unable to find IPv6 ULA/GUA address"
assert expected_stdout in detectPlatform.stdout
def test_IPv6_only_ULA(host):
"""
confirms IPv6 blocking is enabled for ULA addresses
"""
# mock ip -6 address to return ULA address
mock_command_2(
"ip",
{
"-6 address": (
"inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global",
"0",
)
},
host,
)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
find_IPv6_information
"""
)
expected_stdout = "Found IPv6 ULA address"
assert expected_stdout in detectPlatform.stdout
def test_IPv6_only_GUA(host):
"""
confirms IPv6 blocking is enabled for GUA addresses
"""
# mock ip -6 address to return GUA address
mock_command_2(
"ip",
{
"-6 address": (
"inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global",
"0",
)
},
host,
)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
find_IPv6_information
"""
)
expected_stdout = "Found IPv6 GUA address"
assert expected_stdout in detectPlatform.stdout
def test_IPv6_GUA_ULA_test(host):
"""
confirms IPv6 blocking is enabled for GUA and ULA addresses
"""
# mock ip -6 address to return GUA and ULA addresses
mock_command_2(
"ip",
{
"-6 address": (
"inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global\n"
"inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global",
"0",
)
},
host,
)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
find_IPv6_information
"""
)
expected_stdout = "Found IPv6 ULA address"
assert expected_stdout in detectPlatform.stdout
def test_IPv6_ULA_GUA_test(host):
"""
confirms IPv6 blocking is enabled for GUA and ULA addresses
"""
# mock ip -6 address to return ULA and GUA addresses
mock_command_2(
"ip",
{
"-6 address": (
"inet6 fda2:2001:5555:0:d210:52fa:fe00:7ad7/64 scope global\n"
"inet6 2003:12:1e43:301:d210:52fa:fe00:7ad7/64 scope global",
"0",
)
},
host,
)
detectPlatform = host.run(
"""
source /opt/pihole/basic-install.sh
find_IPv6_information
"""
)
expected_stdout = "Found IPv6 ULA address"
assert expected_stdout in detectPlatform.stdout
def test_validate_ip(host):
"""
Tests valid_ip for various IP addresses
"""
def test_address(addr, success=True):
output = host.run(
"""
source /opt/pihole/basic-install.sh
valid_ip "{addr}"
""".format(
addr=addr
)
)
assert output.rc == 0 if success else 1
test_address("192.168.1.1")
test_address("127.0.0.1")
test_address("255.255.255.255")
test_address("255.255.255.256", False)
test_address("255.255.256.255", False)
test_address("255.256.255.255", False)
test_address("256.255.255.255", False)
test_address("1092.168.1.1", False)
test_address("not an IP", False)
test_address("8.8.8.8#", False)
test_address("8.8.8.8#0")
test_address("8.8.8.8#1")
test_address("8.8.8.8#42")
test_address("8.8.8.8#888")
test_address("8.8.8.8#1337")
test_address("8.8.8.8#65535")
test_address("8.8.8.8#65536", False)
test_address("8.8.8.8#-1", False)
test_address("00.0.0.0", False)
test_address("010.0.0.0", False)
test_address("001.0.0.0", False)
test_address("0.0.0.0#00", False)
test_address("0.0.0.0#01", False)
test_address("0.0.0.0#001", False)
test_address("0.0.0.0#0001", False)
test_address("0.0.0.0#00001", False)
def test_os_check_fails(host):
"""Confirms install fails on unsupported OS"""
host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
install_dependent_packages ${OS_CHECK_DEPS[@]}
install_dependent_packages ${INSTALLER_DEPS[@]}
cat <<EOT > /etc/os-release
ID=UnsupportedOS
VERSION_ID="2"
EOT
"""
)
detectOS = host.run(
"""t
source /opt/pihole/basic-install.sh
os_check
"""
)
expected_stdout = "Unsupported OS detected: UnsupportedOS"
assert expected_stdout in detectOS.stdout
def test_os_check_passes(host):
"""Confirms OS meets the requirements"""
host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
install_dependent_packages ${OS_CHECK_DEPS[@]}
install_dependent_packages ${INSTALLER_DEPS[@]}
"""
)
detectOS = host.run(
"""
source /opt/pihole/basic-install.sh
os_check
"""
)
expected_stdout = "Supported OS detected"
assert expected_stdout in detectOS.stdout
def test_package_manager_has_installer_deps(host):
"""Confirms OS is able to install the required packages for the installer"""
mock_command("dialog", {"*": ("", "0")}, host)
output = host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
install_dependent_packages ${INSTALLER_DEPS[@]}
"""
)
assert "No package" not in output.stdout
assert output.rc == 0
def test_package_manager_has_pihole_deps(host):
"""Confirms OS is able to install the required packages for Pi-hole"""
mock_command("dialog", {"*": ("", "0")}, host)
output = host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
install_dependent_packages ${PIHOLE_DEPS[@]}
"""
)
assert "No package" not in output.stdout
assert output.rc == 0
def test_package_manager_has_web_deps(host):
"""Confirms OS is able to install the required packages for web"""
mock_command("dialog", {"*": ("", "0")}, host)
output = host.run(
"""
source /opt/pihole/basic-install.sh
package_manager_detect
install_dependent_packages ${PIHOLE_WEB_DEPS[@]}
"""
)
assert "No package" not in output.stdout
assert output.rc == 0