Omit empty fields from bootstrap config output
probod-bootstrap was writing empty strings and stub blocks such as
`esign: {}` into generated YAML. The post-marshal prune pass caused
part of that by stripping empty leaf strings while leaving empty
parent maps behind.
Drop the prune round-trip in WriteConfig and rely on struct-level
omitzero/omitempty tags plus custom IsZero() helpers on probodconfig.
Only include LLM providers when an API key is set, use a nil map for
extra API headers, and extend the dev-config Makefile recipe with the
local dev defaults already documented in .env.example.
Config loading is unchanged: omitted keys still decode to Go zero
values.
Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
@@ -78,7 +78,7 @@ func (b *Builder) Build() (*probodconfig.FullConfig, error) {
|
||||
Cors: probodconfig.CorsConfig{
|
||||
AllowedOrigins: b.parseOriginsList(b.resolver.getEnv("PROBOD_API_CORS_ALLOWED_ORIGINS")),
|
||||
},
|
||||
ExtraHeaderFields: make(map[string]string),
|
||||
ExtraHeaderFields: nil,
|
||||
GraphQL: probodconfig.GraphQLConfig{
|
||||
ParserTokenLimit: b.resolver.getEnvIntOrDefault("PROBOD_API_GRAPHQL_PARSER_TOKEN_LIMIT", 15000),
|
||||
ComplexityLimit: b.resolver.getEnvIntOrDefault("PROBOD_API_GRAPHQL_COMPLEXITY_LIMIT", 2000),
|
||||
@@ -191,83 +191,78 @@ func (b *Builder) Build() (*probodconfig.FullConfig, error) {
|
||||
ReminderInterval: b.resolver.getEnvIntOrDefault("PROBOD_DOCUMENT_NOTIFICATION_REMINDER_INTERVAL", 86400),
|
||||
},
|
||||
},
|
||||
Agents: probodconfig.AgentsConfig{
|
||||
Providers: map[string]probodconfig.LLMProviderConfig{
|
||||
"openai": {
|
||||
Type: "openai",
|
||||
APIKey: b.resolver.getEnv("PROBOD_OPENAI_API_KEY"),
|
||||
Agents: func() probodconfig.AgentsConfig {
|
||||
defaultProvider := b.resolver.getEnvOrDefault("PROBOD_AGENT_DEFAULT_PROVIDER", "openai")
|
||||
|
||||
return probodconfig.AgentsConfig{
|
||||
Providers: b.buildLLMProviders(),
|
||||
Default: probodconfig.LLMAgentConfig{
|
||||
Provider: defaultProvider,
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_DEFAULT_MODEL_NAME", "gpt-4o"),
|
||||
Temperature: new(b.resolver.getEnvFloatOrDefault("PROBOD_AGENT_DEFAULT_TEMPERATURE", 0.1)),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_DEFAULT_MAX_TOKENS", 4096)),
|
||||
},
|
||||
"anthropic": {
|
||||
Type: "anthropic",
|
||||
APIKey: b.resolver.getEnv("PROBOD_ANTHROPIC_API_KEY"),
|
||||
Probo: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_PROBO_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_PROBO_MODEL_NAME", ""),
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_PROBO_TEMPERATURE"),
|
||||
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_PROBO_MAX_TOKENS"),
|
||||
},
|
||||
},
|
||||
Default: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_DEFAULT_PROVIDER", "openai"),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_DEFAULT_MODEL_NAME", "gpt-4o"),
|
||||
Temperature: new(b.resolver.getEnvFloatOrDefault("PROBOD_AGENT_DEFAULT_TEMPERATURE", 0.1)),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_DEFAULT_MAX_TOKENS", 4096)),
|
||||
},
|
||||
Probo: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_PROBO_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_PROBO_MODEL_NAME", ""),
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_PROBO_TEMPERATURE"),
|
||||
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_PROBO_MAX_TOKENS"),
|
||||
},
|
||||
EvidenceDescriber: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_EVIDENCE_DESCRIBER_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_EVIDENCE_DESCRIBER_MODEL_NAME", ""),
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_EVIDENCE_DESCRIBER_TEMPERATURE"),
|
||||
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_EVIDENCE_DESCRIBER_MAX_TOKENS"),
|
||||
},
|
||||
ThirdPartyVetter: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_VETTER_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_VETTER_MODEL_NAME", ""),
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_THIRD_PARTY_VETTER_TEMPERATURE"),
|
||||
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_THIRD_PARTY_VETTER_MAX_TOKENS"),
|
||||
},
|
||||
ThirdPartyDisambiguation: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_MODEL_NAME", ""),
|
||||
// The disambiguation agent emits a single id plus a
|
||||
// short rationale, but the budget must leave headroom
|
||||
// for reasoning models whose reasoning tokens count
|
||||
// against max_tokens; too small a budget truncates the
|
||||
// JSON.
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_TEMPERATURE"),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_MAX_TOKENS", 4096)),
|
||||
},
|
||||
TrackerMapping: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_MAPPING_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_MAPPING_MODEL_NAME", ""),
|
||||
// The tracker agents emit tiny structured JSON, but
|
||||
// the budget must leave headroom for reasoning
|
||||
// models whose reasoning tokens count against
|
||||
// max_tokens; too small a budget truncates the JSON.
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_TRACKER_MAPPING_TEMPERATURE"),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_TRACKER_MAPPING_MAX_TOKENS", 4096)),
|
||||
},
|
||||
TrackerEnrichment: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_MODEL_NAME", ""),
|
||||
// See the tracker-mapping note: keep ample headroom so
|
||||
// reasoning models do not truncate the structured JSON.
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_TRACKER_ENRICHMENT_TEMPERATURE"),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_MAX_TOKENS", 4096)),
|
||||
},
|
||||
CommonThirdPartyEnrichment: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_MODEL_NAME", ""),
|
||||
// Agent B browses pages and emits a moderate structured
|
||||
// output; the budget must leave headroom for reasoning
|
||||
// models whose reasoning tokens count against max_tokens.
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_TEMPERATURE"),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_MAX_TOKENS", 8192)),
|
||||
},
|
||||
Tools: probodconfig.AgentToolsConfig{
|
||||
FirecrawlAPIKey: b.resolver.getEnv("PROBOD_FIRECRAWL_API_KEY"),
|
||||
},
|
||||
},
|
||||
EvidenceDescriber: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_EVIDENCE_DESCRIBER_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_EVIDENCE_DESCRIBER_MODEL_NAME", ""),
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_EVIDENCE_DESCRIBER_TEMPERATURE"),
|
||||
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_EVIDENCE_DESCRIBER_MAX_TOKENS"),
|
||||
},
|
||||
ThirdPartyVetter: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_VETTER_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_VETTER_MODEL_NAME", ""),
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_THIRD_PARTY_VETTER_TEMPERATURE"),
|
||||
MaxTokens: b.resolver.getEnvIntPtr("PROBOD_AGENT_THIRD_PARTY_VETTER_MAX_TOKENS"),
|
||||
},
|
||||
ThirdPartyDisambiguation: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_MODEL_NAME", ""),
|
||||
// The disambiguation agent emits a single id plus a
|
||||
// short rationale, but the budget must leave headroom
|
||||
// for reasoning models whose reasoning tokens count
|
||||
// against max_tokens; too small a budget truncates the
|
||||
// JSON.
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_TEMPERATURE"),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_THIRD_PARTY_DISAMBIGUATION_MAX_TOKENS", 4096)),
|
||||
},
|
||||
TrackerMapping: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_MAPPING_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_MAPPING_MODEL_NAME", ""),
|
||||
// The tracker agents emit tiny structured JSON, but
|
||||
// the budget must leave headroom for reasoning
|
||||
// models whose reasoning tokens count against
|
||||
// max_tokens; too small a budget truncates the JSON.
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_TRACKER_MAPPING_TEMPERATURE"),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_TRACKER_MAPPING_MAX_TOKENS", 4096)),
|
||||
},
|
||||
TrackerEnrichment: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_MODEL_NAME", ""),
|
||||
// See the tracker-mapping note: keep ample headroom so
|
||||
// reasoning models do not truncate the structured JSON.
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_TRACKER_ENRICHMENT_TEMPERATURE"),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_TRACKER_ENRICHMENT_MAX_TOKENS", 4096)),
|
||||
},
|
||||
CommonThirdPartyEnrichment: probodconfig.LLMAgentConfig{
|
||||
Provider: b.resolver.getEnvOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_PROVIDER", ""),
|
||||
ModelName: b.resolver.getEnvOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_MODEL_NAME", ""),
|
||||
// Agent B browses pages and emits a moderate structured
|
||||
// output; the budget must leave headroom for reasoning
|
||||
// models whose reasoning tokens count against max_tokens.
|
||||
Temperature: b.resolver.getEnvFloatPtr("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_TEMPERATURE"),
|
||||
MaxTokens: new(b.resolver.getEnvIntOrDefault("PROBOD_AGENT_COMMON_THIRD_PARTY_ENRICHMENT_MAX_TOKENS", 8192)),
|
||||
},
|
||||
Tools: probodconfig.AgentToolsConfig{
|
||||
FirecrawlAPIKey: b.resolver.getEnv("PROBOD_FIRECRAWL_API_KEY"),
|
||||
},
|
||||
}
|
||||
}(),
|
||||
CustomDomains: probodconfig.CustomDomainsConfig{
|
||||
RenewalInterval: b.resolver.getEnvIntOrDefault("PROBOD_CUSTOM_DOMAINS_RENEWAL_INTERVAL", 3600),
|
||||
ProvisionInterval: b.resolver.getEnvIntOrDefault("PROBOD_CUSTOM_DOMAINS_PROVISION_INTERVAL", 30),
|
||||
@@ -647,6 +642,30 @@ func (b *Builder) getPgCACertBundle() string {
|
||||
return b.resolver.getEnv("PROBOD_PG_CA_BUNDLE")
|
||||
}
|
||||
|
||||
func (b *Builder) buildLLMProviders() map[string]probodconfig.LLMProviderConfig {
|
||||
providers := map[string]probodconfig.LLMProviderConfig{}
|
||||
|
||||
if apiKey := b.resolver.getEnv("PROBOD_OPENAI_API_KEY"); apiKey != "" {
|
||||
providers["openai"] = probodconfig.LLMProviderConfig{
|
||||
Type: "openai",
|
||||
APIKey: apiKey,
|
||||
}
|
||||
}
|
||||
|
||||
if apiKey := b.resolver.getEnv("PROBOD_ANTHROPIC_API_KEY"); apiKey != "" {
|
||||
providers["anthropic"] = probodconfig.LLMProviderConfig{
|
||||
Type: "anthropic",
|
||||
APIKey: apiKey,
|
||||
}
|
||||
}
|
||||
|
||||
if len(providers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
|
||||
func (b *Builder) parseOriginsList(s string) []string {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
|
||||
@@ -214,6 +214,7 @@ func TestBuilder_Build_Defaults(t *testing.T) {
|
||||
assert.Equal(t, "gpt-4o", cfg.Probod.Agents.Default.ModelName)
|
||||
assert.Equal(t, new(0.1), cfg.Probod.Agents.Default.Temperature)
|
||||
assert.Equal(t, new(4096), cfg.Probod.Agents.Default.MaxTokens)
|
||||
assert.Nil(t, cfg.Probod.Agents.Providers)
|
||||
// Agents config — per-agent overrides are empty (inherit from default)
|
||||
assert.Empty(t, cfg.Probod.Agents.Probo.Provider)
|
||||
assert.Empty(t, cfg.Probod.Agents.Probo.ModelName)
|
||||
|
||||
@@ -37,28 +37,21 @@ func WriteConfig(cfg *probodconfig.FullConfig, path string, format Format) error
|
||||
return fmt.Errorf("create directory %s: %w", dir, err)
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal config: %w", err)
|
||||
}
|
||||
|
||||
var tree any
|
||||
if err := yaml.Unmarshal(data, &tree); err != nil {
|
||||
return fmt.Errorf("unmarshal config: %w", err)
|
||||
}
|
||||
|
||||
pruned := pruneEmptyStrings(tree)
|
||||
var (
|
||||
data []byte
|
||||
err error
|
||||
)
|
||||
|
||||
switch format {
|
||||
case FormatJSON:
|
||||
data, err = json.MarshalIndent(pruned, "", " ")
|
||||
data, err = json.MarshalIndent(cfg, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal pruned config as json: %w", err)
|
||||
return fmt.Errorf("marshal config as json: %w", err)
|
||||
}
|
||||
case FormatYAML:
|
||||
data, err = yaml.Marshal(pruned)
|
||||
data, err = yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal pruned config as yaml: %w", err)
|
||||
return fmt.Errorf("marshal config as yaml: %w", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported config format: %q", format)
|
||||
@@ -70,28 +63,3 @@ func WriteConfig(cfg *probodconfig.FullConfig, path string, format Format) error
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func pruneEmptyStrings(value any) any {
|
||||
switch v := value.(type) {
|
||||
case map[string]any:
|
||||
for key, child := range v {
|
||||
if s, ok := child.(string); ok && s == "" {
|
||||
delete(v, key)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
v[key] = pruneEmptyStrings(child)
|
||||
}
|
||||
|
||||
return v
|
||||
case []any:
|
||||
for i, child := range v {
|
||||
v[i] = pruneEmptyStrings(child)
|
||||
}
|
||||
|
||||
return v
|
||||
default:
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestWriteConfig_FilePermissions(t *testing.T) {
|
||||
assert.Equal(t, os.FileMode(0600), info.Mode().Perm())
|
||||
}
|
||||
|
||||
func TestWriteConfig_DropsEmptyStrings(t *testing.T) {
|
||||
func TestWriteConfig_OmitsOptionalFields(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "probod.yml")
|
||||
|
||||
@@ -96,9 +96,8 @@ func TestWriteConfig_DropsEmptyStrings(t *testing.T) {
|
||||
Tracing: probodconfig.TracingConfig{Addr: ""},
|
||||
},
|
||||
Probod: probodconfig.Config{
|
||||
BaseURL: "http://localhost:8080",
|
||||
EncryptionKey: "",
|
||||
ChromeDPAddr: "",
|
||||
BaseURL: "http://localhost:8080",
|
||||
ChromeDPAddr: "",
|
||||
Pg: probodconfig.PgConfig{
|
||||
Addr: "localhost:5432",
|
||||
Username: "postgres",
|
||||
@@ -124,8 +123,8 @@ func TestWriteConfig_DropsEmptyStrings(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, "http://localhost:8080", probod["base-url"])
|
||||
assert.NotContains(t, probod, "encryption-key")
|
||||
assert.NotContains(t, probod, "chrome-dp-addr")
|
||||
assert.NotContains(t, probod, "esign")
|
||||
|
||||
pg, ok := probod["pg"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
@@ -146,12 +145,168 @@ func TestWriteConfig_DropsEmptyStrings(t *testing.T) {
|
||||
tracing, ok := unit["tracing"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, tracing, "addr")
|
||||
}
|
||||
|
||||
loaded := probodconfig.FullConfig{}
|
||||
err = yaml.Unmarshal(data, &loaded)
|
||||
func TestWriteConfig_OmitsEmptyOptionalBlocks(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "probod.yml")
|
||||
|
||||
cfg := &probodconfig.FullConfig{
|
||||
Probod: probodconfig.Config{
|
||||
BaseURL: "http://localhost:8080",
|
||||
EncryptionKey: "test-key",
|
||||
Api: probodconfig.APIConfig{
|
||||
Addr: ":8080",
|
||||
},
|
||||
Auth: probodconfig.AuthConfig{
|
||||
Cookie: probodconfig.CookieConfig{
|
||||
Name: "SSID",
|
||||
Secret: "secret",
|
||||
},
|
||||
Password: probodconfig.PasswordConfig{
|
||||
Pepper: "pepper",
|
||||
},
|
||||
},
|
||||
TrustCenter: probodconfig.TrustCenterConfig{
|
||||
HTTPAddr: ":80",
|
||||
},
|
||||
CustomDomains: probodconfig.CustomDomainsConfig{
|
||||
RenewalInterval: 3600,
|
||||
},
|
||||
Agents: probodconfig.AgentsConfig{
|
||||
Default: probodconfig.LLMAgentConfig{
|
||||
Provider: "openai",
|
||||
ModelName: "gpt-4o",
|
||||
},
|
||||
ThirdPartyDisambiguation: probodconfig.LLMAgentConfig{
|
||||
MaxTokens: new(4096),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := WriteConfig(cfg, configPath, FormatYAML)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, cfg.Probod.BaseURL, loaded.Probod.BaseURL)
|
||||
assert.Empty(t, loaded.Probod.EncryptionKey)
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
var tree map[string]any
|
||||
|
||||
err = yaml.Unmarshal(data, &tree)
|
||||
require.NoError(t, err)
|
||||
|
||||
probod, ok := tree["probod"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.NotContains(t, probod, "esign")
|
||||
|
||||
api, ok := probod["api"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, api, "cors")
|
||||
assert.NotContains(t, api, "proxy-protocol")
|
||||
assert.NotContains(t, api, "extra-header-fields")
|
||||
|
||||
auth, ok := probod["auth"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, auth, "google")
|
||||
assert.NotContains(t, auth, "microsoft")
|
||||
|
||||
customDomains, ok := probod["custom-domains"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, customDomains, "acme")
|
||||
|
||||
trustCenter, ok := probod["trust-center"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, trustCenter, "proxy-protocol")
|
||||
|
||||
llm, ok := probod["llm"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, llm, "probo")
|
||||
assert.NotContains(t, llm, "third-party-disambiguation")
|
||||
assert.NotContains(t, llm, "tools")
|
||||
}
|
||||
|
||||
func TestWriteConfig_OmitsEmptyProxyProtocolAndCorsSlices(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "probod.yml")
|
||||
|
||||
cfg := &probodconfig.FullConfig{
|
||||
Probod: probodconfig.Config{
|
||||
BaseURL: "http://localhost:8080",
|
||||
Api: probodconfig.APIConfig{
|
||||
Addr: ":8080",
|
||||
ProxyProtocol: probodconfig.ProxyProtocolConfig{
|
||||
TrustedProxies: []string{},
|
||||
},
|
||||
Cors: probodconfig.CorsConfig{
|
||||
AllowedOrigins: []string{},
|
||||
},
|
||||
},
|
||||
TrustCenter: probodconfig.TrustCenterConfig{
|
||||
HTTPAddr: ":10080",
|
||||
ProxyProtocol: probodconfig.ProxyProtocolConfig{
|
||||
TrustedProxies: make([]string, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := WriteConfig(cfg, configPath, FormatYAML)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
var tree map[string]any
|
||||
|
||||
err = yaml.Unmarshal(data, &tree)
|
||||
require.NoError(t, err)
|
||||
|
||||
probod, ok := tree["probod"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
|
||||
api, ok := probod["api"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, api, "proxy-protocol")
|
||||
assert.NotContains(t, api, "cors")
|
||||
|
||||
trustCenter, ok := probod["trust-center"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, trustCenter, "proxy-protocol")
|
||||
}
|
||||
|
||||
func TestWriteConfig_OmitsEmptyExtraHeaderFieldsMap(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
configPath := filepath.Join(tmpDir, "probod.yml")
|
||||
|
||||
cfg := &probodconfig.FullConfig{
|
||||
Probod: probodconfig.Config{
|
||||
BaseURL: "http://localhost:8080",
|
||||
Api: probodconfig.APIConfig{
|
||||
Addr: ":8080",
|
||||
ExtraHeaderFields: map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := WriteConfig(cfg, configPath, FormatYAML)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
require.NoError(t, err)
|
||||
|
||||
var tree map[string]any
|
||||
|
||||
err = yaml.Unmarshal(data, &tree)
|
||||
require.NoError(t, err)
|
||||
|
||||
probod, ok := tree["probod"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
|
||||
api, ok := probod["api"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.NotContains(t, api, "extra-header-fields")
|
||||
}
|
||||
|
||||
func TestWriteConfig_CompleteConfig(t *testing.T) {
|
||||
@@ -178,7 +333,6 @@ func TestWriteConfig_CompleteConfig(t *testing.T) {
|
||||
Cors: probodconfig.CorsConfig{
|
||||
AllowedOrigins: []string{"http://localhost:8080"},
|
||||
},
|
||||
ExtraHeaderFields: map[string]string{},
|
||||
},
|
||||
Pg: probodconfig.PgConfig{
|
||||
Addr: "localhost:5432",
|
||||
@@ -256,7 +410,7 @@ func TestWriteConfig_JSON(t *testing.T) {
|
||||
probod, ok := tree["probod"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "http://localhost:8080", probod["base-url"])
|
||||
assert.NotContains(t, probod, "encryption-key")
|
||||
assert.Equal(t, "", probod["encryption-key"])
|
||||
|
||||
var loaded probodconfig.FullConfig
|
||||
|
||||
|
||||
Reference in New Issue
Block a user