From cc3707e8bbbb5b7f7bd52218eb0bfd63084d4c25 Mon Sep 17 00:00:00 2001 From: MelloTonio Date: Tue, 5 Oct 2021 19:45:57 -0400 Subject: [PATCH 1/5] test: unsubscribe --- domain/entities/errors.go | 1 + domain/entities/topic.go | 23 +++- domain/usecases/repository_mock.go | 49 +++++++++ domain/usecases/unsubscribe.go | 7 +- domain/usecases/unsubscribe_test.go | 119 +++++++++++++++++++++ domain/usecases/usecase.go | 1 + repositories/memory/topics/repository.go | 1 + repositories/memory/topics/update_topic.go | 11 ++ 8 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 domain/usecases/unsubscribe_test.go create mode 100644 repositories/memory/topics/update_topic.go diff --git a/domain/entities/errors.go b/domain/entities/errors.go index a818678..a9e21aa 100644 --- a/domain/entities/errors.go +++ b/domain/entities/errors.go @@ -7,4 +7,5 @@ var ( ErrTopicsNotFound = errors.New("no topic was found") ErrTopicNotFound = errors.New("there isn't any subscriber listening to this topic") ErrTopicAlreadyExists = errors.New("this topic already exists") + ErrSubscriberNotFound = errors.New("subscriber not found") ) diff --git a/domain/entities/topic.go b/domain/entities/topic.go index d3ec82b..984f172 100644 --- a/domain/entities/topic.go +++ b/domain/entities/topic.go @@ -1,6 +1,7 @@ package entities import ( + "fmt" "sync" "github.com/matheusmosca/walrus/domain/vos" @@ -44,14 +45,34 @@ func (t Topic) Dispatch(message vos.Message) error { return nil } -func (t Topic) RemoveSubscriber(subscriberID vos.SubscriberID) { +func (t *Topic) RemoveSubscriber(subscriberID vos.SubscriberID) error { + if _, ok := t.subscribers.Load(subscriberID); !ok { + return ErrSubscriberNotFound + } + t.killSubCh <- subscriberID + + return nil } func (t Topic) addSubscriber(sub Subscriber) { t.newSubCh <- sub } +func (t Topic) GetSubscriber(subscriberID vos.SubscriberID) interface{} { + if value, ok := t.subscribers.Load(subscriberID); ok { + return value + } + + return nil +} + +func (t Topic) UpdateTopic(topic Topic) (Topic, error) { + fmt.Println("implement me") + + return Topic{}, nil +} + func (t *Topic) listenForSubscriptions() { for newSubCh := range t.newSubCh { t.subscribers.Store(newSubCh.GetID(), newSubCh) diff --git a/domain/usecases/repository_mock.go b/domain/usecases/repository_mock.go index f5da6b7..685eeac 100644 --- a/domain/usecases/repository_mock.go +++ b/domain/usecases/repository_mock.go @@ -30,6 +30,9 @@ var _ Repository = &RepositoryMock{} // ListTopicsFunc: func(ctx context.Context) ([]entities.Topic, error) { // panic("mock out the ListTopics method") // }, +// UpdateTopicFunc: func(ctx context.Context, topicName entities.Topic) (entities.Topic, error) { +// panic("mock out the UpdateTopic method") +// }, // } // // // use mockedRepository in code that requires Repository @@ -46,6 +49,9 @@ type RepositoryMock struct { // ListTopicsFunc mocks the ListTopics method. ListTopicsFunc func(ctx context.Context) ([]entities.Topic, error) + // UpdateTopicFunc mocks the UpdateTopic method. + UpdateTopicFunc func(ctx context.Context, topicName entities.Topic) (entities.Topic, error) + // calls tracks calls to the methods. calls struct { // CreateTopic holds details about calls to the CreateTopic method. @@ -69,10 +75,18 @@ type RepositoryMock struct { // Ctx is the ctx argument value. Ctx context.Context } + // UpdateTopic holds details about calls to the UpdateTopic method. + UpdateTopic []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // TopicName is the topicName argument value. + TopicName entities.Topic + } } lockCreateTopic sync.RWMutex lockGetTopic sync.RWMutex lockListTopics sync.RWMutex + lockUpdateTopic sync.RWMutex } // CreateTopic calls CreateTopicFunc. @@ -179,3 +193,38 @@ func (mock *RepositoryMock) ListTopicsCalls() []struct { mock.lockListTopics.RUnlock() return calls } + +// UpdateTopic calls UpdateTopicFunc. +func (mock *RepositoryMock) UpdateTopic(ctx context.Context, topicName entities.Topic) (entities.Topic, error) { + if mock.UpdateTopicFunc == nil { + panic("RepositoryMock.UpdateTopicFunc: method is nil but Repository.UpdateTopic was just called") + } + callInfo := struct { + Ctx context.Context + TopicName entities.Topic + }{ + Ctx: ctx, + TopicName: topicName, + } + mock.lockUpdateTopic.Lock() + mock.calls.UpdateTopic = append(mock.calls.UpdateTopic, callInfo) + mock.lockUpdateTopic.Unlock() + return mock.UpdateTopicFunc(ctx, topicName) +} + +// UpdateTopicCalls gets all the calls that were made to UpdateTopic. +// Check the length with: +// len(mockedRepository.UpdateTopicCalls()) +func (mock *RepositoryMock) UpdateTopicCalls() []struct { + Ctx context.Context + TopicName entities.Topic +} { + var calls []struct { + Ctx context.Context + TopicName entities.Topic + } + mock.lockUpdateTopic.RLock() + calls = mock.calls.UpdateTopic + mock.lockUpdateTopic.RUnlock() + return calls +} diff --git a/domain/usecases/unsubscribe.go b/domain/usecases/unsubscribe.go index 506aaf7..7893672 100644 --- a/domain/usecases/unsubscribe.go +++ b/domain/usecases/unsubscribe.go @@ -12,7 +12,12 @@ func (u useCase) Unsubscribe(ctx context.Context, subscriberID vos.SubscriberID, return err } - topic.RemoveSubscriber(subscriberID) + err = topic.RemoveSubscriber(subscriberID) + if err != nil { + return err + } + + //u.storage.UpdateTopic(ctx, topic) return nil } diff --git a/domain/usecases/unsubscribe_test.go b/domain/usecases/unsubscribe_test.go new file mode 100644 index 0000000..5e83e0f --- /dev/null +++ b/domain/usecases/unsubscribe_test.go @@ -0,0 +1,119 @@ +package usecases + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/matheusmosca/walrus/domain/entities" + "github.com/matheusmosca/walrus/domain/vos" +) + +func TestUnsubscribe(t *testing.T) { + t.Parallel() + + type args struct { + ctx context.Context + message vos.Message + } + + type fields struct { + storage Repository + topicName vos.TopicName + } + + tests := []struct { + name string + args args + fields fields + beforeRun func(topic entities.Topic) (chan vos.Message, vos.SubscriberID) + wantErr error + }{ + { + name: "unsubscribe should success", + args: args{ + ctx: context.Background(), + message: vos.Message{ + TopicName: "unsubscribing", + PublishedBy: "unsubscribing_test", + Body: []byte("empty lol"), + }, + }, + fields: fields{ + storage: &RepositoryMock{}, + topicName: "unsubscribing", + }, + beforeRun: func(topic entities.Topic) (chan vos.Message, vos.SubscriberID) { + subscriber := entities.NewSubscriber(topic) + subscriber.Subscribe() + ch, ID := subscriber.Subscribe() + + return ch, ID + }, + wantErr: nil, + }, + { + name: "try to unsubscribe a subscriber that doesnt exist should fail", + args: args{ + ctx: context.Background(), + message: vos.Message{ + TopicName: "unsubscribing", + PublishedBy: "unsubscribing_test", + Body: []byte("empty lol"), + }, + }, + fields: fields{ + storage: &RepositoryMock{}, + topicName: "unsubscribing", + }, + beforeRun: func(topic entities.Topic) (chan vos.Message, vos.SubscriberID) { + msgChan := make(chan vos.Message) + + return msgChan, vos.SubscriberID("") + }, + wantErr: entities.ErrSubscriberNotFound, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + topic, err := entities.NewTopic(tt.fields.topicName) + require.NoError(t, err) + + topic.Activate() + + subsCh, subsID := tt.beforeRun(topic) + defer close(subsCh) + + sss := topic.GetSubscriber(subsID) + assert.NotNil(t, sss) + + tt.fields.storage = &RepositoryMock{ + GetTopicFunc: func(ctx context.Context, topicName vos.TopicName) (entities.Topic, error) { + if tt.fields.topicName != tt.args.message.TopicName { + return entities.Topic{}, entities.ErrTopicNotFound + } + + return topic, nil + }, + } + + useCase := New(tt.fields.storage) + err = useCase.Unsubscribe(context.Background(), subsID, tt.fields.topicName) + if tt.wantErr != nil { + assert.ErrorIs(t, err, tt.wantErr) + return + } + + time.Sleep(5 * time.Second) + nilSubs := topic.GetSubscriber(subsID) + assert.Nil(t, nilSubs) + + }) + } +} diff --git a/domain/usecases/usecase.go b/domain/usecases/usecase.go index f7158cd..2c09a28 100644 --- a/domain/usecases/usecase.go +++ b/domain/usecases/usecase.go @@ -18,6 +18,7 @@ type useCase struct { type Repository interface { CreateTopic(ctx context.Context, name vos.TopicName, topic entities.Topic) error GetTopic(ctx context.Context, topicName vos.TopicName) (entities.Topic, error) + UpdateTopic(ctx context.Context, topicName entities.Topic) (entities.Topic, error) ListTopics(ctx context.Context) ([]entities.Topic, error) } diff --git a/repositories/memory/topics/repository.go b/repositories/memory/topics/repository.go index f3fd9c8..929fc50 100644 --- a/repositories/memory/topics/repository.go +++ b/repositories/memory/topics/repository.go @@ -17,6 +17,7 @@ type MemoryRepository interface { GetTopic(ctx context.Context, topicName vos.TopicName) (entities.Topic, error) CreateTopic(ctx context.Context, name vos.TopicName, topic entities.Topic) error ListTopics(ctx context.Context) ([]entities.Topic, error) + UpdateTopic(ctx context.Context, topicName entities.Topic) (entities.Topic, error) } func NewMemoryRepository(storage map[vos.TopicName]entities.Topic) MemoryRepository { diff --git a/repositories/memory/topics/update_topic.go b/repositories/memory/topics/update_topic.go new file mode 100644 index 0000000..306ba5a --- /dev/null +++ b/repositories/memory/topics/update_topic.go @@ -0,0 +1,11 @@ +package topics + +import ( + "context" + + "github.com/matheusmosca/walrus/domain/entities" +) + +func (r *repository) UpdateTopic(ctx context.Context, topicName entities.Topic) (entities.Topic, error) { + return entities.Topic{}, nil +} From c7a66bcfe82be96e3297f26ebe65eea0932a5332 Mon Sep 17 00:00:00 2001 From: MelloTonio Date: Wed, 6 Oct 2021 12:34:20 -0400 Subject: [PATCH 2/5] refact: sync unsubscribe --- domain/entities/topic.go | 11 +---------- domain/usecases/unsubscribe_test.go | 7 +------ 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/domain/entities/topic.go b/domain/entities/topic.go index 984f172..1475034 100644 --- a/domain/entities/topic.go +++ b/domain/entities/topic.go @@ -32,7 +32,6 @@ func NewTopic(topicName vos.TopicName) (Topic, error) { func (t Topic) Activate() { go t.listenForSubscriptions() go t.listenForMessages() - go t.listenForKills() } func (t Topic) Dispatch(message vos.Message) error { @@ -46,12 +45,10 @@ func (t Topic) Dispatch(message vos.Message) error { } func (t *Topic) RemoveSubscriber(subscriberID vos.SubscriberID) error { - if _, ok := t.subscribers.Load(subscriberID); !ok { + if _, ok := t.subscribers.LoadAndDelete(subscriberID); !ok { return ErrSubscriberNotFound } - t.killSubCh <- subscriberID - return nil } @@ -79,12 +76,6 @@ func (t *Topic) listenForSubscriptions() { } } -func (t *Topic) listenForKills() { - for subscriberID := range t.killSubCh { - t.subscribers.Delete(subscriberID) - } -} - func (t *Topic) listenForMessages() { for msg := range t.newMessageCh { m := msg diff --git a/domain/usecases/unsubscribe_test.go b/domain/usecases/unsubscribe_test.go index 5e83e0f..8a6940f 100644 --- a/domain/usecases/unsubscribe_test.go +++ b/domain/usecases/unsubscribe_test.go @@ -3,7 +3,6 @@ package usecases import ( "context" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -33,7 +32,7 @@ func TestUnsubscribe(t *testing.T) { wantErr error }{ { - name: "unsubscribe should success", + name: "unsubscribe should succeed", args: args{ ctx: context.Background(), message: vos.Message{ @@ -90,9 +89,6 @@ func TestUnsubscribe(t *testing.T) { subsCh, subsID := tt.beforeRun(topic) defer close(subsCh) - sss := topic.GetSubscriber(subsID) - assert.NotNil(t, sss) - tt.fields.storage = &RepositoryMock{ GetTopicFunc: func(ctx context.Context, topicName vos.TopicName) (entities.Topic, error) { if tt.fields.topicName != tt.args.message.TopicName { @@ -110,7 +106,6 @@ func TestUnsubscribe(t *testing.T) { return } - time.Sleep(5 * time.Second) nilSubs := topic.GetSubscriber(subsID) assert.Nil(t, nilSubs) From 520f1077ddfc04e28733b50d7dcbc286d9ac3579 Mon Sep 17 00:00:00 2001 From: MelloTonio Date: Wed, 6 Oct 2021 13:04:18 -0400 Subject: [PATCH 3/5] refact: tests --- domain/entities/topic.go | 9 ++++--- domain/usecases/unsubscribe_test.go | 39 ++++++++++++++++------------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/domain/entities/topic.go b/domain/entities/topic.go index 1475034..694d48e 100644 --- a/domain/entities/topic.go +++ b/domain/entities/topic.go @@ -56,12 +56,15 @@ func (t Topic) addSubscriber(sub Subscriber) { t.newSubCh <- sub } -func (t Topic) GetSubscriber(subscriberID vos.SubscriberID) interface{} { +func (t Topic) GetSubscriber(subscriberID vos.SubscriberID) (*Subscriber, error) { if value, ok := t.subscribers.Load(subscriberID); ok { - return value + subsInterface := value.(Subscriber) + + return &subsInterface, nil + } - return nil + return nil, ErrSubscriberNotFound } func (t Topic) UpdateTopic(topic Topic) (Topic, error) { diff --git a/domain/usecases/unsubscribe_test.go b/domain/usecases/unsubscribe_test.go index 8a6940f..772d633 100644 --- a/domain/usecases/unsubscribe_test.go +++ b/domain/usecases/unsubscribe_test.go @@ -25,20 +25,19 @@ func TestUnsubscribe(t *testing.T) { } tests := []struct { - name string - args args - fields fields - beforeRun func(topic entities.Topic) (chan vos.Message, vos.SubscriberID) - wantErr error + name string + args args + fields fields + beforeRun func(topic entities.Topic) (chan vos.Message, vos.SubscriberID) + wantErr error + wantSubscriber bool }{ { name: "unsubscribe should succeed", args: args{ ctx: context.Background(), message: vos.Message{ - TopicName: "unsubscribing", - PublishedBy: "unsubscribing_test", - Body: []byte("empty lol"), + TopicName: "unsubscribing", }, }, fields: fields{ @@ -47,21 +46,19 @@ func TestUnsubscribe(t *testing.T) { }, beforeRun: func(topic entities.Topic) (chan vos.Message, vos.SubscriberID) { subscriber := entities.NewSubscriber(topic) - subscriber.Subscribe() ch, ID := subscriber.Subscribe() return ch, ID }, - wantErr: nil, + wantErr: nil, + wantSubscriber: true, }, { name: "try to unsubscribe a subscriber that doesnt exist should fail", args: args{ ctx: context.Background(), message: vos.Message{ - TopicName: "unsubscribing", - PublishedBy: "unsubscribing_test", - Body: []byte("empty lol"), + TopicName: "unsubscribing", }, }, fields: fields{ @@ -73,7 +70,8 @@ func TestUnsubscribe(t *testing.T) { return msgChan, vos.SubscriberID("") }, - wantErr: entities.ErrSubscriberNotFound, + wantErr: entities.ErrSubscriberNotFound, + wantSubscriber: false, }, } for _, tt := range tests { @@ -89,6 +87,13 @@ func TestUnsubscribe(t *testing.T) { subsCh, subsID := tt.beforeRun(topic) defer close(subsCh) + // Assert that the subscriber has been created successfully + if tt.wantSubscriber { + sub, err := topic.GetSubscriber(subsID) + require.NoError(t, err) + assert.NotNil(t, sub) + } + tt.fields.storage = &RepositoryMock{ GetTopicFunc: func(ctx context.Context, topicName vos.TopicName) (entities.Topic, error) { if tt.fields.topicName != tt.args.message.TopicName { @@ -106,9 +111,9 @@ func TestUnsubscribe(t *testing.T) { return } - nilSubs := topic.GetSubscriber(subsID) - assert.Nil(t, nilSubs) - + // Assert the unsubscription + _, err = topic.GetSubscriber(subsID) + assert.ErrorIs(t, entities.ErrSubscriberNotFound, err) }) } } From 3bd0168c33392eae6e790915b4d766d79788a57f Mon Sep 17 00:00:00 2001 From: MelloTonio Date: Wed, 6 Oct 2021 13:09:12 -0400 Subject: [PATCH 4/5] refact: tests --- domain/usecases/unsubscribe_test.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/domain/usecases/unsubscribe_test.go b/domain/usecases/unsubscribe_test.go index 772d633..105c0b3 100644 --- a/domain/usecases/unsubscribe_test.go +++ b/domain/usecases/unsubscribe_test.go @@ -48,6 +48,11 @@ func TestUnsubscribe(t *testing.T) { subscriber := entities.NewSubscriber(topic) ch, ID := subscriber.Subscribe() + // Assert that the subscriber has been created successfully + sub, err := topic.GetSubscriber(ID) + require.NoError(t, err) + assert.NotNil(t, sub) + return ch, ID }, wantErr: nil, @@ -87,13 +92,6 @@ func TestUnsubscribe(t *testing.T) { subsCh, subsID := tt.beforeRun(topic) defer close(subsCh) - // Assert that the subscriber has been created successfully - if tt.wantSubscriber { - sub, err := topic.GetSubscriber(subsID) - require.NoError(t, err) - assert.NotNil(t, sub) - } - tt.fields.storage = &RepositoryMock{ GetTopicFunc: func(ctx context.Context, topicName vos.TopicName) (entities.Topic, error) { if tt.fields.topicName != tt.args.message.TopicName { From 5ebc3fb8b018857c7f1d2277995dbe09e68abc75 Mon Sep 17 00:00:00 2001 From: MelloTonio Date: Wed, 6 Oct 2021 13:11:13 -0400 Subject: [PATCH 5/5] refact: addSubscriber --- domain/entities/topic.go | 1 - 1 file changed, 1 deletion(-) diff --git a/domain/entities/topic.go b/domain/entities/topic.go index 694d48e..aae7ef8 100644 --- a/domain/entities/topic.go +++ b/domain/entities/topic.go @@ -61,7 +61,6 @@ func (t Topic) GetSubscriber(subscriberID vos.SubscriberID) (*Subscriber, error) subsInterface := value.(Subscriber) return &subsInterface, nil - } return nil, ErrSubscriberNotFound