package service import ( "strings" "testing" ) func TestExtractDeployStepName(t *testing.T) { tests := []struct { name string stepYaml string want string }{ { name: "service template with deploy step", stepYaml: `build-studio-api: depends_on: [preflight] image: woodpeckerci/plugin-kaniko deploy-studio-api: depends_on: [verify-studio-api] image: bitnami/kubectl:latest`, want: "deploy-studio-api", }, { name: "worker template with deploy step", stepYaml: `build-processor: depends_on: [preflight] deploy-processor: image: bitnami/kubectl:latest`, want: "deploy-processor", }, { name: "CLI template without deploy step", stepYaml: `build-mytool: depends_on: [deps] image: golang:1.25-alpine commands: - go build ./cmd`, want: "", }, { name: "empty input", stepYaml: "", want: "", }, { name: "hyphenated component name", stepYaml: `deploy-my-cool-service: image: bitnami/kubectl:latest`, want: "deploy-my-cool-service", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got := extractDeployStepName(tc.stepYaml) if got != tc.want { t.Errorf("extractDeployStepName() = %q, want %q", got, tc.want) } }) } } func TestAddBuildCompleteDep(t *testing.T) { baseYml := `steps: preflight: depends_on: [deps] image: alpine/curl # COMPONENT_STEPS_BELOW build-complete: depends_on: [preflight] # BUILD_COMPLETE_DEPS image: alpine:3.19 commands: - echo "All component builds complete"` tests := []struct { name string yml string stepName string wantDeps string }{ { name: "first component added", yml: baseYml, stepName: "deploy-studio-api", wantDeps: "depends_on: [preflight, deploy-studio-api] # BUILD_COMPLETE_DEPS", }, { name: "second component added", yml: strings.Replace(baseYml, "depends_on: [preflight] # BUILD_COMPLETE_DEPS", "depends_on: [preflight, deploy-studio-api] # BUILD_COMPLETE_DEPS", 1), stepName: "deploy-studio-ui", wantDeps: "depends_on: [preflight, deploy-studio-api, deploy-studio-ui] # BUILD_COMPLETE_DEPS", }, { name: "duplicate detection", yml: strings.Replace(baseYml, "depends_on: [preflight] # BUILD_COMPLETE_DEPS", "depends_on: [preflight, deploy-studio-api] # BUILD_COMPLETE_DEPS", 1), stepName: "deploy-studio-api", wantDeps: "depends_on: [preflight, deploy-studio-api] # BUILD_COMPLETE_DEPS", }, { name: "missing marker returns unchanged", yml: "steps:\n build-complete:\n image: alpine:3.19", stepName: "deploy-foo", wantDeps: "", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got := addBuildCompleteDep(tc.yml, tc.stepName) if tc.wantDeps == "" { // Expect no change if got != tc.yml { t.Errorf("expected unchanged yml, but got modification") } return } if !strings.Contains(got, tc.wantDeps) { t.Errorf("result does not contain expected depends_on line\nwant: %s\ngot:\n%s", tc.wantDeps, got) } }) } } func TestAddBuildCompleteDep_PreservesIndentation(t *testing.T) { yml := `steps: build-complete: depends_on: [preflight] # BUILD_COMPLETE_DEPS image: alpine:3.19` got := addBuildCompleteDep(yml, "deploy-foo") // The indentation (8 spaces) should be preserved want := " depends_on: [preflight, deploy-foo] # BUILD_COMPLETE_DEPS" if !strings.Contains(got, want) { t.Errorf("indentation not preserved\nwant line: %q\ngot:\n%s", want, got) } } func TestUpdateWoodpeckerYml_WiresBuildCompleteDeps(t *testing.T) { skeleton := `steps: preflight: depends_on: [deps] image: alpine/curl # COMPONENT_STEPS_BELOW # Do not remove the marker above build-complete: depends_on: [preflight] # BUILD_COMPLETE_DEPS image: alpine:3.19 commands: - echo "All component builds complete"` stepYaml := `build-studio-api: depends_on: [preflight] image: woodpeckerci/plugin-kaniko verify-studio-api: depends_on: [build-studio-api] image: alpine/curl deploy-studio-api: depends_on: [verify-studio-api] image: bitnami/kubectl:latest` svc := &ComponentService{} result := svc.updateWoodpeckerYml(skeleton, stepYaml) // Verify step YAML was inserted after marker if !strings.Contains(result, " build-studio-api:") { t.Error("component build step not inserted") } if !strings.Contains(result, " verify-studio-api:") { t.Error("component verify step not inserted") } if !strings.Contains(result, " deploy-studio-api:") { t.Error("component deploy step not inserted") } // Verify build-complete depends_on was updated if !strings.Contains(result, "depends_on: [preflight, deploy-studio-api] # BUILD_COMPLETE_DEPS") { t.Errorf("build-complete depends_on not updated\ngot:\n%s", result) } // Verify marker is preserved for future components if !strings.Contains(result, "# COMPONENT_STEPS_BELOW") { t.Error("COMPONENT_STEPS_BELOW marker was removed") } } func TestUpdateWoodpeckerYml_TwoComponents(t *testing.T) { skeleton := `steps: preflight: depends_on: [deps] # COMPONENT_STEPS_BELOW build-complete: depends_on: [preflight] # BUILD_COMPLETE_DEPS image: alpine:3.19` step1 := `build-studio-api: depends_on: [preflight] verify-studio-api: depends_on: [build-studio-api] deploy-studio-api: depends_on: [verify-studio-api]` step2 := `build-studio-ui: depends_on: [preflight] verify-studio-ui: depends_on: [build-studio-ui] deploy-studio-ui: depends_on: [verify-studio-ui]` svc := &ComponentService{} // Add first component result := svc.updateWoodpeckerYml(skeleton, step1) // Add second component result = svc.updateWoodpeckerYml(result, step2) // Both deploy steps should be in build-complete depends_on if !strings.Contains(result, "depends_on: [preflight, deploy-studio-api, deploy-studio-ui] # BUILD_COMPLETE_DEPS") { t.Errorf("build-complete doesn't depend on both deploy steps\ngot:\n%s", result) } } func TestUpdateWoodpeckerYml_CLISkipsBuildCompleteDep(t *testing.T) { skeleton := `steps: # COMPONENT_STEPS_BELOW build-complete: depends_on: [preflight] # BUILD_COMPLETE_DEPS image: alpine:3.19` cliStep := `build-mytool: depends_on: [deps] image: golang:1.25-alpine commands: - go build ./cmd` svc := &ComponentService{} result := svc.updateWoodpeckerYml(skeleton, cliStep) // CLI has no deploy step, so build-complete should stay unchanged if !strings.Contains(result, "depends_on: [preflight] # BUILD_COMPLETE_DEPS") { t.Errorf("build-complete was modified for CLI component (no deploy step)\ngot:\n%s", result) } } func TestUpdateWoodpeckerYml_MissingMarker(t *testing.T) { noMarker := `steps: build: image: golang:1.25` svc := &ComponentService{} result := svc.updateWoodpeckerYml(noMarker, "deploy-foo:\n image: test") if result != noMarker { t.Error("expected unchanged yml when marker is missing") } }