#!./test/libs/bats/bin/bats @test "Starting tests without prior history" { run bash -c 'grep -c "Total DNS queries: 0" /var/log/pihole-FTL.log' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "1" ]] } @test "Initial blocking status is enabled" { run bash -c 'grep -c "Blocking status is enabled" /var/log/pihole-FTL.log' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "1" ]] } @test "Number of compiled regex filters as expected" { run bash -c 'grep -c "Compiled 2 whitelist and 1 blacklist regex filters" /var/log/pihole-FTL.log' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "1" ]] } @test "Blacklisted domain is blocked" { run bash -c "dig blacklist-blocked.test.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0.0.0.0" ]] [[ ${lines[1]} == "" ]] } @test "Gravity domain is blocked" { run bash -c "dig gravity-blocked.test.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0.0.0.0" ]] [[ ${lines[1]} == "" ]] } @test "Gravity domain is blocked (TCP)" { run bash -c "dig gravity-blocked.test.pi-hole.net @127.0.0.1 +tcp +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0.0.0.0" ]] [[ ${lines[1]} == "" ]] } @test "Gravity domain + whitelist exact match is not blocked" { run bash -c "dig whitelisted.test.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Gravity domain + whitelist regex match is not blocked" { run bash -c "dig discourse.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Regex blacklist match is blocked" { run bash -c "dig regex5.test.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0.0.0.0" ]] [[ ${lines[1]} == "" ]] } @test "Regex blacklist mismatch is not blocked" { run bash -c "dig regexA.test.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Regex blacklist match + whitelist exact match is not blocked" { run bash -c "dig regex1.test.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Regex blacklist match + whitelist regex match is not blocked" { run bash -c "dig regex2.test.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Client 2: Gravity match matching unassociated whitelist is blocked" { run bash -c "dig whitelisted.test.pi-hole.net -b 127.0.0.2 @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0.0.0.0" ]] } @test "Client 2: Regex blacklist match matching unassociated whitelist is blocked" { run bash -c "dig regex1.test.pi-hole.net -b 127.0.0.2 @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0.0.0.0" ]] } @test "Same domain is not blocked for client 1 ..." { run bash -c "dig regex1.test.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "... or client 3" { run bash -c "dig regex1.test.pi-hole.net -b 127.0.0.3 @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Client 2: Unassociated blacklist match is not blocked" { run bash -c "dig blacklist-blocked.test.pi-hole.net -b 127.0.0.2 @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Client 3: Exact blacklist domain is not blocked" { run bash -c "dig blacklist-blocked.test.pi-hole.net -b 127.0.0.3 @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Client 3: Regex blacklist domain is not blocked" { run bash -c "dig regex1.test.pi-hole.net -b 127.0.0.3 @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Client 3: Gravity domain is not blocked" { run bash -c "dig discourse.pi-hole.net -b 127.0.0.3 @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Google.com (A) is not blocked" { run bash -c "dig A google.com @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "0.0.0.0" ]] } @test "Google.com (AAAA) is not blocked (TCP query)" { run bash -c "dig AAAA google.com @127.0.0.1 +short +tcp" printf "%s\n" "${lines[@]}" [[ ${lines[0]} != "::" ]] [[ ${lines[1]} == "" ]] } @test "Known host is resolved as expected" { run bash -c "dig ftl.pi-hole.net @127.0.0.1 +short" printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "139.59.170.52" ]] [[ ${lines[1]} == "" ]] } @test "pihole-FTL.db schema as expected" { run bash -c 'sqlite3 /etc/pihole/pihole-FTL.db .dump' printf "%s\n" "${lines[@]}" [[ "${lines[@]}" == *"CREATE TABLE queries ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, type INTEGER NOT NULL, status INTEGER NOT NULL, domain TEXT NOT NULL, client TEXT NOT NULL, forward TEXT );"* ]] [[ "${lines[@]}" == *"CREATE TABLE ftl ( id INTEGER PRIMARY KEY NOT NULL, value BLOB NOT NULL );"* ]] [[ "${lines[@]}" == *"CREATE TABLE counters ( id INTEGER PRIMARY KEY NOT NULL, value INTEGER NOT NULL );"* ]] [[ "${lines[@]}" == *"CREATE TABLE IF NOT EXISTS \"network\" ( id INTEGER PRIMARY KEY NOT NULL, hwaddr TEXT UNIQUE NOT NULL, interface TEXT NOT NULL, name TEXT, firstSeen INTEGER NOT NULL, lastQuery INTEGER NOT NULL, numQueries INTEGER NOT NULL, macVendor TEXT);"* ]] [[ "${lines[@]}" == *"CREATE TABLE network_addresses ( network_id INTEGER NOT NULL, ip TEXT NOT NULL, lastSeen INTEGER NOT NULL DEFAULT (cast(strftime('%s', 'now') as int)), UNIQUE(network_id,ip), FOREIGN KEY(network_id) REFERENCES network(id));"* ]] [[ "${lines[@]}" == *"CREATE INDEX idx_queries_timestamps ON queries (timestamp);"* ]] } @test "Fail on invalid argument" { run bash -c '/home/pihole/pihole-FTL abc' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "pihole-FTL: invalid option -- 'abc'" ]] [[ ${lines[1]} == "Try '/home/pihole/pihole-FTL --help' for more information" ]] } @test "Help argument return help text" { run bash -c '/home/pihole/pihole-FTL help' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "pihole-FTL - The Pi-hole FTL engine" ]] [[ ${lines[3]} == "Available arguments:" ]] } @test "No WARNING messages in pihole-FTL.log (besides known capability issues)" { run bash -c 'grep "WARNING:" /var/log/pihole-FTL.log | grep -c -v -E "CAP_NET_ADMIN|CAP_NET_RAW"' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0" ]] } @test "No ERROR messages in pihole-FTL.log (besides known index.html error)" { run bash -c 'grep "ERROR:" /var/log/pihole-FTL.log | grep -c -v -E "index\.html"' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0" ]] } @test "No FATAL messages in pihole-FTL.log" { run bash -c 'grep -c "FATAL:" /var/log/pihole-FTL.log' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0" ]] } # x86_64-musl is built on busybox which has a slightly different # variant of ls displaying three, instead of one, spaces between the # user and group names. @test "Ownership and permissions of pihole-FTL.db correct" { run bash -c 'ls -l /etc/pihole/pihole-FTL.db' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == *"pihole pihole"* || ${lines[0]} == *"pihole pihole"* ]] [[ ${lines[0]} == "-rw-r--r--"* ]] } # "ldd" prints library dependencies and the used interpreter for a given program # # Dependencies on shared libraries are displayed like # libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fa7d28be000) # # In this test, we use ldd and check for the dependency arrow "=>" to check if # our generated binary depends on shared libraries in the way we expect it to @test "Dependence on shared libraries" { run bash -c 'ldd ./pihole-FTL' printf "%s\n" "${lines[@]}" [[ "${STATIC}" != "true" && "${lines[@]}" == *"=>"* ]] || \ [[ "${STATIC}" == "true" && "${lines[@]}" != *"=>"* ]] } # "file" determines the file type of our generated binary # # We use its ability to test whether a specific interpreter is # required by the given executable. What the interpreter is, is not # really well documented in "man elf(5)", however, one can say that # the interpreter is a program that finds and loads the shared # libraries needed by a program, prepares the program to run, and then # runs it. # # In this test, we use "file" to confirm the absence of the dependence # on an interpreter for the static binary. @test "Dependence on specific interpreter" { run bash -c 'file ./pihole-FTL' printf "%s\n" "${lines[@]}" [[ "${STATIC}" != "true" && "${lines[@]}" == *"interpreter"* ]] || \ [[ "${STATIC}" == "true" && "${lines[@]}" != *"interpreter"* ]] } @test "Architecture is correctly reported on startup" { run bash -c 'grep "Compiled for" /var/log/pihole-FTL.log' printf "Output: %s\n\$CIRCLE_JOB: %s\nuname -m: %s\n" "${lines[@]:-not set}" "${CIRCLE_JOB:-not set}" "$(uname -m)" [[ ${lines[0]} == *"Compiled for ${CIRCLE_JOB:-$(uname -m)}"* ]] } @test "Building machine (CI) is reported on startup" { [[ ${CIRCLE_JOB} != "" ]] && compiled_str="on CI" || compiled_str="locally" && export compiled_str run bash -c 'grep "Compiled for" /var/log/pihole-FTL.log' printf "Output: %s\n\$CIRCLE_JOB: %s\n" "${lines[@]:-not set}" "${CIRCLE_JOB:-not set}" [[ ${lines[0]} == *"(compiled ${compiled_str})"* ]] } @test "Compiler version is correctly reported on startup" { compiler_version="$(${CC} --version | head -n1)" && export compiler_version run bash -c 'grep "Compiled for" /var/log/pihole-FTL.log' printf "Output: %s\n\$CC: %s\nVersion: %s\n" "${lines[@]:-not set}" "${CC:-not set}" "${compiler_version:-not set}" [[ ${lines[0]} == *"using ${compiler_version}"* ]] } @test "No errors on setting busy handlers for the databases" { run bash -c 'grep -c "Cannot set busy handler" /var/log/pihole-FTL.log' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "0" ]] } @test "Blocking status is correctly logged in pihole.log" { run bash -c 'grep -c "gravity blocked gravity-blocked.test.pi-hole.net is 0.0.0.0" /var/log/pihole.log' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "2" ]] } @test "HTTP server responds correctly to ping" { run bash -c 'curl -s 127.0.0.1:8080/ping' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "pong" ]] } @test "HTTP server responds with JSON error 404 to unknown API path" { run bash -c 'curl -s 127.0.0.1:8080/admin/api/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "{\"error\":{\"key\":\"not_found\",\"message\":\"Not found\",\"data\":{\"path\":\"/admin/api/undefined\"}}}" ]] } @test "HTTP server responds with normal error 404 to path outside /admin" { run bash -c 'curl -s 127.0.0.1:8080/undefined' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "Error 404: Not Found" ]] } # This test does not work without actually hosting the web interface #@test "HTTP server responds without error to undefined path inside /admin (rerouted to index.html)" { # run bash -c 'curl -I -s 127.0.0.1:8080/admin/undefined' # printf "%s\n" "${lines[@]}" # [[ ${lines[0]} == "HTTP/1.1 200 OK"* ]] #} @test "API authorization: Unauthorized for request without password" { run bash -c 'curl -I -s 127.0.0.1:8080/admin/api/auth' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "HTTP/1.1 401 Unauthorized"* ]] } # This test is assuming the user password is empty @test "API authorization: Success for request with correct password" { run bash -c 'curl -s -H "X-Pi-hole-Authenticate: cd372fb85148700fa88095e3492d3f9f5beb43e555e5ff26d95f5a6adc36f8e6" 127.0.0.1:8080/admin/api/auth' printf "%s\n" "${lines[@]}" [[ ${lines[0]} == "{\"status\":\"success\"}" ]] }