修复 go test -short 在设置 TEST_DATABASE_URL 时的既有欠债: - storage/notification/docgov:移除硬编码 agent_system_dev 回退 DSN,改为 无 TEST_DATABASE_URL 即 skip;固定项目名改为 name||code 唯一化。 - docgov engine_db:改为隔离库 + 自建项目行,替换随机 projectID 撞外键的旧写法。 - server/*_pg、projectinstance/pg_integration:迁移到官方 main 工作流 (BuildDefaultPlan official-only 治理后的正确前提),补齐在任成员 (DUTY_ASSIGNEE_NOT_ACTIVE_MEMBER)、任务键 bd_intake、阶段键 V0.1_triage、 项目作用域 run record(events.ValidateEnvelope 拒绝 project<=0)、 task_run_resume 去除内嵌 dev 凭证的 config 硬编码。 - 新增 seedProjectForRun 测试辅助。 验证:fresh 隔离库(55432)上 go test -short -p 1 ./... 除 archtest (另行注册表分支修复)外全部通过;包内并行迁移竞争需 -p 1。
111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
package notification_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/notification"
|
|
)
|
|
|
|
func TestPG_StageAdvanceReminder24h(t *testing.T) {
|
|
pool := openPool(t)
|
|
defer pool.Close()
|
|
|
|
notification.SetStageReminderAfterForTest(1 * time.Millisecond)
|
|
t.Cleanup(func() {
|
|
notification.SetStageReminderAfterForTest(24 * time.Hour)
|
|
})
|
|
|
|
svc := notification.NewService(pool)
|
|
ctx := context.Background()
|
|
|
|
var projectID, instanceID, stageInstID int64
|
|
code := "remind-" + time.Now().Format("150405.000000")
|
|
if err := pool.QueryRow(ctx, `
|
|
INSERT INTO projects (code, name, status, creator_user_id, lifecycle_status)
|
|
VALUES ($1, 'Reminder Test ' || $2, 'active', 2, 'active') RETURNING id
|
|
`, code, code).Scan(&projectID); err != nil {
|
|
t.Fatalf("insert project: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
_, _ = pool.Exec(context.Background(), `DELETE FROM projects WHERE id = $1`, projectID)
|
|
})
|
|
|
|
var wfVersionID int64
|
|
if err := pool.QueryRow(ctx, `SELECT id FROM workflow_versions ORDER BY id DESC LIMIT 1`).Scan(&wfVersionID); err != nil {
|
|
t.Skip("no workflow version")
|
|
}
|
|
|
|
if err := pool.QueryRow(ctx, `
|
|
INSERT INTO project_workflow_instances (project_id, workflow_version_id, instance_no, status, config_revision, plan_hash, config_snapshot, created_by)
|
|
VALUES ($1, $2, 1, 'active', 5, 'h', '{}'::jsonb, 2) RETURNING id
|
|
`, projectID, wfVersionID).Scan(&instanceID); err != nil {
|
|
t.Fatalf("insert instance: %v", err)
|
|
}
|
|
|
|
if err := pool.QueryRow(ctx, `
|
|
INSERT INTO project_stage_instances (workflow_instance_id, stage_key, position, enabled, status)
|
|
VALUES ($1, 'biz_intake', 0, true, 'active') RETURNING id
|
|
`, instanceID).Scan(&stageInstID); err != nil {
|
|
t.Fatalf("insert stage: %v", err)
|
|
}
|
|
|
|
created, err := svc.EmitStageReadyToAdvance(ctx, 2, notification.StageReadyPayload{
|
|
ProjectID: projectID,
|
|
WorkflowInstanceID: instanceID,
|
|
StageInstanceID: stageInstID,
|
|
StageKey: "biz_intake",
|
|
StageName: "商务摸底",
|
|
ConfigRevision: 5,
|
|
RequiredDocCount: 1,
|
|
ApprovedDocCount: 1,
|
|
})
|
|
if err != nil || !created {
|
|
t.Fatalf("initial emit: created=%v err=%v", created, err)
|
|
}
|
|
|
|
_, err = pool.Exec(ctx, `
|
|
UPDATE stage_advance_requests SET requested_at = now() - interval '25 hours'
|
|
WHERE stage_instance_id = $1 AND readiness_revision = 5
|
|
`, stageInstID)
|
|
if err != nil {
|
|
t.Fatalf("backdate request: %v", err)
|
|
}
|
|
|
|
if _, err := svc.ProcessStageAdvanceReminders(ctx); err != nil {
|
|
t.Fatalf("process reminders: %v", err)
|
|
}
|
|
|
|
var remindCount int
|
|
err = pool.QueryRow(ctx, `
|
|
SELECT COUNT(*) FROM notifications
|
|
WHERE user_id = 2 AND project_id = $1 AND type = $2
|
|
`, projectID, notification.TypeStageReadyReminder24).Scan(&remindCount)
|
|
if err != nil {
|
|
t.Fatalf("count reminders: %v", err)
|
|
}
|
|
if remindCount != 1 {
|
|
t.Fatalf("expected 1 reminder notification for test project, got %d", remindCount)
|
|
}
|
|
|
|
if _, err := svc.ProcessStageAdvanceReminders(ctx); err != nil {
|
|
t.Fatalf("process reminders again: %v", err)
|
|
}
|
|
err = pool.QueryRow(ctx, `
|
|
SELECT COUNT(*) FROM notifications
|
|
WHERE user_id = 2 AND project_id = $1 AND type = $2
|
|
`, projectID, notification.TypeStageReadyReminder24).Scan(&remindCount)
|
|
if err != nil || remindCount != 1 {
|
|
t.Fatalf("expected deduped reminder count 1, got %d err=%v", remindCount, err)
|
|
}
|
|
|
|
if err := svc.ConfirmStageAdvanceRequest(ctx, stageInstID, 5, 2); err != nil {
|
|
t.Fatalf("confirm: %v", err)
|
|
}
|
|
|
|
if _, err := svc.ProcessStageAdvanceReminders(ctx); err != nil {
|
|
t.Fatalf("process after confirm: %v", err)
|
|
}
|
|
}
|