A section tagged init:"hidden" isn't written to the file config init generates; fts_mock and sleeper are. config init --all writes them too. They still load, and the config commands read and set them as before.
268 lines
7.2 KiB
Go
268 lines
7.2 KiB
Go
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
"go.yaml.in/yaml/v3"
|
|
|
|
"git.avsdev.uk/Go/job-search/internal/cli"
|
|
"git.avsdev.uk/Go/job-search/internal/cli/cmdtest"
|
|
)
|
|
|
|
// 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,
|
|
)
|
|
}
|
|
|
|
database := strings.Index(out, "database.file")
|
|
logging := strings.Index(out, "logging.level")
|
|
if database > logging {
|
|
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)
|
|
|
|
for _, tt := range []struct {
|
|
key, want string
|
|
}{
|
|
{"logging.level", "info <string>"},
|
|
{"sources.sleeper.interval", "2m0s <string>"},
|
|
{"sources.sleeper.enabled", "false <bool>"},
|
|
} {
|
|
t.Run(tt.key, func(t *testing.T) {
|
|
cmdtest.Contains(t, mustRun(t, "config", "get", tt.key), tt.want)
|
|
})
|
|
}
|
|
|
|
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")
|
|
}
|
|
}
|
|
|
|
// The test sources are left out of the file unless --all is given.
|
|
func TestConfigInitTestSources(t *testing.T) {
|
|
cmdtest.Setup(t, "")
|
|
|
|
mustRun(t, "config", "init", "./default.yaml")
|
|
written := cmdtest.ReadFile(t, "default.yaml")
|
|
cmdtest.Contains(t, written, "find_a_tender:", "eu_supply:")
|
|
for _, hidden := range []string{"fts_mock", "sleeper"} {
|
|
if strings.Contains(written, hidden) {
|
|
t.Errorf("init wrote %s without --all:\n%s", hidden, written)
|
|
}
|
|
}
|
|
|
|
mustRun(t, "config", "init", "--all", "./all.yaml")
|
|
cmdtest.Contains(
|
|
t, cmdtest.ReadFile(t, "all.yaml"), "fts_mock:", "sleeper:",
|
|
)
|
|
|
|
// The test sources still load and are set as usual.
|
|
mustRun(
|
|
t, "--config", "default.yaml",
|
|
"config", "set", "sources.sleeper.enabled", "true",
|
|
)
|
|
cmdtest.Contains(t, cmdtest.ReadFile(t, "default.yaml"), "sleeper:")
|
|
}
|
|
|
|
// 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", " "},
|
|
} {
|
|
t.Run(strings.Join(args, " "), func(t *testing.T) {
|
|
_, 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)
|
|
}
|
|
}
|