You've already forked agentic-coding-workflow
ACP / MCP 完善
This commit is contained in:
@@ -245,6 +245,19 @@ func (r *fakeAcpRepo) UpdateSessionRunning(_ context.Context, _ uuid.UUID, _ str
|
||||
func (r *fakeAcpRepo) UpdateSessionFinished(_ context.Context, _ uuid.UUID, _ acp.SessionStatus, _ *int32, _ *string) error {
|
||||
return nil
|
||||
}
|
||||
func (r *fakeAcpRepo) MarkSessionFailedIfActive(_ context.Context, id uuid.UUID, lastErr string) (bool, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
for _, s := range r.sessions {
|
||||
if s.ID == id && s.Status.IsActive() {
|
||||
s.Status = acp.SessionCrashed
|
||||
msg := lastErr
|
||||
s.LastError = &msg
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
func (r *fakeAcpRepo) ResetStuckSessionsOnRestart(_ context.Context) ([]*acp.Session, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -444,6 +457,101 @@ func TestSessionService_Create_TokenIssueFails_RollsBack(t *testing.T) {
|
||||
repo.mu.Unlock()
|
||||
}
|
||||
|
||||
func TestSessionService_Create_SpawnFails_MarksSessionCrashed(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
uid := uuid.New()
|
||||
wsID := uuid.New()
|
||||
pid := uuid.New()
|
||||
akID := uuid.New()
|
||||
|
||||
repo := &fakeAcpRepo{
|
||||
kinds: []*acp.AgentKind{
|
||||
// BinaryPath 不存在 → supervisor.Spawn 在 cmd.Start 早期失败,
|
||||
// 进程未注册、onExit 不会触发。
|
||||
{ID: akID, Name: "claude", Enabled: true, BinaryPath: "/nonexistent/agent-binary"},
|
||||
},
|
||||
}
|
||||
wsSvc := &fakeWsSvc{ws: &workspace.Workspace{
|
||||
ID: wsID, ProjectID: pid, DefaultBranch: "main", MainPath: "/tmp/main",
|
||||
}}
|
||||
pa := fakeProjectAccess{pj: &project.Project{ID: pid, Slug: "test-proj"}}
|
||||
mcpTokens := &fakeMCPTokenSvc{}
|
||||
|
||||
sup := acp.NewSupervisor(
|
||||
repo, nil, nil, nil, nil, mcpTokens, nil,
|
||||
acp.SupervisorConfig{SpawnTimeout: 5 * time.Second, KillGrace: 1 * time.Second},
|
||||
nil,
|
||||
)
|
||||
|
||||
svc := acp.NewSessionService(
|
||||
repo, wsSvc, nil, nil, pa, sup, nil,
|
||||
acp.SessionServiceConfig{
|
||||
MaxActiveGlobal: 10,
|
||||
MaxActivePerUser: 5,
|
||||
SystemTokenTTL: 24 * time.Hour,
|
||||
MCPPublicURL: "http://localhost:8080/mcp",
|
||||
},
|
||||
mcpTokens,
|
||||
)
|
||||
|
||||
branch := "main"
|
||||
sess, err := svc.Create(context.Background(), acp.Caller{UserID: uid}, acp.CreateSessionInput{
|
||||
WorkspaceID: wsID,
|
||||
AgentKindID: akID,
|
||||
Branch: &branch,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, sess)
|
||||
|
||||
// async spawn 失败后:session 被守卫式标记为 crashed 且 last_error 非空
|
||||
require.Eventually(t, func() bool {
|
||||
repo.mu.Lock()
|
||||
defer repo.mu.Unlock()
|
||||
for _, s := range repo.sessions {
|
||||
if s.ID == sess.ID {
|
||||
return s.Status == acp.SessionCrashed && s.LastError != nil && *s.LastError != ""
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, 5*time.Second, 50*time.Millisecond, "session should be marked crashed with non-empty last_error")
|
||||
}
|
||||
|
||||
func TestSessionService_Create_MutuallyExclusiveInputs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
uid := uuid.New()
|
||||
wsID := uuid.New()
|
||||
akID := uuid.New()
|
||||
|
||||
svc := acp.NewSessionService(
|
||||
&fakeAcpRepo{}, nil, nil, nil, nil, nil, nil,
|
||||
acp.SessionServiceConfig{}, nil,
|
||||
)
|
||||
|
||||
branch := "feat/x"
|
||||
issueNum := 42
|
||||
reqNum := 7
|
||||
cases := []struct {
|
||||
name string
|
||||
in acp.CreateSessionInput
|
||||
}{
|
||||
{"branch+issue", acp.CreateSessionInput{Branch: &branch, IssueNumber: &issueNum}},
|
||||
{"branch+requirement", acp.CreateSessionInput{Branch: &branch, RequirementNumber: &reqNum}},
|
||||
{"issue+requirement", acp.CreateSessionInput{IssueNumber: &issueNum, RequirementNumber: &reqNum}},
|
||||
{"all three", acp.CreateSessionInput{Branch: &branch, IssueNumber: &issueNum, RequirementNumber: &reqNum}},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tc.in.WorkspaceID = wsID
|
||||
tc.in.AgentKindID = akID
|
||||
_, err := svc.Create(context.Background(), acp.Caller{UserID: uid}, tc.in)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "mutually exclusive")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ===== PermissionRequest(权限审批框架,测试桩) =====
|
||||
func (f *fakeAcpRepo) InsertPermissionRequest(context.Context, *acp.PermissionRequest) (*acp.PermissionRequest, error) {
|
||||
return &acp.PermissionRequest{}, nil
|
||||
|
||||
Reference in New Issue
Block a user