diff --git a/core/adapter/adapter.go b/core/adapter/event.go similarity index 100% rename from core/adapter/adapter.go rename to core/adapter/event.go diff --git a/core/adapter/experiment.go b/core/adapter/experiment.go new file mode 100644 index 0000000..999387b --- /dev/null +++ b/core/adapter/experiment.go @@ -0,0 +1,23 @@ +package adapter + +import ( + "fmt" + + "Growth/core/entity" +) + +type ExperimentStore interface { + Save(entity.Experiment) entity.Experiment + FetchByID(id entity.ID) entity.Experiment + ErrNotFound() *ErrExperimentNotFound + ErrOther() error +} + +type ErrExperimentNotFound struct { + ID entity.ID + Stack string +} + +func (e *ErrExperimentNotFound) Error() string { + return fmt.Sprintf("experiment:%d not found", e.ID) +} diff --git a/core/adapter/testadapter/test_adapter.go b/core/adapter/testadapter/test_event.go similarity index 100% rename from core/adapter/testadapter/test_adapter.go rename to core/adapter/testadapter/test_event.go diff --git a/core/adapter/testadapter/test_experiment.go b/core/adapter/testadapter/test_experiment.go new file mode 100644 index 0000000..80a2881 --- /dev/null +++ b/core/adapter/testadapter/test_experiment.go @@ -0,0 +1,61 @@ +package testadapter + +import ( + "runtime/debug" + + "Growth/core/adapter" + "Growth/core/entity" +) + +type FakeExperimentStore struct { + experiments []entity.Experiment + errNotFound *adapter.ErrExperimentNotFound + errOther error +} + +func (store *FakeExperimentStore) Save(e entity.Experiment) entity.Experiment { + store.init() + e.ID = entity.ID(len(store.experiments) + 1) + store.experiments = append(store.experiments, e) + return e +} + +func (store *FakeExperimentStore) FetchByID(id entity.ID) entity.Experiment { + for _, e := range store.experiments { + if e.ID == id { + return e + } + } + store.errNotFound =&adapter.ErrExperimentNotFound{ID: id, Stack: string(debug.Stack())} + return entity.Experiment{} +} + +func (store *FakeExperimentStore) ErrNotFound() *adapter.ErrExperimentNotFound { + defer func() { + store.errNotFound = nil + }() + return store.errNotFound +} + +func (store *FakeExperimentStore) ErrNotFoundTrace() *adapter.ErrExperimentNotFound { + defer func() { + store.errNotFound = nil + }() + return store.errNotFound +} + +func (store *FakeExperimentStore) ErrOther() error { + defer func() { + store.errOther = nil + }() + return store.errOther +} + +func (store *FakeExperimentStore) init() { + if store == nil { + *store = FakeExperimentStore{} + } + if store.experiments == nil { + store.experiments = make([]entity.Experiment, 0) + } +} diff --git a/core/usecase/evenet_test.go b/core/usecase/evenet_test.go new file mode 100644 index 0000000..84eece5 --- /dev/null +++ b/core/usecase/evenet_test.go @@ -0,0 +1,86 @@ +package usecase + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type EventType int + +type Event struct { + Type EventType + Data interface{} +} + +type EventBus struct { + qs map[EventType][]chan Out +} + +func (bus *EventBus) init() { + if bus.qs == nil { + bus.qs = make(map[EventType][]chan Out) + } +} + +func (bus *EventBus) Emit(e Event) error { + bus.init() + for _, out := range bus.qs[e.Type] { + go func(out chan Out) { + out <- Out{ + Data: e.Data, + Error: nil, + } + }(out) + } + return nil +} + +type Out struct { + Data interface{} + Error error +} + +func (bus *EventBus) On(et EventType) chan Out { + bus.init() + // register a queue for this listener + outs := bus.qs[et] + out := make(chan Out) + bus.qs[et] = append(outs, out) + return out +} + +const ( + EventX EventType = iota + EventY + EventZ +) + +func TestX(t *testing.T) { + defer func() { + recover() + }() + + bus := &EventBus{} + + xChan := bus.On(EventX) + xChan2 := bus.On(EventX) + + bus.Emit(Event{ + Type: EventType(EventX), + Data: "1", + }) + bus.Emit(Event{ + Type: EventType(EventX), + Data: "1", + }) + + out := <-xChan + require.Equal(t, "1", out.Data) + require.Equal(t, nil, out.Error) + + out = <-xChan2 + require.Equal(t, "1", out.Data) + require.Equal(t, nil, out.Error) + +} diff --git a/core/usecase/event_test.go b/core/usecase/event_test.go index a089588..c8d6f19 100644 --- a/core/usecase/event_test.go +++ b/core/usecase/event_test.go @@ -21,7 +21,7 @@ func TestFetchEventById(t *testing.T) { t.Run("event not found when store is empty", func(t *testing.T) { e, err := FetchEventByID(1, eventStore) - require.IsType(t, &adapter.ErrEventNotFound{}, err) + require.IsType(t, &adapter.ErrEventNotFound{}, err) // todo: an error leak here, should define use case level error for it require.Equal(t, "event:1 not found", err.Error()) require.Equal(t, entity.Event{}, e) }) diff --git a/core/usecase/experiment.go b/core/usecase/experiment.go index 3d31d85..c20357e 100644 --- a/core/usecase/experiment.go +++ b/core/usecase/experiment.go @@ -1,7 +1,43 @@ package usecase -import "Growth/core/entity" +import ( + "Growth/core/adapter" + "Growth/core/entity" +) -func CreateExperiment(userID entity.ID) (entity.Experiment, error) { - return entity.Experiment{Owner: userID}, nil +func NewExperiment(store adapter.ExperimentStore) *Experiment { + return &Experiment{store: store} +} + +type Experiment struct { + store adapter.ExperimentStore + errExperimentNotFound *adapter.ErrExperimentNotFound + errOther error +} + +func (e *Experiment) CreateExperiment(userID entity.ID) (entity.Experiment) { + exp := e.store.Save(entity.Experiment{Owner: userID}) + e.errOther = e.store.ErrOther() + return exp +} + +func (e *Experiment) FetchExperimentByID(id entity.ID) entity.Experiment { + exp := e.store.FetchByID(id) + e.errExperimentNotFound = e.store.ErrNotFound() + e.errOther = e.store.ErrOther() + return exp +} + +func (e *Experiment) ErrExperimentNotFound() *adapter.ErrExperimentNotFound { + defer func() { + e.errExperimentNotFound = nil + }() + return e.errExperimentNotFound +} + +func (e *Experiment) ErrOther() error { + defer func() { + e.errOther = nil + }() + return e.errOther } diff --git a/core/usecase/experiment_test.go b/core/usecase/experiment_test.go index 84ba6e9..96afc8d 100644 --- a/core/usecase/experiment_test.go +++ b/core/usecase/experiment_test.go @@ -5,19 +5,34 @@ import ( "testing" "time" + "Growth/core/adapter/testadapter" "Growth/core/entity" "github.com/stretchr/testify/require" ) -func TestCreateExperiment(t *testing.T) { - t.Run("the owner of an experiment is correctly set", func(t *testing.T) { +func TestExperiment(t *testing.T) { + e := NewExperiment(&testadapter.FakeExperimentStore{}) + + t.Run("no data when the store is empty", func(t *testing.T) { + e.FetchExperimentByID(entity.ID(1)) + require.Equal(t, entity.ID(1), e.ErrExperimentNotFound().ID) + }) + + t.Run("can find the same experiment which has been created", func(t *testing.T) { rand.Seed(time.Now().Unix()) for i := 0; i < 10; i++ { - id := entity.ID(rand.Int63()) - exp, err := CreateExperiment(id) - require.NoError(t, err) - require.Equal(t, id, exp.Owner) + userID := entity.ID(rand.Int63()) + exp := e.CreateExperiment(userID) + require.NoError(t, e.ErrOther()) + require.Equal(t, userID, exp.Owner) + + exp2 := e.FetchExperimentByID(exp.ID) + require.Nil(t, e.ErrExperimentNotFound()) + require.NoError(t, e.ErrOther()) + + require.Equal(t, exp2.ID, exp.ID, entity.ID(i+1)) + require.Equal(t, userID, exp2.Owner) } }) } diff --git a/dep/dep.go b/dep/dep.go index 0bf198b..a5495f5 100644 --- a/dep/dep.go +++ b/dep/dep.go @@ -31,6 +31,7 @@ func (d *Dep) RelayHandler() http.Handler { EventStore: d.EventStore, }, } + h, err := server.RelayHandler(schema, r) d.Err = errors.WithStack(err) return h