#!/bin/sh

# Regression tests for the two Debian patches carried by this package:
#
# - fix-invalid-auto-reload-segfault.patch
#   Verify that an invalid auto_reload interval is reported as a configuration
#   error instead of crashing nginx.  Both the HTTP and stream modules are
#   tested.
#
# - invalidate-cache-after-database-reload.patch
#   Populate the one-entry lookup cache, atomically replace and automatically
#   reload the MMDB database, then repeat the lookup for the same source IP.
#   Both modules must return data from the replacement database rather than
#   reusing the cached result associated with the closed database.

set -eu

TEST_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
WORK_DIR="${AUTOPKGTEST_TMP:-/tmp}/geoip2-regressions"
NGINX=/usr/sbin/nginx

mkdir -p "${WORK_DIR}"

http_module="${GEOIP2_HTTP_MODULE:-$(dpkg-query -L libnginx-mod-http-geoip2 |
    sed -n '/\/ngx_http_geoip2_module\.so$/p' | head -n 1)}"
stream_module="${GEOIP2_STREAM_MODULE:-$(dpkg-query -L libnginx-mod-stream-geoip2 |
    sed -n '/\/ngx_stream_geoip2_module\.so$/p' | head -n 1)}"
stream_core_module="$(dpkg-query -L libnginx-mod-stream |
    sed -n '/\/ngx_stream_module\.so$/p' | head -n 1)"

for module in "${http_module}" "${stream_module}" "${stream_core_module}"; do
    if [ ! -f "${module}" ]; then
        echo "module not found: ${module:-<empty>}" >&2
        exit 1
    fi
done

base64 --decode "${TEST_DIR}/data/before.mmdb.b64" \
    > "${WORK_DIR}/before.mmdb"
base64 --decode "${TEST_DIR}/data/after.mmdb.b64" \
    > "${WORK_DIR}/after.mmdb"
cp "${WORK_DIR}/before.mmdb" "${WORK_DIR}/active.mmdb"

cat > "${WORK_DIR}/invalid-http.conf" <<EOF
load_module "${http_module}";
worker_processes 1;
error_log stderr notice;
pid "${WORK_DIR}/invalid-http.pid";
events {}
http {
    geoip2 "${WORK_DIR}/active.mmdb" {
        auto_reload invalid;
    }
}
EOF

cat > "${WORK_DIR}/invalid-stream.conf" <<EOF
load_module "${stream_core_module}";
load_module "${stream_module}";
worker_processes 1;
error_log stderr notice;
pid "${WORK_DIR}/invalid-stream.pid";
events {}
stream {
    geoip2 "${WORK_DIR}/active.mmdb" {
        auto_reload invalid;
    }
}
EOF

expect_invalid_interval()
{
    name="$1"
    config="${WORK_DIR}/invalid-${name}.conf"
    output="${WORK_DIR}/invalid-${name}.log"

    set +e
    "${NGINX}" -p "${WORK_DIR}/" -t -c "${config}" >"${output}" 2>&1
    status=$?
    set -e

    if [ "${status}" -ne 1 ]; then
        echo "${name}: expected nginx -t to exit 1, got ${status}" >&2
        cat "${output}" >&2
        exit 1
    fi

    if ! grep -F 'invalid interval for auto_reload "invalid"' "${output}"; then
        echo "${name}: expected configuration error was not reported" >&2
        cat "${output}" >&2
        exit 1
    fi

    echo "${name}: invalid auto_reload rejected without a crash ... OK"
}

expect_invalid_interval http
expect_invalid_interval stream

cat > "${WORK_DIR}/nginx.conf" <<EOF
load_module "${stream_core_module}";
load_module "${http_module}";
load_module "${stream_module}";
worker_processes 1;
error_log "${WORK_DIR}/error.log" info;
pid "${WORK_DIR}/nginx.pid";
events {
    worker_connections 32;
}
http {
    access_log off;
    geoip2 "${WORK_DIR}/active.mmdb" {
        auto_reload 1s;
        \$geoip2_http_test source=\$http_x_test_ip test;
    }
    server {
        listen "unix:${WORK_DIR}/http.sock";
        location / {
            return 200 "\$geoip2_http_test\n";
        }
    }
}
stream {
    geoip2 "${WORK_DIR}/active.mmdb" {
        auto_reload 1s;
        \$geoip2_stream_test source=\$proxy_protocol_addr test;
    }
    server {
        listen "unix:${WORK_DIR}/stream.sock" proxy_protocol;
        return "\$geoip2_stream_test\n";
    }
}
EOF

nginx_running=0
cleanup()
{
    if [ "${nginx_running}" -eq 1 ] && [ -f "${WORK_DIR}/nginx.pid" ]; then
        "${NGINX}" -p "${WORK_DIR}/" -c "${WORK_DIR}/nginx.conf" -s quit || :
    fi
}
trap cleanup EXIT HUP INT TERM

"${NGINX}" -p "${WORK_DIR}/" -t -c "${WORK_DIR}/nginx.conf"
"${NGINX}" -p "${WORK_DIR}/" -c "${WORK_DIR}/nginx.conf"
nginx_running=1

tries=0
while [ ! -S "${WORK_DIR}/http.sock" ] || [ ! -S "${WORK_DIR}/stream.sock" ]; do
    tries=$((tries + 1))
    if [ "${tries}" -ge 50 ]; then
        echo "nginx sockets were not created" >&2
        cat "${WORK_DIR}/error.log" >&2
        exit 1
    fi
    sleep 0.1
done

http_lookup()
{
    curl --fail --silent --show-error --unix-socket "${WORK_DIR}/http.sock" \
        --header 'X-Test-IP: 192.0.2.1' http://localhost/
}

stream_lookup()
{
    printf 'PROXY TCP4 192.0.2.1 198.51.100.1 12345 80\r\n' |
        nc -w 2 -U "${WORK_DIR}/stream.sock"
}

assert_lookup()
{
    protocol="$1"
    expected="$2"
    actual="$(${protocol}_lookup)"

    if [ "${actual}" != "${expected}" ]; then
        echo "${protocol}: expected '${expected}', got '${actual}'" >&2
        cat "${WORK_DIR}/error.log" >&2
        exit 1
    fi
}

# Populate each module's one-entry lookup cache with the same source address.
assert_lookup http before
assert_lookup stream before

# Ensure the replacement has an mtime newer than the initial last_change value,
# then install it atomically as required for an mmap-backed database.
sleep 2
cp "${WORK_DIR}/after.mmdb" "${WORK_DIR}/active.mmdb.new"
mv "${WORK_DIR}/active.mmdb.new" "${WORK_DIR}/active.mmdb"
sleep 2

# The request/session that notices the mtime change still uses the old database;
# its log-phase handler performs the reload for the following lookup.
assert_lookup http before
assert_lookup stream before
assert_lookup http after
assert_lookup stream after

echo "http: cached lookup invalidated after database reload ... OK"
echo "stream: cached lookup invalidated after database reload ... OK"
