You've already forked agentic-coding-workflow
feat(app): wire workspace module + integration test for ws closed loop
This commit is contained in:
@@ -116,7 +116,9 @@ func (s *workspaceService) Create(ctx context.Context, c Caller, projectSlug str
|
||||
}
|
||||
|
||||
s.audit(ctx, &c.UserID, "workspace.create", "workspace", wsID.String(), map[string]any{"slug": in.Slug})
|
||||
s.clones.Schedule(wsID)
|
||||
if s.clones != nil {
|
||||
s.clones.Schedule(wsID)
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
|
||||
@@ -304,6 +306,28 @@ func (s *workspaceService) encryptForUpsert(wsID uuid.UUID, in UpsertCredentialI
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
// CredentialAccessor 让 app 装配代码不重复实现凭据加载。
|
||||
// CloneRunner 与 GitOpsService 都通过这个窄接口拿明文 git.Credential。
|
||||
type CredentialAccessor interface {
|
||||
LoadDecryptedCredential(ctx context.Context, wsID uuid.UUID) (*git.Credential, error)
|
||||
}
|
||||
|
||||
// SchedulerSetter 让 app 装配代码在创建 cloneRunner 后回填到 service。
|
||||
// NewWorkspaceService 阶段允许传 nil scheduler;构造完 cloneRunner 再 SetScheduler。
|
||||
type SchedulerSetter interface {
|
||||
SetScheduler(s CloneScheduler)
|
||||
}
|
||||
|
||||
// LoadDecryptedCredential 暴露 loadDecryptedCredential 给 app 装配代码(CloneRunner、GitOps 共用)。
|
||||
func (s *workspaceService) LoadDecryptedCredential(ctx context.Context, wsID uuid.UUID) (*git.Credential, error) {
|
||||
return s.loadDecryptedCredential(ctx, wsID)
|
||||
}
|
||||
|
||||
// SetScheduler 在装配链路把后构造的 CloneRunner 回填到 service(NewWorkspaceService 阶段允许 nil)。
|
||||
func (s *workspaceService) SetScheduler(sched CloneScheduler) {
|
||||
s.clones = sched
|
||||
}
|
||||
|
||||
func (s *workspaceService) loadDecryptedCredential(ctx context.Context, wsID uuid.UUID) (*git.Credential, error) {
|
||||
cred, err := s.repo.GetCredentialByWorkspace(ctx, wsID)
|
||||
if err != nil {
|
||||
|
||||
@@ -220,6 +220,55 @@ func TestWorkspaceService_Create_HappyPath(t *testing.T) {
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
func TestWorkspaceService_Create_NilScheduler_NoPanic(t *testing.T) {
|
||||
// app 装配有"先建 service(scheduler=nil)→ 再用 service 构 cloneRunner → SetScheduler 回填"的环依赖。
|
||||
// Create 必须在 scheduler==nil 时安全跳过 Schedule,否则首个 workspace 会 panic。
|
||||
t.Parallel()
|
||||
pa := &fakePA{pid: uuid.New(), canRead: true, canWrite: true}
|
||||
svc, repo := newSvc(t, pa, nil, &fakeGit{})
|
||||
caller := Caller{UserID: uuid.New()}
|
||||
ws, err := svc.Create(context.Background(), caller, "p", CreateWorkspaceInput{
|
||||
Slug: "ws1", Name: "W1", GitRemoteURL: "https://x", DefaultBranch: "main",
|
||||
Credential: UpsertCredentialInput{Kind: CredKindNone},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, SyncStatusCloning, ws.SyncStatus)
|
||||
_, ok := repo.creds[ws.ID]
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
func TestWorkspaceService_SetScheduler_BackfillSchedules(t *testing.T) {
|
||||
// SetScheduler 把 nil scheduler 替换为真正实现,下一次 Create 应触发 Schedule。
|
||||
t.Parallel()
|
||||
pa := &fakePA{pid: uuid.New(), canRead: true, canWrite: true}
|
||||
svc, _ := newSvc(t, pa, nil, &fakeGit{})
|
||||
sched := &fakeSched{}
|
||||
// SchedulerSetter 接口契约:app.go 在构建 CloneRunner 后回填。
|
||||
var setter SchedulerSetter = svc
|
||||
setter.SetScheduler(sched)
|
||||
caller := Caller{UserID: uuid.New()}
|
||||
ws, err := svc.Create(context.Background(), caller, "p", CreateWorkspaceInput{
|
||||
Slug: "ws2", Name: "W2", GitRemoteURL: "https://x", DefaultBranch: "main",
|
||||
Credential: UpsertCredentialInput{Kind: CredKindNone},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sched.called, 1)
|
||||
require.Equal(t, ws.ID, sched.called[0])
|
||||
}
|
||||
|
||||
func TestWorkspaceService_LoadDecryptedCredential_PassThrough(t *testing.T) {
|
||||
// CredentialAccessor 接口暴露内部 loadDecryptedCredential,CloneRunner / GitOpsService 复用。
|
||||
t.Parallel()
|
||||
pa := &fakePA{pid: uuid.New(), canRead: true, canWrite: true}
|
||||
svc, _ := newSvc(t, pa, nil, &fakeGit{})
|
||||
var acc CredentialAccessor = svc
|
||||
cred, err := acc.LoadDecryptedCredential(context.Background(), uuid.New())
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, cred)
|
||||
// 没有凭据行 → 返回 git.CredentialNone(loadDecryptedCredential 的兜底)。
|
||||
require.Equal(t, git.CredentialNone, cred.Kind)
|
||||
}
|
||||
|
||||
func TestWorkspaceService_Create_BadSlug(t *testing.T) {
|
||||
t.Parallel()
|
||||
pa := &fakePA{pid: uuid.New(), canWrite: true, canRead: true}
|
||||
|
||||
Reference in New Issue
Block a user