diff --git a/cmd/prices-service/internal/prices/export_presenter.go b/cmd/prices-service/internal/prices/export_presenter.go deleted file mode 100644 index 5031627..0000000 --- a/cmd/prices-service/internal/prices/export_presenter.go +++ /dev/null @@ -1,36 +0,0 @@ -package prices - -import ( - "time" - - "github.com/SU-FMI-DESIGN-PATTERNS-2022/crypto-and-stocks/pkg/repository/mongo/database" -) - -type ExportPricesRepository[Prices database.CryptoPrices | database.StockPrices] interface { - GetAllPrices() ([]Prices, error) - GetAllPricesBySymbol(symbol string) ([]Prices, error) - GetAllPricesByExchange(exchange string) ([]Prices, error) - GetAllPricesInPeriod(from time.Time, to time.Time) ([]Prices, error) - GetAllPricesInPeriodSymbol(from time.Time, to time.Time, symbol string) ([]Prices, error) - GetMostRecentPriceBySymbol(symbol string) (Prices, error) -} - -type ExportStockPriceRepository interface { - ExportPricesRepository[database.StockPrices] -} - -type ExportCryptoPriceRepository interface { - ExportPricesRepository[database.CryptoPrices] -} - -type ExportPricesPresenter struct { - cryptoPricesRepo ExportCryptoPriceRepository - stockPricesRepo ExportStockPriceRepository -} - -func NewExportPricesPresenter(cryptoPricesRepository ExportCryptoPriceRepository, stockPricesRepository ExportStockPriceRepository) ExportPricesPresenter { - return ExportPricesPresenter{ - cryptoPricesRepo: cryptoPricesRepository, - stockPricesRepo: stockPricesRepository, - } -} diff --git a/cmd/prices-service/internal/prices/mocks/presenter.go b/cmd/prices-service/internal/prices/mocks/presenter.go index 4676bde..10aa19b 100644 --- a/cmd/prices-service/internal/prices/mocks/presenter.go +++ b/cmd/prices-service/internal/prices/mocks/presenter.go @@ -8,8 +8,8 @@ import ( http "net/http" reflect "reflect" + prices "github.com/SU-FMI-DESIGN-PATTERNS-2022/crypto-and-stocks/cmd/prices-service/internal/prices" gomock "github.com/golang/mock/gomock" - websocket "github.com/gorilla/websocket" ) // MockUpgrader is a mock of Upgrader interface. @@ -36,10 +36,10 @@ func (m *MockUpgrader) EXPECT() *MockUpgraderMockRecorder { } // Upgrade mocks base method. -func (m *MockUpgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error) { +func (m *MockUpgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (prices.Connection, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Upgrade", w, r, responseHeader) - ret0, _ := ret[0].(*websocket.Conn) + ret0, _ := ret[0].(prices.Connection) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -86,3 +86,40 @@ func (mr *MockEventBusMockRecorder) Subscribe(topic, fn interface{}) *gomock.Cal mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subscribe", reflect.TypeOf((*MockEventBus)(nil).Subscribe), topic, fn) } + +// MockConnection is a mock of Connection interface. +type MockConnection struct { + ctrl *gomock.Controller + recorder *MockConnectionMockRecorder +} + +// MockConnectionMockRecorder is the mock recorder for MockConnection. +type MockConnectionMockRecorder struct { + mock *MockConnection +} + +// NewMockConnection creates a new mock instance. +func NewMockConnection(ctrl *gomock.Controller) *MockConnection { + mock := &MockConnection{ctrl: ctrl} + mock.recorder = &MockConnectionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockConnection) EXPECT() *MockConnectionMockRecorder { + return m.recorder +} + +// WriteJSON mocks base method. +func (m *MockConnection) WriteJSON(v interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteJSON", v) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteJSON indicates an expected call of WriteJSON. +func (mr *MockConnectionMockRecorder) WriteJSON(v interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteJSON", reflect.TypeOf((*MockConnection)(nil).WriteJSON), v) +} diff --git a/cmd/prices-service/internal/prices/presenter.go b/cmd/prices-service/internal/prices/presenter.go index ceaef93..222720e 100644 --- a/cmd/prices-service/internal/prices/presenter.go +++ b/cmd/prices-service/internal/prices/presenter.go @@ -3,33 +3,35 @@ package prices import ( "log" "net/http" - - "github.com/gorilla/websocket" ) //go:generate mockgen -source=presenter.go -destination=mocks/presenter.go type Upgrader interface { - Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error) + Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (Connection, error) } type EventBus interface { Subscribe(topic string, fn interface{}) error } -type presenter struct { +type Connection interface { + WriteJSON(v interface{}) error +} + +type Presenter struct { upgrader Upgrader bus EventBus } -func NewPresenter(upgrader Upgrader, bus EventBus) *presenter { - return &presenter{ +func NewPresenter(upgrader Upgrader, bus EventBus) *Presenter { + return &Presenter{ upgrader: upgrader, bus: bus, } } -func (p *presenter) StockHandler(w http.ResponseWriter, r *http.Request) { +func (p *Presenter) StockHandler(w http.ResponseWriter, r *http.Request) { conn, err := p.upgrader.Upgrade(w, r, nil) if err != nil { log.Println(err) @@ -39,7 +41,7 @@ func (p *presenter) StockHandler(w http.ResponseWriter, r *http.Request) { p.subscribeForResponding(conn, "stocks") } -func (p *presenter) CryptoHandler(w http.ResponseWriter, r *http.Request) { +func (p *Presenter) CryptoHandler(w http.ResponseWriter, r *http.Request) { conn, err := p.upgrader.Upgrade(w, r, nil) if err != nil { log.Println(err) @@ -49,8 +51,16 @@ func (p *presenter) CryptoHandler(w http.ResponseWriter, r *http.Request) { p.subscribeForResponding(conn, "crypto") } -func (p *presenter) subscribeForResponding(conn *websocket.Conn, topic string) { - p.bus.Subscribe(topic, func(resp interface{}) { - conn.WriteJSON(resp) +func (p *Presenter) subscribeForResponding(conn Connection, topic string) { + err := p.bus.Subscribe(topic, func(resp interface{}) { + err := conn.WriteJSON(resp) + if err != nil { + log.Println(err) + return + } }) + if err != nil { + log.Println(err) + return + } } diff --git a/cmd/prices-service/internal/prices/presenter_test.go b/cmd/prices-service/internal/prices/presenter_test.go new file mode 100644 index 0000000..e85a488 --- /dev/null +++ b/cmd/prices-service/internal/prices/presenter_test.go @@ -0,0 +1,186 @@ +package prices_test + +import ( + "errors" + + "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/SU-FMI-DESIGN-PATTERNS-2022/crypto-and-stocks/cmd/prices-service/internal/prices" + mock_prices "github.com/SU-FMI-DESIGN-PATTERNS-2022/crypto-and-stocks/cmd/prices-service/internal/prices/mocks" +) + +var _ = Describe("Presenter", func() { + const ( + cryptoTopic = "crypto" + stocksTopic = "stocks" + msg = "hi" + upgradeErrMsg = "upgrade failed" + subscribeErrMsg = "subscribe failed" + writeJSONErrMsg = "write JSON failed" + ) + + var ( + ctrl *gomock.Controller + mockUpgarder *mock_prices.MockUpgrader + mockBus *mock_prices.MockEventBus + mockConn *mock_prices.MockConnection + presenter *prices.Presenter + response string + ) + + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + mockUpgarder = mock_prices.NewMockUpgrader(ctrl) + mockBus = mock_prices.NewMockEventBus(ctrl) + mockConn = mock_prices.NewMockConnection(ctrl) + presenter = prices.NewPresenter(mockUpgarder, mockBus) + response = "" + }) + + Context("StockHandler", func() { + When("upgrading the HTTP server connection to the WebSocket protocol fails", func() { + BeforeEach(func() { + gomock.InOrder( + mockUpgarder.EXPECT().Upgrade(nil, nil, nil).Return(nil, errors.New(upgradeErrMsg)), + ) + }) + It("should return an error", func() { + presenter.StockHandler(nil, nil) + Expect(response).To(Equal("")) + }) + }) + + When("Subscribing for responding fails", func() { + BeforeEach(func() { + gomock.InOrder( + mockUpgarder.EXPECT().Upgrade(nil, nil, nil).Return(mockConn, nil), + mockBus.EXPECT().Subscribe(stocksTopic, gomock.Any()).Return(errors.New(subscribeErrMsg))) + }) + It("should return an error", func() { + presenter.StockHandler(nil, nil) + Expect(response).To(Equal("")) + }) + }) + + When("Writing JSON fails", func() { + BeforeEach(func() { + gomock.InOrder( + mockUpgarder.EXPECT().Upgrade(nil, nil, nil).Return(mockConn, nil), + mockBus.EXPECT().Subscribe(stocksTopic, gomock.Any()).DoAndReturn( + func(topic string, fn interface{}) error { + f, ok := fn.(func(resp interface{})) + Expect(ok).To(BeTrue()) + f(msg) + return nil + }, + ), + mockConn.EXPECT().WriteJSON(gomock.Any()).Return(errors.New(writeJSONErrMsg))) + }) + It("should return an error", func() { + presenter.StockHandler(nil, nil) + Expect(response).To(Equal("")) + }) + }) + + When("upgrading the HTTP server connection to the WebSocket protocol succeed and subscribing for responding", func() { + BeforeEach(func() { + gomock.InOrder( + mockUpgarder.EXPECT().Upgrade(nil, nil, nil).Return(mockConn, nil), + mockBus.EXPECT().Subscribe(stocksTopic, gomock.Any()).DoAndReturn( + func(topic string, fn interface{}) error { + f, ok := fn.(func(resp interface{})) + Expect(ok).To(BeTrue()) + f(msg) + return nil + }, + ), + mockConn.EXPECT().WriteJSON(gomock.Any()).DoAndReturn( + func(json string) error { + response = json + return nil + }, + ), + ) + }) + It("should not return an error", func() { + presenter.StockHandler(nil, nil) + Expect(response).To(Equal(msg)) + }) + }) + }) + + Context("CryptoHandler", func() { + When("upgrading the HTTP server connection to the WebSocket protocol fails", func() { + BeforeEach(func() { + gomock.InOrder( + mockUpgarder.EXPECT().Upgrade(nil, nil, nil).Return(nil, errors.New(upgradeErrMsg)), + ) + }) + It("should return an error", func() { + presenter.CryptoHandler(nil, nil) + Expect(response).To(Equal("")) + }) + }) + + When("Subscribing for responding fails", func() { + BeforeEach(func() { + gomock.InOrder( + mockUpgarder.EXPECT().Upgrade(nil, nil, nil).Return(mockConn, nil), + mockBus.EXPECT().Subscribe(cryptoTopic, gomock.Any()).Return(errors.New(subscribeErrMsg))) + }) + It("should return an error", func() { + presenter.CryptoHandler(nil, nil) + Expect(response).To(Equal("")) + }) + }) + + When("Writing JSON fails", func() { + BeforeEach(func() { + gomock.InOrder( + mockUpgarder.EXPECT().Upgrade(nil, nil, nil).Return(mockConn, nil), + mockBus.EXPECT().Subscribe(cryptoTopic, gomock.Any()).DoAndReturn( + func(topic string, fn interface{}) error { + f, ok := fn.(func(resp interface{})) + Expect(ok).To(BeTrue()) + f(msg) + return nil + }, + ), + mockConn.EXPECT().WriteJSON(gomock.Any()).Return(errors.New(writeJSONErrMsg))) + }) + + It("should return an error", func() { + presenter.CryptoHandler(nil, nil) + Expect(response).To(Equal("")) + }) + }) + + When("upgrading the HTTP server connection to the WebSocket protocol succeed and subscribing for responding", func() { + BeforeEach(func() { + gomock.InOrder( + mockUpgarder.EXPECT().Upgrade(nil, nil, nil).Return(mockConn, nil), + mockBus.EXPECT().Subscribe(cryptoTopic, gomock.Any()).DoAndReturn( + func(topic string, fn interface{}) error { + f, ok := fn.(func(resp interface{})) + Expect(ok).To(BeTrue()) + f(msg) + return nil + }, + ), + mockConn.EXPECT().WriteJSON(gomock.Any()).DoAndReturn( + func(json string) error { + response = json + return nil + }, + ), + ) + }) + It("should not return an error", func() { + presenter.CryptoHandler(nil, nil) + Expect(response).To(Equal(msg)) + }) + }) + }) +}) diff --git a/cmd/prices-service/main.go b/cmd/prices-service/main.go index a656283..419b9dc 100644 --- a/cmd/prices-service/main.go +++ b/cmd/prices-service/main.go @@ -18,6 +18,14 @@ import ( "github.com/SU-FMI-DESIGN-PATTERNS-2022/crypto-and-stocks/cmd/prices-service/internal/stream" ) +type upgrader struct { + wsUpgarder *websocket.Upgrader +} + +func (u *upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (prices.Connection, error) { + return u.wsUpgarder.Upgrade(w, r, responseHeader) +} + func main() { mongoConfig, err := mongoEnv.LoadMongoDBConfig() if err != nil { @@ -58,12 +66,12 @@ func main() { streamController := stream.NewController(cryptoStream, stockStream, bus) streamController.StartStreamsToWrite() - upgrader := &websocket.Upgrader{ + wsUpgrader := &websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, } - pricesPresenter := prices.NewPresenter(upgrader, bus) + pricesPresenter := prices.NewPresenter(&upgrader{wsUpgrader}, bus) http.HandleFunc("/crypto", pricesPresenter.CryptoHandler) http.HandleFunc("/stocks", pricesPresenter.StockHandler)