140 lines
5.7 KiB
Go
140 lines
5.7 KiB
Go
package config
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func dispatcherFileFixture(t *testing.T) map[string]any {
|
|
t.Helper()
|
|
t.Setenv("MQ_TEST_OSS_KEY", "test-key-not-a-real-credential")
|
|
t.Setenv("MQ_TEST_OSS_SECRET", "test-secret-not-a-real-credential")
|
|
return map[string]any{
|
|
"schema_version": "1.0",
|
|
"dispatcher_id": "c046b893-8628-4589-ae50-619d049248a6",
|
|
"oss": map[string]any{
|
|
"endpoint": "https://oss.example.invalid", "region": "test-region",
|
|
"bucket": "test-bucket", "object_prefix": "recordings",
|
|
"access_key_id_env": "MQ_TEST_OSS_KEY", "access_key_secret_env": "MQ_TEST_OSS_SECRET",
|
|
},
|
|
}
|
|
}
|
|
|
|
func writeDispatcherFile(t *testing.T, raw []byte) string {
|
|
t.Helper()
|
|
path := filepath.Join(t.TempDir(), "dispatcher.json")
|
|
if err := os.WriteFile(path, raw, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return path
|
|
}
|
|
|
|
func marshalDispatcherFile(t *testing.T, value map[string]any) []byte {
|
|
t.Helper()
|
|
raw, err := json.Marshal(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func TestLoadDispatcherFile(t *testing.T) {
|
|
value := dispatcherFileFixture(t)
|
|
t.Setenv("DISPATCHER_OSS_ENDPOINT", "https://ignored.example.invalid")
|
|
cfg := Config{Mode: "mock", DBPath: "preserved.db", OSSGrantTTL: time.Hour, OSSBucket: "old"}
|
|
if err := cfg.LoadDispatcherFile(writeDispatcherFile(t, marshalDispatcherFile(t, value))); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cfg.DispatcherID != value["dispatcher_id"] || cfg.OSSEndpoint != "https://oss.example.invalid" || cfg.OSSRegion != "test-region" || cfg.OSSBucket != "test-bucket" || cfg.OSSKeyPrefix != "recordings" || cfg.OSSGrantTTL != 15*time.Minute {
|
|
t.Fatalf("wrong file mapping: id=%q endpoint=%q region=%q bucket=%q prefix=%q ttl=%v", cfg.DispatcherID, cfg.OSSEndpoint, cfg.OSSRegion, cfg.OSSBucket, cfg.OSSKeyPrefix, cfg.OSSGrantTTL)
|
|
}
|
|
if cfg.OSSAccessKeyID != os.Getenv("MQ_TEST_OSS_KEY") || cfg.OSSAccessKeySecret != os.Getenv("MQ_TEST_OSS_SECRET") {
|
|
t.Fatal("explicit credential references not resolved")
|
|
}
|
|
if cfg.Mode != "mock" || cfg.DBPath != "preserved.db" {
|
|
t.Fatal("unrelated deployment settings overwritten")
|
|
}
|
|
}
|
|
|
|
func TestLoadDispatcherFileRejectsInvalidValuesAtomically(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
mutate func(map[string]any)
|
|
}{
|
|
{"version", func(m map[string]any) { m["schema_version"] = "2.0" }},
|
|
{"missing ID", func(m map[string]any) { delete(m, "dispatcher_id") }},
|
|
{"invalid ID", func(m map[string]any) { m["dispatcher_id"] = "dispatcher" }},
|
|
{"unknown root", func(m map[string]any) { m["legacy"] = true }},
|
|
{"case alias", func(m map[string]any) { m["SCHEMA_VERSION"] = m["schema_version"]; delete(m, "schema_version") }},
|
|
{"nested case alias", func(m map[string]any) {
|
|
m["oss"].(map[string]any)["BUCKET"] = "test-bucket"
|
|
delete(m["oss"].(map[string]any), "bucket")
|
|
}},
|
|
{"missing oss", func(m map[string]any) { delete(m, "oss") }},
|
|
{"plaintext secret", func(m map[string]any) { m["oss"].(map[string]any)["access_key_secret"] = "not-a-real-secret" }},
|
|
{"TTL override", func(m map[string]any) { m["oss"].(map[string]any)["grant_ttl_seconds"] = 3600 }},
|
|
{"missing bucket", func(m map[string]any) { delete(m["oss"].(map[string]any), "bucket") }},
|
|
{"blank region", func(m map[string]any) { m["oss"].(map[string]any)["region"] = " " }},
|
|
{"invalid endpoint", func(m map[string]any) { m["oss"].(map[string]any)["endpoint"] = "file:///tmp/oss" }},
|
|
{"URL credentials", func(m map[string]any) {
|
|
m["oss"].(map[string]any)["endpoint"] = "https://user:password@example.invalid"
|
|
}},
|
|
{"empty prefix", func(m map[string]any) { m["oss"].(map[string]any)["object_prefix"] = "" }},
|
|
{"prefix escape", func(m map[string]any) { m["oss"].(map[string]any)["object_prefix"] = "recordings/../other" }},
|
|
{"invalid env reference", func(m map[string]any) { m["oss"].(map[string]any)["access_key_id_env"] = "${SECRET}" }},
|
|
{"missing credential", func(m map[string]any) { m["oss"].(map[string]any)["access_key_secret_env"] = "MQ_TEST_MISSING" }},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
value := dispatcherFileFixture(t)
|
|
t.Setenv("MQ_TEST_MISSING", "")
|
|
tc.mutate(value)
|
|
before := Config{DispatcherID: "unchanged", OSSAccessKeySecret: "untouched"}
|
|
cfg := before
|
|
err := cfg.LoadDispatcherFile(writeDispatcherFile(t, marshalDispatcherFile(t, value)))
|
|
if err == nil {
|
|
t.Fatal("invalid file accepted")
|
|
}
|
|
if cfg != before {
|
|
t.Fatal("invalid file partially changed configuration")
|
|
}
|
|
if strings.Contains(err.Error(), os.Getenv("MQ_TEST_OSS_SECRET")) {
|
|
t.Fatal("credential leaked in error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoadDispatcherFileRejectsDuplicateAndTrailingJSON(t *testing.T) {
|
|
raw := string(marshalDispatcherFile(t, dispatcherFileFixture(t)))
|
|
for _, bad := range []string{
|
|
strings.Replace(raw, `"schema_version":"1.0"`, `"schema_version":"1.0","schema_version":"1.0"`, 1),
|
|
strings.Replace(raw, `"bucket":"test-bucket"`, `"bucket":"test-bucket","bucket":"other"`, 1),
|
|
raw + ` {}`, raw + ` null`, raw[:len(raw)-1], `null`, `[]`,
|
|
strings.Repeat("[", 100) + strings.Repeat("]", 100),
|
|
strings.Repeat(" ", 65537), string([]byte{0xff}),
|
|
} {
|
|
var cfg Config
|
|
if err := cfg.LoadDispatcherFile(writeDispatcherFile(t, []byte(bad))); err == nil {
|
|
t.Fatal("invalid/ambiguous JSON accepted")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadDispatcherFileDoesNotFallBack(t *testing.T) {
|
|
var cfg Config
|
|
t.Setenv("DISPATCHER_OSS_BUCKET", "legacy-bucket")
|
|
for _, path := range []string{"", filepath.Join(t.TempDir(), "missing.json"), t.TempDir()} {
|
|
if err := cfg.LoadDispatcherFile(path); err == nil {
|
|
t.Fatal("missing/invalid file accepted")
|
|
}
|
|
}
|
|
var absent *Config
|
|
if err := absent.LoadDispatcherFile("unused"); err == nil {
|
|
t.Fatal("nil config accepted")
|
|
}
|
|
}
|