195 lines
5.9 KiB
Go
195 lines
5.9 KiB
Go
package config
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func validConfig() Config {
|
|
return Config{
|
|
GLPIURL: "https://glpi.internal.example",
|
|
GLPIClientID: "client-id",
|
|
GLPIClientSecret: "real-secret-value",
|
|
GLPIUsername: "svc-agent",
|
|
GLPIPassword: "real-password-value",
|
|
GLPIAllowedStatusIDs: []int64{1},
|
|
WebAllowAnonymous: true,
|
|
Workers: 1,
|
|
QueueSize: 1,
|
|
CategoryConfidence: .9,
|
|
ReplyConfidence: .9,
|
|
KnowledgeMinScore: .8,
|
|
KnowledgeAllowedSources: []string{"internal-kb"},
|
|
KnowledgeAutoReplySources: []string{"internal-kb"},
|
|
CommunicationLanguage: "de-DE",
|
|
CommunicationStyle: "formal",
|
|
OllamaTimeout: time.Minute,
|
|
OllamaNumPredict: 256,
|
|
OllamaKeepAlive: 10 * time.Minute,
|
|
OllamaThink: false,
|
|
OllamaMaxConcurrent: 1,
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsPlainHTTPByDefault(t *testing.T) {
|
|
c := validConfig()
|
|
c.GLPIURL = "http://glpi.internal.example"
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected plain HTTP to be rejected")
|
|
}
|
|
c.GLPIAllowInsecureHTTP = true
|
|
if err := c.Validate(); err != nil {
|
|
t.Fatalf("explicit insecure override should validate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsPlaceholderSecrets(t *testing.T) {
|
|
c := validConfig()
|
|
c.GLPIClientSecret = "CHANGE_ME"
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected placeholder secret to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestValidateRequiresAllowedStatus(t *testing.T) {
|
|
c := validConfig()
|
|
c.GLPIAllowedStatusIDs = nil
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected empty status whitelist to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsAutoReplySourceOutsideAllowlist(t *testing.T) {
|
|
c := validConfig()
|
|
c.KnowledgeAutoReplySources = []string{"vendor-docs"}
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected auto-reply source outside allowlist to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsUnknownCommunicationStyle(t *testing.T) {
|
|
c := validConfig()
|
|
c.CommunicationStyle = "super-friendly"
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected unsupported communication style to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestValidateRequiresMajorIncidentFilter(t *testing.T) {
|
|
c := validConfig()
|
|
c.ContextEnabled = true
|
|
c.ContextTimeout = 1
|
|
c.MajorIncidentsEnabled = true
|
|
c.GLPIMajorIncidentLimit = 20
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected major incident filter to be required")
|
|
}
|
|
}
|
|
|
|
func TestValidateUptimeKumaMetricsRequiresAPIKey(t *testing.T) {
|
|
c := validConfig()
|
|
c.ContextEnabled = true
|
|
c.ContextTimeout = 1
|
|
c.UptimeKumaEnabled = true
|
|
c.UptimeKumaURL = "https://uptime.internal.example"
|
|
c.UptimeKumaMode = "metrics"
|
|
c.UptimeKumaMaxIssues = 20
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected Uptime Kuma API key to be required in metrics mode")
|
|
}
|
|
c.UptimeKumaAPIKey = "test-api-key"
|
|
if err := c.Validate(); err != nil {
|
|
t.Fatalf("expected metrics mode with API key to validate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateUserDeviceFilterRequiresUserPlaceholder(t *testing.T) {
|
|
c := validConfig()
|
|
c.ContextEnabled = true
|
|
c.ContextTimeout = 1
|
|
c.UserDeviceContextEnabled = true
|
|
c.GLPIUserDevicePaths = []string{"/Assets/Computer"}
|
|
c.GLPIUserDeviceFilterTemplate = "user.id==42"
|
|
c.GLPIUserDeviceLimit = 20
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected user device filter template to require {{user_id}}")
|
|
}
|
|
}
|
|
|
|
func TestKnowledgeWebEditRequiresAuthentication(t *testing.T) {
|
|
c := validConfig()
|
|
c.KnowledgeWebEditEnabled = true
|
|
c.WebAllowAnonymous = true
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected anonymous KB editing to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestValidateGLPIKBRequiresAllowedSource(t *testing.T) {
|
|
c := validConfig()
|
|
c.GLPIKBEnabled = true
|
|
c.GLPIKBPath = "auto"
|
|
c.GLPIKBLimit = 100
|
|
c.GLPIKBSyncInterval = 10 * time.Minute
|
|
c.GLPIKBSource = "glpi-kb"
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected GLPI KB source outside allowlist to be rejected")
|
|
}
|
|
c.KnowledgeAllowedSources = []string{"internal-kb", "glpi-kb"}
|
|
if err := c.Validate(); err != nil {
|
|
t.Fatalf("expected GLPI KB config to validate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateGLPIKBAutoReplyRequiresCategoryWhitelist(t *testing.T) {
|
|
c := validConfig()
|
|
c.GLPIKBEnabled = true
|
|
c.GLPIKBPath = "auto"
|
|
c.GLPIKBLimit = 100
|
|
c.GLPIKBSyncInterval = 10 * time.Minute
|
|
c.GLPIKBSource = "glpi-kb"
|
|
c.KnowledgeAllowedSources = []string{"internal-kb", "glpi-kb"}
|
|
c.KnowledgeAutoReplySources = []string{"internal-kb", "glpi-kb"}
|
|
c.GLPIKBAutoReply = true
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected auto reply category whitelist to be required")
|
|
}
|
|
c.GLPIKBAutoReplyCategoryIDs = []int64{3}
|
|
if err := c.Validate(); err != nil {
|
|
t.Fatalf("expected explicit category whitelist to validate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateRejectsInvalidKnowledgeScoringConfig(t *testing.T) {
|
|
c := validConfig()
|
|
c.KnowledgeSemanticWeight = 1.2
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected knowledge weight > 1 to be rejected")
|
|
}
|
|
c = validConfig()
|
|
c.KnowledgeChunkWords = 100
|
|
c.KnowledgeChunkOverlapWords = 100
|
|
if err := c.Validate(); err == nil {
|
|
t.Fatal("expected overlap >= chunk size to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestValidateKnowledgeCategoryModeTrimsWhitespace(t *testing.T) {
|
|
c := validConfig()
|
|
c.KnowledgeCategoryMode = " UNSCOPED "
|
|
if err := c.Validate(); err != nil {
|
|
t.Fatalf("expected whitespace/case normalized category mode to validate: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestEnvNormalizedLowerDefaultsBlankOrWhitespace(t *testing.T) {
|
|
t.Setenv("KNOWLEDGE_CATEGORY_MODE", " ")
|
|
if got := envNormalizedLower("KNOWLEDGE_CATEGORY_MODE", "unscoped"); got != "unscoped" {
|
|
t.Fatalf("expected unscoped default, got %q", got)
|
|
}
|
|
t.Setenv("KNOWLEDGE_CATEGORY_MODE", " STRICT ")
|
|
if got := envNormalizedLower("KNOWLEDGE_CATEGORY_MODE", "unscoped"); got != "strict" {
|
|
t.Fatalf("expected strict, got %q", got)
|
|
}
|
|
}
|