forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

Create a BuildCloneStep function that returns a step struct that inherits from models.Step

Signed-off-by: Evan Jarrett <evan@evanjarrett.com>

evan.jarrett.net d21ae89b d1a8d62b

verified
Changed files
+414 -491
spindle
+1 -1
spindle/engines/nixery/engine.go
··· 109 109 setup := &setupSteps{} 110 110 111 111 setup.addStep(nixConfStep()) 112 - setup.addStep(cloneStep(twf, *tpl.TriggerMetadata, e.cfg.Server.Dev)) 112 + setup.addStep(models.BuildCloneStep(twf, *tpl.TriggerMetadata, workspaceDir, e.cfg.Server.Dev)) 113 113 // this step could be empty 114 114 if s := dependencyStep(dwf.Dependencies); s != nil { 115 115 setup.addStep(*s)
-34
spindle/engines/nixery/setup_steps.go
··· 3 3 import ( 4 4 "fmt" 5 5 "strings" 6 - 7 - "tangled.org/core/api/tangled" 8 - "tangled.org/core/spindle/workflow" 9 6 ) 10 7 11 8 func nixConfStep() Step { ··· 15 12 return Step{ 16 13 command: setupCmd, 17 14 name: "Configure Nix", 18 - } 19 - } 20 - 21 - // cloneStep uses the shared clone step builder to generate git clone commands. 22 - // The shared builder handles: 23 - // - git init 24 - // - git remote add origin <url> 25 - // - git fetch --depth=<d> --recurse-submodules=<yes|no> <sha> 26 - // - git checkout FETCH_HEAD 27 - // And supports all trigger types (push, PR, manual) and clone options. 28 - func cloneStep(twf tangled.Pipeline_Workflow, tr tangled.Pipeline_TriggerMetadata, dev bool) Step { 29 - info, err := workflow.GetCloneInfo(workflow.CloneOptions{ 30 - Workflow: twf, 31 - TriggerMetadata: tr, 32 - DevMode: dev, 33 - WorkspaceDir: workspaceDir, 34 - }) 35 - if err != nil { 36 - return Step{ 37 - command: fmt.Sprintf("echo 'Failed to get clone info: %s' && exit 1", err.Error()), 38 - name: "Clone repository into workspace (error)", 39 - } 40 - } 41 - 42 - if info.Skip { 43 - return Step{} 44 - } 45 - 46 - return Step{ 47 - command: strings.Join(info.Commands, "\n"), 48 - name: "Clone repository into workspace", 49 15 } 50 16 } 51 17
+364
spindle/models/clone_test.go
··· 1 + package models 2 + 3 + import ( 4 + "strings" 5 + "testing" 6 + 7 + "tangled.org/core/api/tangled" 8 + "tangled.org/core/workflow" 9 + ) 10 + 11 + func TestBuildCloneStep_PushTrigger(t *testing.T) { 12 + twf := tangled.Pipeline_Workflow{ 13 + Clone: &tangled.Pipeline_CloneOpts{ 14 + Depth: 1, 15 + Submodules: false, 16 + Skip: false, 17 + }, 18 + } 19 + tr := tangled.Pipeline_TriggerMetadata{ 20 + Kind: string(workflow.TriggerKindPush), 21 + Push: &tangled.Pipeline_PushTriggerData{ 22 + NewSha: "abc123", 23 + OldSha: "def456", 24 + Ref: "refs/heads/main", 25 + }, 26 + Repo: &tangled.Pipeline_TriggerRepo{ 27 + Knot: "example.com", 28 + Did: "did:plc:user123", 29 + Repo: "my-repo", 30 + }, 31 + } 32 + 33 + step := BuildCloneStep(twf, tr, "/tangled/workspace", false) 34 + 35 + if step.Kind() != StepKindSystem { 36 + t.Errorf("Expected StepKindSystem, got %v", step.Kind()) 37 + } 38 + 39 + if step.Name() != "Clone repository into workspace" { 40 + t.Errorf("Expected 'Clone repository into workspace', got '%s'", step.Name()) 41 + } 42 + 43 + commands := step.Commands() 44 + if len(commands) != 5 { 45 + t.Errorf("Expected 5 commands, got %d", len(commands)) 46 + } 47 + 48 + // Verify commands contain expected git operations 49 + allCmds := strings.Join(commands, " ") 50 + if !strings.Contains(allCmds, "git init") { 51 + t.Error("Commands should contain 'git init'") 52 + } 53 + if !strings.Contains(allCmds, "git remote add origin") { 54 + t.Error("Commands should contain 'git remote add origin'") 55 + } 56 + if !strings.Contains(allCmds, "git fetch") { 57 + t.Error("Commands should contain 'git fetch'") 58 + } 59 + if !strings.Contains(allCmds, "abc123") { 60 + t.Error("Commands should contain commit SHA") 61 + } 62 + if !strings.Contains(allCmds, "git checkout FETCH_HEAD") { 63 + t.Error("Commands should contain 'git checkout FETCH_HEAD'") 64 + } 65 + if !strings.Contains(allCmds, "https://example.com/did:plc:user123/my-repo") { 66 + t.Error("Commands should contain expected repo URL") 67 + } 68 + } 69 + 70 + func TestBuildCloneStep_PullRequestTrigger(t *testing.T) { 71 + twf := tangled.Pipeline_Workflow{ 72 + Clone: &tangled.Pipeline_CloneOpts{ 73 + Depth: 1, 74 + Skip: false, 75 + }, 76 + } 77 + tr := tangled.Pipeline_TriggerMetadata{ 78 + Kind: string(workflow.TriggerKindPullRequest), 79 + PullRequest: &tangled.Pipeline_PullRequestTriggerData{ 80 + SourceSha: "pr-sha-789", 81 + SourceBranch: "feature-branch", 82 + TargetBranch: "main", 83 + Action: "opened", 84 + }, 85 + Repo: &tangled.Pipeline_TriggerRepo{ 86 + Knot: "example.com", 87 + Did: "did:plc:user123", 88 + Repo: "my-repo", 89 + }, 90 + } 91 + 92 + step := BuildCloneStep(twf, tr, "/tangled/workspace", false) 93 + 94 + allCmds := strings.Join(step.Commands(), " ") 95 + if !strings.Contains(allCmds, "pr-sha-789") { 96 + t.Error("Commands should contain PR commit SHA") 97 + } 98 + } 99 + 100 + func TestBuildCloneStep_ManualTrigger(t *testing.T) { 101 + twf := tangled.Pipeline_Workflow{ 102 + Clone: &tangled.Pipeline_CloneOpts{ 103 + Depth: 1, 104 + Skip: false, 105 + }, 106 + } 107 + tr := tangled.Pipeline_TriggerMetadata{ 108 + Kind: string(workflow.TriggerKindManual), 109 + Manual: &tangled.Pipeline_ManualTriggerData{ 110 + Inputs: nil, 111 + }, 112 + Repo: &tangled.Pipeline_TriggerRepo{ 113 + Knot: "example.com", 114 + Did: "did:plc:user123", 115 + Repo: "my-repo", 116 + }, 117 + } 118 + 119 + step := BuildCloneStep(twf, tr, "/tangled/workspace", false) 120 + 121 + // Manual triggers don't have a SHA yet (TODO), so git fetch won't include a SHA 122 + allCmds := strings.Join(step.Commands(), " ") 123 + // Should still have basic git commands 124 + if !strings.Contains(allCmds, "git init") { 125 + t.Error("Commands should contain 'git init'") 126 + } 127 + if !strings.Contains(allCmds, "git fetch") { 128 + t.Error("Commands should contain 'git fetch'") 129 + } 130 + } 131 + 132 + func TestBuildCloneStep_SkipFlag(t *testing.T) { 133 + twf := tangled.Pipeline_Workflow{ 134 + Clone: &tangled.Pipeline_CloneOpts{ 135 + Skip: true, 136 + }, 137 + } 138 + tr := tangled.Pipeline_TriggerMetadata{ 139 + Kind: string(workflow.TriggerKindPush), 140 + Push: &tangled.Pipeline_PushTriggerData{ 141 + NewSha: "abc123", 142 + }, 143 + Repo: &tangled.Pipeline_TriggerRepo{ 144 + Knot: "example.com", 145 + Did: "did:plc:user123", 146 + Repo: "my-repo", 147 + }, 148 + } 149 + 150 + step := BuildCloneStep(twf, tr, "/tangled/workspace", false) 151 + 152 + // Empty step when skip is true 153 + if step.Name() != "" { 154 + t.Error("Expected empty step name when Skip is true") 155 + } 156 + if len(step.Commands()) != 0 { 157 + t.Errorf("Expected no commands when Skip is true, got %d commands", len(step.Commands())) 158 + } 159 + } 160 + 161 + func TestBuildCloneStep_DevMode(t *testing.T) { 162 + twf := tangled.Pipeline_Workflow{ 163 + Clone: &tangled.Pipeline_CloneOpts{ 164 + Depth: 1, 165 + Skip: false, 166 + }, 167 + } 168 + tr := tangled.Pipeline_TriggerMetadata{ 169 + Kind: string(workflow.TriggerKindPush), 170 + Push: &tangled.Pipeline_PushTriggerData{ 171 + NewSha: "abc123", 172 + }, 173 + Repo: &tangled.Pipeline_TriggerRepo{ 174 + Knot: "localhost:3000", 175 + Did: "did:plc:user123", 176 + Repo: "my-repo", 177 + }, 178 + } 179 + 180 + step := BuildCloneStep(twf, tr, "/tangled/workspace", true) 181 + 182 + // In dev mode, should use http:// and replace localhost with host.docker.internal 183 + allCmds := strings.Join(step.Commands(), " ") 184 + expectedURL := "http://host.docker.internal:3000/did:plc:user123/my-repo" 185 + if !strings.Contains(allCmds, expectedURL) { 186 + t.Errorf("Expected dev mode URL '%s' in commands", expectedURL) 187 + } 188 + } 189 + 190 + func TestBuildCloneStep_DepthAndSubmodules(t *testing.T) { 191 + twf := tangled.Pipeline_Workflow{ 192 + Clone: &tangled.Pipeline_CloneOpts{ 193 + Depth: 10, 194 + Submodules: true, 195 + Skip: false, 196 + }, 197 + } 198 + tr := tangled.Pipeline_TriggerMetadata{ 199 + Kind: string(workflow.TriggerKindPush), 200 + Push: &tangled.Pipeline_PushTriggerData{ 201 + NewSha: "abc123", 202 + }, 203 + Repo: &tangled.Pipeline_TriggerRepo{ 204 + Knot: "example.com", 205 + Did: "did:plc:user123", 206 + Repo: "my-repo", 207 + }, 208 + } 209 + 210 + step := BuildCloneStep(twf, tr, "/tangled/workspace", false) 211 + 212 + allCmds := strings.Join(step.Commands(), " ") 213 + if !strings.Contains(allCmds, "--depth=10") { 214 + t.Error("Commands should contain '--depth=10'") 215 + } 216 + 217 + if !strings.Contains(allCmds, "--recurse-submodules=yes") { 218 + t.Error("Commands should contain '--recurse-submodules=yes'") 219 + } 220 + } 221 + 222 + func TestBuildCloneStep_DefaultDepth(t *testing.T) { 223 + twf := tangled.Pipeline_Workflow{ 224 + Clone: &tangled.Pipeline_CloneOpts{ 225 + Depth: 0, // Default should be 1 226 + Skip: false, 227 + }, 228 + } 229 + tr := tangled.Pipeline_TriggerMetadata{ 230 + Kind: string(workflow.TriggerKindPush), 231 + Push: &tangled.Pipeline_PushTriggerData{ 232 + NewSha: "abc123", 233 + }, 234 + Repo: &tangled.Pipeline_TriggerRepo{ 235 + Knot: "example.com", 236 + Did: "did:plc:user123", 237 + Repo: "my-repo", 238 + }, 239 + } 240 + 241 + step := BuildCloneStep(twf, tr, "/tangled/workspace", false) 242 + 243 + allCmds := strings.Join(step.Commands(), " ") 244 + if !strings.Contains(allCmds, "--depth=1") { 245 + t.Error("Commands should default to '--depth=1'") 246 + } 247 + } 248 + 249 + func TestBuildCloneStep_NilPushData(t *testing.T) { 250 + twf := tangled.Pipeline_Workflow{ 251 + Clone: &tangled.Pipeline_CloneOpts{ 252 + Depth: 1, 253 + Skip: false, 254 + }, 255 + } 256 + tr := tangled.Pipeline_TriggerMetadata{ 257 + Kind: string(workflow.TriggerKindPush), 258 + Push: nil, // Nil push data should create error step 259 + Repo: &tangled.Pipeline_TriggerRepo{ 260 + Knot: "example.com", 261 + Did: "did:plc:user123", 262 + Repo: "my-repo", 263 + }, 264 + } 265 + 266 + step := BuildCloneStep(twf, tr, "/tangled/workspace", false) 267 + 268 + // Should return an error step 269 + if !strings.Contains(step.Name(), "error") { 270 + t.Error("Expected error in step name when push data is nil") 271 + } 272 + 273 + allCmds := strings.Join(step.Commands(), " ") 274 + if !strings.Contains(allCmds, "Failed to get clone info") { 275 + t.Error("Commands should contain error message") 276 + } 277 + if !strings.Contains(allCmds, "exit 1") { 278 + t.Error("Commands should exit with error") 279 + } 280 + } 281 + 282 + func TestBuildCloneStep_NilPRData(t *testing.T) { 283 + twf := tangled.Pipeline_Workflow{ 284 + Clone: &tangled.Pipeline_CloneOpts{ 285 + Depth: 1, 286 + Skip: false, 287 + }, 288 + } 289 + tr := tangled.Pipeline_TriggerMetadata{ 290 + Kind: string(workflow.TriggerKindPullRequest), 291 + PullRequest: nil, // Nil PR data should create error step 292 + Repo: &tangled.Pipeline_TriggerRepo{ 293 + Knot: "example.com", 294 + Did: "did:plc:user123", 295 + Repo: "my-repo", 296 + }, 297 + } 298 + 299 + step := BuildCloneStep(twf, tr, "/tangled/workspace", false) 300 + 301 + // Should return an error step 302 + if !strings.Contains(step.Name(), "error") { 303 + t.Error("Expected error in step name when pull request data is nil") 304 + } 305 + 306 + allCmds := strings.Join(step.Commands(), " ") 307 + if !strings.Contains(allCmds, "Failed to get clone info") { 308 + t.Error("Commands should contain error message") 309 + } 310 + } 311 + 312 + func TestBuildCloneStep_CustomWorkspace(t *testing.T) { 313 + twf := tangled.Pipeline_Workflow{ 314 + Clone: &tangled.Pipeline_CloneOpts{ 315 + Depth: 1, 316 + Skip: false, 317 + }, 318 + } 319 + tr := tangled.Pipeline_TriggerMetadata{ 320 + Kind: string(workflow.TriggerKindPush), 321 + Push: &tangled.Pipeline_PushTriggerData{ 322 + NewSha: "abc123", 323 + }, 324 + Repo: &tangled.Pipeline_TriggerRepo{ 325 + Knot: "example.com", 326 + Did: "did:plc:user123", 327 + Repo: "my-repo", 328 + }, 329 + } 330 + 331 + step := BuildCloneStep(twf, tr, "/custom/path", false) 332 + 333 + allCmds := strings.Join(step.Commands(), " ") 334 + if !strings.Contains(allCmds, "/custom/path") { 335 + t.Error("Commands should use custom workspace directory") 336 + } 337 + } 338 + 339 + func TestBuildCloneStep_DefaultWorkspace(t *testing.T) { 340 + twf := tangled.Pipeline_Workflow{ 341 + Clone: &tangled.Pipeline_CloneOpts{ 342 + Depth: 1, 343 + Skip: false, 344 + }, 345 + } 346 + tr := tangled.Pipeline_TriggerMetadata{ 347 + Kind: string(workflow.TriggerKindPush), 348 + Push: &tangled.Pipeline_PushTriggerData{ 349 + NewSha: "abc123", 350 + }, 351 + Repo: &tangled.Pipeline_TriggerRepo{ 352 + Knot: "example.com", 353 + Did: "did:plc:user123", 354 + Repo: "my-repo", 355 + }, 356 + } 357 + 358 + step := BuildCloneStep(twf, tr, "", false) // Empty should default to /tangled/workspace 359 + 360 + allCmds := strings.Join(step.Commands(), " ") 361 + if !strings.Contains(allCmds, "/tangled/workspace") { 362 + t.Error("Commands should default to /tangled/workspace") 363 + } 364 + }
+49 -36
spindle/workflow/clone.go spindle/models/clone.go
··· 1 - package workflow 1 + package models 2 2 3 3 import ( 4 4 "fmt" ··· 8 8 "tangled.org/core/workflow" 9 9 ) 10 10 11 - type CloneOptions struct { 12 - Workflow tangled.Pipeline_Workflow 13 - TriggerMetadata tangled.Pipeline_TriggerMetadata 14 - DevMode bool 15 - WorkspaceDir string 11 + type CloneStep struct { 12 + name string 13 + kind StepKind 14 + commands []string 15 + } 16 + 17 + func (s CloneStep) Name() string { 18 + return s.name 19 + } 20 + 21 + func (s CloneStep) Commands() []string { 22 + return s.commands 23 + } 24 + 25 + func (s CloneStep) Command() string { 26 + return strings.Join(s.commands, "\n") 16 27 } 17 28 18 - type CloneInfo struct { 19 - Commands []string 20 - RepoURL string 21 - CommitSHA string 22 - Skip bool 29 + func (s CloneStep) Kind() StepKind { 30 + return s.kind 23 31 } 24 32 25 - // GetCloneInfo generates git clone commands and metadata from pipeline trigger metadata 26 - func GetCloneInfo(opts CloneOptions) (*CloneInfo, error) { 27 - if opts.Workflow.Clone != nil && opts.Workflow.Clone.Skip { 28 - return &CloneInfo{Skip: true}, nil 33 + // BuildCloneStep generates git clone commands. 34 + // The shared builder handles: 35 + // - git init 36 + // - git remote add origin <url> 37 + // - git fetch --depth=<d> --recurse-submodules=<yes|no> <sha> 38 + // - git checkout FETCH_HEAD 39 + // And supports all trigger types (push, PR, manual) and clone options. 40 + func BuildCloneStep(twf tangled.Pipeline_Workflow, tr tangled.Pipeline_TriggerMetadata, workspaceDir string, dev bool) CloneStep { 41 + if twf.Clone != nil && twf.Clone.Skip { 42 + return CloneStep{} 29 43 } 30 44 31 - commitSHA, err := extractCommitSHA(opts.TriggerMetadata) 45 + commitSHA, err := extractCommitSHA(tr) 32 46 if err != nil { 33 - return nil, fmt.Errorf("failed to extract commit SHA: %w", err) 47 + return CloneStep{ 48 + kind: StepKindSystem, 49 + name: "Clone repository into workspace (error)", 50 + commands: []string{fmt.Sprintf("echo 'Failed to get clone info: %s' && exit 1", err.Error())}, 51 + } 34 52 } 35 53 36 - repoURL := buildRepoURL(opts.TriggerMetadata, opts.DevMode) 54 + repoURL := buildRepoURL(tr, dev) 37 55 38 - workspaceDir := opts.WorkspaceDir 39 56 if workspaceDir == "" { 40 57 workspaceDir = "/tangled/workspace" 41 58 } ··· 44 61 remoteCmd := fmt.Sprintf("git remote add origin %s", repoURL) 45 62 46 63 var cloneOpts tangled.Pipeline_CloneOpts 47 - if opts.Workflow.Clone != nil { 48 - cloneOpts = *opts.Workflow.Clone 64 + if twf.Clone != nil { 65 + cloneOpts = *twf.Clone 49 66 } 50 67 fetchArgs := buildFetchArgs(cloneOpts, commitSHA) 51 68 fetchCmd := fmt.Sprintf("git fetch %s", strings.Join(fetchArgs, " ")) 52 - 53 69 checkoutCmd := "git checkout FETCH_HEAD" 54 - 55 - commands := []string{ 56 - initCmd, 57 - fmt.Sprintf("cd %s", workspaceDir), 58 - remoteCmd, 59 - fetchCmd, 60 - checkoutCmd, 70 + 71 + return CloneStep{ 72 + kind: StepKindSystem, 73 + name: "Clone repository into workspace", 74 + commands: []string{ 75 + initCmd, 76 + fmt.Sprintf("cd %s", workspaceDir), 77 + remoteCmd, 78 + fetchCmd, 79 + checkoutCmd, 80 + }, 61 81 } 62 - 63 - return &CloneInfo{ 64 - Commands: commands, 65 - RepoURL: repoURL, 66 - CommitSHA: commitSHA, 67 - Skip: false, 68 - }, nil 69 82 } 70 83 71 84 // extractCommitSHA extracts the commit SHA from trigger metadata based on trigger type
-420
spindle/workflow/clone_test.go
··· 1 - package workflow 2 - 3 - import ( 4 - "strings" 5 - "testing" 6 - 7 - "tangled.org/core/api/tangled" 8 - "tangled.org/core/workflow" 9 - ) 10 - 11 - func TestGetCloneInfo_PushTrigger(t *testing.T) { 12 - cfg := CloneOptions{ 13 - Workflow: tangled.Pipeline_Workflow{ 14 - Clone: &tangled.Pipeline_CloneOpts{ 15 - Depth: 1, 16 - Submodules: false, 17 - Skip: false, 18 - }, 19 - }, 20 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 21 - Kind: string(workflow.TriggerKindPush), 22 - Push: &tangled.Pipeline_PushTriggerData{ 23 - NewSha: "abc123", 24 - OldSha: "def456", 25 - Ref: "refs/heads/main", 26 - }, 27 - Repo: &tangled.Pipeline_TriggerRepo{ 28 - Knot: "example.com", 29 - Did: "did:plc:user123", 30 - Repo: "my-repo", 31 - }, 32 - }, 33 - DevMode: false, 34 - WorkspaceDir: "/tangled/workspace", 35 - } 36 - 37 - info, err := GetCloneInfo(cfg) 38 - if err != nil { 39 - t.Fatalf("GetCloneInfo failed: %v", err) 40 - } 41 - 42 - if info.Skip { 43 - t.Error("Expected Skip to be false") 44 - } 45 - 46 - if info.CommitSHA != "abc123" { 47 - t.Errorf("Expected CommitSHA 'abc123', got '%s'", info.CommitSHA) 48 - } 49 - 50 - expectedURL := "https://example.com/did:plc:user123/my-repo" 51 - if info.RepoURL != expectedURL { 52 - t.Errorf("Expected RepoURL '%s', got '%s'", expectedURL, info.RepoURL) 53 - } 54 - 55 - if len(info.Commands) != 5 { 56 - t.Errorf("Expected 5 commands, got %d", len(info.Commands)) 57 - } 58 - 59 - // Verify commands contain expected git operations 60 - allCmds := strings.Join(info.Commands, " ") 61 - if !strings.Contains(allCmds, "git init") { 62 - t.Error("Commands should contain 'git init'") 63 - } 64 - if !strings.Contains(allCmds, "git remote add origin") { 65 - t.Error("Commands should contain 'git remote add origin'") 66 - } 67 - if !strings.Contains(allCmds, "git fetch") { 68 - t.Error("Commands should contain 'git fetch'") 69 - } 70 - if !strings.Contains(allCmds, "abc123") { 71 - t.Error("Commands should contain commit SHA") 72 - } 73 - if !strings.Contains(allCmds, "git checkout FETCH_HEAD") { 74 - t.Error("Commands should contain 'git checkout FETCH_HEAD'") 75 - } 76 - } 77 - 78 - func TestGetCloneInfo_PullRequestTrigger(t *testing.T) { 79 - cfg := CloneOptions{ 80 - Workflow: tangled.Pipeline_Workflow{ 81 - Clone: &tangled.Pipeline_CloneOpts{ 82 - Depth: 1, 83 - Skip: false, 84 - }, 85 - }, 86 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 87 - Kind: string(workflow.TriggerKindPullRequest), 88 - PullRequest: &tangled.Pipeline_PullRequestTriggerData{ 89 - SourceSha: "pr-sha-789", 90 - SourceBranch: "feature-branch", 91 - TargetBranch: "main", 92 - Action: "opened", 93 - }, 94 - Repo: &tangled.Pipeline_TriggerRepo{ 95 - Knot: "example.com", 96 - Did: "did:plc:user123", 97 - Repo: "my-repo", 98 - }, 99 - }, 100 - DevMode: false, 101 - WorkspaceDir: "/tangled/workspace", 102 - } 103 - 104 - info, err := GetCloneInfo(cfg) 105 - if err != nil { 106 - t.Fatalf("GetCloneInfo failed: %v", err) 107 - } 108 - 109 - if info.CommitSHA != "pr-sha-789" { 110 - t.Errorf("Expected CommitSHA 'pr-sha-789', got '%s'", info.CommitSHA) 111 - } 112 - 113 - allCmds := strings.Join(info.Commands, " ") 114 - if !strings.Contains(allCmds, "pr-sha-789") { 115 - t.Error("Commands should contain PR commit SHA") 116 - } 117 - } 118 - 119 - func TestGetCloneInfo_ManualTrigger(t *testing.T) { 120 - cfg := CloneOptions{ 121 - Workflow: tangled.Pipeline_Workflow{ 122 - Clone: &tangled.Pipeline_CloneOpts{ 123 - Depth: 1, 124 - Skip: false, 125 - }, 126 - }, 127 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 128 - Kind: string(workflow.TriggerKindManual), 129 - Manual: &tangled.Pipeline_ManualTriggerData{ 130 - Inputs: nil, 131 - }, 132 - Repo: &tangled.Pipeline_TriggerRepo{ 133 - Knot: "example.com", 134 - Did: "did:plc:user123", 135 - Repo: "my-repo", 136 - }, 137 - }, 138 - DevMode: false, 139 - WorkspaceDir: "/tangled/workspace", 140 - } 141 - 142 - info, err := GetCloneInfo(cfg) 143 - if err != nil { 144 - t.Fatalf("GetCloneInfo failed: %v", err) 145 - } 146 - 147 - // Manual triggers don't have a SHA yet (TODO) 148 - if info.CommitSHA != "" { 149 - t.Errorf("Expected empty CommitSHA for manual trigger, got '%s'", info.CommitSHA) 150 - } 151 - } 152 - 153 - func TestGetCloneInfo_SkipFlag(t *testing.T) { 154 - cfg := CloneOptions{ 155 - Workflow: tangled.Pipeline_Workflow{ 156 - Clone: &tangled.Pipeline_CloneOpts{ 157 - Skip: true, 158 - }, 159 - }, 160 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 161 - Kind: string(workflow.TriggerKindPush), 162 - Push: &tangled.Pipeline_PushTriggerData{ 163 - NewSha: "abc123", 164 - }, 165 - Repo: &tangled.Pipeline_TriggerRepo{ 166 - Knot: "example.com", 167 - Did: "did:plc:user123", 168 - Repo: "my-repo", 169 - }, 170 - }, 171 - } 172 - 173 - info, err := GetCloneInfo(cfg) 174 - if err != nil { 175 - t.Fatalf("GetCloneInfo failed: %v", err) 176 - } 177 - 178 - if !info.Skip { 179 - t.Error("Expected Skip to be true") 180 - } 181 - 182 - if len(info.Commands) != 0 { 183 - t.Errorf("Expected no commands when Skip is true, got %d commands", len(info.Commands)) 184 - } 185 - } 186 - 187 - func TestGetCloneInfo_DevMode(t *testing.T) { 188 - cfg := CloneOptions{ 189 - Workflow: tangled.Pipeline_Workflow{ 190 - Clone: &tangled.Pipeline_CloneOpts{ 191 - Depth: 1, 192 - Skip: false, 193 - }, 194 - }, 195 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 196 - Kind: string(workflow.TriggerKindPush), 197 - Push: &tangled.Pipeline_PushTriggerData{ 198 - NewSha: "abc123", 199 - }, 200 - Repo: &tangled.Pipeline_TriggerRepo{ 201 - Knot: "localhost:3000", 202 - Did: "did:plc:user123", 203 - Repo: "my-repo", 204 - }, 205 - }, 206 - DevMode: true, 207 - WorkspaceDir: "/tangled/workspace", 208 - } 209 - 210 - info, err := GetCloneInfo(cfg) 211 - if err != nil { 212 - t.Fatalf("GetCloneInfo failed: %v", err) 213 - } 214 - 215 - // In dev mode, should use http:// and replace localhost with host.docker.internal 216 - expectedURL := "http://host.docker.internal:3000/did:plc:user123/my-repo" 217 - if info.RepoURL != expectedURL { 218 - t.Errorf("Expected dev mode URL '%s', got '%s'", expectedURL, info.RepoURL) 219 - } 220 - } 221 - 222 - func TestGetCloneInfo_DepthAndSubmodules(t *testing.T) { 223 - cfg := CloneOptions{ 224 - Workflow: tangled.Pipeline_Workflow{ 225 - Clone: &tangled.Pipeline_CloneOpts{ 226 - Depth: 10, 227 - Submodules: true, 228 - Skip: false, 229 - }, 230 - }, 231 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 232 - Kind: string(workflow.TriggerKindPush), 233 - Push: &tangled.Pipeline_PushTriggerData{ 234 - NewSha: "abc123", 235 - }, 236 - Repo: &tangled.Pipeline_TriggerRepo{ 237 - Knot: "example.com", 238 - Did: "did:plc:user123", 239 - Repo: "my-repo", 240 - }, 241 - }, 242 - DevMode: false, 243 - WorkspaceDir: "/tangled/workspace", 244 - } 245 - 246 - info, err := GetCloneInfo(cfg) 247 - if err != nil { 248 - t.Fatalf("GetCloneInfo failed: %v", err) 249 - } 250 - 251 - allCmds := strings.Join(info.Commands, " ") 252 - if !strings.Contains(allCmds, "--depth=10") { 253 - t.Error("Commands should contain '--depth=10'") 254 - } 255 - 256 - if !strings.Contains(allCmds, "--recurse-submodules=yes") { 257 - t.Error("Commands should contain '--recurse-submodules=yes'") 258 - } 259 - } 260 - 261 - func TestGetCloneInfo_DefaultDepth(t *testing.T) { 262 - cfg := CloneOptions{ 263 - Workflow: tangled.Pipeline_Workflow{ 264 - Clone: &tangled.Pipeline_CloneOpts{ 265 - Depth: 0, // Default should be 1 266 - Skip: false, 267 - }, 268 - }, 269 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 270 - Kind: string(workflow.TriggerKindPush), 271 - Push: &tangled.Pipeline_PushTriggerData{ 272 - NewSha: "abc123", 273 - }, 274 - Repo: &tangled.Pipeline_TriggerRepo{ 275 - Knot: "example.com", 276 - Did: "did:plc:user123", 277 - Repo: "my-repo", 278 - }, 279 - }, 280 - WorkspaceDir: "/tangled/workspace", 281 - } 282 - 283 - info, err := GetCloneInfo(cfg) 284 - if err != nil { 285 - t.Fatalf("GetCloneInfo failed: %v", err) 286 - } 287 - 288 - allCmds := strings.Join(info.Commands, " ") 289 - if !strings.Contains(allCmds, "--depth=1") { 290 - t.Error("Commands should default to '--depth=1'") 291 - } 292 - } 293 - 294 - func TestGetCloneInfo_NilPushData(t *testing.T) { 295 - cfg := CloneOptions{ 296 - Workflow: tangled.Pipeline_Workflow{ 297 - Clone: &tangled.Pipeline_CloneOpts{ 298 - Depth: 1, 299 - Skip: false, 300 - }, 301 - }, 302 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 303 - Kind: string(workflow.TriggerKindPush), 304 - Push: nil, // Nil push data should return error 305 - Repo: &tangled.Pipeline_TriggerRepo{ 306 - Knot: "example.com", 307 - Did: "did:plc:user123", 308 - Repo: "my-repo", 309 - }, 310 - }, 311 - WorkspaceDir: "/tangled/workspace", 312 - } 313 - 314 - _, err := GetCloneInfo(cfg) 315 - if err == nil { 316 - t.Error("Expected error when push data is nil") 317 - } 318 - 319 - if !strings.Contains(err.Error(), "push trigger metadata is nil") { 320 - t.Errorf("Expected error about nil push metadata, got: %v", err) 321 - } 322 - } 323 - 324 - func TestGetCloneInfo_NilPRData(t *testing.T) { 325 - cfg := CloneOptions{ 326 - Workflow: tangled.Pipeline_Workflow{ 327 - Clone: &tangled.Pipeline_CloneOpts{ 328 - Depth: 1, 329 - Skip: false, 330 - }, 331 - }, 332 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 333 - Kind: string(workflow.TriggerKindPullRequest), 334 - PullRequest: nil, // Nil PR data should return error 335 - Repo: &tangled.Pipeline_TriggerRepo{ 336 - Knot: "example.com", 337 - Did: "did:plc:user123", 338 - Repo: "my-repo", 339 - }, 340 - }, 341 - WorkspaceDir: "/tangled/workspace", 342 - } 343 - 344 - _, err := GetCloneInfo(cfg) 345 - if err == nil { 346 - t.Error("Expected error when pull request data is nil") 347 - } 348 - 349 - if !strings.Contains(err.Error(), "pull request trigger metadata is nil") { 350 - t.Errorf("Expected error about nil PR metadata, got: %v", err) 351 - } 352 - } 353 - 354 - func TestGetCloneInfo_CustomWorkspace(t *testing.T) { 355 - cfg := CloneOptions{ 356 - Workflow: tangled.Pipeline_Workflow{ 357 - Clone: &tangled.Pipeline_CloneOpts{ 358 - Depth: 1, 359 - Skip: false, 360 - }, 361 - }, 362 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 363 - Kind: string(workflow.TriggerKindPush), 364 - Push: &tangled.Pipeline_PushTriggerData{ 365 - NewSha: "abc123", 366 - }, 367 - Repo: &tangled.Pipeline_TriggerRepo{ 368 - Knot: "example.com", 369 - Did: "did:plc:user123", 370 - Repo: "my-repo", 371 - }, 372 - }, 373 - DevMode: false, 374 - WorkspaceDir: "/custom/path", 375 - } 376 - 377 - info, err := GetCloneInfo(cfg) 378 - if err != nil { 379 - t.Fatalf("GetCloneInfo failed: %v", err) 380 - } 381 - 382 - allCmds := strings.Join(info.Commands, " ") 383 - if !strings.Contains(allCmds, "/custom/path") { 384 - t.Error("Commands should use custom workspace directory") 385 - } 386 - } 387 - 388 - func TestGetCloneInfo_DefaultWorkspace(t *testing.T) { 389 - cfg := CloneOptions{ 390 - Workflow: tangled.Pipeline_Workflow{ 391 - Clone: &tangled.Pipeline_CloneOpts{ 392 - Depth: 1, 393 - Skip: false, 394 - }, 395 - }, 396 - TriggerMetadata: tangled.Pipeline_TriggerMetadata{ 397 - Kind: string(workflow.TriggerKindPush), 398 - Push: &tangled.Pipeline_PushTriggerData{ 399 - NewSha: "abc123", 400 - }, 401 - Repo: &tangled.Pipeline_TriggerRepo{ 402 - Knot: "example.com", 403 - Did: "did:plc:user123", 404 - Repo: "my-repo", 405 - }, 406 - }, 407 - DevMode: false, 408 - WorkspaceDir: "", // Empty should default to /tangled/workspace 409 - } 410 - 411 - info, err := GetCloneInfo(cfg) 412 - if err != nil { 413 - t.Fatalf("GetCloneInfo failed: %v", err) 414 - } 415 - 416 - allCmds := strings.Join(info.Commands, " ") 417 - if !strings.Contains(allCmds, "/tangled/workspace") { 418 - t.Error("Commands should default to /tangled/workspace") 419 - } 420 - }