wftest

package
v0.3.57 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"`
	// State checks per-store key/value pairs in the StateStore.
	// Maps store_name → key → expected_value.
	// Requires WithState() (or state config in YAML).
	State map[string]map[string]any `yaml:"state"`
}

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 FixtureDef added in v0.3.57

type FixtureDef struct {
	// File is the path to the fixture file (JSON or YAML).
	File string `yaml:"file"`
	// Target is the store name to seed.
	Target string `yaml:"target"`
}

FixtureDef loads a JSON or YAML file into a named state store.

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) State added in v0.3.57

func (h *Harness) State() *StateStore

State returns the in-memory StateStore (only non-nil when WithState() was used). Use it to seed and assert state around pipeline executions.

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 WithState added in v0.3.57

func WithState() Option

WithState initializes an in-memory StateStore for the harness. The store is accessible via h.State() and is also registered as the "wftest.state_store" service so pipeline steps can look it up.

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 SequenceStep added in v0.3.57

type SequenceStep struct {
	// Name is a label shown in test output.
	Name string `yaml:"name"`
	// Pipeline is a shorthand for a pipeline trigger name.
	// If Trigger.Name is empty, Pipeline is used.
	Pipeline string `yaml:"pipeline"`
	// Trigger describes how to invoke this step.
	Trigger TriggerDef `yaml:"trigger"`
	// Assertions are checked after this step executes.
	Assertions []Assertion `yaml:"assertions"`
}

SequenceStep is one step in a multi-step stateful test.

type StateConfig added in v0.3.57

type StateConfig struct {
	// Fixtures are files to load into named stores.
	Fixtures []FixtureDef `yaml:"fixtures"`
	// Seed maps store_name → key → value for inline initial state.
	Seed map[string]map[string]any `yaml:"seed"`
}

StateConfig describes state to set up before a test case runs.

type StateStore added in v0.3.57

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

StateStore provides in-memory state for test pipelines. It is safe for concurrent use.

func NewStateStore added in v0.3.57

func NewStateStore() *StateStore

NewStateStore creates an empty StateStore.

func (*StateStore) Assert added in v0.3.57

func (s *StateStore) Assert(store string, expected map[string]any) error

Assert checks that each key/value pair in expected matches the actual state in the named store. Comparison is done via JSON marshaling so numeric types are normalised. Returns nil on full match, or an error describing the first mismatch.

func (*StateStore) Get added in v0.3.57

func (s *StateStore) Get(store, key string) (any, bool)

Get retrieves a value from a named store.

func (*StateStore) GetAll added in v0.3.57

func (s *StateStore) GetAll(store string) map[string]any

GetAll returns a shallow copy of all entries in a named store. Returns nil if the store does not exist.

func (*StateStore) LoadFixture added in v0.3.57

func (s *StateStore) LoadFixture(path string, store string) error

LoadFixture loads state from a JSON or YAML file into a named store. The file must unmarshal to map[string]any.

func (*StateStore) Seed added in v0.3.57

func (s *StateStore) Seed(store string, data map[string]any)

Seed loads initial state into a named store. Existing keys are overwritten; other keys are preserved.

func (*StateStore) Set added in v0.3.57

func (s *StateStore) Set(store, key string, value any)

Set writes a value to a named store.

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.
	// Mutually exclusive with Sequence.
	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.
	// Used with Trigger; ignored when Sequence is set.
	Assertions []Assertion `yaml:"assertions"`
	// State configures initial state to seed before the test runs.
	State *StateConfig `yaml:"state"`
	// Sequence replaces the single Trigger for multi-step stateful tests.
	// Each step fires a trigger and may assert pipeline output and state.
	Sequence []SequenceStep `yaml:"sequence"`
}

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