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.
137 lines
5.0 KiB
Go
137 lines
5.0 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
package config
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestTypeOfInference covers every case design D1 names: an explicit Type
|
|
// wins; a non-empty Enum implies enum; otherwise the Default's Go type
|
|
// decides (bool, int, []string, time.Duration); a nil Default, or any other
|
|
// Go type (a bare string), implies string.
|
|
func TestTypeOfInference(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
key ConfigKey
|
|
want Type
|
|
}{
|
|
{"explicit type wins over a nil default", ConfigKey{Type: TypeURL}, TypeURL},
|
|
{"explicit type wins over a matching default", ConfigKey{Type: TypeString, Default: "blue"}, TypeString},
|
|
{"non-empty enum implies enum", ConfigKey{Default: "https", Enum: []string{"http", "https"}}, TypeEnum},
|
|
{"bool default implies bool", ConfigKey{Default: false}, TypeBool},
|
|
{"int default implies int", ConfigKey{Default: 7}, TypeInt},
|
|
{"[]string default implies list", ConfigKey{Default: []string(nil)}, TypeList},
|
|
{"time.Duration default implies duration", ConfigKey{Default: time.Hour}, TypeDuration},
|
|
{"string default implies string", ConfigKey{Default: "blue"}, TypeString},
|
|
{"nil default implies string", ConfigKey{}, TypeString},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := tc.key.TypeOf(); got != tc.want {
|
|
t.Errorf("TypeOf() = %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCheckSpecs covers every declaration-shape refusal design D1/D6 names,
|
|
// and that a well-formed spec (mirroring every real declaration shape:
|
|
// string, url, duration, positive duration, int, bool, list, enum) passes
|
|
// clean.
|
|
func TestCheckSpecs(t *testing.T) {
|
|
t.Run("a well-formed spec passes", func(t *testing.T) {
|
|
specs := []ConfigKey{
|
|
{Name: "it-color", Default: "blue"},
|
|
{Name: "it-url", Type: TypeURL},
|
|
{Name: "it-url-with-default", Type: TypeURL, Default: "https://it.example.com"},
|
|
{Name: "it-timeout", Default: time.Hour},
|
|
{Name: "it-sync-interval", Default: time.Hour, Positive: true},
|
|
{Name: "it-limit", Default: 7},
|
|
{Name: "it-count", Default: 3, Positive: true},
|
|
{Name: "it-enabled", Default: false},
|
|
{Name: "it-domains", Default: []string(nil)},
|
|
{Name: "it-scheme", Default: "https", Enum: []string{"http", "https"}},
|
|
}
|
|
if err := CheckSpecs(specs); err != nil {
|
|
t.Fatalf("expected a well-formed spec to pass, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("a type that contradicts its default is refused naming the key and both types", func(t *testing.T) {
|
|
err := CheckSpecs([]ConfigKey{{Name: "it-flag", Type: TypeDuration, Default: true}})
|
|
if err == nil {
|
|
t.Fatal("expected an error, got nil")
|
|
}
|
|
for _, want := range []string{"it-flag", "duration", "bool"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("error %q missing %q", err.Error(), want)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("TypeEnum with no Enum members is refused", func(t *testing.T) {
|
|
err := CheckSpecs([]ConfigKey{{Name: "it-mode", Type: TypeEnum}})
|
|
if err == nil || !strings.Contains(err.Error(), "it-mode") {
|
|
t.Fatalf("want an error naming it-mode, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("an enum with a non-string default is refused", func(t *testing.T) {
|
|
err := CheckSpecs([]ConfigKey{{Name: "it-mode", Default: true, Enum: []string{"a", "b"}}})
|
|
if err == nil || !strings.Contains(err.Error(), "it-mode") {
|
|
t.Fatalf("want an error naming it-mode, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Positive on a bool key is refused", func(t *testing.T) {
|
|
err := CheckSpecs([]ConfigKey{{Name: "it-enabled", Default: false, Positive: true}})
|
|
if err == nil || !strings.Contains(err.Error(), "it-enabled") {
|
|
t.Fatalf("want an error naming it-enabled, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Positive on a string key is refused", func(t *testing.T) {
|
|
err := CheckSpecs([]ConfigKey{{Name: "it-color", Default: "blue", Positive: true}})
|
|
if err == nil || !strings.Contains(err.Error(), "it-color") {
|
|
t.Fatalf("want an error naming it-color, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("Positive on an int or duration key is fine", func(t *testing.T) {
|
|
specs := []ConfigKey{
|
|
{Name: "it-limit", Default: 7, Positive: true},
|
|
{Name: "it-timeout", Default: time.Hour, Positive: true},
|
|
}
|
|
if err := CheckSpecs(specs); err != nil {
|
|
t.Fatalf("expected no error, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("a URL key with no default contradicts nothing", func(t *testing.T) {
|
|
if err := CheckSpecs([]ConfigKey{{Name: "it-url", Type: TypeURL}}); err != nil {
|
|
t.Fatalf("expected no error, got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("every problem aggregates into one error", func(t *testing.T) {
|
|
specs := []ConfigKey{
|
|
{Name: "it-flag", Type: TypeDuration, Default: true},
|
|
{Name: "it-mode", Type: TypeEnum},
|
|
{Name: "it-enabled", Default: false, Positive: true},
|
|
}
|
|
err := CheckSpecs(specs)
|
|
if err == nil {
|
|
t.Fatal("expected an error, got nil")
|
|
}
|
|
for _, want := range []string{"it-flag", "it-mode", "it-enabled"} {
|
|
if !strings.Contains(err.Error(), want) {
|
|
t.Errorf("aggregated error missing %q: %v", want, err)
|
|
}
|
|
}
|
|
})
|
|
}
|