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 }