Files
tapir/internal/config/config_test.go
T
mathiasandClaude Opus 4.8 c320ed88aa feat(config): add TAPIR_ONBOARD_SUMMARIZE_COUNT (default 3, hard cap 5)
Bounds the connect-time onboarding summary burst (Feature 1). Hard-capped at 5
and clamped (negative->0, >cap->cap) so onboarding can never bulk-fetch; 0
disables. The cap bounds COUNT only — every fetch still flows through the shared
caption rate gate (ADR-014).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:35:05 +02:00

159 lines
4.7 KiB
Go

package config
import (
"strings"
"testing"
"time"
)
// setEnv sets env vars for the test and clears them afterward, so cases don't
// leak into one another. t.Setenv handles restoration.
func setEnv(t *testing.T, kv map[string]string) {
t.Helper()
for k, v := range kv {
t.Setenv(k, v)
}
}
func TestLoad_AppliesDefaults(t *testing.T) {
// Clear the ones with defaults so we observe the fallback, not the host env.
setEnv(t, map[string]string{
"TAPIR_GATEWAY_URL": "",
"TAPIR_SUMMARIZER_MODEL": "",
"TAPIR_YT_TOKEN_REF": "",
"TAPIR_OAUTH_REDIRECT_ADDR": "",
"TAPIR_SUMMARIZER_TIMEOUT": "",
"TAPIR_POLL_INTERVAL": "",
})
c, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if c.GatewayURL != defaultGatewayURL {
t.Errorf("GatewayURL = %q, want default %q", c.GatewayURL, defaultGatewayURL)
}
if c.SummarizerModel != defaultSummarizerModel {
t.Errorf("SummarizerModel = %q, want default %q", c.SummarizerModel, defaultSummarizerModel)
}
if c.YTTokenRef != defaultYTTokenRef {
t.Errorf("YTTokenRef = %q, want default %q", c.YTTokenRef, defaultYTTokenRef)
}
if c.SummarizerTimeout != defaultSummarizerTimeout {
t.Errorf("SummarizerTimeout = %v, want default %v", c.SummarizerTimeout, defaultSummarizerTimeout)
}
if c.PollInterval != 0 {
t.Errorf("PollInterval = %v, want 0 (run once)", c.PollInterval)
}
if c.FetchBackoff != defaultFetchBackoff {
t.Errorf("FetchBackoff = %v, want default %v", c.FetchBackoff, defaultFetchBackoff)
}
if c.AutoSummarizeWindow != defaultAutoSummarizeWindow {
t.Errorf("AutoSummarizeWindow = %v, want default %v", c.AutoSummarizeWindow, defaultAutoSummarizeWindow)
}
}
func TestLoad_OnboardSummarizeCount(t *testing.T) {
cases := []struct {
name, env string
want int
}{
{"default", "", defaultOnboardSummarizeCount},
{"explicit", "4", 4},
{"zero disables", "0", 0},
{"clamped to hard cap", "50", maxOnboardSummarizeCount},
{"negative clamps to zero", "-3", 0},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
setEnv(t, map[string]string{"TAPIR_ONBOARD_SUMMARIZE_COUNT": c.env})
cfg, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.OnboardSummarizeCount != c.want {
t.Fatalf("OnboardSummarizeCount = %d, want %d", cfg.OnboardSummarizeCount, c.want)
}
})
}
}
func TestLoad_OnboardSummarizeCountInvalid(t *testing.T) {
setEnv(t, map[string]string{"TAPIR_ONBOARD_SUMMARIZE_COUNT": "three"})
if _, err := Load(); err == nil {
t.Fatal("Load: want error for non-numeric TAPIR_ONBOARD_SUMMARIZE_COUNT")
}
}
func TestLoad_ParsesValues(t *testing.T) {
setEnv(t, map[string]string{
"TAPIR_USER_ID": "11111111-1111-1111-1111-111111111111",
"TAPIR_GATEWAY_URL": "http://example/v1",
"TAPIR_GATEWAY_KEY": "sk-test",
"TAPIR_SUMMARIZER_MODEL": "iguana/deepseek-r1-14b",
"TAPIR_SUMMARIZER_TIMEOUT": "90s",
"TAPIR_DB_DSN": "postgres://x",
"TAPIR_POLL_INTERVAL": "10m",
"TAPIR_FETCH_BACKOFF": "30m",
"TAPIR_AUTO_SUMMARIZE_WINDOW": "48h",
})
c, err := Load()
if err != nil {
t.Fatalf("Load: %v", err)
}
if c.UserID != "11111111-1111-1111-1111-111111111111" {
t.Errorf("UserID = %q", c.UserID)
}
if c.GatewayKey != "sk-test" {
t.Errorf("GatewayKey = %q", c.GatewayKey)
}
if c.SummarizerModel != "iguana/deepseek-r1-14b" {
t.Errorf("SummarizerModel = %q", c.SummarizerModel)
}
if c.SummarizerTimeout != 90*time.Second {
t.Errorf("SummarizerTimeout = %v, want 90s", c.SummarizerTimeout)
}
if c.PollInterval != 10*time.Minute {
t.Errorf("PollInterval = %v, want 10m", c.PollInterval)
}
if c.FetchBackoff != 30*time.Minute {
t.Errorf("FetchBackoff = %v, want 30m", c.FetchBackoff)
}
if c.AutoSummarizeWindow != 48*time.Hour {
t.Errorf("AutoSummarizeWindow = %v, want 48h", c.AutoSummarizeWindow)
}
}
func TestLoad_RejectsBadDuration(t *testing.T) {
setEnv(t, map[string]string{"TAPIR_SUMMARIZER_TIMEOUT": "not-a-duration"})
if _, err := Load(); err == nil {
t.Fatal("want error on unparsable duration, got nil")
}
}
func TestValidateForRun_ReportsMissing(t *testing.T) {
c := Config{} // nothing set
err := c.ValidateForRun()
if err == nil {
t.Fatal("want error when required run fields are missing")
}
for _, want := range []string{"TAPIR_USER_ID", "TAPIR_DB_DSN", "TAPIR_YT_CLIENT_ID"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("error %q must name missing field %q", err, want)
}
}
}
func TestValidateForAuth_PassesWhenComplete(t *testing.T) {
c := Config{
YTClientID: "id",
YTClientSecret: "secret",
YTTokenRef: "youtube/refresh_token",
SecretsFile: "/tmp/secrets.json",
}
if err := c.ValidateForAuth(); err != nil {
t.Errorf("ValidateForAuth: unexpected error %v", err)
}
}