wftest

package
v0.3.56 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 23, 2026 License: MIT Imports: 41 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterTriggerAdapter

func RegisterTriggerAdapter(adapter TriggerAdapter)

RegisterTriggerAdapter registers a TriggerAdapter by name. Call this from plugin init() or TestMain to make the adapter available to all harnesses.

func RunAllYAMLTests

func RunAllYAMLTests(t *testing.T, dir string)

RunAllYAMLTests discovers all *_test.yaml files in dir and runs them.

func RunYAMLTests

func RunYAMLTests(t *testing.T, testFilePath string)

RunYAMLTests parses a *_test.yaml file and runs each test case as a subtest. testFilePath may be absolute or relative to the working directory.

func UnregisterTriggerAdapter

func UnregisterTriggerAdapter(name string)

UnregisterTriggerAdapter removes a previously registered adapter. Useful in test cleanup to avoid cross-test pollution.

Types

type Assertion

type Assertion struct {
	// Step scopes this assertion to a specific step's output.
	// If empty, the assertion applies to the overall pipeline output.
	Step string `yaml:"step"`
	// Output checks that all key/value pairs in this map appear in the output.
	Output map[string]any `yaml:"output"`
	// Executed checks whether the named step ran (true) or was skipped (false).
	Executed *bool `yaml:"executed"`
	// Response checks HTTP response fields (status, body).
	Response *ResponseAssert `yaml:"response"`
}

Assertion is one check applied to the test result.

type Call

type Call struct {
	StepContext
}

Call records one invocation of a mock step.

type ExecOption

type ExecOption func(*execConfig)

ExecOption configures a single pipeline execution (used by ExecutePipelineOpts).

func StopAfter

func StopAfter(stepName string) ExecOption

StopAfter halts pipeline execution after the named step completes. Steps after the named step are not executed.

type Harness

type Harness struct {
	// contains filtered or unexported fields
}

Harness is an in-process workflow engine for integration testing.

func New

func New(t *testing.T, opts ...Option) *Harness

New creates a test harness with the given options.

func (*Harness) BaseURL

func (h *Harness) BaseURL() string

BaseURL returns the base URL of the test HTTP server (e.g. "http://127.0.0.1:PORT"). Panics via t.Fatal if WithServer() was not used.

func (*Harness) DELETE

func (h *Harness) DELETE(path string, opts ...RequestOption) *Result

DELETE sends a DELETE request to the harness HTTP handler. Requires an http.router module in the config.

func (*Harness) ExecutePipeline

func (h *Harness) ExecutePipeline(name string, data map[string]any) *Result

ExecutePipeline runs a named pipeline with the given trigger data. StepResults in the returned Result is populated with per-step outputs.

func (*Harness) ExecutePipelineOpts

func (h *Harness) ExecutePipelineOpts(name string, data map[string]any, opts ...ExecOption) *Result

ExecutePipelineOpts runs a named pipeline with optional per-execution options. It supports StopAfter to halt execution at a named step.

func (*Harness) FireEvent

func (h *Harness) FireEvent(topic string, data map[string]any) *Result

FireEvent simulates an eventbus trigger firing for the given topic. It finds all EventBusTrigger instances registered with the engine and invokes their subscriptions synchronously using the provided context, so the pipeline runs inline before FireEvent returns.

Unlike going through the real EventBus pub/sub channel, this bypasses the async dispatch goroutine, making it safe and deterministic for tests.

The engine is started lazily on the first call so that trigger subscriptions are configured. The YAML config must include an eventbus trigger for the topic.

func (*Harness) FireSchedule

func (h *Harness) FireSchedule(pipelineName string, params map[string]any) *Result

FireSchedule simulates a schedule trigger firing for the named pipeline. It calls TriggerWorkflow directly (bypassing cron timing) and returns the pipeline result. params is merged into the trigger data; pass nil for none.

func (*Harness) GET

func (h *Harness) GET(path string, opts ...RequestOption) *Result

GET sends a GET request to the harness HTTP handler and returns the result. Requires an http.router module in the config.

func (*Harness) InjectTrigger

func (h *Harness) InjectTrigger(name, event string, data map[string]any) *Result

InjectTrigger fires the named trigger adapter with the given event and data, returning the pipeline result. Fails the test if no adapter is registered with that name.

func (*Harness) POST

func (h *Harness) POST(path, body string, opts ...RequestOption) *Result

POST sends a POST request with a body to the harness HTTP handler. Content-Type is set to application/json unless overridden via Header(). Requires an http.router module in the config.

func (*Harness) PUT

func (h *Harness) PUT(path, body string, opts ...RequestOption) *Result

PUT sends a PUT request with a body to the harness HTTP handler. Content-Type is set to application/json unless overridden via Header(). Requires an http.router module in the config.

func (*Harness) RecordStep

func (h *Harness) RecordStep(stepType string) *Recorder

RecordStep registers a new Recorder for the given step type and returns it. It overrides any previously registered factory for stepType, including real implementations loaded by plugins. Unlike MockStep (used at construction via New), RecordStep can be called after the harness is created:

h := wftest.New(t, wftest.WithYAML(`...`))
rec := h.RecordStep("step.db_query")
h.ExecutePipeline("fetch", nil)
t.Logf("called %d times", rec.CallCount())

func (*Harness) WSDialer

func (h *Harness) WSDialer(path string) (*websocket.Conn, *http.Response, error)

WSDialer opens a WebSocket connection to the given path on the test server. Requires WithServer().

type MockConfig

type MockConfig struct {
	Steps map[string]map[string]any `yaml:"steps"`
}

MockConfig holds YAML-level step mock declarations. Each entry maps a step type (e.g. "step.db_query") to a fixed output map.

type MockModule

type MockModule struct {
	// contains filtered or unexported fields
}

MockModule is a minimal modular.Module that exposes a service in the app's service registry under a given name. Use it so steps that look up a named dependency (e.g. a database connection) find this mock instead of a real module.

func NewMockModule

func NewMockModule(name string, service any) *MockModule

NewMockModule creates a MockModule that registers service under name.

func (*MockModule) Init

func (m *MockModule) Init(app modular.Application) error

Init registers the service in the application's service registry.

func (*MockModule) Name

func (m *MockModule) Name() string

Name satisfies modular.Module.

func (*MockModule) ProvidesServices

func (m *MockModule) ProvidesServices() []modular.ServiceProvider

ProvidesServices advertises the service for dependency wiring.

func (*MockModule) RequiresServices

func (m *MockModule) RequiresServices() []modular.ServiceDependency

RequiresServices returns nil — mock modules have no dependencies.

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option configures a Harness when passed to New. All built-in option constructors (WithYAML, MockStep, etc.) implement this interface. *Recorder also implements Option so RecordStep can be passed directly to New without a separate MockStep call.

func MockStep

func MockStep(stepType string, handler StepHandler) Option

MockStep registers a mock factory for stepType that calls handler on every execution. The mock is installed after built-in plugins load, so it overrides real implementations. Use Returns for fixed output or NewRecorder to capture calls.

func WithConfig

func WithConfig(path string) Option

WithConfig loads config from a YAML file path.

func WithMockModule

func WithMockModule(mod *MockModule) Option

WithMockModule registers a fake module in the service registry so steps that look up a named dependency find this mock instead of a real module.

func WithPlugin

func WithPlugin(p plugin.EnginePlugin) Option

WithPlugin loads an additional engine plugin into the harness before BuildFromConfig.

func WithServer

func WithServer() Option

WithServer starts a real HTTP listener backed by the engine's HTTP router. After harness creation, use h.BaseURL() to get the server URL. The server is stopped automatically via t.Cleanup.

func WithYAML

func WithYAML(yaml string) Option

WithYAML configures the harness with inline YAML config.

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

Recorder is a StepHandler that records every call made to a mock step. Use NewRecorder to create one, then pass it to MockStep. Recorder also implements StepHandler and Option so a Recorder returned by RecordStep can be passed directly to New without a separate MockStep call.

func NewRecorder

func NewRecorder() *Recorder

NewRecorder creates a Recorder that returns an empty output map by default.

func RecordStep

func RecordStep(stepType string) *Recorder

RecordStep creates a Recorder bound to stepType and returns it. The returned *Recorder implements Option, so it can be passed directly to New:

rec := wftest.RecordStep("step.db_query")
h   := wftest.New(t, wftest.WithYAML(`...`), rec)
h.ExecutePipeline("fetch", nil)
t.Logf("called %d times", rec.CallCount())

func (*Recorder) CallCount

func (r *Recorder) CallCount() int

CallCount returns the number of times the step was called.

func (*Recorder) Calls

func (r *Recorder) Calls() []Call

Calls returns a snapshot of all recorded invocations.

func (*Recorder) Handle

func (r *Recorder) Handle(ctx context.Context, config, input map[string]any) (map[string]any, error)

Handle records the call and returns configured output (or empty map).

func (*Recorder) Reset

func (r *Recorder) Reset()

Reset clears all recorded calls.

func (*Recorder) WithOutput

func (r *Recorder) WithOutput(output map[string]any) *Recorder

WithOutput sets fixed output that the recorder returns on every call.

type RequestOption

type RequestOption func(*http.Request)

RequestOption configures an HTTP test request.

func Header(key, value string) RequestOption

Header returns a RequestOption that sets a custom request header.

type ResponseAssert

type ResponseAssert struct {
	// Status is the expected HTTP status code (0 means "don't check").
	Status int `yaml:"status"`
	// Body is a substring expected in the response body.
	Body string `yaml:"body"`
}

ResponseAssert checks HTTP response fields.

type Result

type Result struct {
	Output      map[string]any            // Final pipeline output
	StepResults map[string]map[string]any // Per-step outputs
	Error       error
	Duration    time.Duration

	// HTTP-specific (populated for HTTP triggers)
	StatusCode int
	Headers    map[string]string
	RawBody    []byte
}

Result holds the outcome of a pipeline execution or HTTP request.

func (*Result) Header

func (r *Result) Header(key string) string

Header returns the value of the named HTTP response header.

func (*Result) JSON

func (r *Result) JSON() map[string]any

JSON parses the HTTP response body as JSON.

func (*Result) StepCount

func (r *Result) StepCount() int

StepCount returns the number of steps that were executed.

func (*Result) StepExecuted

func (r *Result) StepExecuted(name string) bool

StepExecuted returns whether a step was executed.

func (*Result) StepOutput

func (r *Result) StepOutput(name string) map[string]any

StepOutput returns the output map for a specific step.

func (*Result) StepOutputs

func (r *Result) StepOutputs() map[string]map[string]any

StepOutputs returns all per-step output maps keyed by step name.

type StepContext

type StepContext struct {
	// Ctx is the context from the pipeline execution.
	Ctx context.Context
	// Config is the step's YAML config from BuildFromConfig.
	Config map[string]any
	// Input is the merged pipeline state (pc.Current) at execution time.
	Input map[string]any
}

StepContext holds the full execution context passed to a mock step handler.

type StepHandler

type StepHandler interface {
	Handle(ctx context.Context, config, input map[string]any) (map[string]any, error)
}

StepHandler is invoked by a mock step during pipeline execution. config is the step's YAML config; input is the merged pipeline state (pc.Current).

func Returns

func Returns(output map[string]any) StepHandler

Returns creates a StepHandler that always returns the given output map.

type StepHandlerFunc

type StepHandlerFunc func(ctx context.Context, config, input map[string]any) (map[string]any, error)

StepHandlerFunc adapts a plain function to StepHandler.

func (StepHandlerFunc) Handle

func (f StepHandlerFunc) Handle(ctx context.Context, config, input map[string]any) (map[string]any, error)

type TestCase

type TestCase struct {
	// Description is an optional human-readable label shown in test output.
	Description string `yaml:"description"`
	// Trigger describes how to invoke the pipeline or HTTP endpoint.
	Trigger TriggerDef `yaml:"trigger"`
	// StopAfter halts pipeline execution after the named step completes.
	StopAfter string `yaml:"stop_after"`
	// Mocks overrides the file-level Mocks for this test case only.
	Mocks *MockConfig `yaml:"mocks"`
	// Assertions is an ordered list of checks applied to the result.
	Assertions []Assertion `yaml:"assertions"`
}

TestCase defines one test within a TestFile.

type TestFile

type TestFile struct {
	// Config is a file path to a workflow YAML config.
	// Mutually exclusive with YAML.
	Config string `yaml:"config"`
	// YAML is an inline workflow YAML config string.
	// Mutually exclusive with Config.
	YAML string `yaml:"yaml"`
	// Mocks are step-level mocks shared across all test cases.
	// Per-test Mocks override these.
	Mocks MockConfig `yaml:"mocks"`
	// Tests maps test case names to their definitions.
	Tests map[string]TestCase `yaml:"tests"`
}

TestFile is the top-level structure of a *_test.yaml file.

type TriggerAdapter

type TriggerAdapter interface {
	// Name returns the unique adapter identifier (e.g. "websocket", "grpc").
	Name() string
	// Inject simulates a trigger firing and returns the pipeline result.
	Inject(ctx context.Context, h *Harness, event string, data map[string]any) (*Result, error)
}

TriggerAdapter allows external plugins to add test injection support for custom trigger types. Register an adapter with RegisterTriggerAdapter, then inject events via Harness.InjectTrigger.

type TriggerAdapterFunc

type TriggerAdapterFunc struct {
	AdapterName string
	Fn          func(ctx context.Context, h *Harness, event string, data map[string]any) (*Result, error)
}

TriggerAdapterFunc adapts a plain function to TriggerAdapter.

func (*TriggerAdapterFunc) Inject

func (f *TriggerAdapterFunc) Inject(ctx context.Context, h *Harness, event string, data map[string]any) (*Result, error)

Inject calls the underlying function.

func (*TriggerAdapterFunc) Name

func (f *TriggerAdapterFunc) Name() string

Name returns the adapter name.

type TriggerDef

type TriggerDef struct {
	// Type is the trigger kind: "pipeline", "http".
	Type string `yaml:"type"`
	// Name is the pipeline name (used when Type is "pipeline").
	Name string `yaml:"name"`
	// Data is the trigger input data (pipeline trigger data or HTTP body).
	Data map[string]any `yaml:"data"`
	// Method is the HTTP method (used when Type is "http").
	Method string `yaml:"method"`
	// Path is the HTTP path (used when Type is "http").
	Path string `yaml:"path"`
	// Headers are extra HTTP request headers.
	Headers map[string]string `yaml:"headers"`
}

TriggerDef describes how to invoke the system under test.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL