celeritytest

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Oct 9, 2026 License: Apache-2.0 Imports: 28 Imported by: 0

Documentation

Overview

Package celeritytest runs an application's handlers in a test, without a runtime, a network or a cloud account.

A handler is dispatched through the application's own pipeline, so the layers, guards and context a deployment would apply are the ones a test exercises:

func TestCreatingAnOrder(t *testing.T) {
    res := celeritytest.Resources()
    harness := celeritytest.New(t, orders.App(celerity.WithResourceProvider(res)))

    harness.POST(t, "/orders", celeritytest.JSONBody(order{Total: 10})).
        AssertStatus(t, http.StatusCreated)
}

The application is built by the application's own constructor, not rebuilt by the test.

There are two kinds of dependency and they are substituted in two different places. Firstly, what the application itself depends on, a payment gateway or a clock, arrives through the constructor the application wrote, as an argument or an option of its own. Secondly, what Celerity provides, the resources a blueprint declares, is substituted here: one celerity.WithResourceProvider redirects all of them at once, and nothing else about the application changes.

Every dispatch takes the test it belongs to rather than the one the application was built with, so a harness built once in a parent test is used from subtests that each have their own.

Resources

A handler reaching a bucket, a queue, a topic or a data store is given a working one held in memory, so a write followed by a read answers with what was written. A cache is a recording stub, since reimplementing a surface that large for in-memory testing is a large undertaking and you can get all the information you need from call assertions on a stub.

A SQL database has neither. What a handler has to be right about is the statement it sends, and only an engine can judge that, so a database a test did not supply refuses the call and says where to get one. Provider.WithDatabase takes the engine a suite brought up, which in a development session is the one celerity dev already runs.

Live resources

Live is for the integration cases, resources served by what a `celerity dev test` session brought up, reached through the provider the build linked rather than through anything here. A handler then runs against a real engine, a real object store and a real cache, while still being dispatched in process through the application's own pipeline, which is a smaller and faster test than one driving the runtime container.

The two mix per resource. Asking for a double by name before the application takes its handles substitutes it for that one resource, so a suite can exercise a real database and keep the queue in memory.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

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

App dispatches events to an application's handlers.

func New

func New(t testing.TB, app *celerity.App, opts ...Option) *App

New returns an application ready to dispatch to, failing the test where registration did not succeed.

Registration errors are collected rather than returned, so an application that named a resource no provider can build, or registered two handlers under one tag, reports it here rather than on the first dispatch.

func NewWithResources

func NewWithResources(t testing.TB, app *celerity.App, p *Provider, opts ...Option) *App

NewWithResources returns an application whose handlers reach the test doubles in p, and the doubles themselves for a test to read afterwards.

The provider has to be given to celerity.New as well, since handles are taken while the application is being built:

res := celeritytest.Resources()
harness := celeritytest.NewWithResources(
    t, orders.App(celerity.WithResourceProvider(res)), res)

func (*App) Consume

func (a *App) Consume(t testing.TB, name string, bodies ...[]byte) *handler.BatchResult

Consume delivers a batch of messages to the consumer handler registered under a blueprint name, and reports which records it asked to be delivered again.

The bodies are delivered as one batch, which is how a consumer is called: a handler that fails one record of several is the case worth testing, and it cannot be reached a message at a time.

func (*App) ConsumeJSON

func (a *App) ConsumeJSON(t testing.TB, name string, messages ...any) *handler.BatchResult

ConsumeJSON delivers a batch whose bodies are the encodings of the values given, which is how a message is usually carried.

func (*App) DELETE

func (a *App) DELETE(t testing.TB, path string, opts ...RequestOption) *Response

DELETE dispatches a DELETE request.

func (*App) Do

func (a *App) Do(
	t testing.TB, method, path string, opts ...RequestOption,
) *Response

Do dispatches a request of any method to the handler registered for the path.

The path is the one a client would send rather than the route a handler was registered under, so the parameters in it are bound the way a runtime binds them. A path no handler is registered for fails the test, since a request that reached nothing is a mistake in the test rather than a 404 worth asserting on.

func (*App) GET

func (a *App) GET(t testing.TB, path string, opts ...RequestOption) *Response

GET dispatches a GET request to the handler registered for the path.

func (*App) Invoke

func (a *App) Invoke(t testing.TB, name string, input any, out any)

Invoke calls a handler directly by its blueprint name and decodes its answer into out, which may be nil where the answer is not wanted.

func (*App) PATCH

func (a *App) PATCH(t testing.TB, path string, opts ...RequestOption) *Response

PATCH dispatches a PATCH request.

func (*App) POST

func (a *App) POST(t testing.TB, path string, opts ...RequestOption) *Response

POST dispatches a POST request.

func (*App) PUT

func (a *App) PUT(t testing.TB, path string, opts ...RequestOption) *Response

PUT dispatches a PUT request.

func (*App) Resources

func (a *App) Resources() *Provider

Resources returns the test doubles an application reaches through celerity.WithResourceProvider.

func (*App) Schedule

func (a *App) Schedule(t testing.TB, name string, input ...[]byte)

Schedule fires the schedule handler registered under a blueprint name.

func (*App) Send

func (a *App) Send(t testing.TB, route string, body []byte, opts ...WebSocketMessage)

Send delivers a WebSocket message to the handler registered for a route.

The route is the one the message's route key names, which is how the runtime picks a handler, so a test names what a client would send.

type Bucket

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

Bucket is an object store held in memory.

Versioning is always on, which is the shape that can answer both kinds of test. A bucket without it is one whose keys each hold a single version, and the reads a handler makes are the same either way.

func NewBucket

func NewBucket(name string) *Bucket

NewBucket returns an empty bucket.

func (*Bucket) Contents

func (b *Bucket) Contents(key string) ([]byte, bool)

Contents returns what a key currently holds, and whether it holds anything.

func (*Bucket) Copy

func (b *Bucket) Copy(
	ctx context.Context, sourceKey string, dest bucket.Destination, opts ...bucket.CopyOption,
) (bucket.CopyResult, error)

func (*Bucket) Delete

func (b *Bucket) Delete(ctx context.Context, key string, opts ...bucket.DeleteOption) error

func (*Bucket) DeleteMany

func (b *Bucket) DeleteMany(
	ctx context.Context, refs []bucket.ObjectRef,
) (bucket.DeleteManyResult, error)

func (*Bucket) Exists

func (b *Bucket) Exists(
	ctx context.Context, key string, opts ...bucket.ExistsOption,
) (bool, error)

func (*Bucket) Get

func (b *Bucket) Get(
	ctx context.Context, key string, opts ...bucket.GetOption,
) (*bucket.Object, error)

func (*Bucket) Info

func (b *Bucket) Info(
	ctx context.Context, key string, opts ...bucket.InfoOption,
) (bucket.ObjectInfo, error)

func (*Bucket) Keys

func (b *Bucket) Keys() []string

Keys returns the keys holding a current object, in order.

func (*Bucket) Len

func (b *Bucket) Len() int

Len reports how many keys hold a current object, for a test asserting on what a handler left behind.

func (*Bucket) List

func (b *Bucket) List(
	ctx context.Context, prefix string, opts ...bucket.ListOption,
) ([]bucket.ObjectInfo, bucket.Cursor, error)

func (*Bucket) Put

func (b *Bucket) Put(
	ctx context.Context, key string, body io.Reader, opts ...bucket.PutOption,
) (bucket.PutResult, error)

func (*Bucket) SignedURL

func (b *Bucket) SignedURL(
	ctx context.Context, key string, action bucket.SignAction,
	expiry time.Duration, opts ...bucket.SignOption,
) (bucket.SignedURL, error)

func (*Bucket) Versions

func (b *Bucket) Versions(
	ctx context.Context, prefix string, opts ...bucket.ListOption,
) ([]bucket.ObjectVersion, bucket.Cursor, error)

type CacheStub

type CacheStub struct {

	// AppendFunc answers a call to Append.
	AppendFunc func(ctx context.Context, key string, value string) (int64, error)
	// ConnectionFunc answers a call to Connection.
	ConnectionFunc func(ctx context.Context) (cache.Connection, error)
	// DecrementFunc answers a call to Decrement.
	DecrementFunc func(ctx context.Context, key string, delta int64) (int64, error)
	// DeleteFunc answers a call to Delete.
	DeleteFunc func(ctx context.Context, key string) (bool, error)
	// DeleteManyFunc answers a call to DeleteMany.
	DeleteManyFunc func(ctx context.Context, keys []string) (int64, error)
	// ExistsFunc answers a call to Exists.
	ExistsFunc func(ctx context.Context, key string) (bool, error)
	// ExpireFunc answers a call to Expire.
	ExpireFunc func(ctx context.Context, key string, ttl time.Duration) (bool, error)
	// GetFunc answers a call to Get.
	GetFunc func(ctx context.Context, key string) (string, error)
	// GetManyFunc answers a call to GetMany.
	GetManyFunc func(ctx context.Context, keys []string) (map[string]string, error)
	// GetSetFunc answers a call to GetSet.
	GetSetFunc func(ctx context.Context, key string, value string) (string, error)
	// HashDeleteFunc answers a call to HashDelete.
	HashDeleteFunc func(ctx context.Context, key string, fields []string) (int64, error)
	// HashExistsFunc answers a call to HashExists.
	HashExistsFunc func(ctx context.Context, key string, field string) (bool, error)
	// HashGetFunc answers a call to HashGet.
	HashGetFunc func(ctx context.Context, key string, field string) (string, error)
	// HashGetAllFunc answers a call to HashGetAll.
	HashGetAllFunc func(ctx context.Context, key string) (map[string]string, error)
	// HashIncrementFunc answers a call to HashIncrement.
	HashIncrementFunc func(ctx context.Context, key string, field string, delta int64) (int64, error)
	// HashKeysFunc answers a call to HashKeys.
	HashKeysFunc func(ctx context.Context, key string) ([]string, error)
	// HashLenFunc answers a call to HashLen.
	HashLenFunc func(ctx context.Context, key string) (int64, error)
	// HashSetFunc answers a call to HashSet.
	HashSetFunc func(ctx context.Context, key string, fields map[string]string) error
	// IncrementFunc answers a call to Increment.
	IncrementFunc func(ctx context.Context, key string, delta int64) (int64, error)
	// IncrementFloatFunc answers a call to IncrementFloat.
	IncrementFloatFunc func(ctx context.Context, key string, delta float64) (float64, error)
	// ListIndexFunc answers a call to ListIndex.
	ListIndexFunc func(ctx context.Context, key string, index int64) (string, error)
	// ListLenFunc answers a call to ListLen.
	ListLenFunc func(ctx context.Context, key string) (int64, error)
	// ListPopFunc answers a call to ListPop.
	ListPopFunc func(ctx context.Context, key string, count int64, opts ...cache.EndOption) ([]string, error)
	// ListPushFunc answers a call to ListPush.
	ListPushFunc func(ctx context.Context, key string, values []string, opts ...cache.EndOption) (int64, error)
	// ListRangeFunc answers a call to ListRange.
	ListRangeFunc func(ctx context.Context, key string, start int64, stop int64) ([]string, error)
	// ListTrimFunc answers a call to ListTrim.
	ListTrimFunc func(ctx context.Context, key string, start int64, stop int64) error
	// PersistFunc answers a call to Persist.
	PersistFunc func(ctx context.Context, key string) (bool, error)
	// RenameFunc answers a call to Rename.
	RenameFunc func(ctx context.Context, key string, newKey string) error
	// ScanFunc answers a call to Scan.
	ScanFunc func(ctx context.Context, opts ...cache.ScanOption) iter.Seq2[string, error]
	// SetFunc answers a call to Set.
	SetFunc func(ctx context.Context, key string, value string, opts ...cache.SetOption) (bool, error)
	// SetAddFunc answers a call to SetAdd.
	SetAddFunc func(ctx context.Context, key string, members []string) (int64, error)
	// SetDiffFunc answers a call to SetDiff.
	SetDiffFunc func(ctx context.Context, keys []string) ([]string, error)
	// SetIntersectFunc answers a call to SetIntersect.
	SetIntersectFunc func(ctx context.Context, keys []string) ([]string, error)
	// SetIsMemberFunc answers a call to SetIsMember.
	SetIsMemberFunc func(ctx context.Context, key string, member string) (bool, error)
	// SetLenFunc answers a call to SetLen.
	SetLenFunc func(ctx context.Context, key string) (int64, error)
	// SetManyFunc answers a call to SetMany.
	SetManyFunc func(ctx context.Context, values map[string]string) error
	// SetMembersFunc answers a call to SetMembers.
	SetMembersFunc func(ctx context.Context, key string) ([]string, error)
	// SetRemoveFunc answers a call to SetRemove.
	SetRemoveFunc func(ctx context.Context, key string, members []string) (int64, error)
	// SetUnionFunc answers a call to SetUnion.
	SetUnionFunc func(ctx context.Context, keys []string) ([]string, error)
	// SortedSetAddFunc answers a call to SortedSetAdd.
	SortedSetAddFunc func(ctx context.Context, key string, members []cache.SortedSetMember) (int64, error)
	// SortedSetCountByScoreFunc answers a call to SortedSetCountByScore.
	SortedSetCountByScoreFunc func(ctx context.Context, key string, scores cache.ScoreRange) (int64, error)
	// SortedSetIncrementFunc answers a call to SortedSetIncrement.
	SortedSetIncrementFunc func(ctx context.Context, key string, member string, delta float64) (float64, error)
	// SortedSetLenFunc answers a call to SortedSetLen.
	SortedSetLenFunc func(ctx context.Context, key string) (int64, error)
	// SortedSetRangeFunc answers a call to SortedSetRange.
	SortedSetRangeFunc func(ctx context.Context, key string, start int64, stop int64, opts ...cache.RangeOption) ([]cache.SortedSetMember, error)
	// SortedSetRangeByScoreFunc answers a call to SortedSetRangeByScore.
	SortedSetRangeByScoreFunc func(ctx context.Context, key string, scores cache.ScoreRange, opts ...cache.RangeOption) ([]cache.SortedSetMember, error)
	// SortedSetRankFunc answers a call to SortedSetRank.
	SortedSetRankFunc func(ctx context.Context, key string, member string, opts ...cache.RangeOption) (int64, error)
	// SortedSetRemoveFunc answers a call to SortedSetRemove.
	SortedSetRemoveFunc func(ctx context.Context, key string, members []string) (int64, error)
	// SortedSetRemoveByRankFunc answers a call to SortedSetRemoveByRank.
	SortedSetRemoveByRankFunc func(ctx context.Context, key string, start int64, stop int64) (int64, error)
	// SortedSetRemoveByScoreFunc answers a call to SortedSetRemoveByScore.
	SortedSetRemoveByScoreFunc func(ctx context.Context, key string, scores cache.ScoreRange) (int64, error)
	// SortedSetScoreFunc answers a call to SortedSetScore.
	SortedSetScoreFunc func(ctx context.Context, key string, member string) (float64, error)
	// TTLFunc answers a call to TTL.
	TTLFunc func(ctx context.Context, key string) (time.Duration, bool, error)
	// TransactionFunc answers a call to Transaction.
	TransactionFunc func(ctx context.Context, queue func(cache.Tx)) ([]any, error)
	// TypeFunc answers a call to Type.
	TypeFunc func(ctx context.Context, key string) (cache.KeyType, error)
	// contains filtered or unexported fields
}

CacheStub records what a handler asked a cache for and answers what the test arranged.

This is a stub rather than a fully functional cache. The contract is fifty-three operations across seven groups, and reimplementing sorted sets, hashes and expiry faithfully enough to be worth trusting is a large undertaking for a test utility. A test that needs real behaviour is better pointed at a real Redis, which resources/redis already reaches.

A call the test did not arrange answers with an error naming it, rather than a zero value that turns up as a puzzle somewhere else.

func NewCacheStub

func NewCacheStub(name string) *CacheStub

NewCacheStub returns a stub answering nothing until a test arranges it.

func (*CacheStub) Append

func (s *CacheStub) Append(ctx context.Context, key string, value string) (int64, error)

func (*CacheStub) Called

func (s *CacheStub) Called(name string) bool

Called reports whether an operation was asked for.

func (*CacheStub) Calls

func (s *CacheStub) Calls() []string

Calls returns the operations a handler asked for, in order.

func (*CacheStub) Connection

func (s *CacheStub) Connection(ctx context.Context) (cache.Connection, error)

func (*CacheStub) Decrement

func (s *CacheStub) Decrement(ctx context.Context, key string, delta int64) (int64, error)

func (*CacheStub) Delete

func (s *CacheStub) Delete(ctx context.Context, key string) (bool, error)

func (*CacheStub) DeleteMany

func (s *CacheStub) DeleteMany(ctx context.Context, keys []string) (int64, error)

func (*CacheStub) Exists

func (s *CacheStub) Exists(ctx context.Context, key string) (bool, error)

func (*CacheStub) Expire

func (s *CacheStub) Expire(ctx context.Context, key string, ttl time.Duration) (bool, error)

func (*CacheStub) Get

func (s *CacheStub) Get(ctx context.Context, key string) (string, error)

func (*CacheStub) GetMany

func (s *CacheStub) GetMany(ctx context.Context, keys []string) (map[string]string, error)

func (*CacheStub) GetSet

func (s *CacheStub) GetSet(ctx context.Context, key string, value string) (string, error)

func (*CacheStub) HashDelete

func (s *CacheStub) HashDelete(ctx context.Context, key string, fields []string) (int64, error)

func (*CacheStub) HashExists

func (s *CacheStub) HashExists(ctx context.Context, key string, field string) (bool, error)

func (*CacheStub) HashGet

func (s *CacheStub) HashGet(ctx context.Context, key string, field string) (string, error)

func (*CacheStub) HashGetAll

func (s *CacheStub) HashGetAll(ctx context.Context, key string) (map[string]string, error)

func (*CacheStub) HashIncrement

func (s *CacheStub) HashIncrement(ctx context.Context, key string, field string, delta int64) (int64, error)

func (*CacheStub) HashKeys

func (s *CacheStub) HashKeys(ctx context.Context, key string) ([]string, error)

func (*CacheStub) HashLen

func (s *CacheStub) HashLen(ctx context.Context, key string) (int64, error)

func (*CacheStub) HashSet

func (s *CacheStub) HashSet(ctx context.Context, key string, fields map[string]string) error

func (*CacheStub) Increment

func (s *CacheStub) Increment(ctx context.Context, key string, delta int64) (int64, error)

func (*CacheStub) IncrementFloat

func (s *CacheStub) IncrementFloat(ctx context.Context, key string, delta float64) (float64, error)

func (*CacheStub) ListIndex

func (s *CacheStub) ListIndex(ctx context.Context, key string, index int64) (string, error)

func (*CacheStub) ListLen

func (s *CacheStub) ListLen(ctx context.Context, key string) (int64, error)

func (*CacheStub) ListPop

func (s *CacheStub) ListPop(ctx context.Context, key string, count int64, opts ...cache.EndOption) ([]string, error)

func (*CacheStub) ListPush

func (s *CacheStub) ListPush(ctx context.Context, key string, values []string, opts ...cache.EndOption) (int64, error)

func (*CacheStub) ListRange

func (s *CacheStub) ListRange(ctx context.Context, key string, start int64, stop int64) ([]string, error)

func (*CacheStub) ListTrim

func (s *CacheStub) ListTrim(ctx context.Context, key string, start int64, stop int64) error

func (*CacheStub) Persist

func (s *CacheStub) Persist(ctx context.Context, key string) (bool, error)

func (*CacheStub) Rename

func (s *CacheStub) Rename(ctx context.Context, key string, newKey string) error

func (*CacheStub) Scan

func (s *CacheStub) Scan(ctx context.Context, opts ...cache.ScanOption) iter.Seq2[string, error]

func (*CacheStub) Set

func (s *CacheStub) Set(ctx context.Context, key string, value string, opts ...cache.SetOption) (bool, error)

func (*CacheStub) SetAdd

func (s *CacheStub) SetAdd(ctx context.Context, key string, members []string) (int64, error)

func (*CacheStub) SetDiff

func (s *CacheStub) SetDiff(ctx context.Context, keys []string) ([]string, error)

func (*CacheStub) SetIntersect

func (s *CacheStub) SetIntersect(ctx context.Context, keys []string) ([]string, error)

func (*CacheStub) SetIsMember

func (s *CacheStub) SetIsMember(ctx context.Context, key string, member string) (bool, error)

func (*CacheStub) SetLen

func (s *CacheStub) SetLen(ctx context.Context, key string) (int64, error)

func (*CacheStub) SetMany

func (s *CacheStub) SetMany(ctx context.Context, values map[string]string) error

func (*CacheStub) SetMembers

func (s *CacheStub) SetMembers(ctx context.Context, key string) ([]string, error)

func (*CacheStub) SetRemove

func (s *CacheStub) SetRemove(ctx context.Context, key string, members []string) (int64, error)

func (*CacheStub) SetUnion

func (s *CacheStub) SetUnion(ctx context.Context, keys []string) ([]string, error)

func (*CacheStub) SortedSetAdd

func (s *CacheStub) SortedSetAdd(ctx context.Context, key string, members []cache.SortedSetMember) (int64, error)

func (*CacheStub) SortedSetCountByScore

func (s *CacheStub) SortedSetCountByScore(ctx context.Context, key string, scores cache.ScoreRange) (int64, error)

func (*CacheStub) SortedSetIncrement

func (s *CacheStub) SortedSetIncrement(ctx context.Context, key string, member string, delta float64) (float64, error)

func (*CacheStub) SortedSetLen

func (s *CacheStub) SortedSetLen(ctx context.Context, key string) (int64, error)

func (*CacheStub) SortedSetRange

func (s *CacheStub) SortedSetRange(ctx context.Context, key string, start int64, stop int64, opts ...cache.RangeOption) ([]cache.SortedSetMember, error)

func (*CacheStub) SortedSetRangeByScore

func (s *CacheStub) SortedSetRangeByScore(ctx context.Context, key string, scores cache.ScoreRange, opts ...cache.RangeOption) ([]cache.SortedSetMember, error)

func (*CacheStub) SortedSetRank

func (s *CacheStub) SortedSetRank(ctx context.Context, key string, member string, opts ...cache.RangeOption) (int64, error)

func (*CacheStub) SortedSetRemove

func (s *CacheStub) SortedSetRemove(ctx context.Context, key string, members []string) (int64, error)

func (*CacheStub) SortedSetRemoveByRank

func (s *CacheStub) SortedSetRemoveByRank(ctx context.Context, key string, start int64, stop int64) (int64, error)

func (*CacheStub) SortedSetRemoveByScore

func (s *CacheStub) SortedSetRemoveByScore(ctx context.Context, key string, scores cache.ScoreRange) (int64, error)

func (*CacheStub) SortedSetScore

func (s *CacheStub) SortedSetScore(ctx context.Context, key string, member string) (float64, error)

func (*CacheStub) TTL

func (s *CacheStub) TTL(ctx context.Context, key string) (time.Duration, bool, error)

func (*CacheStub) Transaction

func (s *CacheStub) Transaction(ctx context.Context, queue func(cache.Tx)) ([]any, error)

func (*CacheStub) Type

func (s *CacheStub) Type(ctx context.Context, key string) (cache.KeyType, error)

type Datastore

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

Datastore is a document store held in memory.

Items are kept as the JSON they marshal to, which is how a real store keeps them as far as a handler is concerned: what goes in comes back out through the same encoding, so a field a type does not declare is not invented here and one it declares badly fails here too.

func NewDatastore

func NewDatastore(name string) *Datastore

NewDatastore returns an empty data store.

func (*Datastore) Atomically

func (d *Datastore) Atomically(
	ctx context.Context, partition string, ops []datastore.AtomicOp,
) error

func (*Datastore) BatchGet

func (d *Datastore) BatchGet(
	ctx context.Context, keys []datastore.Key, out any,
) ([]datastore.Key, error)

func (*Datastore) BatchWrite

func (d *Datastore) BatchWrite(
	ctx context.Context, ops []datastore.BatchOp,
) ([]datastore.BatchOp, error)

func (*Datastore) Delete

func (d *Datastore) Delete(
	ctx context.Context, key datastore.Key, opts ...datastore.WriteOption,
) error

func (*Datastore) Get

func (d *Datastore) Get(
	ctx context.Context, key datastore.Key, out any,
) (datastore.Revision, error)

func (*Datastore) Len

func (d *Datastore) Len() int

Len reports how many items the store holds.

func (*Datastore) Put

func (d *Datastore) Put(
	ctx context.Context, key datastore.Key, item any, opts ...datastore.WriteOption,
) (datastore.Revision, error)

func (*Datastore) Query

func (d *Datastore) Query(
	ctx context.Context, q datastore.Query, out any,
) (datastore.Cursor, error)

func (*Datastore) Scan

func (d *Datastore) Scan(
	ctx context.Context, s datastore.Scan, out any,
) (datastore.Cursor, error)

func (*Datastore) Seed

func (d *Datastore) Seed(key datastore.Key, item any) error

Seed puts an item in without going through a handler, for arranging the state a test starts from.

func (*Datastore) Update

func (d *Datastore) Update(
	ctx context.Context, key datastore.Key, updates []datastore.Update,
	opts ...datastore.WriteOption,
) (datastore.Revision, error)

type Option

type Option func(*App)

Option configures a test application.

func WithClock

func WithClock(now func() time.Time) Option

WithClock fixes the time events are stamped with, for a test asserting on it.

func WithDeadline

func WithDeadline(d time.Duration) Option

WithDeadline sets how long after an event's timestamp its deadline falls.

The deadline is applied to the handler's context the way a runtime applies one, so a handler that checks for cancellation can be tested against it.

func WithTraceContext

func WithTraceContext(tc map[string]string) Option

WithTraceContext gives every dispatch the W3C trace context an event would have arrived with.

type Provider

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

Provider hands an application test doubles in place of deployed resources.

It is given to the application rather than registered, since a test says what its handlers reach rather than inferring it from an environment:

res := celeritytest.Resources()
app := orders.App(celerity.WithResourceProvider(res))

A resource is made the first time a handle is taken for it and kept under that name, so the handle a handler closed over and the one a test reads afterwards are the same object.

func Live

func Live(t testing.TB) *Provider

Live returns resources backed by the services a `celerity dev test` session brought up, rather than by doubles.

What serves them is the provider the test's build linked, found the way a deployed application finds it, so a handler under test resolves its resources through the same code a real session runs. Which means the test's build has to link them, as a session does:

import (
	_ "github.com/newstack-cloud/celerity-go-sdk/resources/aws"
	_ "github.com/newstack-cloud/celerity-go-sdk/resources/local"
	_ "github.com/newstack-cloud/celerity-go-sdk/resources/local/redis"
)

A double can still be substituted per resource, by asking for one before the application takes its handles: Provider.QueueNamed and the rest make a double under that name and the live provider is not asked for it. So a suite can exercise a real database and keep the queue in memory.

Releases what the provider holds when the test ends, since a live provider holds pools and sockets rather than maps.

func LiveWith

func LiveWith(t testing.TB, provider resources.Provider) *Provider

LiveWith is Live against a provider named explicitly rather than the one the build linked.

For a suite that builds its own provider, and for testing the harness itself.

func Resources

func Resources() *Provider

Resources returns a provider holding no resources yet.

func (*Provider) Bucket

func (p *Provider) Bucket(ref resources.Ref) (bucket.Store, error)

Bucket returns the bucket an application took a handle for, making it on first use.

func (*Provider) BucketNamed

func (p *Provider) BucketNamed(name string) *Bucket

BucketNamed returns the bucket a blueprint calls name, making it where no handle has been taken for it yet so that a test can arrange its contents before the application reads them.

Under live resources this is also how a double is substituted for one of them, and has to be called before the application takes its handles.

func (*Provider) Cache

func (p *Provider) Cache(ref resources.Ref) (cache.Client, error)

Cache returns the cache stub an application took a handle for.

func (*Provider) CacheNamed

func (p *Provider) CacheNamed(name string) *CacheStub

CacheNamed returns the cache stub a blueprint calls name.

func (*Provider) Close

func (p *Provider) Close(ctx context.Context) error

Close gives back what the resources hold.

Nothing, where they are doubles as they are maps in this process. Present even then so that a test exercising shutdown finds the same resources.Closer a real provider offers. Under live resources it releases the live provider's pools, once however many times it is called.

func (*Provider) Datastore

func (p *Provider) Datastore(ref resources.Ref) (datastore.Client, error)

Datastore returns the data store an application took a handle for.

func (*Provider) DatastoreNamed

func (p *Provider) DatastoreNamed(name string) *Datastore

DatastoreNamed returns the data store a blueprint calls name.

func (*Provider) Name

func (p *Provider) Name() string

Name identifies this provider in errors and in the handler manifest.

func (*Provider) Queue

func (p *Provider) Queue(ref resources.Ref) (queue.Client, error)

Queue returns the queue an application took a handle for.

func (*Provider) QueueNamed

func (p *Provider) QueueNamed(name string) *Queue

QueueNamed returns the queue a blueprint calls name.

func (*Provider) SQLDatabase

func (p *Provider) SQLDatabase(ref resources.Ref) (sqldb.Client, error)

SQLDatabase returns the database a test supplied with Provider.WithDatabase, and otherwise one that refuses every call and says why.

func (*Provider) Topic

func (p *Provider) Topic(ref resources.Ref) (topic.Client, error)

Topic returns the topic an application took a handle for.

func (*Provider) TopicNamed

func (p *Provider) TopicNamed(name string) *Topic

TopicNamed returns the topic a blueprint calls name.

func (*Provider) WithDatabase

func (p *Provider) WithDatabase(name string, client sqldb.Client) *Provider

WithDatabase hands the test's own database to the handlers that take a handle for the blueprint resource called name.

There is no in-memory database to arrange, so this takes a real one: the engine a development session brings up, or whatever a suite starts for itself. A name no test supplied is refused rather than recorded, so a handler that reaches a database the suite did not arrange says so.

type PublishedMessage

type PublishedMessage struct {
	// ID is what the topic called it, which is what the publish returned.
	ID   string
	Body []byte
	// Options are what the publish asked for, resolved.
	Options topic.SendOptions
}

PublishedMessage is one message a handler published to a Topic.

func (PublishedMessage) Text

func (m PublishedMessage) Text() string

Text is the body as a string, for the ordinary case of asserting on one.

type Queue

type Queue struct {

	// Refuse fails the next send, for a test exercising what a handler does
	// when a queue will not take a message. Cleared once it has fired.
	Refuse error
	// contains filtered or unexported fields
}

Queue is a point-to-point queue held in memory.

Nothing consumes from it: what a test asserts on is what a handler sent, so the messages are kept rather than delivered.

func NewQueue

func NewQueue(name string) *Queue

NewQueue returns an empty queue.

func (*Queue) Len

func (q *Queue) Len() int

Len reports how many messages were sent.

func (*Queue) Reset

func (q *Queue) Reset()

Reset forgets what was sent, for a test reusing one queue across cases.

func (*Queue) Send

func (q *Queue) Send(
	ctx context.Context, body []byte, opts ...queue.SendOption,
) (string, error)

func (*Queue) SendBatch

func (q *Queue) SendBatch(
	ctx context.Context, entries []queue.BatchEntry,
) (queue.BatchResult, error)

func (*Queue) Sent

func (q *Queue) Sent() []SentMessage

Sent returns the messages a handler sent, in order.

type RecordedSpan

type RecordedSpan struct {
	Name string
	// TraceID and SpanID are what a log written during the span carries, so a
	// test can assert that a record and a span name the same trace.
	TraceID string
	SpanID  string
	// Attrs are what the span recorded, in the order they were added.
	Attrs []telemetry.Attr
	// Err is what failed inside the span, and nil where nothing did.
	Err error
	// Parent is the name of the span this one opened inside, and empty for the
	// outermost. What a test usually wants to know about a resource span is
	// that it sits under the dispatch.
	Parent string
	// Ended reports whether the span was closed, which every span has to be.
	Ended bool
}

RecordedSpan is one span a handler produced.

func (RecordedSpan) Attr

func (s RecordedSpan) Attr(key string) (any, bool)

Attr returns the value recorded under a key, and whether it was recorded.

type RequestOption

type RequestOption func(*celerityhandler.Request)

RequestOption configures a request before it is dispatched.

func Body

func Body(body []byte) RequestOption

Body sends raw bytes, leaving the content type to the caller.

func Header(name, value string) RequestOption

Header adds a request header. Names are lowercased, as a handler sees them.

func JSONBody

func JSONBody(v any) RequestOption

JSONBody encodes a value as the request body and declares it as JSON.

A value that cannot be encoded is sent as no body at all, which the handler will reject: a test that got here has a fault in what it is sending rather than in what it is testing, and App.Do reports it.

func Query

func Query(name, value string) RequestOption

Query adds a query parameter.

func SourceIP

func SourceIP(ip string) RequestOption

SourceIP sets the address the request appears to come from, which a guard or a rate limiter may read.

type Response

type Response struct {
	// Result is the whole answer, for an assertion the helpers do not cover.
	// Nil where the handler failed before producing one.
	Result *celerityhandler.Result
	// Handler is the blueprint name of the handler that answered, named in
	// failures so that a test says which one disappointed it.
	Handler string
	// contains filtered or unexported fields
}

Response is what an HTTP handler answered.

func (*Response) AssertStatus

func (r *Response) AssertStatus(t testing.TB, want int) *Response

AssertStatus fails the test unless the handler answered with want.

The body is reported on a mismatch, since the usual cause is an error the handler described there.

func (*Response) Body

func (r *Response) Body() []byte

Body is the raw response body.

func (*Response) Decode

func (r *Response) Decode(t testing.TB, out any)

Decode reads a JSON response body into out, failing the test where it is not the shape asked for.

func (*Response) Error

func (r *Response) Error() error

Error is the failure the handler reported, and nil where it answered normally.

A handler that returns an error does not answer with a status: turning one into a response is the runtime's job rather than the handler's, so this is what a test asserting on a failure reads.

func (*Response) Header

func (r *Response) Header(name string) string

Header returns the first value of a response header.

func (*Response) Status

func (r *Response) Status() int

Status is the status the handler answered with, and zero where it failed instead of answering.

type SentMessage

type SentMessage struct {
	// ID is what the queue called it, which is what the send returned.
	ID   string
	Body []byte
	// Options are what the send asked for, resolved.
	Options queue.SendOptions
}

SentMessage is one message a handler put on a Queue.

func (SentMessage) Text

func (m SentMessage) Text() string

Text is the body as a string, for the ordinary case of asserting on one.

type Topic

type Topic struct {

	// Refuse fails the next publish, for a test exercising what a handler does
	// when a topic will not take a message. Cleared once it has fired.
	Refuse error
	// contains filtered or unexported fields
}

Topic is publish-subscribe held in memory.

Nothing subscribes to it: what a test asserts on is what a handler published, so the messages are kept rather than delivered.

func NewTopic

func NewTopic(name string) *Topic

NewTopic returns an empty topic.

func (*Topic) Len

func (t *Topic) Len() int

Len reports how many messages were published.

func (*Topic) Publish

func (t *Topic) Publish(
	ctx context.Context, body []byte, opts ...topic.SendOption,
) (string, error)

func (*Topic) PublishBatch

func (t *Topic) PublishBatch(
	ctx context.Context, entries []topic.BatchEntry,
) (topic.BatchResult, error)

func (*Topic) Published

func (t *Topic) Published() []PublishedMessage

Published returns the messages a handler published, in order.

func (*Topic) Reset

func (t *Topic) Reset()

Reset forgets what was published, for a test reusing one topic across cases.

type Tracer

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

Tracer records the spans a handler produced rather than exporting them.

What it is for is telemetry the application wrote: a span a handler opened with telemetry.Traced, the attributes it set on it, the error it recorded. Those are the application's own behaviour, and a test asserting them is testing the handler.

tracer := celeritytest.NewTracer(t)
harness.POST(t, "/orders", celeritytest.JSONBody(order{ID: "o-1", Total: 10}))

span := tracer.AssertSpan(t, "orders.price_quote")
total, _ := span.Attr("order.total")

Asserting the SDK's own span names is not what it is for. Those belong to the SDK and are the same in every language Celerity supports, so a test naming one asserts this package's behaviour rather than the application's, and fails when a name here changes without the application having changed at all.

Only one recorder can be installed at a time, because the tracer seam is process-wide. See NewTracer.

func NewTracer

func NewTracer(t testing.TB) *Tracer

NewTracer installs a recording tracer and removes it when the test ends.

Fails the test where a recorder is already installed. The tracer is installed process-wide rather than carried per dispatch, so two recorders at once means the second collects the first's spans as well as its own and both tests read something other than what they produced. A test calling this therefore cannot call t.Parallel.

func (*Tracer) AssertSpan

func (r *Tracer) AssertSpan(t testing.TB, name string) RecordedSpan

AssertSpan returns the first span recorded under a name, failing the test where nothing was recorded under it.

What was recorded is reported on failure, since a span that is missing is usually one recorded under a different name rather than one never opened.

func (*Tracer) Names

func (r *Tracer) Names() []string

Names returns the names of the spans recorded, which is what most assertions are about.

func (*Tracer) Reset

func (r *Tracer) Reset()

Reset forgets the spans recorded so far, for a test making more than one dispatch and asserting on each.

func (*Tracer) Span

func (r *Tracer) Span(name string) (RecordedSpan, bool)

Span returns the first span recorded under a name, and whether there was one.

func (*Tracer) Spans

func (r *Tracer) Spans() []RecordedSpan

Spans returns the spans recorded, in the order they were started.

func (*Tracer) Start

func (r *Tracer) Start(
	ctx context.Context, name string, attrs ...telemetry.Attr,
) (context.Context, telemetry.Span)

Start opens a span, recording it against the one it was opened inside.

type WebSocketMessage

type WebSocketMessage func(*handler.WebSocketMessage)

WebSocketMessage configures a message before it is delivered.

func Binary

func Binary() WebSocketMessage

Binary delivers the message as a binary frame rather than a text one.

func From

func From(connectionID string) WebSocketMessage

From sets the connection a message arrives on, which is what a handler answers back to.

Jump to

Keyboard shortcuts

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