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

89 lines
4.6 KiB
Go

package httpapi
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
"github.com/jackc/pgx/v5/pgxpool"
"knowledge-governance/internal/governance"
postgresstore "knowledge-governance/internal/platform/postgres"
)
func TestUISettingsWriteReadBackIntegration(t *testing.T) {
databaseURL := os.Getenv("TEST_DATABASE_URL")
if databaseURL == "" {
t.Skip("TEST_DATABASE_URL is not set")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, databaseURL)
if err != nil {
t.Fatal(err)
}
defer pool.Close()
if err := postgresstore.ApplyMigrations(ctx, pool); err != nil {
t.Fatal(err)
}
now := time.Date(2026, 9, 10, 9, 0, 0, 0, time.UTC)
handler, err := NewHandler(Config{Store: postgresstore.NewStore(pool), Now: func() time.Time { return now }})
if err != nil {
t.Fatal(err)
}
create := httptest.NewRecorder()
createRequest := httptest.NewRequest(http.MethodPost, "/api/v1/ui-settings/candidates", bytes.NewBufferString(`{"document_refresh_seconds":30,"resource_refresh_seconds":60,"stalled_after_seconds":3600,"finalizing_after_seconds":14400,"ingestion_concurrency":2,"test_weknora_base_url":"https://weknora.test.example:8443/weknora","production_weknora_base_url":"https://weknora.prod.example:8443/weknora","production_tenant_id":"10000","production_knowledge_base_id":"kb-prod-1"}`))
createRequest.Header.Set("X-Governance-Action", "ui-settings")
createRequest.Header.Set("Idempotency-Key", "op-e2e-uis-create")
handler.ServeHTTP(create, createRequest)
if create.Code != http.StatusCreated {
t.Fatalf("create status=%d body=%s", create.Code, create.Body.String())
}
var candidate struct {
Data governance.UISettings `json:"data"`
}
if err := json.NewDecoder(create.Body).Decode(&candidate); err != nil || candidate.Data.State != "validated" {
t.Fatalf("candidate=%#v error=%v", candidate, err)
}
if candidate.Data.ProductionWeKnoraBaseURL != "https://weknora.prod.example:8443/weknora" || candidate.Data.TestWeKnoraBaseURL != "https://weknora.test.example:8443/weknora" || candidate.Data.ProductionTenantID != "10000" || candidate.Data.ProductionKnowledgeBaseID != "kb-prod-1" {
t.Fatalf("service fields lost in the web write path: %#v", candidate.Data)
}
activate := httptest.NewRecorder()
activateRequest := httptest.NewRequest(http.MethodPost, "/api/v1/ui-settings/"+candidate.Data.ID+"/activate", bytes.NewBufferString(`{}`))
activateRequest.Header.Set("X-Governance-Action", "ui-settings")
activateRequest.Header.Set("Idempotency-Key", "op-e2e-uis-activate")
handler.ServeHTTP(activate, activateRequest)
if activate.Code != http.StatusOK {
t.Fatalf("activate status=%d body=%s", activate.Code, activate.Body.String())
}
get := httptest.NewRecorder()
handler.ServeHTTP(get, httptest.NewRequest(http.MethodGet, "/api/v1/ui-settings", nil))
if get.Code != http.StatusOK {
t.Fatalf("get status=%d body=%s", get.Code, get.Body.String())
}
var active struct {
Data governance.UISettings `json:"data"`
}
if err := json.NewDecoder(get.Body).Decode(&active); err != nil || active.Data.ID != candidate.Data.ID || active.Data.State != "active" || active.Data.DocumentRefreshSeconds != 30 {
t.Fatalf("active=%#v error=%v", active, err)
}
if active.Data.ProductionWeKnoraBaseURL != "https://weknora.prod.example:8443/weknora" || active.Data.TestWeKnoraBaseURL != "https://weknora.test.example:8443/weknora" || active.Data.ProductionTenantID != "10000" || active.Data.ProductionKnowledgeBaseID != "kb-prod-1" {
t.Fatalf("active service fields not served back to the console: %#v", active.Data)
}
var persistedEndpoint, persistedTestEndpoint, persistedTenant, persistedKnowledgeBase string
if err := pool.QueryRow(ctx, `SELECT production_weknora_base_url,test_weknora_base_url,production_tenant_id,production_knowledge_base_id FROM ui_setting_revisions WHERE state='active'`).Scan(&persistedEndpoint, &persistedTestEndpoint, &persistedTenant, &persistedKnowledgeBase); err != nil || persistedEndpoint != "https://weknora.prod.example:8443/weknora" || persistedTestEndpoint != "https://weknora.test.example:8443/weknora" || persistedTenant != "10000" || persistedKnowledgeBase != "kb-prod-1" {
t.Fatalf("persisted service fields=%q/%q/%q/%q error=%v", persistedEndpoint, persistedTestEndpoint, persistedTenant, persistedKnowledgeBase, err)
}
var activeCount, retiredCount int
if err := pool.QueryRow(ctx, `SELECT COUNT(*) FILTER(WHERE state='active'),COUNT(*) FILTER(WHERE state='retired') FROM ui_setting_revisions`).Scan(&activeCount, &retiredCount); err != nil || activeCount != 1 || retiredCount != 1 {
t.Fatalf("active=%d retired=%d error=%v", activeCount, retiredCount, err)
}
}