You've already forked woodpecker
mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-06-03 16:35:37 +02:00
191 lines
6.0 KiB
Go
191 lines
6.0 KiB
Go
// Copyright 2026 Woodpecker Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//go:build test
|
|
|
|
package scenarios
|
|
|
|
import (
|
|
"embed"
|
|
"encoding/json"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types"
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/model"
|
|
)
|
|
|
|
//go:embed fixtures/*.yaml fixtures/*.json fixtures/*/*.yaml fixtures/*/*.json
|
|
var fixtureFS embed.FS
|
|
|
|
// Scenario is the single source of truth for one integration test case.
|
|
//
|
|
// Single-workflow scenarios use a flat fixture pair:
|
|
//
|
|
// fixtures/NN_name.yaml — the pipeline YAML served by the mock forge
|
|
// fixtures/NN_name.json — assertions (Scenario fields)
|
|
//
|
|
// Multi-workflow scenarios use a subdirectory:
|
|
//
|
|
// fixtures/NN_name/workflow-a.yaml
|
|
// fixtures/NN_name/workflow-b.yaml
|
|
// fixtures/NN_name/scenario.json — assertions; Workflows field is populated from the YAMLs
|
|
type Scenario struct {
|
|
// Name is a human-readable label shown in test output.
|
|
Name string `json:"name"`
|
|
|
|
// Event is the webhook event that triggers the pipeline (default: push).
|
|
Event model.WebhookEvent `json:"event"`
|
|
|
|
// ExpectedStatus is the final pipeline status we assert on.
|
|
ExpectedStatus model.StatusValue `json:"expected_status"`
|
|
|
|
// ExpectedSteps lists per-step assertions (matched by step name).
|
|
// Steps not listed here are not checked.
|
|
ExpectedSteps []ExpectedStep `json:"expected_steps"`
|
|
|
|
// ExpectedWorkflows lists per-workflow assertions (matched by workflow name).
|
|
// Only checked when non-empty. For single-workflow pipelines, the workflow
|
|
// name is derived from the YAML filename by the step builder.
|
|
ExpectedWorkflows []ExpectedWorkflow `json:"expected_workflows"`
|
|
|
|
// Files is the set of workflow YAML files served by the mock forge.
|
|
// Single-workflow: one entry named ".woodpecker.yaml".
|
|
// Multi-workflow: one entry per file in the fixtures subdirectory,
|
|
// with paths like ".woodpecker/workflow-a.yaml".
|
|
// Populated by LoadScenarios — not present in the JSON.
|
|
Files []*forge_types.FileMeta `json:"-"`
|
|
}
|
|
|
|
// ExpectedStep describes what we expect for one named step after the pipeline finishes.
|
|
type ExpectedStep struct {
|
|
Name string `json:"name"`
|
|
Status model.StatusValue `json:"status"`
|
|
ExitCode int `json:"exit_code"`
|
|
}
|
|
|
|
// ExpectedWorkflow describes what we expect for one named workflow after the pipeline finishes.
|
|
type ExpectedWorkflow struct {
|
|
Name string `json:"name"`
|
|
Status model.StatusValue `json:"status"`
|
|
}
|
|
|
|
// LoadScenarios reads all fixture pairs and subdirectories from the embedded
|
|
// fixtures/ directory and returns them sorted by filesystem order.
|
|
//
|
|
// Flat pairs (NN_name.yaml + NN_name.json) → single-workflow scenario.
|
|
// Directories (NN_name/ with *.yaml + scenario.json) → multi-workflow scenario.
|
|
func LoadScenarios(t *testing.T) []Scenario {
|
|
t.Helper()
|
|
|
|
entries, err := fixtureFS.ReadDir("fixtures")
|
|
require.NoError(t, err, "read fixtures dir")
|
|
|
|
// Index flat YAML files by stem.
|
|
yamlByStem := make(map[string][]byte)
|
|
jsonByStem := make(map[string][]byte)
|
|
|
|
var scenarios []Scenario
|
|
|
|
for _, e := range entries {
|
|
name := e.Name()
|
|
|
|
if e.IsDir() {
|
|
// Multi-workflow scenario: load scenario.json + all *.yaml files.
|
|
s := loadMultiWorkflowScenario(t, name)
|
|
scenarios = append(scenarios, s)
|
|
continue
|
|
}
|
|
|
|
data, err := fixtureFS.ReadFile(filepath.Join("fixtures", name))
|
|
require.NoError(t, err, "read fixture %s", name)
|
|
|
|
stem := strings.TrimSuffix(strings.TrimSuffix(name, ".yaml"), ".json")
|
|
switch filepath.Ext(name) {
|
|
case ".yaml":
|
|
yamlByStem[stem] = data
|
|
case ".json":
|
|
jsonByStem[stem] = data
|
|
}
|
|
}
|
|
|
|
// Pair flat YAML + JSON files.
|
|
for stem, jsonData := range jsonByStem {
|
|
var s Scenario
|
|
require.NoError(t, json.Unmarshal(jsonData, &s), "parse %s.json", stem)
|
|
|
|
yamlData, ok := yamlByStem[stem]
|
|
require.True(t, ok, "missing %s.yaml for %s.json", stem, stem)
|
|
|
|
// Single-workflow: serve as ".woodpecker.yaml" so the config service
|
|
// calls File() and gets back the YAML directly.
|
|
s.Files = []*forge_types.FileMeta{
|
|
{Name: ".woodpecker.yaml", Data: yamlData},
|
|
}
|
|
|
|
if s.Event == "" {
|
|
s.Event = model.EventPush
|
|
}
|
|
scenarios = append(scenarios, s)
|
|
}
|
|
|
|
require.NotEmpty(t, scenarios, "no scenarios loaded")
|
|
return scenarios
|
|
}
|
|
|
|
// loadMultiWorkflowScenario reads a fixtures/dirName/ subdirectory.
|
|
// It expects a scenario.json and one or more *.yaml workflow files.
|
|
func loadMultiWorkflowScenario(t *testing.T, dirName string) Scenario {
|
|
t.Helper()
|
|
|
|
dir := filepath.Join("fixtures", dirName)
|
|
entries, err := fixtureFS.ReadDir(dir)
|
|
require.NoError(t, err, "read multi-workflow dir %s", dir)
|
|
|
|
var s Scenario
|
|
var files []*forge_types.FileMeta
|
|
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
continue
|
|
}
|
|
name := e.Name()
|
|
data, err := fixtureFS.ReadFile(filepath.Join(dir, name))
|
|
require.NoError(t, err, "read %s/%s", dirName, name)
|
|
|
|
switch {
|
|
case name == "scenario.json":
|
|
require.NoError(t, json.Unmarshal(data, &s), "parse %s/scenario.json", dirName)
|
|
case strings.HasSuffix(name, ".yaml"):
|
|
// Serve under .woodpecker/<filename> so Dir() returns them.
|
|
files = append(files, &forge_types.FileMeta{
|
|
Name: ".woodpecker/" + name,
|
|
Data: data,
|
|
})
|
|
}
|
|
}
|
|
|
|
require.NotEmpty(t, files, "no YAML files in multi-workflow dir %s", dirName)
|
|
require.NotEmpty(t, s.Name, "scenario.json missing 'name' in %s", dirName)
|
|
|
|
s.Files = forge_types.SortByName(files)
|
|
if s.Event == "" {
|
|
s.Event = model.EventPush
|
|
}
|
|
return s
|
|
}
|