package config import ( "os" "path/filepath" "regexp" "runtime" "sort" "strconv" "strings" "testing" ) // configKeyCallRE matches viper accessor call sites whose first argument is // a string literal: viper.GetString("db-dsn"), viper.Set("port", v), // viper.SetDefault("env", "development"), viper.IsSet("x"), // viper.BindEnv("x"), and so on. It intentionally also matches a few Viper // bootstrap calls (SetConfigType, SetConfigName, SetEnvPrefix) that are not // application configuration keys at all — those are excluded below via // excludedLiterals, one entry per call site, each commented with why. // // This is a lightweight regex scan, not a Go parser: it does not understand // multi-line string concatenation or build tags, and a call site whose key // argument is a variable or struct field (e.g. viper.GetString(pair.Name)) // is invisible to it by design — those keys are declared data-driven // (internal/config.ConfigKey) and validated by other means // (RequiredKeysUnresolved, ApplyOverlay), not by this literal scan. var configKeyCallRE = regexp.MustCompile(`viper\.(?:Get\w*|Set\w*|IsSet|BindEnv|RegisterAlias)\(\s*"([^"]*)"`) // excludedLiterals lists string literals configKeyCallRE matches that are // not application configuration keys, with the reason each is not one. // Keep this list small — a real new config key belongs in // docs/environment-reference.md, not here. var excludedLiterals = map[string]string{ "yaml": "viper.SetConfigType(\"yaml\") in cmd/root.go — the config file format, not a config key", "mc-config": "viper.SetConfigName(\"mc-config\") in cmd/root.go — the config file's base name, not a config key", "MC": "viper.SetEnvPrefix(\"MC\") in cmd/root.go — the env var prefix itself, not a config key", } // repoRootFromCaller locates the repository root from this test file's own // path, so the test works regardless of the working directory it runs // under (`go test ./...` from the repo root, `go test .` from this // package's directory, an IDE runner, etc). func repoRootFromCaller(t *testing.T) string { t.Helper() _, thisFile, _, ok := runtime.Caller(0) if !ok { t.Fatal("runtime.Caller(0) failed; cannot locate repo root") } // This file lives at /internal/config/config_reference_test.go. root := filepath.Clean(filepath.Join(filepath.Dir(thisFile), "..", "..")) return root } // extractConfigKeys walks dir (skipping _test.go files and non-.go files) // and returns the sorted, de-duplicated set of string-literal keys found at // viper accessor call sites, along with the first file:line each was found // at (for failure messages). func extractConfigKeys(t *testing.T, dir string) (keys []string, firstSeenAt map[string]string) { t.Helper() firstSeenAt = make(map[string]string) seen := make(map[string]bool) err := filepath.WalkDir(dir, func(path string, d os.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { return nil } if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { return nil } content, err := os.ReadFile(path) if err != nil { return err } for i, line := range strings.Split(string(content), "\n") { trimmed := strings.TrimSpace(line) // Skip whole-line comments; this is a line-based scan (the // codebase's viper call sites are all single-line), so a // commented-out call on its own line never produces a false // positive. if strings.HasPrefix(trimmed, "//") { continue } for _, m := range configKeyCallRE.FindAllStringSubmatch(line, -1) { key := m[1] if !seen[key] { seen[key] = true rel, relErr := filepath.Rel(dir, path) if relErr != nil { rel = path } firstSeenAt[key] = rel + ":" + strconv.Itoa(i+1) } } } return nil }) if err != nil { t.Fatalf("walking %s: %v", dir, err) } for k := range seen { keys = append(keys, k) } sort.Strings(keys) return keys, firstSeenAt } // TestEnvironmentReferenceDocumentsEveryConfigKey scans cmd/ and internal/ // for every string-literal key read or written through Viper and asserts // each one (other than the deliberately excluded bootstrap literals) is // documented in docs/environment-reference.md. It is a completeness check, // not a correctness check: it does not verify the documented purpose, // default, or required/optional call is accurate, only that the key is not // silently undocumented. func TestEnvironmentReferenceDocumentsEveryConfigKey(t *testing.T) { root := repoRootFromCaller(t) docPath := filepath.Join(root, "docs", "environment-reference.md") docBytes, err := os.ReadFile(docPath) if err != nil { t.Fatalf("reading %s: %v", docPath, err) } doc := string(docBytes) var allKeys []string allFirstSeen := make(map[string]string) for _, sub := range []string{"cmd", "internal"} { dir := filepath.Join(root, sub) keys, firstSeen := extractConfigKeys(t, dir) allKeys = append(allKeys, keys...) for k, v := range firstSeen { // Keep the first location found across both directories. if _, ok := allFirstSeen[k]; !ok { allFirstSeen[k] = filepath.Join(sub, v) } } } sort.Strings(allKeys) var missing []string for _, key := range allKeys { if _, excluded := excludedLiterals[key]; excluded { continue } if !strings.Contains(doc, key) { missing = append(missing, key) } } if len(missing) > 0 { var b strings.Builder b.WriteString("the following config keys are read via viper but not documented in docs/environment-reference.md:\n") for _, key := range missing { b.WriteString(" - " + key + " (first seen at " + allFirstSeen[key] + ")\n") } b.WriteString("add each to docs/environment-reference.md (or, if it is deliberately not an application config key, to excludedLiterals in internal/config/config_reference_test.go with a reason)") t.Error(b.String()) } } // TestExcludedLiteralsStillMatchesRealCallSites guards against a stale // allowlist entry: if a bootstrap call site is ever rewritten to no longer // use one of these literals, the entry should be removed rather than left // silently unused. func TestExcludedLiteralsStillMatchesRealCallSites(t *testing.T) { root := repoRootFromCaller(t) var found = make(map[string]bool) for _, sub := range []string{"cmd", "internal"} { keys, _ := extractConfigKeys(t, filepath.Join(root, sub)) for _, k := range keys { found[k] = true } } for literal := range excludedLiterals { if !found[literal] { t.Errorf("excludedLiterals entry %q no longer matches any viper call site; remove it from internal/config/config_reference_test.go", literal) } } }