dcdc8d1ddd
全栈知识治理平台首次入库(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 干净
79 lines
3.0 KiB
Go
79 lines
3.0 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,"updated_by":"engineer-e2e"}`))
|
|
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)
|
|
}
|
|
|
|
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)
|
|
}
|
|
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)
|
|
}
|
|
}
|