208 lines
6.2 KiB
Go
208 lines
6.2 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.avsdev.uk/Go/job-search/internal/cli"
|
|
"git.avsdev.uk/Go/job-search/internal/cli/cmdtest"
|
|
"github.com/spf13/cobra"
|
|
"go.yaml.in/yaml/v3"
|
|
)
|
|
|
|
// These tests run the config commands as a user would, through a root
|
|
// command like job-search's (see cmdtest).
|
|
|
|
const appConfig = `logging:
|
|
level: info
|
|
sources:
|
|
sleeper:
|
|
interval: 2m
|
|
`
|
|
|
|
func newRoot() *cobra.Command {
|
|
opts := &cli.Options{}
|
|
return cmdtest.NewRoot(opts, NewCommand(opts))
|
|
}
|
|
|
|
func run(t *testing.T, args ...string) (string, error) {
|
|
t.Helper()
|
|
return cmdtest.Run(t, newRoot, "", args...)
|
|
}
|
|
|
|
func mustRun(t *testing.T, args ...string) string {
|
|
t.Helper()
|
|
return cmdtest.MustRun(t, newRoot, args...)
|
|
}
|
|
|
|
// fileSettings returns the settings in the YAML file at path, flattened to
|
|
// dotted keys.
|
|
func fileSettings(t *testing.T, path string) map[string]any {
|
|
t.Helper()
|
|
var nested map[string]any
|
|
if err := yaml.Unmarshal([]byte(cmdtest.ReadFile(t, path)), &nested); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
flat := map[string]any{}
|
|
var walk func(prefix string, m map[string]any)
|
|
walk = func(prefix string, m map[string]any) {
|
|
for k, v := range m {
|
|
if sub, ok := v.(map[string]any); ok {
|
|
walk(prefix+k+".", sub)
|
|
} else {
|
|
flat[prefix+k] = v
|
|
}
|
|
}
|
|
}
|
|
walk("", nested)
|
|
return flat
|
|
}
|
|
|
|
func TestConfigShow(t *testing.T) {
|
|
cmdtest.Setup(t, appConfig)
|
|
|
|
out := mustRun(t, "config", "show")
|
|
cmdtest.Contains(t, out,
|
|
"logging.level = info <string>", "sources.sleeper.interval = 2m0s <string>")
|
|
// Keys are listed in order, the same every time.
|
|
if again := mustRun(t, "config", "show"); again != out {
|
|
t.Errorf("config show output changed between runs:\n%s\n---\n%s", out, again)
|
|
}
|
|
if i, j := strings.Index(out, "database.file"), strings.Index(out, "logging.level"); i > j {
|
|
t.Errorf("keys not sorted:\n%s", out)
|
|
}
|
|
|
|
out = mustRun(t, "config", "show", "--json")
|
|
cmdtest.Contains(t, out, `"level": "info"`, `"interval": "2m0s"`)
|
|
|
|
out = mustRun(t, "config", "show", "--yaml")
|
|
cmdtest.Contains(t, out, "level: info", "interval: 2m0s")
|
|
}
|
|
|
|
func TestConfigGet(t *testing.T) {
|
|
cmdtest.Setup(t, appConfig)
|
|
|
|
cmdtest.Contains(t, mustRun(t, "config", "get", "logging.level"), "info <string>")
|
|
cmdtest.Contains(t, mustRun(t, "config", "get", "sources.sleeper.interval"), "2m0s <string>")
|
|
cmdtest.Contains(t, mustRun(t, "config", "get", "sources.sleeper.enabled"), "false <bool>")
|
|
|
|
if _, err := run(t, "config", "get", "no.such.key"); err == nil {
|
|
t.Error("get of an unknown key succeeded")
|
|
}
|
|
if _, err := run(t, "config", "get"); err == nil {
|
|
t.Error("get without a key succeeded")
|
|
}
|
|
}
|
|
|
|
func TestConfigSet(t *testing.T) {
|
|
cmdtest.Setup(t, appConfig)
|
|
|
|
out := mustRun(t, "config", "set", "logging.level", "debug")
|
|
cmdtest.Contains(t, out, "logging.level = debug <string>")
|
|
cmdtest.Contains(t, mustRun(t, "config", "get", "logging.level"), "debug <string>")
|
|
|
|
out = mustRun(t, "config", "set", "sources.sleeper.enabled", "true")
|
|
cmdtest.Contains(t, out, "sources.sleeper.enabled = true <bool>")
|
|
|
|
// Durations are stored as given out, e.g. 5m0s.
|
|
out = mustRun(t, "config", "set", "sources.sleeper.interval", "5m")
|
|
cmdtest.Contains(t, out, "sources.sleeper.interval = 5m0s <string>")
|
|
|
|
// Only the settings the file had, plus those set, are written: no
|
|
// defaults.
|
|
want := map[string]any{
|
|
"logging.level": "debug",
|
|
"sources.sleeper.enabled": true,
|
|
"sources.sleeper.interval": "5m0s",
|
|
}
|
|
if got := fileSettings(t, "config.yaml"); !equalSettings(got, want) {
|
|
t.Errorf("config.yaml settings = %v; want %v", got, want)
|
|
}
|
|
|
|
for _, args := range [][]string{
|
|
{"config", "set", "logging.level", "loud"}, // rejected by validation
|
|
{"config", "set", "sources.sleeper.enabled", "yes"}, // not a bool
|
|
{"config", "set", "sources.sleeper.interval", "soon"},
|
|
{"config", "set", "no.such.key", "1"},
|
|
{"config", "set", "logging.level"}, // no value, no --unset
|
|
} {
|
|
if _, err := run(t, args...); err == nil {
|
|
t.Errorf("%v succeeded", args)
|
|
}
|
|
}
|
|
if got := fileSettings(t, "config.yaml"); !equalSettings(got, want) {
|
|
t.Errorf("failed sets changed config.yaml: %v", got)
|
|
}
|
|
}
|
|
|
|
func equalSettings(a, b map[string]any) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for k, v := range a {
|
|
if b[k] != v {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestConfigSetUnset(t *testing.T) {
|
|
cmdtest.Setup(t, appConfig)
|
|
|
|
mustRun(t, "config", "set", "--unset", "sources.sleeper.interval")
|
|
// Only the unset key is gone: no defaults are written in its place.
|
|
want := map[string]any{"logging.level": "info"}
|
|
if got := fileSettings(t, "config.yaml"); !equalSettings(got, want) {
|
|
t.Errorf("config.yaml settings = %v; want %v", got, want)
|
|
}
|
|
// Back to the default.
|
|
cmdtest.Contains(t, mustRun(t, "config", "get", "sources.sleeper.interval"),
|
|
"10m0s <string>")
|
|
}
|
|
|
|
func TestConfigSetWithoutConfigFile(t *testing.T) {
|
|
cmdtest.Setup(t, "")
|
|
_, err := run(t, "config", "set", "logging.level", "debug")
|
|
cmdtest.ErrContains(t, err, "no configuration file exists")
|
|
}
|
|
|
|
func TestConfigInit(t *testing.T) {
|
|
cmdtest.Setup(t, "")
|
|
|
|
out := mustRun(t, "config", "init", "./new.yaml")
|
|
cmdtest.Contains(t, out, "writing default configuration to file: ./new.yaml")
|
|
cmdtest.Contains(t, cmdtest.ReadFile(t, "new.yaml"), "level: warn", "interval: 10m0s")
|
|
|
|
_, err := run(t, "config", "init", "./new.yaml")
|
|
cmdtest.ErrContains(t, err, "already exists")
|
|
|
|
if _, err := run(t, "config", "init", "a.yaml", "b.yaml"); err == nil {
|
|
t.Error("init with two paths succeeded")
|
|
}
|
|
}
|
|
|
|
// With no path (or a blank one), init searches the default locations. The
|
|
// working directory is one, so its config.yaml is found. (Not finding one
|
|
// would write to /etc/job-search, which a test can't do.)
|
|
func TestConfigInitDefaultLocations(t *testing.T) {
|
|
cmdtest.Setup(t, appConfig)
|
|
for _, args := range [][]string{
|
|
{"config", "init"},
|
|
{"config", "init", " "},
|
|
} {
|
|
_, err := run(t, args...)
|
|
cmdtest.ErrContains(t, err, "already exists")
|
|
}
|
|
}
|
|
|
|
// init takes the path as an argument, so --config isn't shown in its help.
|
|
func TestConfigInitHelpHidesConfigFlag(t *testing.T) {
|
|
cmdtest.Setup(t, "")
|
|
out := mustRun(t, "config", "init", "--help")
|
|
cmdtest.Contains(t, out, "init [config_path]", "--debug")
|
|
if strings.Contains(out, "--config") {
|
|
t.Errorf("init help shows --config:\n%s", out)
|
|
}
|
|
}
|