Files
knowledge-governance/internal/appconfig/config_test.go
T
zheromiku dcdc8d1ddd feat: 知识治理平台(含已验证发布/回滚契约)
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环):

- 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口)
- 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入
  parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截
- 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过):
  * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id)
  * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配
  * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数
  * 回滚 job payload 支持 prior_execution_id(strict 解码)
- 三通道发布+回滚演练实测:publish 4/4 verified,检索命中,
  rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false
- 前端 A·Data-console 风格全站统一,120 用例通过
- 门禁:go vet / go test 全包通过,gofmt 干净
2026-09-30 10:54:06 +08:00

244 lines
9.9 KiB
Go

package appconfig
import (
"testing"
"time"
)
func TestLoadUsesSafeDefaults(t *testing.T) {
values := map[string]string{"DATABASE_URL": "postgres://example"}
config, err := Load(func(key string) string { return values[key] })
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if config.AppAddr != ":8080" || config.WorkerID != "worker-1" {
t.Fatalf("address/worker defaults = %#v", config)
}
if config.PollInterval != time.Second || config.LeaseDuration != 30*time.Second || config.RetryDelay != 5*time.Second {
t.Fatalf("duration defaults = %#v", config)
}
if config.ReleaseWorkerEnabled {
t.Fatal("release worker must be disabled by default")
}
if config.ReleaseConfigFile != "" {
t.Fatalf("release config default = %q", config.ReleaseConfigFile)
}
if config.ProductionReleaseRequestsEnabled {
t.Fatal("production release requests must be disabled by default")
}
if config.TestWeKnoraEnabled {
t.Fatal("test WeKnora must be disabled by default")
}
if config.PPScanEnabled {
t.Fatal("PP source scan must be disabled by default")
}
if config.PPScanAllowedRoot != "" || config.PPScanLedgerPath != "" || config.PPScanManifestPath != "" {
t.Fatalf("PP scan paths must be empty by default: %#v", config)
}
if config.TestWeKnoraAllowDockerHTTP {
t.Fatal("test WeKnora Docker HTTP must be disabled by default")
}
if config.TestWeKnoraAPIKeyFile != "/run/secrets/test_weknora_api_key" {
t.Fatalf("test API key file = %q", config.TestWeKnoraAPIKeyFile)
}
if config.GovernanceSpoolRoot != "/var/lib/governance/spool" {
t.Fatalf("spool root = %q", config.GovernanceSpoolRoot)
}
if config.TestWeKnoraHTTPTimeout != 60*time.Second || config.TestWeKnoraPollInitial != 10*time.Second || config.TestWeKnoraPollMax != time.Minute || config.TestWeKnoraStalledAfter != 30*time.Minute {
t.Fatalf("test WeKnora timing defaults = %#v", config)
}
if config.TestWeKnoraMaxUploadBytes != 50*1024*1024 || config.TestAcceptancePolicyVersion != "structural-v1" {
t.Fatalf("test WeKnora safety defaults = %#v", config)
}
}
func TestLoadRequiresDatabaseURL(t *testing.T) {
if _, err := Load(func(string) string { return "" }); err == nil {
t.Fatal("Load() error = nil, want missing DATABASE_URL error")
}
}
func TestLoadParsesProductionReleaseRequestGate(t *testing.T) {
values := map[string]string{
"DATABASE_URL": "postgres://example",
"PRODUCTION_RELEASE_REQUESTS_ENABLED": "true",
"PRODUCTION_BACKUP_RESTORE_VERIFIED": "true",
"PRODUCTION_RELEASE_AUTHORIZER_REF": "release-owner",
"PRODUCTION_RELEASE_AUTH_TOKEN_FILE": "/run/secrets/production_release_auth_token",
}
config, err := Load(func(key string) string { return values[key] })
if err != nil || !config.ProductionReleaseRequestsEnabled || !config.ProductionBackupRestoreVerified || config.ProductionReleaseAuthorizerRef != "release-owner" || config.ProductionReleaseAuthTokenFile != "/run/secrets/production_release_auth_token" {
t.Fatalf("config=%#v error=%v", config, err)
}
values["PRODUCTION_RELEASE_REQUESTS_ENABLED"] = "not-a-bool"
if _, err := Load(func(key string) string { return values[key] }); err == nil {
t.Fatal("invalid production release request gate must be rejected")
}
values["PRODUCTION_RELEASE_REQUESTS_ENABLED"] = "true"
values["PRODUCTION_BACKUP_RESTORE_VERIFIED"] = "not-a-bool"
if _, err := Load(func(key string) string { return values[key] }); err == nil {
t.Fatal("invalid production backup restore gate must be rejected")
}
}
func TestLoadRequiresProductionReleaseAuthorizationConfigWhenGateEnabled(t *testing.T) {
base := map[string]string{
"DATABASE_URL": "postgres://example",
"PRODUCTION_RELEASE_REQUESTS_ENABLED": "true",
"PRODUCTION_RELEASE_AUTHORIZER_REF": "release-owner",
"PRODUCTION_RELEASE_AUTH_TOKEN_FILE": "/run/secrets/production_release_auth_token",
}
for _, missing := range []string{"PRODUCTION_RELEASE_AUTHORIZER_REF", "PRODUCTION_RELEASE_AUTH_TOKEN_FILE"} {
values := make(map[string]string, len(base))
for key, value := range base {
values[key] = value
}
delete(values, missing)
if _, err := Load(func(key string) string { return values[key] }); err == nil {
t.Fatalf("missing %s accepted", missing)
}
}
base["PRODUCTION_RELEASE_AUTH_TOKEN_FILE"] = "relative/token"
if _, err := Load(func(key string) string { return base[key] }); err == nil {
t.Fatal("relative production release token file accepted")
}
}
func TestLoadRequiresReleaseConfigFileWhenReleaseWorkerEnabled(t *testing.T) {
if _, err := Load(func(key string) string {
values := map[string]string{"DATABASE_URL": "postgres://example", "RELEASE_WORKER_ENABLED": "true"}
return values[key]
}); err == nil {
t.Fatal("enabled release worker without config file accepted")
}
config, err := Load(func(key string) string {
values := map[string]string{"DATABASE_URL": "postgres://example", "RELEASE_WORKER_ENABLED": "true", "RELEASE_CONFIG_FILE": "/run/secrets/release-config"}
return values[key]
})
if err != nil || config.ReleaseConfigFile != "/run/secrets/release-config" {
t.Fatalf("config=%#v err=%v", config, err)
}
}
func TestLoadRequiresAllPPScanPathsWhenEnabled(t *testing.T) {
base := map[string]string{
"DATABASE_URL": "postgres://example",
"PP_SCAN_ENABLED": "true",
"PP_SCAN_ALLOWED_ROOT": "/source",
"PP_SCAN_LEDGER_PATH": "/source/state.jsonl",
"PP_SCAN_MANIFEST_PATH": "/source/manifest.json",
}
for _, missing := range []string{"PP_SCAN_ALLOWED_ROOT", "PP_SCAN_LEDGER_PATH", "PP_SCAN_MANIFEST_PATH"} {
values := make(map[string]string, len(base))
for key, value := range base {
values[key] = value
}
delete(values, missing)
if _, err := Load(func(key string) string { return values[key] }); err == nil {
t.Fatalf("Load() error = nil, want missing %s rejected", missing)
}
}
}
func TestLoadRequiresTestWeKnoraEndpointAndKBWhenEnabled(t *testing.T) {
base := map[string]string{
"DATABASE_URL": "postgres://example",
"TEST_WEKNORA_ENABLED": "true",
}
for _, key := range []string{"TEST_WEKNORA_BASE_URL", "TEST_WEKNORA_KB_ID"} {
values := make(map[string]string, len(base)+1)
for name, value := range base {
values[name] = value
}
if key == "TEST_WEKNORA_BASE_URL" {
values["TEST_WEKNORA_KB_ID"] = "kb-test"
} else {
values["TEST_WEKNORA_BASE_URL"] = "https://weknora.test.example"
}
if _, err := Load(func(name string) string { return values[name] }); err == nil {
t.Fatalf("Load() error = nil, want missing %s error", key)
}
}
}
func TestLoadParsesTestWeKnoraSettings(t *testing.T) {
values := map[string]string{
"DATABASE_URL": "postgres://example",
"TEST_WEKNORA_ENABLED": "true",
"TEST_WEKNORA_ALLOW_DOCKER_HTTP": "true",
"TEST_WEKNORA_BASE_URL": "https://weknora.test.example",
"TEST_WEKNORA_KB_ID": "kb-test",
"TEST_WEKNORA_API_KEY_FILE": "/run/secrets/custom_key",
"GOVERNANCE_SPOOL_ROOT": "/srv/spool",
"TEST_WEKNORA_HTTP_TIMEOUT": "45s",
"TEST_WEKNORA_POLL_INITIAL": "5s",
"TEST_WEKNORA_POLL_MAX": "20s",
"TEST_WEKNORA_STALLED_AFTER": "15m",
"TEST_WEKNORA_MAX_UPLOAD_BYTES": "1048576",
"TEST_ACCEPTANCE_POLICY_VERSION": "structural-v2",
}
config, err := Load(func(name string) string { return values[name] })
if err != nil {
t.Fatalf("Load() error = %v", err)
}
if !config.TestWeKnoraEnabled || !config.TestWeKnoraAllowDockerHTTP || config.TestWeKnoraBaseURL != values["TEST_WEKNORA_BASE_URL"] || config.TestWeKnoraKBID != "kb-test" {
t.Fatalf("test WeKnora identity = %#v", config)
}
if config.TestWeKnoraAPIKeyFile != "/run/secrets/custom_key" || config.GovernanceSpoolRoot != "/srv/spool" {
t.Fatalf("test WeKnora file settings = %#v", config)
}
if config.TestWeKnoraHTTPTimeout != 45*time.Second || config.TestWeKnoraPollInitial != 5*time.Second || config.TestWeKnoraPollMax != 20*time.Second || config.TestWeKnoraStalledAfter != 15*time.Minute {
t.Fatalf("test WeKnora timing settings = %#v", config)
}
if config.TestWeKnoraMaxUploadBytes != 1048576 || config.TestAcceptancePolicyVersion != "structural-v2" {
t.Fatalf("test WeKnora safety settings = %#v", config)
}
}
func TestLoadRejectsUnsafeTestWeKnoraSettings(t *testing.T) {
tests := []struct {
name string
key string
value string
}{
{name: "poll max before initial", key: "TEST_WEKNORA_POLL_MAX", value: "5s"},
{name: "stalled before poll max", key: "TEST_WEKNORA_STALLED_AFTER", value: "30s"},
{name: "upload limit above 50 MiB", key: "TEST_WEKNORA_MAX_UPLOAD_BYTES", value: "52428801"},
{name: "empty policy", key: "TEST_ACCEPTANCE_POLICY_VERSION", value: " "},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
values := map[string]string{
"DATABASE_URL": "postgres://example",
"TEST_WEKNORA_ENABLED": "true",
"TEST_WEKNORA_BASE_URL": "https://weknora.test.example",
"TEST_WEKNORA_KB_ID": "kb-test",
"TEST_WEKNORA_POLL_INITIAL": "10s",
"TEST_WEKNORA_POLL_MAX": "60s",
"TEST_WEKNORA_STALLED_AFTER": "30m",
"TEST_ACCEPTANCE_POLICY_VERSION": "structural-v1",
}
values[tt.key] = tt.value
if _, err := Load(func(name string) string { return values[name] }); err == nil {
t.Fatalf("Load() error = nil, want unsafe %s rejected", tt.key)
}
})
}
}
func TestLoadRequiresBothCatalogRoutingKnowledgeBases(t *testing.T) {
values := map[string]string{
"DATABASE_URL": "postgres://example",
"TEST_INTERNAL_WEKNORA_KB_ID": "kb-internal",
"TEST_EXTERNAL_WEKNORA_KB_ID": "kb-external",
}
config, err := Load(func(key string) string { return values[key] })
if err != nil || config.InternalRoutingKnowledgeBaseID != "kb-internal" || config.ExternalRoutingKnowledgeBaseID != "kb-external" {
t.Fatalf("config=%#v err=%v", config, err)
}
delete(values, "TEST_EXTERNAL_WEKNORA_KB_ID")
if _, err = Load(func(key string) string { return values[key] }); err == nil {
t.Fatal("one-sided routing target must fail closed")
}
}