Files
smallwebwaf/internal/smallwebwaf/healthcheck_test.go
T
clawbot bff65f4e2f
check / check (push) Successful in 4m53s
Settings given as files: the _FILE form of every setting (closes #87)
Every setting X may instead be given as a file that X_FILE names, read
once at start: its contents, less one trailing newline, are the value,
checked as X would be. X and X_FILE both set, or a file that cannot be
read, stops the start with a message naming the variable. The logged
settings name the file, and mask a token read from one.
SWWAF_LOG_REMOTE_TLS_CA_FILE, whose value is a file already, has no
_FILE form. The health check reads only SWWAF_LISTEN_ADDR and
SWWAF_UPSTREAM_URL, so no other setting or file can fail it.

Judgement call: an invalid value read from a file is named as X, not X_FILE.
Rule suppressed: gosec G304 on reading the named file, as for the CA file.

Model: opus-5-5
2026-10-07 00:01:19 +02:00

133 lines
3.4 KiB
Go

package smallwebwaf_test
import (
"bytes"
"context"
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"sneak.berlin/go/smallwebwaf/internal/smallwebwaf"
)
func TestHealthCheck(t *testing.T) {
t.Parallel()
app := httptest.NewServer(http.NotFoundHandler())
defer app.Close()
ctx, stop := context.WithCancel(t.Context())
defer stop()
out := &output{}
exited := make(chan int, 1)
settings := map[string]string{
listenAddr: localhost + ":0",
upstreamURL: app.URL,
stateDir: t.TempDir(),
rulesDir: t.TempDir(),
}
go func() {
exited <- run(ctx, settings, out)
}()
addr, _ := out.line(t, "msg", "starting")["address"].(string)
_, port, _ := net.SplitHostPort(addr)
// The container's settings: an address to listen on with an empty
// host part, which the health check asks at 127.0.0.1.
env := map[string]string{listenAddr: ":" + port, upstreamURL: app.URL}
wantHealthCheck(t, env, 0, "")
// The health check reads those two settings alone, here given as
// files: a removed or invalid token file, or an invalid value of
// another setting, does not fail it.
for _, other := range []struct{ name, value string }{
{"SWWAF_METRICS_TOKEN_FILE", filepath.Join(t.TempDir(), "removed")},
{"SWWAF_METRICS_TOKEN_FILE", writeFile(t, "too short\n")},
{"SWWAF_MODE", "neither"},
} {
wantHealthCheck(t, map[string]string{
listenAddr + "_FILE": writeFile(t, ":"+port+"\n"),
upstreamURL + "_FILE": writeFile(t, app.URL+"\n"),
other.name: other.value,
}, 0, "")
}
app.Close()
wantHealthCheck(t, env, 1, "unhealthy: connect to the app: ")
stop()
select {
case <-exited:
case <-time.After(waitLimit):
t.Fatal("still running after being told to stop")
}
wantHealthCheck(t, env, 1, "unhealthy: ask smallwebwaf: ")
wantHealthCheck(t, map[string]string{listenAddr: "8080"}, 1,
"unhealthy: invalid setting: SWWAF_LISTEN_ADDR: ")
}
func TestHealthCheckRefusesAnArgument(t *testing.T) {
t.Parallel()
var stderr bytes.Buffer
noSettings := func(string) (string, bool) {
return "", false
}
got := smallwebwaf.HealthCheck(t.Context(), []string{"now"}, noSettings, &stderr)
want := "smallwebwaf healthcheck: unexpected argument \"now\"\n"
if got != 1 || stderr.String() != want {
t.Errorf("health check returned %d and wrote %q, want 1 and %q",
got, stderr.String(), want)
}
}
// wantHealthCheck runs the health check with the settings in env, and
// checks its exit status and the start of what it writes to stderr,
// which is nothing when message is empty.
func wantHealthCheck(t *testing.T, env map[string]string, status int, message string) {
t.Helper()
var stderr bytes.Buffer
got := smallwebwaf.HealthCheck(t.Context(), nil, func(name string) (string, bool) {
value, ok := env[name]
return value, ok
}, &stderr)
wrote := stderr.String()
if got != status || !strings.HasPrefix(wrote, message) ||
(message == "" && wrote != "") {
t.Errorf("health check returned %d and wrote %q, want %d and %q",
got, wrote, status, message)
}
}
// writeFile writes contents to a file in a directory of its own, removed
// when the test ends, and returns the file's path.
func writeFile(t *testing.T, contents string) string {
t.Helper()
path := filepath.Join(t.TempDir(), "setting")
err := os.WriteFile(path, []byte(contents), 0o600)
if err != nil {
t.Fatalf("write %s: %v", path, err)
}
return path
}