Files
geo/server/internal/shared/config/config_test.go
T
root 794a2d89db feat(server/membership): add ai_points plan policy fields
Introduces ai_points_monthly and ai_point_base_chars on the membership
plan policy, exposes them through TenantPlanAccess and MembershipInfo,
and seeds Pro with a 1500-point monthly allowance across the local,
server, and deploy configs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 11:33:30 +08:00

262 lines
7.1 KiB
Go

package config
import (
"os"
"path/filepath"
"testing"
"time"
)
func TestLoadPrefersEnvLLMAPIKey(t *testing.T) {
t.Setenv("LLM_API_KEY", "env-priority-key")
configPath := writeTestConfig(t, `
llm:
provider: ark
api_key: "config-file-key"
`)
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.LLM.APIKey != "env-priority-key" {
t.Fatalf("expected env api key, got %q", cfg.LLM.APIKey)
}
}
func TestLoadFallsBackToConfigLLMAPIKey(t *testing.T) {
t.Setenv("LLM_API_KEY", "")
configPath := writeTestConfig(t, `
llm:
provider: ark
api_key: "config-file-key"
`)
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.LLM.APIKey != "config-file-key" {
t.Fatalf("expected config api key, got %q", cfg.LLM.APIKey)
}
}
func TestLoadPrefersEnvRetrievalAndQdrantSettings(t *testing.T) {
t.Setenv("SILICONFLOW_API_KEY", "siliconflow-env-key")
t.Setenv("SILICONFLOW_EMBEDDING_MODEL", "env-embedding")
t.Setenv("SILICONFLOW_RERANKER_MODEL", "env-reranker")
t.Setenv("QDRANT_API_KEY", "qdrant-env-key")
t.Setenv("QDRANT_URL", "qdrant-env:6334")
t.Setenv("QDRANT_COLLECTION", "env_collection")
configPath := writeTestConfig(t, `
qdrant:
url: qdrant-config:6334
api_key: "qdrant-config-key"
collection: "config_collection"
retrieval:
provider: siliconflow
api_key: "retrieval-config-key"
embedding_model: "config-embedding"
reranker_model: "config-reranker"
`)
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.Retrieval.APIKey != "siliconflow-env-key" {
t.Fatalf("expected env retrieval api key, got %q", cfg.Retrieval.APIKey)
}
if cfg.Retrieval.EmbeddingModel != "env-embedding" {
t.Fatalf("expected env embedding model, got %q", cfg.Retrieval.EmbeddingModel)
}
if cfg.Retrieval.RerankerModel != "env-reranker" {
t.Fatalf("expected env reranker model, got %q", cfg.Retrieval.RerankerModel)
}
if cfg.Qdrant.APIKey != "qdrant-env-key" {
t.Fatalf("expected env qdrant api key, got %q", cfg.Qdrant.APIKey)
}
if cfg.Qdrant.URL != "qdrant-env:6334" {
t.Fatalf("expected env qdrant url, got %q", cfg.Qdrant.URL)
}
if cfg.Qdrant.Collection != "env_collection" {
t.Fatalf("expected env qdrant collection, got %q", cfg.Qdrant.Collection)
}
}
func TestLoadAppliesBrandLibraryDefaults(t *testing.T) {
configPath := writeTestConfig(t, `
brand_library: {}
`)
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.BrandLibrary.FreeBrandLimit != 1 {
t.Fatalf("expected default free brand limit 1, got %d", cfg.BrandLibrary.FreeBrandLimit)
}
if cfg.BrandLibrary.PaidBrandLimit != 2 {
t.Fatalf("expected default paid brand limit 2, got %d", cfg.BrandLibrary.PaidBrandLimit)
}
if cfg.BrandLibrary.MaxKeywords != 5 {
t.Fatalf("expected default max keywords 5, got %d", cfg.BrandLibrary.MaxKeywords)
}
if cfg.BrandLibrary.MaxQuestionsPerKeyword != 5 {
t.Fatalf("expected default max questions per keyword 5, got %d", cfg.BrandLibrary.MaxQuestionsPerKeyword)
}
}
func TestLoadAppliesSchedulerHTTPDefaultsAndDisableSentinel(t *testing.T) {
defaultConfigPath := writeTestConfig(t, `
scheduler: {}
`)
cfg, err := Load(defaultConfigPath)
if err != nil {
t.Fatalf("Load() default error = %v", err)
}
if cfg.Scheduler.HTTPHost != "127.0.0.1" {
t.Fatalf("expected default scheduler http host 127.0.0.1, got %q", cfg.Scheduler.HTTPHost)
}
if cfg.Scheduler.HTTPPort != 8081 {
t.Fatalf("expected default scheduler http port 8081, got %d", cfg.Scheduler.HTTPPort)
}
disabledConfigPath := writeTestConfig(t, `
scheduler:
http_port: -1
`)
cfg, err = Load(disabledConfigPath)
if err != nil {
t.Fatalf("Load() disabled error = %v", err)
}
if cfg.Scheduler.HTTPPort != -1 {
t.Fatalf("expected scheduler http port -1 to disable server, got %d", cfg.Scheduler.HTTPPort)
}
}
func TestLoadPrefersEnvSchedulerMetricsSettings(t *testing.T) {
t.Setenv("SCHEDULER_HTTP_HOST", "0.0.0.0")
t.Setenv("SCHEDULER_INTERNAL_METRICS_TOKEN", "env-metrics-token")
configPath := writeTestConfig(t, `
scheduler:
http_host: 127.0.0.1
internal_metrics_token: config-token
`)
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.Scheduler.HTTPHost != "0.0.0.0" {
t.Fatalf("expected env scheduler http host, got %q", cfg.Scheduler.HTTPHost)
}
if cfg.Scheduler.InternalMetricsToken != "env-metrics-token" {
t.Fatalf("expected env scheduler metrics token, got %q", cfg.Scheduler.InternalMetricsToken)
}
}
func TestLoadPrefersEnvJWTSettings(t *testing.T) {
t.Setenv("JWT_SECRET", "env-jwt-secret")
t.Setenv("JWT_ACCESS_TTL", "20m")
t.Setenv("JWT_REFRESH_TTL", "720h")
configPath := writeTestConfig(t, `
jwt:
secret: config-jwt-secret
access_ttl: 15m
refresh_ttl: 168h
`)
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.JWT.Secret != "env-jwt-secret" {
t.Fatalf("expected env jwt secret, got %q", cfg.JWT.Secret)
}
if cfg.JWT.AccessTTL != 20*time.Minute {
t.Fatalf("expected env jwt access ttl 20m, got %s", cfg.JWT.AccessTTL)
}
if cfg.JWT.RefreshTTL != 720*time.Hour {
t.Fatalf("expected env jwt refresh ttl 720h, got %s", cfg.JWT.RefreshTTL)
}
}
func TestLoadAppliesMembershipDefaults(t *testing.T) {
configPath := writeTestConfig(t, `
membership: {}
`)
cfg, err := Load(configPath)
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if cfg.Membership.DefaultPlanCode != "free" {
t.Fatalf("expected default plan code free, got %q", cfg.Membership.DefaultPlanCode)
}
if len(cfg.Membership.Plans) != 3 {
t.Fatalf("expected 3 default plans, got %d", len(cfg.Membership.Plans))
}
free, ok := cfg.Membership.FindPlan("free")
if !ok {
t.Fatalf("expected free plan to exist")
}
if free.ArticleGeneration != 3 {
t.Fatalf("expected free article generation 3, got %d", free.ArticleGeneration)
}
if free.ImageStorageBytes != 10*1024*1024 {
t.Fatalf("expected free image storage 10MB, got %d", free.ImageStorageBytes)
}
if free.CompanyLimit != 1 {
t.Fatalf("expected free company limit 1, got %d", free.CompanyLimit)
}
if free.SubscriptionDuration != 72*time.Hour {
t.Fatalf("expected free subscription duration 72h, got %s", free.SubscriptionDuration)
}
if !free.ContactAdminOnExpiry {
t.Fatalf("expected free plan to require admin contact on expiry")
}
pro, ok := cfg.Membership.FindPlan("pro")
if !ok {
t.Fatalf("expected pro plan to exist")
}
if pro.ArticleGeneration != 400 {
t.Fatalf("expected pro article generation 400, got %d", pro.ArticleGeneration)
}
if pro.AIPointsMonthly != 1500 {
t.Fatalf("expected pro ai points 1500, got %d", pro.AIPointsMonthly)
}
if pro.AIPointBaseChars != 1000 {
t.Fatalf("expected pro ai point base chars 1000, got %d", pro.AIPointBaseChars)
}
if pro.CompanyLimit != 2 {
t.Fatalf("expected pro company limit 2, got %d", pro.CompanyLimit)
}
}
func writeTestConfig(t *testing.T, body string) string {
t.Helper()
dir := t.TempDir()
path := filepath.Join(dir, "config.yaml")
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatalf("write config: %v", err)
}
return path
}