fix(quality): 闭环 Go 质量门禁(鉴权富化/V0.5/vet/health/KB gate/记忆轮询)
This commit is contained in:
@@ -121,12 +121,15 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) {
|
||||
if !resV05.Success {
|
||||
t.Fatalf("Stage V0.5 should succeed, got error: %s", resV05.ErrorReason)
|
||||
}
|
||||
if len(resV05.SubArtifacts) != 3 {
|
||||
t.Fatalf("Stage V0.5 should produce 3 sub-artifacts (research, feasibility_reg, prelim_design), got %d", len(resV05.SubArtifacts))
|
||||
if len(resV05.SubArtifacts) != 5 {
|
||||
t.Fatalf("Stage V0.5 should produce 5 sub-artifacts, got %d", len(resV05.SubArtifacts))
|
||||
}
|
||||
t.Logf("✓ Stage V0.5 成功完成: 包含任务=[%s, %s, %s], 成熟度评分=%d (%s)",
|
||||
resV05.SubArtifacts[0].TaskKey, resV05.SubArtifacts[1].TaskKey, resV05.SubArtifacts[2].TaskKey,
|
||||
resV05.AdvisorBrief.MaturityScore, resV05.AdvisorBrief.MaturityLevel)
|
||||
keys := make([]string, len(resV05.SubArtifacts))
|
||||
for i, sub := range resV05.SubArtifacts {
|
||||
keys[i] = sub.TaskKey
|
||||
}
|
||||
t.Logf("✓ Stage V0.5 成功完成: 包含任务=%v, 成熟度评分=%d (%s)",
|
||||
keys, resV05.AdvisorBrief.MaturityScore, resV05.AdvisorBrief.MaturityLevel)
|
||||
|
||||
// 验证 feasibility_reg 受控文档已建立
|
||||
docID := "doc_proj1001_feasibility_reg"
|
||||
|
||||
@@ -159,7 +159,7 @@ func NewService(cfg *config.Config) (*Service, error) {
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
// SchemaVersion 返回当前数据库 schema 版本(goose)。
|
||||
// SchemaVersion 返回启动时缓存的数据库 schema 版本(goose)。
|
||||
func (s *Service) SchemaVersion() int64 {
|
||||
if s == nil {
|
||||
return 0
|
||||
@@ -167,6 +167,21 @@ func (s *Service) SchemaVersion() int64 {
|
||||
return s.schemaVersion
|
||||
}
|
||||
|
||||
// LiveSchemaVersion 从数据库实时读取当前 schema 版本;失败时回退缓存值。
|
||||
func (s *Service) LiveSchemaVersion(ctx context.Context) int64 {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
if s.pool == nil {
|
||||
return s.schemaVersion
|
||||
}
|
||||
ver, err := db.CurrentVersion(ctx, s.pool)
|
||||
if err != nil {
|
||||
return s.schemaVersion
|
||||
}
|
||||
return ver
|
||||
}
|
||||
|
||||
// DatabaseReady 数据库是否已连接且迁移完成。
|
||||
func (s *Service) DatabaseReady() bool {
|
||||
return s != nil && s.pool != nil
|
||||
|
||||
@@ -145,9 +145,9 @@ func TestExecuteStageV01AndV05(t *testing.T) {
|
||||
if stage2Res.AdvisorBrief.MaturityLevel != "V0.5" {
|
||||
t.Errorf("expected V0.5 maturity level, got %s", stage2Res.AdvisorBrief.MaturityLevel)
|
||||
}
|
||||
// Stage 2 包含 3 个任务 (research, feasibility_reg, prelim_design)
|
||||
if len(stage2Res.SubArtifacts) != 3 {
|
||||
t.Errorf("expected 3 sub artifacts in stage 2, got %d", len(stage2Res.SubArtifacts))
|
||||
// Stage V0.5 包含 5 个任务(与 SupportedStages 一致)
|
||||
if len(stage2Res.SubArtifacts) != 5 {
|
||||
t.Errorf("expected 5 sub artifacts in stage 2, got %d", len(stage2Res.SubArtifacts))
|
||||
}
|
||||
|
||||
// 3. 运行 Stage 3 (V1.0 Quoting),透传全部前序累积
|
||||
|
||||
@@ -522,28 +522,35 @@ func TestChatSession_SemanticMemoryRecallAndAsyncTurnRecord(t *testing.T) {
|
||||
t.Errorf("expected snippet to contain YY 0505, got: %s", session.UserCtx.RecalledMemorySnippet)
|
||||
}
|
||||
|
||||
// 4. 等待异步写入 goroutine 落盘
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// 5. 验证本轮当场生成的问答对是否已被向量记忆库索引
|
||||
results, err := ctxMgr.SearchChatMemory(ctx, ctxmgr.ChatMemorySearchOptions{
|
||||
Query: "电磁兼容样机数量",
|
||||
UserID: 99,
|
||||
ProjectID: 200,
|
||||
TopK: 5,
|
||||
MinScore: 0.1,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("SearchChatMemory failed: %v", err)
|
||||
}
|
||||
|
||||
// 4. 等待异步写入 goroutine 落盘(轮询至 deadline,避免固定 sleep 竞态)
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
var results []ctxmgr.RecalledMemoryItem
|
||||
foundCurrentTurn := false
|
||||
for _, r := range results {
|
||||
if r.SessionID == "sess_current_recall" {
|
||||
foundCurrentTurn = true
|
||||
for time.Now().Before(deadline) {
|
||||
var searchErr error
|
||||
results, searchErr = ctxMgr.SearchChatMemory(ctx, ctxmgr.ChatMemorySearchOptions{
|
||||
Query: "电磁兼容样机数量",
|
||||
UserID: 99,
|
||||
ProjectID: 200,
|
||||
TopK: 5,
|
||||
MinScore: 0.1,
|
||||
})
|
||||
if searchErr != nil {
|
||||
t.Fatalf("SearchChatMemory failed: %v", searchErr)
|
||||
}
|
||||
for _, r := range results {
|
||||
if r.SessionID == "sess_current_recall" {
|
||||
foundCurrentTurn = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if foundCurrentTurn {
|
||||
break
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
|
||||
// 5. 验证本轮当场生成的问答对是否已被向量记忆库索引
|
||||
if !foundCurrentTurn {
|
||||
t.Errorf("expected current turn to be asynchronously indexed into chat memory")
|
||||
}
|
||||
|
||||
@@ -93,6 +93,19 @@ func (e *CoordinatorEngine) executeIntentPlan(
|
||||
if pendingConfirm != nil && userCtx.PendingExpectation != nil {
|
||||
last.NextExpectation = userCtx.PendingExpectation
|
||||
}
|
||||
|
||||
slots := map[string]string{}
|
||||
if recRes.TopIntent != nil && len(recRes.TopIntent.FilledSlots) > 0 {
|
||||
slots = recRes.TopIntent.FilledSlots
|
||||
}
|
||||
if last.AdvisorBrief == nil {
|
||||
last.AdvisorBrief = userCtx.LatestAdvisorBrief
|
||||
}
|
||||
if last.AdvisorBrief == nil {
|
||||
last.AdvisorBrief = synthesizeBaselineBrief(userCtx)
|
||||
}
|
||||
e.enrichAdvisorBriefWithCases(ctx, last.AdvisorBrief, userCtx, slots)
|
||||
|
||||
return last, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,9 @@ func TestEngine_IntentPlan_QueryThenQuery(t *testing.T) {
|
||||
if !strings.Contains(res.NaturalReply, "步骤") {
|
||||
t.Errorf("expected multi-step reply, got: %s", res.NaturalReply)
|
||||
}
|
||||
if res.AdvisorBrief == nil {
|
||||
t.Fatal("expected AdvisorBrief on multi-intent plan result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEngine_ExpectationPlusNew_ExecutesQueryKeepsConfirm(t *testing.T) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package kb_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -11,6 +12,12 @@ import (
|
||||
)
|
||||
|
||||
func TestKB_RealIntegration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping KB real integration in short mode")
|
||||
}
|
||||
if os.Getenv("RUN_KB_INTEGRATION") != "1" {
|
||||
t.Skip("set RUN_KB_INTEGRATION=1 to run KB real integration")
|
||||
}
|
||||
cfg, err := config.Load("")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load config: %v", err)
|
||||
|
||||
@@ -87,7 +87,10 @@ func TestUploadProjectAsset_RejectsOversizedFilename(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodPost, ts.URL+"/api/v1/projects/7/assets", &body)
|
||||
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||
withBearerAuth(req, loginTestAdmin(t, srv))
|
||||
resp, _ := http.DefaultClient.Do(req)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("request failed: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusUnsupportedMediaType {
|
||||
t.Fatalf("expected 415 for long filename, got %d", resp.StatusCode)
|
||||
|
||||
@@ -91,7 +91,11 @@ func (s *Server) buildHealthResponse(strict bool) (HealthResponse, int) {
|
||||
dbReady := s.svc != nil && s.svc.DatabaseReady()
|
||||
schemaVersion := int64(0)
|
||||
if s.svc != nil {
|
||||
schemaVersion = s.svc.SchemaVersion()
|
||||
if dbReady {
|
||||
schemaVersion = s.svc.LiveSchemaVersion(context.Background())
|
||||
} else {
|
||||
schemaVersion = s.svc.SchemaVersion()
|
||||
}
|
||||
}
|
||||
schemaReady := schemaVersion > 0 || (s.cfg != nil && s.cfg.AllowInMemory)
|
||||
taskStoreReady := s.svc != nil && taskrun.TablesReady(context.Background(), s.svc.Pool())
|
||||
|
||||
Reference in New Issue
Block a user