Introduce a commercial license option alongside AGPL-3.0-only, require a CLA for contributors, and document the terms in COMMERCIAL.md and NOTICE. Add a script to stamp SPDX headers on Go files and apply it across the tree.
187 lines
6.6 KiB
Go
187 lines
6.6 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
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 <repoRoot>/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)
|
|
}
|
|
}
|
|
}
|