Files
go-omni/config/moduleDiagnostics_test.go

86 lines
3.8 KiB
Go

package config_test
import (
"errors"
"os"
"path/filepath"
"strings"
"testing"
"git.yuntuo.hk/k2safe/go-omni/config"
mysql "git.yuntuo.hk/k2safe/go-omni/database/mysql"
"git.yuntuo.hk/k2safe/go-omni/httpc"
)
type moduleConfig struct {
Database mysql.Config `json:"database"`
Server config.Config `json:"server"`
Client httpc.Config `json:"client"`
}
func (c moduleConfig) Validate() error {
if err := c.Database.Validate(); err != nil {
return config.AtField("database", err)
}
if err := c.Server.Validate(); err != nil {
return config.AtField("server", err)
}
return config.AtField("client", c.Client.Validate())
}
func TestModuleFieldDiagnostics(t *testing.T) {
for _, tc := range []struct{ name, source, field, reason string }{
{"port", "database:\n port: 70000\n", "database.port", "1 and 65535"},
{"host", "database:\n host: 'invalid host'\n", "database.host", "hostname"},
{"pool", "database:\n pool:\n maxIdleConns: 999\n", "database.pool.maxIdleConns", "maxOpenConns"},
{"ping", "database:\n pool:\n pingTimeout: 0s\n", "database.pool.pingTimeout", "positive"},
{"timeout", "database:\n readTimeout: -1s\n", "database.readTimeout", "nonnegative"},
{"mysqlZone", "database:\n location: Secret/Not_A_Zone\n", "database.location", "IANA"},
{"tlsPair", "database:\n tls:\n certFile: secret-file\n keyFile: ''\n", "database.tls.keyFile", "certFile"},
{"level", "server:\n log:\n level: secret-level\n", "server.log.level", "debug"},
{"format", "server:\n log:\n format: secret-format\n", "server.log.format", "json or text"},
{"rotation", "server:\n log:\n rotation:\n maxBackups: -1\n", "server.log.rotation.maxBackups", "10000"},
{"zone", "server:\n log:\n timeZone: Secret/Not_A_Zone\n", "server.log.timeZone", "IANA"},
{"lifecycle", "server:\n lifecycle:\n stopTimeout: -1s\n", "server.lifecycle.stopTimeout", "nonnegative"},
{"policy", "server:\n request:\n body: secret-mode\n", "server.request.body", "required"},
{"clientPool", "client:\n maxIdleConns: -1\n", "client.maxIdleConns", "131072"},
{"proxy", "client:\n proxy:\n mode: url\n url: 'https://private:secret@localhost/path'\n", "client.proxy.url", "origin"},
} {
t.Run(tc.name, func(t *testing.T) {
path := filepath.Join(t.TempDir(), "config.yaml")
if err := os.WriteFile(path, []byte(tc.source), 0600); err != nil {
t.Fatal(err)
}
_, err := config.LoadProject(path, moduleConfig{Database: mysql.DefaultConfig(), Server: config.Default()})
var d *config.Error
if !errors.As(err, &d) || d.Field != tc.field || d.File != path || d.Line <= 1 || d.Column <= 0 || !strings.Contains(d.Reason, tc.reason) {
t.Fatalf("diagnostic: %v", err)
}
if strings.Contains(err.Error(), "secret") || strings.Contains(err.Error(), "Secret/") || strings.Contains(err.Error(), "private") {
t.Fatalf("value leaked: %v", err)
}
})
}
}
func TestModuleDiagnosticsKeepReferenceAndCause(t *testing.T) {
t.Setenv("CHECK_PORT", "70000")
path := filepath.Join(t.TempDir(), "config.yaml")
os.WriteFile(path, []byte("database:\n port: '${CHECK_PORT}'\n"), 0600)
_, err := config.LoadProject(path, moduleConfig{Database: mysql.DefaultConfig(), Server: config.Default()})
var d *config.Error
if !errors.As(err, &d) || d.Field != "database.port" || d.Env != "CHECK_PORT" || d.Line != 2 {
t.Fatal(err)
}
original := mysql.DefaultConfig()
original.Port = 70000
cause := original.Validate()
wrapped := config.AtField("database", cause)
if !errors.Is(wrapped, cause) {
t.Fatal("lost original validation error")
}
// Unknown project errors continue to be redacted rather than echoed.
wrapped = config.AtField("custom", errors.New("private-password"))
if strings.Contains(wrapped.Error(), "private-password") {
t.Fatal(wrapped)
}
}