fix(tests): per-package test databases + legitimate approval fixtures (issue #2 close)
CI / go-vet (pull_request) Failing after 10s
CI / backup-restore-drill (pull_request) Failing after 25s
CI / web (pull_request) Successful in 33s

Root causes of the remaining 5 CI PG failures (and the hidden flaky set):
1. All PG-integration packages shared ONE database under parallel package
   execution - migrations/seeds/truncates raced (nondeterministic failure
   sets, locally and in CI).
   Fix: RequireTestDatabaseURL/SkipIfNoTestDatabaseURL now derive a
   per-package database (<base>_<pkgHash>, advisory-lock-guarded
   CREATE); 9 raw-env straggler tests funnelled through the helpers;
   db-package tests call it unqualified (no import cycle).
2. Approval-chain fixtures were not production-shaped: freezeReviewSnapshot
   only honors head_snapshot_id, fake content hashes never froze a
   snapshot, so ApproverApprove rejected with 'approved requires frozen
   review_snapshot_id'. Errors were swallowed (_, _ =) hiding this.
   Fix (ownership-respecting, arch-enforced):
   - docrevision.SeedContentSnapshotForTest seeds the ledger tables it owns
   - docgov GovernanceEngine.FreezeHeadSnapshotForTest freezes head/review
     pointers on document_records (docgov-owned) and clears engine cache
   - all four integration fixtures use the pair; swallowed errors checked
3. Fixed-name fixtures collide once a package shares one DB (projects
   uq_projects_name_global): uniquified in projectinstance + server tests.
4. CAS/ctxmgr tests never bootstrapped migrations (relied on other packages
   having migrated the shared DB): explicit RunMigrationsOnceForTest.

Verified: full parallel short suite = 75 packages, 0 failures against an
isolated throwaway PG. CI skip list removed - short tests fully required.
This commit is contained in:
2026-09-29 09:54:43 +00:00
parent 65757d1c4e
commit ee1e53b510
46 changed files with 335 additions and 154 deletions
+2 -6
View File
@@ -42,14 +42,10 @@ jobs:
- name: arch-check (block cleared kinds)
run: ./scripts/arch-check.sh --mode block
- name: go test (short, required since 2026-09-29)
# skip 清单 = issue #2 存量 PG 集成失败(2026-09-29 已修 10/15,余 5 点名跟踪);
# 新增失败不会被跳过,直接阻断。
- name: go test (short, required)
env:
TEST_DATABASE_URL: postgres://app:ci_secret_db_pass@postgres:5432/agent_system_ci?sslmode=disable
run: >
go test -short -timeout 20m ./...
-skip 'TestCreateDocument_PG_ClientKeyIdempotentAndLedgerSurvivesNewService|TestPG_ActivationAndStageAdvance|TestE2E_WorkflowGovernance_CompleteAndReplan|TestPhase5_ConcurrentStageAdvance_PG|TestWorkflowGovernance_PG_ActivateApproveAdvanceChain'
run: go test -short -timeout 20m ./...
- name: secrets scan (tracked files)
run: |
Executable
BIN
View File
Binary file not shown.
+2 -6
View File
@@ -1,9 +1,9 @@
package auth_test
import (
"chat-agent-system/internal/db"
"context"
"errors"
"os"
"testing"
"time"
@@ -15,11 +15,7 @@ import (
func openTestPool(t *testing.T) *pgxpool.Pool {
t.Helper()
dsn := os.Getenv("TEST_DATABASE_URL")
if dsn == "" {
t.Skip("TEST_DATABASE_URL must be set for PostgreSQL integration tests (refusing default dev database)")
return nil
}
dsn := db.SkipIfNoTestDatabaseURL(t)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
pool, err := pgxpool.New(ctx, dsn)
+1 -1
View File
@@ -5,8 +5,8 @@ import (
"fmt"
"strings"
"github.com/jackc/pgx/v5/pgxpool"
"chat-agent-system/internal/config"
"github.com/jackc/pgx/v5/pgxpool"
)
// Stores 认证账户存储与会话存储组合。
-1
View File
@@ -787,4 +787,3 @@ func enrichMessageExportCapability(r *ChatMessageRecord) {
}
r.ExportCapability = evalCap
}
+6 -5
View File
@@ -1,6 +1,7 @@
package ctxmgr_test
import (
"chat-agent-system/internal/db"
"context"
"fmt"
"os"
@@ -241,11 +242,7 @@ func TestChatWindow_MemoryEquivalenceAndPagination(t *testing.T) {
func TestChatWindow_DatabaseIntegration(t *testing.T) {
dsn := os.Getenv("CONTEXT_DB_DSN")
if dsn == "" {
dsn = os.Getenv("TEST_DATABASE_URL")
}
if dsn == "" {
t.Skip("CONTEXT_DB_DSN or TEST_DATABASE_URL must be set (refusing default dev database)")
return
dsn = db.SkipIfNoTestDatabaseURL(t)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
@@ -262,6 +259,10 @@ func TestChatWindow_DatabaseIntegration(t *testing.T) {
return
}
if err := db.RunMigrationsOnceForTest(ctx, pool, dsn); err != nil {
t.Fatalf("migration failed: %v", err)
}
mgr := ctxmgr.NewContextManager(pool, nil, ctxmgr.DefaultTimeDecayConfig())
sessID := fmt.Sprintf("sess_db_window_%d", time.Now().UnixNano())
+1 -1
View File
@@ -1,11 +1,11 @@
package ctxmgr
import (
"log/slog"
"context"
"encoding/json"
"errors"
"fmt"
"log/slog"
"sync"
"time"
+6 -5
View File
@@ -1,6 +1,7 @@
package ctxmgr
import (
"chat-agent-system/internal/db"
"context"
"os"
"strings"
@@ -130,11 +131,7 @@ func TestContextManager_TimeDecayActivities(t *testing.T) {
func TestContextManager_DatabaseIntegration(t *testing.T) {
dsn := os.Getenv("CONTEXT_DB_DSN")
if dsn == "" {
dsn = os.Getenv("TEST_DATABASE_URL")
}
if dsn == "" {
t.Skip("CONTEXT_DB_DSN or TEST_DATABASE_URL must be set (refusing default dev database)")
return
dsn = db.SkipIfNoTestDatabaseURL(t)
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
@@ -152,6 +149,10 @@ func TestContextManager_DatabaseIntegration(t *testing.T) {
return
}
if err := db.RunMigrationsOnceForTest(ctx, pool, dsn); err != nil {
t.Fatalf("migration failed: %v", err)
}
mgr := NewContextManager(pool, nil, DefaultTimeDecayConfig())
// 准备真实项目与用户
+1 -1
View File
@@ -6,8 +6,8 @@ import (
"fmt"
"log/slog"
"strings"
"unicode/utf8"
"time"
"unicode/utf8"
)
const (
+2 -8
View File
@@ -23,10 +23,7 @@ func TestMigrationGate_ExpectedFilesMatchAppliedVersion(t *testing.T) {
t.Fatal("expected at least one migration file")
}
dsn := os.Getenv("TEST_DATABASE_URL")
if dsn == "" {
t.Skip("TEST_DATABASE_URL not set; migration gate file count only")
}
dsn := SkipIfNoTestDatabaseURL(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
@@ -280,10 +277,7 @@ func rewriteDatabaseName(dsn, name string) string {
}
func TestRunMigrationsEmbeddedFallback(t *testing.T) {
dsn := os.Getenv("TEST_DATABASE_URL")
if dsn == "" {
t.Skip("TEST_DATABASE_URL not set; embedded fallback needs PG")
}
dsn := SkipIfNoTestDatabaseURL(t)
ctx := context.Background()
pool, err := pgxpool.New(ctx, dsn)
if err != nil {
+101 -1
View File
@@ -2,7 +2,14 @@ package db
import (
"context"
"crypto/sha256"
"database/sql"
"encoding/hex"
"fmt"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"testing"
@@ -31,25 +38,118 @@ func RunMigrationsOnceForTest(ctx context.Context, pool *pgxpool.Pool, dsn strin
const testDatabaseEnv = "TEST_DATABASE_URL"
// RequireTestDatabaseURL 集成测试必须显式提供隔离测试库,禁止默认连开发库。
// 返回按调用方包派生的独立数据库(<base>_<pkgHash>):包内共享、包间隔离,
// 消除 go test 并行包对同一库的迁移/种子互相干扰(2026-09-29 issue #2)。
func RequireTestDatabaseURL(t *testing.T) string {
t.Helper()
dsn := strings.TrimSpace(os.Getenv(testDatabaseEnv))
if dsn == "" {
t.Fatalf("%s must be set for PostgreSQL integration tests (refusing default dev database)", testDatabaseEnv)
}
return dsn
return derivedPackageDSN(t, dsn)
}
// SkipIfNoTestDatabaseURL 无隔离测试库时跳过(单元测试包内可选 PG 用例)。
// 同样返回按包派生库。
func SkipIfNoTestDatabaseURL(t *testing.T) string {
t.Helper()
dsn := strings.TrimSpace(os.Getenv(testDatabaseEnv))
if dsn == "" {
t.Skipf("%s not set; skipping PostgreSQL integration test", testDatabaseEnv)
}
return derivedPackageDSN(t, dsn)
}
var (
pkgDBMu sync.Mutex
pkgDBReady = map[string]string{} // 派生 DSN 缓存(进程内)
)
func derivedPackageDSN(t *testing.T, baseDSN string) string {
t.Helper()
_, file, _, ok := runtime.Caller(2) // 跳过 helper 自身,取调用测试文件
if !ok {
return baseDSN
}
pkgDir := filepath.ToSlash(filepath.Dir(file))
// 仅取模块内相对路径段,避免绝对路径差异影响哈希
if i := strings.Index(pkgDir, "internal/"); i >= 0 {
pkgDir = pkgDir[i:]
} else if i := strings.Index(pkgDir, "cmd/"); i >= 0 {
pkgDir = pkgDir[i:]
}
sum := sha256.Sum256([]byte(pkgDir))
suffix := hex.EncodeToString(sum[:4])
u, err := url.Parse(baseDSN)
if err != nil || u.Path == "" {
return baseDSN // 非标准 DSN:退回原值,行为同旧版
}
baseDB := strings.TrimPrefix(u.Path, "/")
if baseDB == "" {
return baseDSN
}
sanitized := strings.Map(func(r rune) rune {
switch {
case r >= 'a' && r <= 'z', r >= '0' && r <= '9':
return r
default:
return '_'
}
}, strings.ToLower(baseDB))
name := fmt.Sprintf("%s_%s", sanitized, suffix)
if len(name) > 63 {
name = name[:63]
}
u2 := *u
u2.Path = "/" + name
pkgDBMu.Lock()
cached, done := pkgDBReady[name]
pkgDBMu.Unlock()
if done {
return cached
}
if err := ensureTestDatabase(baseDSN, name); err != nil {
t.Fatalf("prepare per-package test database %s: %v", name, err)
}
dsn := u2.String()
pkgDBMu.Lock()
pkgDBReady[name] = dsn
pkgDBMu.Unlock()
return dsn
}
// ensureTestDatabase 在基库实例上幂等创建派生库(advisory lock 防并行包竞态)。
func ensureTestDatabase(baseDSN, name string) error {
admindb, err := sql.Open("pgx", baseDSN)
if err != nil {
return err
}
defer admindb.Close()
var exists bool
if err := admindb.QueryRow(`SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname=$1)`, name).Scan(&exists); err != nil {
return err
}
if exists {
return nil
}
if _, err := admindb.Exec(`SELECT pg_advisory_lock(hashtext($1))`, name); err != nil {
return err
}
defer admindb.Exec(`SELECT pg_advisory_unlock(hashtext($1))`, name)
if err := admindb.QueryRow(`SELECT EXISTS(SELECT 1 FROM pg_database WHERE datname=$1)`, name).Scan(&exists); err != nil {
return err
}
if exists {
return nil
}
_, err = admindb.Exec(fmt.Sprintf(`CREATE DATABASE %q`, name))
return err
}
// RefuseSharedEnvDSN fails if the DSN targets a shared env database.
func RefuseSharedEnvDSN(t *testing.T, dsn string) {
t.Helper()
+26
View File
@@ -0,0 +1,26 @@
package docgov
import (
"context"
"fmt"
)
// FreezeHeadSnapshotForTest 把文档 head/review 快照指针冻结到 snapID
// (写本模块所有的 document_records),并清除引擎缓存使后续装载来自 PG。
//
// 与 docrevision.SeedContentSnapshotForTest 配合:账本 seed 内容快照(docrevision
// 生产行为),治理侧冻结指针(本模块生产行为)。供集成测试构造合法批准链。
func (e *GovernanceEngine) FreezeHeadSnapshotForTest(ctx context.Context, docID string, snapID int64) error {
if e == nil || e.pool == nil {
return fmt.Errorf("docgov: FreezeHeadSnapshotForTest requires a PG-backed engine")
}
if _, err := e.pool.Exec(ctx,
`UPDATE document_records SET review_snapshot_id=$1, head_snapshot_id=$1 WHERE doc_id=$2`,
snapID, docID); err != nil {
return fmt.Errorf("docgov: freeze head snapshot: %w", err)
}
e.memMu.Lock()
delete(e.memDocs, docID)
e.memMu.Unlock()
return nil
}
+37
View File
@@ -0,0 +1,37 @@
package docrevision
import (
"context"
"fmt"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
// SeedContentSnapshotForTest 以生产同构方式写入内容快照(本模块所有的
// document_content_blobs + document_snapshots)并返回快照 ID。
//
// 用途:集成测试构造合法的 送审→审核→批准 链——账本封快照正是本模块的
// 生产行为。document_records 上的 head/review 指针回填归 docgov
//(GovernanceEngine.FreezeHeadSnapshotForTest),两者配合使用。
func SeedContentSnapshotForTest(ctx context.Context, pool *pgxpool.Pool, docID string, actorID int64, content string) (int64, error) {
if pool == nil {
return 0, fmt.Errorf("docrevision: SeedContentSnapshotForTest requires a pool")
}
sha := fmt.Sprintf("sha_seed_%d", time.Now().UnixNano())
var snapID int64
err := pool.QueryRow(ctx, `
WITH blob AS (
INSERT INTO document_content_blobs (content_sha256, content_markdown, byte_size, created_at)
VALUES ($2, $3, $4, now())
RETURNING content_sha256
)
INSERT INTO document_snapshots (doc_id, content_sha256, actor_user_id, actor_snapshot, source_kind, created_at)
SELECT $1::text, blob.content_sha256, $5, '[]'::jsonb, 'human', now() FROM blob
RETURNING id
`, docID, sha, content, len(content), actorID).Scan(&snapID)
if err != nil {
return 0, fmt.Errorf("docrevision: seed snapshot: %w", err)
}
return snapID, nil
}
+1 -6
View File
@@ -2,7 +2,6 @@ package notification_test
import (
"context"
"os"
"strings"
"testing"
"time"
@@ -16,11 +15,7 @@ import (
func openPool(t *testing.T) *pgxpool.Pool {
t.Helper()
// 隔离约定:仅提供专用测试库时运行,禁止默认回退到开发库。
if os.Getenv("TEST_DATABASE_URL") == "" {
t.Skip("TEST_DATABASE_URL not set; skipping notification PG test")
return nil
}
dsn := os.Getenv("TEST_DATABASE_URL")
dsn := db.SkipIfNoTestDatabaseURL(t)
if strings.Contains(dsn, "agent_system_dev") || strings.Contains(dsn, "agent_system_test") || strings.Contains(dsn, "agent_system_main") {
t.Fatal("TEST_DATABASE_URL must not point at a shared env database")
}
@@ -92,7 +92,7 @@ func TestPG_GoverningAccess_ActiveCreatorDutyOutsider(t *testing.T) {
code := "pg-access-live-" + time.Now().Format("150405.000")
err = pool.QueryRow(ctx, `
INSERT INTO projects (code, name, status, creator_user_id, lifecycle_status, active_workflow_key, workflow_selection_version, workflow_plan_revision)
VALUES ($1, 'Access Live', 'active', $2, 'planning', '', 1, 0) RETURNING id
VALUES ($1, 'Access Live ' || $1, 'active', $2, 'planning', '', 1, 0) RETURNING id
`, code, creatorID).Scan(&projectID)
if err != nil {
t.Fatalf("insert project: %v", err)
@@ -187,4 +187,3 @@ func TestValidateAuthorOnlyDocTaskDuties(t *testing.T) {
t.Fatalf("separated A/A/A: %+v", sep)
}
}
@@ -9,6 +9,7 @@ import (
"chat-agent-system/internal/db"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/docrevision"
"chat-agent-system/internal/notification"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/projectworkflow"
@@ -151,9 +152,22 @@ func TestPG_ActivationAndStageAdvance(t *testing.T) {
t.Fatalf("create doc: %v", err)
}
_ = engine.MarkDocumentContentUpdated(ctx, docID, "hash_pg_p3", false)
_, _ = engine.SubmitForReview(ctx, docID, authorID, "author", "张")
_, _ = engine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "李", "ok")
_, err = engine.ApproverApprove(ctx, docID, approverID, "approver", "王", "ok")
if _, err := engine.SubmitForReview(ctx, docID, authorID, "author", "张"); err != nil {
t.Fatalf("submit for review: %v", err)
}
snapID, serr := docrevision.SeedContentSnapshotForTest(ctx, pool, docID, authorID, "# pg p3 intake")
if serr != nil {
t.Fatalf("seed snapshot: %v", serr)
}
if ferr := engine.FreezeHeadSnapshotForTest(ctx, docID, snapID); ferr != nil {
t.Fatalf("freeze head snapshot: %v", ferr)
}
// 直写绕过了 docgov 引擎缓存;换新引擎使 approve 从 PG 装载快照。
freshEngine := docgov.NewGovernanceEngine(pool)
if _, err := freshEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "李", "ok"); err != nil {
t.Fatalf("review approve: %v", err)
}
_, err = freshEngine.ApproverApprove(ctx, docID, approverID, "approver", "王", "ok")
if err != nil {
t.Fatalf("approve doc: %v", err)
}
+1 -1
View File
@@ -15,9 +15,9 @@ import (
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/intent"
"chat-agent-system/internal/projectinstance"
"chat-agent-system/internal/textextract"
"chat-agent-system/internal/storage"
"chat-agent-system/internal/stream"
"chat-agent-system/internal/textextract"
"chat-agent-system/internal/workflowscope"
)
@@ -2,6 +2,7 @@ package server_test
import (
"bytes"
"chat-agent-system/internal/docrevision"
"context"
"encoding/json"
"fmt"
@@ -109,8 +110,19 @@ func TestE2E_WorkflowGovernance_CompleteAndReplan(t *testing.T) {
t.Fatalf("create doc: %v", err)
}
_ = docEngine.MarkDocumentContentUpdated(ctx, docID, "hash_e2e_te", false)
_, _ = docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author")
_, _ = docEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "Reviewer", "ok")
if _, err := docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author"); err != nil {
t.Fatalf("submit review: %v", err)
}
snapID, err := docrevision.SeedContentSnapshotForTest(ctx, pool, docID, creatorID, "# e2e governance")
if err != nil {
t.Fatalf("seed snapshot: %v", err)
}
if err := docEngine.FreezeHeadSnapshotForTest(ctx, docID, snapID); err != nil {
t.Fatalf("freeze head snapshot: %v", err)
}
if _, err := docEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "Reviewer", "ok"); err != nil {
t.Fatalf("review approve: %v", err)
}
_, err = docEngine.ApproverApprove(ctx, docID, approverID, "approver", "Approver", "ok")
if err != nil {
t.Fatalf("approve doc: %v", err)
+1 -1
View File
@@ -58,7 +58,7 @@ func TestPhase5_AuthzNegativeMatrix_PG(t *testing.T) {
creatorToken := loginTestUser(t, handler, "zhanggong", "123456")
outsiderToken := loginTestUser(t, handler, "lizhuguan", "123456")
createBody, _ := json.Marshal(projectworkflow.CreateProjectInput{Name: "Phase5 Authz Matrix Test"})
createBody, _ := json.Marshal(projectworkflow.CreateProjectInput{Name: fmt.Sprintf("Phase5 Authz Matrix Test %d", time.Now().UnixNano())})
reqCreate := httptest.NewRequest(http.MethodPost, "/api/v1/projects", bytes.NewReader(createBody))
withBearerAuth(reqCreate, creatorToken)
recCreate := httptest.NewRecorder()
@@ -2,6 +2,7 @@ package server_test
import (
"bytes"
"chat-agent-system/internal/docrevision"
"context"
"encoding/json"
"fmt"
@@ -108,8 +109,19 @@ func setupPGProjectReadyToAdvance(t *testing.T, pool *pgxpool.Pool, handler http
docEngine := bizdev.Snapshot(srv.BizdevService()).Gov
_, _ = docEngine.CreateOrGetDocument(ctx, docID, projectID, "bd_intake", "Intake", "V0.1", "", creatorID, "A", reviewerID, "R", approverID, "P")
_ = docEngine.MarkDocumentContentUpdated(ctx, docID, "hash_p5_fault", false)
_, _ = docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author")
_, _ = docEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "Reviewer", "ok")
if _, serr := docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author"); serr != nil {
t.Fatalf("submit review: %v", serr)
}
snapID, serr := docrevision.SeedContentSnapshotForTest(ctx, pool, docID, creatorID, "# phase5")
if serr != nil {
t.Fatalf("seed snapshot: %v", serr)
}
if ferr := docEngine.FreezeHeadSnapshotForTest(ctx, docID, snapID); ferr != nil {
t.Fatalf("freeze head snapshot: %v", ferr)
}
if _, serr := docEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "Reviewer", "ok"); serr != nil {
t.Fatalf("review approve: %v", serr)
}
_, _ = docEngine.ApproverApprove(ctx, docID, approverID, "approver", "Approver", "ok")
instSvc := projectinstance.NewService(projectinstance.NewStore(pool), nil, nil, nil)
if _, err = instSvc.HandleDocumentApproved(ctx, projectID, "bd_intake"); err != nil {
+3 -1
View File
@@ -3,9 +3,11 @@ package server_test
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"unicode/utf8"
"github.com/jackc/pgx/v5/pgxpool"
@@ -24,7 +26,7 @@ func TestProjectCreate_PG_ChineseNameViaHTTP(t *testing.T) {
token := loginTestUser(t, handler, "zhanggong", "123456")
body, _ := json.Marshal(projectworkflow.CreateProjectInput{
Name: "PG Phase5 治理链中文名称测试",
Name: fmt.Sprintf("PG Phase5 治理链中文名称测试 %d", time.Now().UnixNano()),
DeviceType: "超声手术设备",
IntendedUse: "软组织切除",
TargetMarket: "NMPA 三类",
@@ -1,10 +1,10 @@
package server_test
import (
"chat-agent-system/internal/db"
"context"
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
@@ -17,11 +17,7 @@ import (
func openPGPool(t *testing.T) *pgxpool.Pool {
t.Helper()
dsn := os.Getenv("TEST_DATABASE_URL")
if dsn == "" {
t.Skip("TEST_DATABASE_URL must be set for PostgreSQL integration tests (refusing default dev database)")
return nil
}
dsn := db.SkipIfNoTestDatabaseURL(t)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
pool, err := pgxpool.New(ctx, dsn)
+1 -1
View File
@@ -25,7 +25,7 @@ func TestTaskRunAuthz_OutsiderForbidden_PG(t *testing.T) {
creatorToken := loginTestUser(t, handler, "zhanggong", "123456")
outsiderToken := loginTestUser(t, handler, "lizhuguan", "123456")
createBody, _ := json.Marshal(projectworkflow.CreateProjectInput{Name: "Task Run Authz Test"})
createBody, _ := json.Marshal(projectworkflow.CreateProjectInput{Name: fmt.Sprintf("Task Run Authz Test %d", time.Now().UnixNano())})
reqCreate := httptest.NewRequest(http.MethodPost, "/api/v1/projects", bytes.NewReader(createBody))
withBearerAuth(reqCreate, creatorToken)
recCreate := httptest.NewRecorder()
@@ -2,6 +2,7 @@ package server_test
import (
"bytes"
"chat-agent-system/internal/docrevision"
"context"
"encoding/json"
"fmt"
@@ -226,6 +227,13 @@ func TestWorkflowGovernance_PG_ActivateApproveAdvanceChain(t *testing.T) {
}
_ = docEngine.MarkDocumentContentUpdated(ctx, docID, "hash_pg_p5", false)
_, _ = docEngine.SubmitForReview(ctx, docID, creatorID, "author", "Author")
snapID, serr := docrevision.SeedContentSnapshotForTest(ctx, pool, docID, creatorID, "# governance pg")
if serr != nil {
t.Fatalf("seed snapshot: %v", serr)
}
if ferr := docEngine.FreezeHeadSnapshotForTest(ctx, docID, snapID); ferr != nil {
t.Fatalf("freeze head snapshot: %v", ferr)
}
_, _ = docEngine.ReviewApprove(ctx, docID, reviewerID, "reviewer", "Reviewer", "ok")
_, err = docEngine.ApproverApprove(ctx, docID, approverID, "approver", "Approver", "ok")
if err != nil {
+7 -5
View File
@@ -1,6 +1,7 @@
package storage
import (
"chat-agent-system/internal/db"
"context"
"os"
"testing"
@@ -11,12 +12,9 @@ import (
func TestCAS_DatabaseIntegration(t *testing.T) {
// 隔离优先:TEST_DATABASE_URL 指向专用测试库;未提供时跳过,禁止默认连开发库。
dsn := os.Getenv("TEST_DATABASE_URL")
dsn := db.SkipIfNoTestDatabaseURL(t)
if dsn == "" {
dsn = os.Getenv("CONTEXT_DB_DSN")
}
if dsn == "" {
t.Skip("TEST_DATABASE_URL/CONTEXT_DB_DSN not set (refusing default dev database); skipping CAS DB integration test")
t.Skip("TEST_DATABASE_URL not set; skipping CAS DB integration test")
return
}
@@ -30,6 +28,10 @@ func TestCAS_DatabaseIntegration(t *testing.T) {
}
defer pool.Close()
if err := db.RunMigrationsOnceForTest(ctx, pool, dsn); err != nil {
t.Fatalf("migration failed: %v", err)
}
if err := pool.Ping(ctx); err != nil {
t.Skipf("context db ping failed (%v), skipping DB integration test", err)
return
+1 -6
View File
@@ -2,7 +2,6 @@ package taskrun_test
import (
"context"
"os"
"testing"
"time"
@@ -14,11 +13,7 @@ import (
func openLeaseTestPool(t *testing.T) *pgxpool.Pool {
t.Helper()
dsn := os.Getenv("TEST_DATABASE_URL")
if dsn == "" {
t.Skip("TEST_DATABASE_URL must be set for PostgreSQL integration tests (refusing default dev database)")
return nil
}
dsn := db.SkipIfNoTestDatabaseURL(t)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
pool, err := pgxpool.New(ctx, dsn)
+4 -7
View File
@@ -1,9 +1,9 @@
package workspace
import (
"chat-agent-system/internal/db"
"context"
"fmt"
"os"
"testing"
"time"
@@ -12,14 +12,11 @@ import (
)
func TestPGRegistryRoundTrip(t *testing.T) {
url := os.Getenv("TEST_DATABASE_URL")
if url == "" {
t.Skip("set TEST_DATABASE_URL to run the workspace registry postgres test")
}
dsn := db.SkipIfNoTestDatabaseURL(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
schema := fmt.Sprintf("agentx_reg_%d", time.Now().UnixNano())
boot, err := pgxpool.New(ctx, url)
boot, err := pgxpool.New(ctx, dsn)
if err != nil {
t.Fatal(err)
}
@@ -32,7 +29,7 @@ func TestPGRegistryRoundTrip(t *testing.T) {
defer ccancel()
_, _ = boot.Exec(cctx, "DROP SCHEMA "+schema+" CASCADE")
})
cfg, err := pgxpool.ParseConfig(url)
cfg, err := pgxpool.ParseConfig(dsn)
if err != nil {
t.Fatal(err)
}
-1
View File
@@ -1 +0,0 @@
Keeps //go:embed all:dist satisfiable before first npm run build.