Repository: looplab/eventhorizon (go)

Issue:
More explicit error message when trying to project event to deleted projection
**Describe the bug**
When a event is projected on a deleted projection it returns "the incorrect projected entity version" 
this is mainly for development experience since you should always check for exists in aggregate 

**To Reproduce**
Steps to reproduce the behavior:
1. Create an aggregate item and a saga that triggers on that created event and later send back `UpdateCommand` to aggreate async creating  `UpdateEvent` 
2. Send delete command against aggregate
3. the aggregate projection should apply events `CreatedEvent`, `DeletedEvent`, `UpdateEvent`
4. See error "the incorrect projected entity version"  in projector

**Expected behavior**
same as **To Reproduce** 1-3
but step 4 should produce "projected entity is deleted"

**Possible soultion**
if the event version that is about be projected is more than one and projection entity version is zero then the entity is deleted


Pull request:
Fix / Return error in projector for removed models
### Description

Return `projector.ErrModelRemoved` in case the model was removed when projecting. This check only works if regular versioning is used as the removed status can't be discerned from a not-created entity that would begin on for example version 2.

### Affected Components

- Projector

### Related Issues

Fixes #362

### Solution and Design

Works by checking that the event is v1 if the model is v0, which means that the model did not exist in the store.

### Steps to test and verify


Patch:
diff --git a/eventhandler/projector/eventhandler.go b/eventhandler/projector/eventhandler.go
index dd0f373..9d2dfbc 100644
--- a/eventhandler/projector/eventhandler.go
+++ b/eventhandler/projector/eventhandler.go
@@ -46,6 +46,8 @@ func (t Type) String() string {
 var (
 	// ErrModelNotSet is when a model factory is not set on the EventHandler.
 	ErrModelNotSet = errors.New("model not set")
+	// ErrModelRemoved is when a model has been removed.
+	ErrModelRemoved = errors.New("model removed")
 	// Returned if the model has not incremented its version as predicted.
 	ErrIncorrectProjectedEntityVersion = errors.New("incorrect projected entity version")
 )
@@ -251,6 +253,16 @@ retryOnce:
 				goto retryOnce
 			}
 
+			if entityVersion == 0 && event.Version() > 1 {
+				return &Error{
+					Err:           ErrModelRemoved,
+					Projector:     h.projector.ProjectorType().String(),
+					Event:         event,
+					EntityID:      id,
+					EntityVersion: entityVersion,
+				}
+			}
+
 			return &Error{
 				Err:           eh.ErrIncorrectEntityVersion,
 				Projector:     h.projector.ProjectorType().String(),


Test patch:
diff --git a/eventhandler/projector/eventhandler_test.go b/eventhandler/projector/eventhandler_test.go
index 7c39d89..c911ec6 100644
--- a/eventhandler/projector/eventhandler_test.go
+++ b/eventhandler/projector/eventhandler_test.go
@@ -359,6 +359,33 @@ func TestEventHandler_DeleteModel(t *testing.T) {
 	}
 }
 
+func TestEventHandler_UpdateModelAfterDelete(t *testing.T) {
+	repo := &mocks.Repo{}
+	projector := &TestProjector{}
+	handler := NewEventHandler(projector, repo)
+	handler.SetEntityFactory(func() eh.Entity {
+		return &mocks.Model{}
+	})
+
+	// Entity must be versionable, but empty.
+	repo.Entity = &mocks.Model{}
+
+	ctx := context.Background()
+
+	id := uuid.New()
+	eventData := &mocks.EventData{Content: "event1"}
+	timestamp := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+	updateEvent := eh.NewEvent(mocks.EventType, eventData, timestamp,
+		eh.ForAggregate(mocks.AggregateType, id, 3))
+
+	err := handler.HandleEvent(ctx, updateEvent)
+
+	errType := &Error{}
+	if !errors.As(err, &errType) || !errors.Is(err, ErrModelRemoved) {
+		t.Error("there should be an error:", err)
+	}
+}
+
 func TestEventHandler_LoadError(t *testing.T) {
 	repo := &mocks.Repo{}
 	projector := &TestProjector{}