Files
Agent-System/internal/projectworkflow/service_test.go
T

167 lines
5.6 KiB
Go

package projectworkflow
import (
"context"
"errors"
"testing"
"chat-agent-system/internal/auth"
)
func TestProjectWorkflowService_CreateAndCreatorACL(t *testing.T) {
ctx := context.Background()
store := NewStore(nil)
catalog := NewDefaultCatalog()
svc := NewService(store, catalog)
creator := &auth.UserAccount{ID: 101, Username: "zhangsan", Name: "张工"}
otherUser := &auth.UserAccount{ID: 102, Username: "lisi", Name: "李主管"}
// 1. 未认证创建应被拦截
_, err := svc.CreateProject(ctx, nil, CreateProjectInput{Name: "测试项目A"})
if err != ErrUnauthenticated {
t.Fatalf("expected ErrUnauthenticated, got: %v", err)
}
// 2. 正常创建
proj, err := svc.CreateProject(ctx, creator, CreateProjectInput{
Name: "神经脉冲射频仪 (NeuroPulse)",
DeviceType: "高频神经射频治疗设备",
IntendedUse: "用于中枢神经阻滞与射频毁损治疗",
TargetMarket: "NMPA Class III",
})
if err != nil {
t.Fatalf("CreateProject failed: %v", err)
}
if proj.CreatorUserID != creator.ID {
t.Errorf("expected CreatorUserID %d, got %d", creator.ID, proj.CreatorUserID)
}
if proj.ActiveWorkflowKey != "main" {
t.Errorf("expected default active workflow 'main', got '%s'", proj.ActiveWorkflowKey)
}
if proj.WorkflowSelectionVersion != 1 {
t.Errorf("expected initial version 1, got %d", proj.WorkflowSelectionVersion)
}
// 3. 验证自动挂载的工作流清单
wfs, err := svc.ListProjectWorkflows(ctx, proj.ID)
if err != nil {
t.Fatalf("ListProjectWorkflows failed: %v", err)
}
if len(wfs) != 3 {
t.Fatalf("expected 3 default workflows, got %d", len(wfs))
}
// 4. 非创建人尝试切换工作流 -> 必须被拒绝
_, err = svc.ActivateWorkflow(ctx, proj.ID, otherUser.ID, ActivateWorkflowInput{
WorkflowKey: "product_manager",
ExpectedVersion: 1,
Reason: "尝试越权切换",
})
if err != ErrForbiddenNotCreator {
t.Fatalf("expected ErrForbiddenNotCreator, got: %v", err)
}
// 5. 创建人正常切换工作流 -> 成功升级版本
res, err := svc.ActivateWorkflow(ctx, proj.ID, creator.ID, ActivateWorkflowInput{
WorkflowKey: "product_manager",
ExpectedVersion: 1,
Reason: "进入产品需求阶段",
})
if err != nil {
t.Fatalf("ActivateWorkflow failed: %v", err)
}
if res.ActiveWorkflowKey != "product_manager" {
t.Errorf("expected active workflow 'product_manager', got '%s'", res.ActiveWorkflowKey)
}
if res.Version != 2 {
t.Errorf("expected version 2 after switch, got %d", res.Version)
}
// 6. 并发冲突:使用旧版本 1 再次切换 -> 必须返回 ErrConcurrentConflict
_, err = svc.ActivateWorkflow(ctx, proj.ID, creator.ID, ActivateWorkflowInput{
WorkflowKey: "test_manager",
ExpectedVersion: 1, // 旧版本
Reason: "并发落后切换",
})
if err != ErrConcurrentConflict {
t.Fatalf("expected ErrConcurrentConflict, got: %v", err)
}
// 7. 幂等测试:重复激活当前工作流 'product_manager'
resIdempotent, err := svc.ActivateWorkflow(ctx, proj.ID, creator.ID, ActivateWorkflowInput{
WorkflowKey: "product_manager",
ExpectedVersion: 2,
Reason: "重复激活",
})
if err != nil {
t.Fatalf("idempotent switch failed: %v", err)
}
if resIdempotent.ActiveWorkflowKey != "product_manager" {
t.Errorf("expected active 'product_manager', got '%s'", resIdempotent.ActiveWorkflowKey)
}
// 8. 切换到不存在的工作流 -> 返回 ErrWorkflowNotFound
_, err = svc.ActivateWorkflow(ctx, proj.ID, creator.ID, ActivateWorkflowInput{
WorkflowKey: "non_existent_wf",
})
if err == nil || !errors.Is(err, ErrWorkflowNotFound) {
t.Fatalf("expected ErrWorkflowNotFound, got: %v", err)
}
}
func TestProjectWorkflowService_LineageAndStale(t *testing.T) {
ctx := context.Background()
store := NewStore(nil)
catalog := NewDefaultCatalog()
svc := NewService(store, catalog)
creator := &auth.UserAccount{ID: 101, Username: "zhangsan", Name: "张工"}
proj, _ := svc.CreateProject(ctx, creator, CreateProjectInput{Name: "超声手术刀"})
// 记录测试经理方案依赖产品经理需求规格书 v1
err := svc.RecordLineage(ctx, &DocumentLineage{
ProjectID: proj.ID,
DownstreamDocID: "doc_test_plan_001",
DownstreamVersion: 1,
UpstreamDocID: "doc_system_reqs_001",
UpstreamVersion: 1,
UpstreamSHA256: "hash_v1_aaa",
})
if err != nil {
t.Fatalf("RecordLineage failed: %v", err)
}
lineages, err := svc.GetLineageForDownstream(ctx, "doc_test_plan_001", 1)
if err != nil || len(lineages) == 0 {
t.Fatalf("expected lineage record, got err: %v, len: %d", err, len(lineages))
}
if lineages[0].IsStale {
t.Errorf("initial lineage should not be stale")
}
// 上游生成相同 SHA256 -> 不触发 stale
staleUpdates, err := svc.CheckAndPropagateStale(ctx, "doc_system_reqs_001", "hash_v1_aaa", "")
if err != nil {
t.Fatalf("CheckAndPropagateStale failed: %v", err)
}
if len(staleUpdates) > 0 {
t.Errorf("expected 0 stale updates for identical hash, got %d", len(staleUpdates))
}
// 上游生成不同 SHA256 (内容更新) -> 触发下游 stale 传播
staleUpdates, err = svc.CheckAndPropagateStale(ctx, "doc_system_reqs_001", "hash_v2_bbb", "系统需求新增 RF-009 射频功率限制")
if err != nil {
t.Fatalf("CheckAndPropagateStale failed: %v", err)
}
if len(staleUpdates) != 1 {
t.Fatalf("expected 1 stale update, got %d", len(staleUpdates))
}
if !staleUpdates[0].IsStale {
t.Errorf("lineage should be marked as stale")
}
if staleUpdates[0].DownstreamDocID != "doc_test_plan_001" {
t.Errorf("expected downstream 'doc_test_plan_001', got '%s'", staleUpdates[0].DownstreamDocID)
}
}