check / check (push) Waiting to run
fx's event lines, errors included, now log at debug level, so a normal start shows none and DEBUG shows them. RunServer starts and stops the fx app itself instead of calling Run, and logs fx's "received signal" line as Run did. When the start fails, it logs the error's root cause as one "cannot start" line, the backend's last, and returns 1. Main returns the server's exit code. Model: opus-5-5
224 lines
6.4 KiB
Go
224 lines
6.4 KiB
Go
package cli_test
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/netwatch/internal/cli"
|
|
"sneak.berlin/go/netwatch/internal/reportbuf"
|
|
)
|
|
|
|
// TestMainRunsCheckCIDR: Main given check-cidr returns 0, writing
|
|
// nothing, for a CIDR the server takes in its TRUSTED_PROXIES, and 1,
|
|
// with an error naming TRUSTED_PROXIES and the CIDR, for one it
|
|
// refuses, and does not start the server.
|
|
func TestMainRunsCheckCIDR(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
code := cli.Main(&stderr, "netwatch", "test",
|
|
[]string{"check-cidr", "10.0.0.0/8"}, failIfServerStarts(t))
|
|
if code != 0 || stderr.Len() != 0 {
|
|
t.Errorf("check-cidr 10.0.0.0/8: exit %d, stderr %q; want exit 0 and no output",
|
|
code, stderr.String())
|
|
}
|
|
|
|
stderr.Reset()
|
|
|
|
code = cli.Main(&stderr, "netwatch", "test",
|
|
[]string{"check-cidr", "1.2.3/32"}, failIfServerStarts(t))
|
|
|
|
want := `TRUSTED_PROXIES "1.2.3/32": `
|
|
if code != 1 || !strings.HasPrefix(stderr.String(), want) {
|
|
t.Errorf("check-cidr 1.2.3/32: exit %d, stderr %q; want exit 1 and %q...",
|
|
code, stderr.String(), want)
|
|
}
|
|
}
|
|
|
|
// TestMainRunsPrepareDataDir: Main given prepare-data-dir runs the
|
|
// prepare-data-dir subcommand for the netwatch user, and does not start
|
|
// the server. The test image has no netwatch user, so that subcommand
|
|
// exits 1 with the error from looking the user up;
|
|
// TestPrepareDataDirRefusesPathOutsideVolume checks the path.
|
|
func TestMainRunsPrepareDataDir(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
code := cli.Main(&stderr, "netwatch", "test",
|
|
[]string{"prepare-data-dir", "/etc"}, failIfServerStarts(t))
|
|
|
|
want := user.UnknownUserError("netwatch").Error() + "\n"
|
|
if code != 1 || stderr.String() != want {
|
|
t.Errorf("prepare-data-dir /etc: exit %d, stderr %q; want exit 1 and %q",
|
|
code, stderr.String(), want)
|
|
}
|
|
}
|
|
|
|
// TestMainRunsServerWithNoArguments: Main given no arguments starts the
|
|
// server with the name and version it was given, writes nothing and
|
|
// returns the server's exit code.
|
|
func TestMainRunsServerWithNoArguments(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
stderr bytes.Buffer
|
|
gotAppname, gotVersion string
|
|
)
|
|
|
|
code := cli.Main(&stderr, "netwatch", "test", []string{},
|
|
func(appname, version string) int {
|
|
gotAppname, gotVersion = appname, version
|
|
|
|
return 3
|
|
})
|
|
if code != 3 || stderr.Len() != 0 {
|
|
t.Errorf("exit %d, stderr %q; want exit 3 and no output",
|
|
code, stderr.String())
|
|
}
|
|
|
|
if gotAppname != "netwatch" || gotVersion != "test" {
|
|
t.Errorf("server started with %q %q; want %q %q",
|
|
gotAppname, gotVersion, "netwatch", "test")
|
|
}
|
|
}
|
|
|
|
// TestMainRefusesOtherCommandLines: Main given a subcommand with too
|
|
// few or too many values, or a misspelt subcommand, writes the usage
|
|
// message to stderr, returns 2 and does not start the server.
|
|
func TestMainRefusesOtherCommandLines(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
want := "usage: netwatch-server " +
|
|
"[check-cidr CIDR | prepare-data-dir DATA_DIR]\n"
|
|
|
|
for _, commandLine := range []string{
|
|
"netwatch-server check-cidr",
|
|
"netwatch-server check-cidr 192.0.2.0/24 198.51.100.0/24",
|
|
"netwatch-server prepare-data-dir",
|
|
"netwatch-server prepare-data-dir /data/reports /data/other",
|
|
"netwatch-server check-cdir 192.0.2.0/24",
|
|
} {
|
|
var stderr bytes.Buffer
|
|
|
|
// Main takes the command line after the program's name.
|
|
args := strings.Fields(commandLine)[1:]
|
|
|
|
code := cli.Main(&stderr, "netwatch", "test", args, failIfServerStarts(t))
|
|
if code != 2 || stderr.String() != want {
|
|
t.Errorf("%q: exit %d, stderr %q; want exit 2 and %q",
|
|
commandLine, code, stderr.String(), want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCheckCIDRTakesCIDR: check-cidr exits 0, writing nothing, for a
|
|
// CIDR the server takes in its TRUSTED_PROXIES.
|
|
func TestCheckCIDRTakesCIDR(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, cidr := range []string{"10.0.0.0/8", "192.0.2.1/32", "2001:db8::/32"} {
|
|
var stderr bytes.Buffer
|
|
|
|
code := cli.CheckCIDR(&stderr, cidr)
|
|
if code != 0 || stderr.Len() != 0 {
|
|
t.Errorf("%q: exit %d, stderr %q; want exit 0 and no output",
|
|
cidr, code, stderr.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestCheckCIDRRefusesOtherInput: check-cidr exits 1, with an error
|
|
// naming TRUSTED_PROXIES and the input, for anything the server would
|
|
// refuse in its TRUSTED_PROXIES, such as 1.2.3/32, which nginx would
|
|
// look up as a hostname.
|
|
func TestCheckCIDRRefusesOtherInput(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
for _, cidr := range []string{
|
|
"1.2.3/32", "example.com/32", "10.0.0.0/33", "10.0.0.1", "",
|
|
} {
|
|
var stderr bytes.Buffer
|
|
|
|
code := cli.CheckCIDR(&stderr, cidr)
|
|
|
|
want := "TRUSTED_PROXIES " + strconv.Quote(cidr) + ": "
|
|
if code != 1 || !strings.HasPrefix(stderr.String(), want) {
|
|
t.Errorf("%q: exit %d, stderr %q; want exit 1 and %q...",
|
|
cidr, code, stderr.String(), want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPrepareDataDirTakesPathInVolume: prepare-data-dir exits 0,
|
|
// writing nothing, for a DATA_DIR below the volume, and creates it.
|
|
func TestPrepareDataDirTakesPathInVolume(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
volume := t.TempDir()
|
|
dataDir := filepath.Join(volume, "reports")
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
code := cli.PrepareDataDir(&stderr, volume, dataDir, currentUsername(t))
|
|
if code != 0 || stderr.Len() != 0 {
|
|
t.Errorf("exit %d, stderr %q; want exit 0 and no output",
|
|
code, stderr.String())
|
|
}
|
|
|
|
info, err := os.Stat(dataDir)
|
|
if err != nil || !info.IsDir() {
|
|
t.Errorf("DATA_DIR not created: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestPrepareDataDirRefusesPathOutsideVolume: prepare-data-dir exits
|
|
// 1, with an error naming DATA_DIR and its value, for a DATA_DIR
|
|
// outside the volume.
|
|
func TestPrepareDataDirRefusesPathOutsideVolume(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
volume := t.TempDir()
|
|
|
|
var stderr bytes.Buffer
|
|
|
|
code := cli.PrepareDataDir(&stderr, volume, "/etc", currentUsername(t))
|
|
|
|
want := "DATA_DIR '/etc': " + reportbuf.ErrDataDirOutsideVolume.Error() + "\n"
|
|
if code != 1 || stderr.String() != want {
|
|
t.Errorf("exit %d, stderr %q; want exit 1 and %q",
|
|
code, stderr.String(), want)
|
|
}
|
|
}
|
|
|
|
// currentUsername is the name of the user the test runs as, the only
|
|
// owner a test not run as root can give files to.
|
|
func currentUsername(t *testing.T) string {
|
|
t.Helper()
|
|
|
|
current, err := user.Current()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return current.Username
|
|
}
|
|
|
|
// failIfServerStarts is what the tests give Main in place of
|
|
// cli.RunServer: it fails the test instead of starting the server.
|
|
func failIfServerStarts(t *testing.T) func(appname, version string) int {
|
|
t.Helper()
|
|
|
|
return func(string, string) int {
|
|
t.Error("Main started the server")
|
|
|
|
return 0
|
|
}
|
|
}
|