Files
knowledge-governance/internal/platform/postgres/ui_settings_test.go
T
姚章浩 1db21fb899 feat(settings): 服务配置四项化并保存时真校验
- 生产/测试 WeKnora 地址、租户 ID、知识库 ID 由静态 env 收敛为版本化 UI 设置,保留修订号与操作人审计
- 新增 serviceverify:保存前真调目标端点探测,端点无效才拒绝;网络抖动或无凭据仅告警
- 迁移 000042(生产 WeKnora 端点)/000043(服务目标);补齐设置候选与校验契约用例
2026-09-30 10:54:07 +08:00

107 lines
7.1 KiB
Go

package postgres
import (
"context"
"errors"
"strings"
"testing"
"time"
"knowledge-governance/internal/governance"
)
func TestUISettingsCandidateAndActiveProjectionAreVersioned(t *testing.T) {
now := time.Date(2026, 9, 10, 6, 0, 0, 0, time.UTC)
row := uiSettingsRow{id: "settings-safe", state: "validated", documentRefresh: 30, resourceRefresh: 60, stalledAfter: 3600, finalizingAfter: 14400, testBaseURL: "https://weknora.test.example", productionBaseURL: "https://weknora.prod.example", productionTenantID: "10000", productionKnowledgeBaseID: "kb-prod-1", updatedBy: "engineer-1", createdAt: now}
db := &recordingDB{row: row}
candidate, err := NewStore(db).CreateUISettingsCandidate(context.Background(), governance.CreateUISettingsCandidateRequest{DocumentRefreshSeconds: 30, ResourceRefreshSeconds: 60, StalledAfterSeconds: 3600, FinalizingAfterSeconds: 14400, IngestionConcurrency: 2, TestWeKnoraBaseURL: "https://weknora.test.example", ProductionWeKnoraBaseURL: "https://weknora.prod.example", ProductionTenantID: "10000", ProductionKnowledgeBaseID: "kb-prod-1", UpdatedBy: "engineer-1", Now: now})
if err != nil {
t.Fatal(err)
}
if candidate.State != "validated" || candidate.DocumentRefreshSeconds != 30 || candidate.UpdatedBy != "engineer-1" || candidate.TestWeKnoraBaseURL != "https://weknora.test.example" || candidate.ProductionWeKnoraBaseURL != "https://weknora.prod.example" || candidate.ProductionTenantID != "10000" || candidate.ProductionKnowledgeBaseID != "kb-prod-1" {
t.Fatalf("candidate=%#v", candidate)
}
sql := strings.ToLower(db.queryRowSQL)
for _, required := range []string{"insert into ui_setting_revisions", "'validated'", "test_weknora_base_url", "production_weknora_base_url", "production_tenant_id", "production_knowledge_base_id", "returning"} {
if !strings.Contains(sql, required) {
t.Fatalf("candidate SQL missing %q", required)
}
}
for _, forbidden := range []string{"secret", "token", "password", "api_key"} {
if strings.Contains(sql, forbidden) {
t.Fatalf("candidate SQL contains forbidden field %q", forbidden)
}
}
db.row = uiSettingsRow{id: "settings-active", state: "active", documentRefresh: 15, resourceRefresh: 30, stalledAfter: 1800, finalizingAfter: 7200, updatedBy: "system:migration", createdAt: now, activatedAt: &now}
active, err := NewStore(db).GetActiveUISettings(context.Background())
if err != nil || active.State != "active" || active.FinalizingAfterSeconds != 7200 {
t.Fatalf("active=%#v error=%v", active, err)
}
if !strings.Contains(strings.ToLower(db.queryRowSQL), "where state = 'active'") {
t.Fatal("active settings query must select only active revision")
}
}
func TestUISettingsRejectInvalidCandidate(t *testing.T) {
_, err := NewStore(&recordingDB{}).CreateUISettingsCandidate(context.Background(), governance.CreateUISettingsCandidateRequest{DocumentRefreshSeconds: 31, ResourceRefreshSeconds: 60, StalledAfterSeconds: 3600, FinalizingAfterSeconds: 14400, UpdatedBy: "engineer-1", Now: time.Now()})
if !errors.Is(err, ErrInvalidUISettings) {
t.Fatalf("error=%v", err)
}
// The web-typed production endpoint is validated before it can reach the
// database: only a bare http(s) origin is accepted, never a URL with
// embedded credentials, a query string or a fragment.
for _, endpoint := range []string{
"weknora.prod.example",
"https://user:pass@weknora.prod.example",
"https://weknora.prod.example/?api_key=leaked",
"https://weknora.prod.example/#fragment",
" http://weknora.prod.example",
} {
request := governance.CreateUISettingsCandidateRequest{DocumentRefreshSeconds: 30, ResourceRefreshSeconds: 60, StalledAfterSeconds: 3600, FinalizingAfterSeconds: 14400, IngestionConcurrency: 2, ProductionWeKnoraBaseURL: endpoint, UpdatedBy: "admin-1", Now: time.Now()}
if _, err := NewStore(&recordingDB{}).CreateUISettingsCandidate(context.Background(), request); !errors.Is(err, ErrInvalidUISettings) {
t.Fatalf("endpoint %q was accepted, error=%v", endpoint, err)
}
}
// Same rule for the test endpoint, plus the two new ID fields: a tenant ID is
// decimal digits, a knowledge base ID carries no whitespace and no URL pieces.
for _, testEndpoint := range []string{"weknora.test.example", "https://user:pass@weknora.test.example", "https://weknora.test.example?token=abc", " https://weknora.test.example"} {
request := governance.CreateUISettingsCandidateRequest{DocumentRefreshSeconds: 30, ResourceRefreshSeconds: 60, StalledAfterSeconds: 3600, FinalizingAfterSeconds: 14400, IngestionConcurrency: 2, TestWeKnoraBaseURL: testEndpoint, UpdatedBy: "admin-1", Now: time.Now()}
if _, err := NewStore(&recordingDB{}).CreateUISettingsCandidate(context.Background(), request); !errors.Is(err, ErrInvalidUISettings) {
t.Fatalf("test endpoint %q was accepted, error=%v", testEndpoint, err)
}
}
for _, tenantID := range []string{"abc", "10 000", "-1", "0", "10000.5", "10000000000000000000000"} {
request := governance.CreateUISettingsCandidateRequest{DocumentRefreshSeconds: 30, ResourceRefreshSeconds: 60, StalledAfterSeconds: 3600, FinalizingAfterSeconds: 14400, IngestionConcurrency: 2, ProductionTenantID: tenantID, UpdatedBy: "admin-1", Now: time.Now()}
if _, err := NewStore(&recordingDB{}).CreateUISettingsCandidate(context.Background(), request); !errors.Is(err, ErrInvalidUISettings) {
t.Fatalf("production tenant id %q was accepted, error=%v", tenantID, err)
}
}
for _, kbID := range []string{"kb/abc", "kb abc", "kb-abc?x", "https://weknora.prod.example/kb", "kb#frag"} {
request := governance.CreateUISettingsCandidateRequest{DocumentRefreshSeconds: 30, ResourceRefreshSeconds: 60, StalledAfterSeconds: 3600, FinalizingAfterSeconds: 14400, IngestionConcurrency: 2, ProductionKnowledgeBaseID: kbID, UpdatedBy: "admin-1", Now: time.Now()}
if _, err := NewStore(&recordingDB{}).CreateUISettingsCandidate(context.Background(), request); !errors.Is(err, ErrInvalidUISettings) {
t.Fatalf("production knowledge base id %q was accepted, error=%v", kbID, err)
}
}
}
type uiSettingsRow struct {
id, state, updatedBy, errorCode string
documentRefresh, resourceRefresh, stalledAfter, finalizingAfter int
ingestionConcurrency int
testBaseURL, productionBaseURL string
productionTenantID, productionKnowledgeBaseID string
createdAt time.Time
activatedAt, retiredAt *time.Time
}
func (row uiSettingsRow) Scan(dest ...any) error {
*dest[0].(*string), *dest[1].(*string) = row.id, row.state
*dest[2].(*int), *dest[3].(*int), *dest[4].(*int), *dest[5].(*int) = row.documentRefresh, row.resourceRefresh, row.stalledAfter, row.finalizingAfter
*dest[6].(*int) = row.ingestionConcurrency
*dest[7].(*string), *dest[8].(*string) = row.testBaseURL, row.productionBaseURL
*dest[9].(*string), *dest[10].(*string) = row.productionTenantID, row.productionKnowledgeBaseID
*dest[11].(*string), *dest[12].(*time.Time), *dest[13].(**time.Time), *dest[14].(**time.Time), *dest[15].(*string) = row.updatedBy, row.createdAt, row.activatedAt, row.retiredAt, row.errorCode
return nil
}