From 53d2c477c606bcf91dacb4e2a520e1038b898935 Mon Sep 17 00:00:00 2001 From: tochemey Date: Sat, 7 Oct 2023 21:40:57 +0000 Subject: [PATCH] refactor: add unit test for entity --- engine.go | 11 ++++++++++- entity.go | 25 +++++++++++++++++++++++++ entity_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 entity_test.go diff --git a/engine.go b/engine.go index 50b30c6..57866e5 100644 --- a/engine.go +++ b/engine.go @@ -24,6 +24,7 @@ type Engine struct { discoveryConfig discovery.Config // discoveryConfig is the discovery provider config for clustering telemetry *telemetry.Telemetry // telemetry is the observability engine partitionsCount uint64 // partitionsCount specifies the number of partitions + started atomic.Bool } // NewEngine creates an instance of Engine @@ -40,6 +41,7 @@ func NewEngine(name string, eventsStore eventstore.EventsStore, opts ...Option) for _, opt := range opts { opt.Apply(e) } + e.started.Store(false) return e } @@ -71,10 +73,17 @@ func (x *Engine) Start(ctx context.Context) error { return err } // start the actor system - return x.actorSystem.Start(ctx) + if err := x.actorSystem.Start(ctx); err != nil { + return err + } + // set the started to true + x.started.Store(true) + return nil } // Stop stops the ego engine func (x *Engine) Stop(ctx context.Context) error { + // set the started to false + x.started.Store(false) return x.actorSystem.Stop(ctx) } diff --git a/entity.go b/entity.go index 7ad02d1..0830a8b 100644 --- a/entity.go +++ b/entity.go @@ -10,6 +10,15 @@ import ( "github.com/tochemey/goakt/actors" ) +var ( + // ErrEngineRequired is returned when the eGo engine is not set + ErrEngineRequired = errors.New("eGo engine is not defined") + // ErrEngineNotStarted is returned when the eGo engine has not started + ErrEngineNotStarted = errors.New("eGo engine has not started") + // ErrUndefinedEntity is returned when sending a command to an undefined entity + ErrUndefinedEntity = errors.New("eGo entity is not defined") +) + // Entity defines the event sourced persistent entity // This handles commands in order type Entity[T State] struct { @@ -18,6 +27,15 @@ type Entity[T State] struct { // NewEntity creates an instance of Entity func NewEntity[T State](ctx context.Context, behavior EntityBehavior[T], engine *Engine) (*Entity[T], error) { + // check whether the ego engine is defined + if engine == nil { + return nil, ErrEngineRequired + } + // check whether the eGo engine has started or not + if !engine.started.Load() { + return nil, ErrEngineNotStarted + } + // create the instance of the actor pid, err := engine.actorSystem.Spawn(ctx, behavior.ID(), newActor(behavior, engine.eventsStore)) // return the error in case there is one @@ -34,7 +52,14 @@ func NewEntity[T State](ctx context.Context, behavior EntityBehavior[T], engine // 2. nil when there is no resulting state or no event persisted // 3. an error in case of error func (x Entity[T]) SendCommand(ctx context.Context, command Command) (resultingState T, revision uint64, err error) { + // define a nil state var nilT T + + // check whether the underlying actor is set and running + if x.actor == nil || !x.actor.IsRunning() { + return nilT, 0, ErrUndefinedEntity + } + // send the command to the actor reply, err := actors.Ask(ctx, x.actor, command, time.Second) // handle the error diff --git a/entity_test.go b/entity_test.go new file mode 100644 index 0000000..3b482f0 --- /dev/null +++ b/entity_test.go @@ -0,0 +1,49 @@ +package ego + +import ( + "context" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/tochemey/ego/eventstore/memory" + samplepb "github.com/tochemey/ego/example/pbs/sample/pb/v1" +) + +func TestNewEntity(t *testing.T) { + t.Run("With engine not defined", func(t *testing.T) { + ctx := context.TODO() + behavior := NewAccountBehavior(uuid.NewString()) + // create an entity + entity, err := NewEntity[*samplepb.Account](ctx, behavior, nil) + require.Error(t, err) + require.Nil(t, entity) + assert.EqualError(t, err, ErrEngineRequired.Error()) + }) + t.Run("With engine not started", func(t *testing.T) { + ctx := context.TODO() + // create the event store + eventStore := memory.NewEventsStore() + // create the ego engine + engine := NewEngine("Sample", eventStore) + behavior := NewAccountBehavior(uuid.NewString()) + // create an entity + entity, err := NewEntity[*samplepb.Account](ctx, behavior, engine) + require.Error(t, err) + require.Nil(t, entity) + assert.EqualError(t, err, ErrEngineNotStarted.Error()) + }) +} + +func TestSendCommand(t *testing.T) { + t.Run("With entity not defined", func(t *testing.T) { + ctx := context.TODO() + entity := &Entity[*samplepb.Account]{} + resultingState, revision, err := entity.SendCommand(ctx, new(samplepb.CreateAccount)) + require.Nil(t, resultingState) + require.Zero(t, revision) + require.Error(t, err) + assert.EqualError(t, err, ErrUndefinedEntity.Error()) + }) +}