Skip to content

Commit

Permalink
[GUILD-407] Add a load all function to help replaying events (#11)
Browse files Browse the repository at this point in the history
* [GUILD-407] Dockerify the makefile a bit more to simplify the code
* [GUILD-407] Improve test coverage
  • Loading branch information
mcarriere committed Mar 5, 2019
1 parent ddff16b commit fd8e9b2
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 86 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ jobs:
include:
- stage: test
if: type == pull_request
script: make services test
script: make test_docker
env:
- GO111MODULE=on

- stage: test_and_cover
name: "Test (with coverage)"
if: type != pull_request
script: make services cover publish_cover
script: make publish_cover
env:
- GO111MODULE=on
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
default: services test
default: test_docker

test:
go test ./...
Expand All @@ -12,7 +12,11 @@ cover:
go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' ./... | xargs -L 1 sh -c
.PHONY: cover

publish_cover: cover
cover_docker:
docker-compose run --rm golang make cover
.PHONY: cover_docker

publish_cover: cover_docker
go get -d golang.org/x/tools/cmd/cover
go get github.com/modocache/gover
go get github.com/mattn/goveralls
Expand Down
6 changes: 4 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ services:
depends_on:
- dynamodb
environment:
DYNAMODB_HOST: "dynamodb:8000"
DYNAMODB_HOST: "http://dynamodb:8000"
AWS_ACCESS_KEY_ID: "fakeKeyId"
AWS_SECRET_ACCESS_KEY: "fakeSecret"
volumes:
- .:/eventhorizon
working_dir: /eventhorizon

dynamodb:
image: peopleperhour/dynamodb:latest
image: amazon/dynamodb-local:latest
ports:
- "8000:8000"
21 changes: 21 additions & 0 deletions eventstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,27 @@ func (s *EventStore) Load(ctx context.Context, id uuid.UUID) ([]eh.Event, error)
}
}

return s.buildEvents(ctx, dbEvents)
}

// LoadAll will load all the events from the event store (useful to replay events)
func (s *EventStore) LoadAll(ctx context.Context) ([]eh.Event, error) {
table := s.service.Table(s.TableName(ctx))

var dbEvents []dbEvent
err := table.Scan().Consistent(true).All(&dbEvents)
if err != nil {
return nil, eh.EventStoreError{
BaseErr: err,
Err: err,
Namespace: eh.NamespaceFromContext(ctx),
}
}

return s.buildEvents(ctx, dbEvents)
}

func (s *EventStore) buildEvents(ctx context.Context, dbEvents []dbEvent) ([]eh.Event, error) {
events := make([]eh.Event, len(dbEvents))
for i, dbEvent := range dbEvents {
// Create an event of the correct type.
Expand Down
134 changes: 86 additions & 48 deletions eventstore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,67 +18,105 @@ import (
"context"
"os"
"testing"
"time"

"github.com/google/uuid"

"github.com/looplab/eventhorizon/mocks"

eh "github.com/looplab/eventhorizon"
"github.com/looplab/eventhorizon/eventstore"

eh "github.com/looplab/eventhorizon"
"github.com/stretchr/testify/assert"

"github.com/stretchr/testify/suite"
)

func TestEventStore(t *testing.T) {
// Local DynamoDb testing with Docker
url := os.Getenv("DYNAMODB_HOST")
if url == "" {
url = "localhost:8000"
}
url = "http://" + url
type EventStoreTestSuite struct {
suite.Suite
ctx context.Context
store *EventStore
}

// These must be set for testing, even when using the mocked server.
awsAccessKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
if awsAccessKeyID == "" {
os.Setenv("AWS_ACCESS_KEY_ID", "fakeMyKeyId")
}
awsSecretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
if awsSecretAccessKey == "" {
os.Setenv("AWS_SECRET_ACCESS_KEY", "fakeSecretAccessKey")
}
// SetupTestSuite will create the store and dynamo table
func (suite *EventStoreTestSuite) SetupTest() {
config := &EventStoreConfig{Endpoint: os.Getenv("DYNAMODB_HOST")}

config := &EventStoreConfig{
Endpoint: url,
}
store, err := NewEventStore(config)
if err != nil {
t.Fatal("there should be no error:", err)
}
if store == nil {
t.Fatal("there should be a store")
}
var err error
suite.store, err = NewEventStore(config)
assert.Nil(suite.T(), err, "there should be no error")
assert.NotNil(suite.T(), suite.store, "there should be a store")

ctx := eh.NewContextWithNamespace(context.Background(), "ns")
suite.ctx = eh.NewContextWithNamespace(context.Background(), "ns")

t.Log("creating tables for:", config.TablePrefix)
if err := store.CreateTable(context.Background()); err != nil {
t.Fatal("could not create table:", err)
}
if err := store.CreateTable(ctx); err != nil {
t.Fatal("could not create table:", err)
assert.Nil(suite.T(), suite.store.CreateTable(context.Background()), "could not create table")
assert.Nil(suite.T(), suite.store.CreateTable(suite.ctx), "could not create table")
}

// TearDownTestSuite will delete the dynamo table
func (suite *EventStoreTestSuite) TearDownTest() {
assert.Nil(suite.T(), suite.store.DeleteTable(context.Background()), "could not delete table")
assert.Nil(suite.T(), suite.store.DeleteTable(suite.ctx), "could not delete table")
}

// TestEventStore will run all the acceptance tests for event stores
func (suite *EventStoreTestSuite) TestEventStore() {
suite.T().Log("event store with default namespace")
eventstore.AcceptanceTest(suite.T(), context.Background(), suite.store)

suite.T().Log("event store with other namespace")
eventstore.AcceptanceTest(suite.T(), suite.ctx, suite.store)

suite.T().Log("event store maintainer")
eventstore.MaintainerAcceptanceTest(suite.T(), context.Background(), suite.store)
}

// TestLoadAll will save a bunch of events and try to load them all from the event store
func (suite *EventStoreTestSuite) TestLoadAll() {
id, _ := uuid.Parse("c1138e5f-f6fb-4dd0-8e79-255c6c8d3756")
timestamp := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)

expectedEvents := []eh.Event{
eh.NewEventForAggregate(mocks.EventType, &mocks.EventData{Content: "event1"},
timestamp, mocks.AggregateType, id, 1),
eh.NewEventForAggregate(mocks.EventType, &mocks.EventData{Content: "event2"},
timestamp, mocks.AggregateType, id, 2),
}

defer func() {
t.Log("deleting tables for:", config.TablePrefix)
if err := store.DeleteTable(context.Background()); err != nil {
t.Fatal("could not delete table: ", err)
_ = suite.store.Save(context.Background(), expectedEvents, 0)

events, err := suite.store.LoadAll(context.Background())
assert.Nil(suite.T(), err)
assert.Len(suite.T(), events, 2)

for i, event := range events {
if err := mocks.CompareEvents(event, expectedEvents[i]); err != nil {
suite.T().Error("the event was incorrect:", err)
}
if err := store.DeleteTable(ctx); err != nil {
t.Fatal("could not delete table: ", err)
if event.Version() != i+1 {
suite.T().Error("the event version should be correct:", event, event.Version())
}
}()
}
}

// Run the actual test suite.
t.Log("event store with default namespace")
eventstore.AcceptanceTest(t, context.Background(), store)
// TestSaveInvalidAggregateId will save an aggregate with an invalid event aggregate ID
func (suite *EventStoreTestSuite) TestSaveInvalidAggregateId() {
id, _ := uuid.Parse("c1138e5f-f6fb-4dd0-8e79-255c6c8d3756")
id2, _ := uuid.Parse("c1138e5f-f6fb-4dd0-8e79-zzzzzzzzzz")
timestamp := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)

t.Log("event store with other namespace")
eventstore.AcceptanceTest(t, ctx, store)
expectedEvents := []eh.Event{
eh.NewEventForAggregate(mocks.EventType, &mocks.EventData{Content: "event1"},
timestamp, mocks.AggregateType, id, 1),
eh.NewEventForAggregate(mocks.EventType, &mocks.EventData{Content: "event1"},
timestamp, mocks.AggregateType, id2, 1),
}

err := suite.store.Save(context.Background(), expectedEvents, 0)
assert.EqualError(suite.T(), err, "invalid event (default)")
}

t.Log("event store maintainer")
eventstore.MaintainerAcceptanceTest(t, context.Background(), store)
// TestEventStoreTestSuite starts the test suite
func TestEventStoreTestSuite(t *testing.T) {
suite.Run(t, new(EventStoreTestSuite))
}
9 changes: 3 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
module github.com/seedboxtech/eh-dynamo

require (
github.com/aws/aws-sdk-go v1.16.23
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gofrs/uuid v3.2.0+incompatible // indirect
github.com/google/uuid v1.1.0
github.com/guregu/dynamo v1.1.0
github.com/aws/aws-sdk-go v1.17.10
github.com/google/uuid v1.1.1
github.com/guregu/dynamo v1.2.0
github.com/looplab/eventhorizon v0.5.0
github.com/stretchr/testify v1.3.0
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e // indirect
)
19 changes: 9 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
contrib.go.opencensus.io/exporter/stackdriver v0.6.0/go.mod h1:QeFzMJDAw8TXt5+aRaSuE8l5BwaMIOIlaVkBOPRuMuw=
github.com/aws/aws-sdk-go v1.16.15/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.16.23 h1:MwBOBeez0XEFVh6DCc888X+nHVBCjUDLnnWXSGGWUgM=
github.com/aws/aws-sdk-go v1.16.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.17.10 h1:m8vArG9yPW5YZ27IXcLg1tRkOXZtGrjgzljAo46qWaE=
github.com/aws/aws-sdk-go v1.17.10/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/globalsign/mgo v0.0.0-20180828104044-6f9f54af1356/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA=
github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s=
github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/guregu/dynamo v1.1.0 h1:VMWIgsX8/Gnccp7vzyU4hGxF+GcjCHVIMAf1sMR0NSs=
github.com/guregu/dynamo v1.1.0/go.mod h1:t17gZDlH3e79JDY5JupzITPmsz2UdKx4PudRNsN0Pdw=
github.com/guregu/dynamo v1.2.0 h1:TLHYgza0YUOGuVYIuMp/ExYUPEHHa5VfafkxeWhdZoA=
github.com/guregu/dynamo v1.2.0/go.mod h1:t17gZDlH3e79JDY5JupzITPmsz2UdKx4PudRNsN0Pdw=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jpillora/backoff v0.0.0-20170918002102-8eab2debe79d/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
Expand All @@ -38,9 +38,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108155000-395948e2f546 h1:tkMg6+6TF2qZ/3I8fw+DiNgPSsABxdVIqWE90w8Vxzk=
golang.org/x/net v0.0.0-20190108155000-395948e2f546/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q=
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180903190138-2b024373dcd9/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down
17 changes: 1 addition & 16 deletions repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,6 @@ func (suite *RepoTestSuite) SetupSuite() {
}

func (suite *RepoTestSuite) getDynamoDB(conf *RepoConfig) *dynamo.DB {
// These must be set for testing, even when using the mocked server.
awsAccessKeyID := os.Getenv("AWS_ACCESS_KEY_ID")
if awsAccessKeyID == "" {
os.Setenv("AWS_ACCESS_KEY_ID", "fakeMyKeyIds")
}
awsSecretAccessKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
if awsSecretAccessKey == "" {
os.Setenv("AWS_SECRET_ACCESS_KEY", "fakeSecretAccessKey")
}
awsConf := &aws.Config{
Region: aws.String("us-east-1"),
Endpoint: aws.String(conf.Endpoint),
Expand All @@ -81,15 +72,9 @@ func (suite *RepoTestSuite) getRepo(conf *RepoConfig) *Repo {
}

func (suite *RepoTestSuite) getRepoConfig() *RepoConfig {
// Local DynamoDb testing with Docker
url := os.Getenv("DYNAMODB_HOST")
if url == "" {
url = "localhost:8000"
}

return &RepoConfig{
TableName: "eventhorizonTest_" + uuid.New().String(),
Endpoint: "http://" + url,
Endpoint: os.Getenv("DYNAMODB_HOST"),
}
}
func (suite *RepoTestSuite) BeforeTest(suiteName, testName string) {
Expand Down

0 comments on commit fd8e9b2

Please sign in to comment.