diff --git a/go.mod b/go.mod index 536e9b72..d34fa6cc 100644 --- a/go.mod +++ b/go.mod @@ -36,16 +36,12 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/golang-jwt/jwt/v4 v4.4.3 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/gorilla/websocket v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -77,7 +73,6 @@ require ( golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - golang.org/x/tools v0.12.0 // indirect google.golang.org/protobuf v1.31.0 // indirect ) @@ -85,7 +80,5 @@ require ( github.com/goccy/go-json v0.10.2 github.com/gorilla/feeds v1.1.1 github.com/mattn/go-mastodon v0.0.6 - github.com/onsi/ginkgo/v2 v2.12.1 - github.com/onsi/gomega v1.28.0 golang.org/x/image v0.5.0 // indirect ) diff --git a/go.sum b/go.sum index f399b1d9..47fb5e7a 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,6 @@ github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SU github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -115,8 +113,6 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -183,8 +179,6 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= @@ -251,10 +245,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo/v2 v2.12.1 h1:uHNEO1RP2SpuZApSkel9nEh1/Mu+hmQe7Q+Pepg5OYA= -github.com/onsi/ginkgo/v2 v2.12.1/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= -github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= -github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= github.com/paulmach/go.geojson v1.5.0 h1:7mhpMK89SQdHFcEGomT7/LuJhwhEgfmpWYVlVmLEdQw= github.com/paulmach/go.geojson v1.5.0/go.mod h1:DgdUy2rRVDDVgKqrjMe2vZAHMfhDTrjVKt3LmHIXGbU= github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= @@ -392,7 +382,6 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -563,8 +552,6 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/api/api_test.go b/internal/api/api_test.go index e99b4f07..3d9cf41d 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -2,7 +2,6 @@ package api import ( "encoding/json" - "fmt" "io" "net/http" "net/http/httptest" @@ -12,80 +11,93 @@ import ( "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/api/response" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/logger" "github.com/systemli/ticker/internal/storage" ) -var l = logger.NewLogrus("debug", "text") +type APITestSuite struct { + cfg config.Config + store *storage.MockStorage + logger *logrus.Logger + suite.Suite +} -func init() { - logrus.SetOutput(io.Discard) +func (s *APITestSuite) SetupTest() { gin.SetMode(gin.TestMode) -} + logrus.SetOutput(io.Discard) -func TestHealthz(t *testing.T) { - c := config.LoadConfig("") - s := &storage.MockStorage{} - r := API(c, s, l) + s.cfg = config.LoadConfig("") + s.store = &storage.MockStorage{} + logger := logger.NewLogrus("debug", "text") + logger.SetOutput(io.Discard) + s.logger = logger +} + +func (s *APITestSuite) TestHealthz() { + r := API(s.cfg, s.store, s.logger) req := httptest.NewRequest(http.MethodGet, "/healthz", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) - fmt.Println(w.Body.String()) - assert.Equal(t, http.StatusOK, w.Code) + s.Equal(http.StatusOK, w.Code) + s.store.AssertExpectations(s.T()) } -func TestLoginNotSuccessful(t *testing.T) { - c := config.LoadConfig("") - s := &storage.MockStorage{} - user := storage.User{} - user.UpdatePassword("password") - s.On("FindUserByEmail", mock.Anything).Return(user, nil) - r := API(c, s, l) +func (s *APITestSuite) TestLogin() { + s.Run("when password is wrong", func() { + user, err := storage.NewUser("user@systemli.org", "password") + s.NoError(err) + s.store.On("FindUserByEmail", mock.Anything).Return(user, nil) + r := API(s.cfg, s.store, s.logger) - body := `{"username":"louis@systemli.org","password":"WRONG"}` - req := httptest.NewRequest(http.MethodPost, "/v1/admin/login", strings.NewReader(body)) - req.Header.Add("Content-Type", "application/json") - w := httptest.NewRecorder() - r.ServeHTTP(w, req) + body := `{"username":"louis@systemli.org","password":"WRONG"}` + req := httptest.NewRequest(http.MethodPost, "/v1/admin/login", strings.NewReader(body)) + req.Header.Add("Content-Type", "application/json") + w := httptest.NewRecorder() + r.ServeHTTP(w, req) - var res response.Response - err := json.Unmarshal(w.Body.Bytes(), &res) - assert.Nil(t, err) - assert.Equal(t, http.StatusUnauthorized, w.Code) - assert.Nil(t, res.Data) - assert.Equal(t, res.Error.Code, response.CodeBadCredentials) - assert.Equal(t, res.Error.Message, response.Unauthorized) -} + var res response.Response + err = json.Unmarshal(w.Body.Bytes(), &res) + s.NoError(err) + s.Equal(http.StatusUnauthorized, w.Code) + s.Nil(res.Data) + s.Equal(res.Error.Code, response.CodeBadCredentials) + s.Equal(res.Error.Message, response.Unauthorized) + s.store.AssertExpectations(s.T()) + }) -func TestLoginSuccessful(t *testing.T) { - c := config.LoadConfig("") - s := &storage.MockStorage{} - user := storage.User{} - user.UpdatePassword("password") - s.On("FindUserByEmail", mock.Anything).Return(user, nil) - r := API(c, s, l) + s.Run("when login is successful", func() { + user, err := storage.NewUser("user@systemli.org", "password") + s.NoError(err) + s.store.On("FindUserByEmail", mock.Anything).Return(user, nil) + r := API(s.cfg, s.store, s.logger) - body := `{"username":"louis@systemli.org","password":"password"}` - req := httptest.NewRequest(http.MethodPost, "/v1/admin/login", strings.NewReader(body)) - req.Header.Add("Content-Type", "application/json") - w := httptest.NewRecorder() - r.ServeHTTP(w, req) + body := `{"username":"louis@systemli.org","password":"password"}` + req := httptest.NewRequest(http.MethodPost, "/v1/admin/login", strings.NewReader(body)) + req.Header.Add("Content-Type", "application/json") + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + var res struct { + Code int `json:"code"` + Expire time.Time `json:"expire"` + Token string `json:"token"` + } + err = json.Unmarshal(w.Body.Bytes(), &res) + s.NoError(err) + s.Equal(http.StatusOK, w.Code) + s.Equal(http.StatusOK, res.Code) + s.NotNil(res.Expire) + s.NotNil(res.Token) + s.store.AssertExpectations(s.T()) + }) +} - var res struct { - Code int `json:"code"` - Expire time.Time `json:"expire"` - Token string `json:"token"` - } - err := json.Unmarshal(w.Body.Bytes(), &res) - assert.Nil(t, err) - assert.Equal(t, http.StatusOK, w.Code) - assert.Equal(t, http.StatusOK, res.Code) - assert.NotNil(t, res.Expire) - assert.NotNil(t, res.Token) +func TestAPITestSuite(t *testing.T) { + suite.Run(t, new(APITestSuite)) } diff --git a/internal/api/features_test.go b/internal/api/features_test.go index 1e8d75dc..9c8dac4b 100644 --- a/internal/api/features_test.go +++ b/internal/api/features_test.go @@ -6,28 +6,35 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { +type FeaturesTestSuite struct { + suite.Suite +} + +func (s *FeaturesTestSuite) SetupTest() { gin.SetMode(gin.TestMode) } -func TestGetFeatures(t *testing.T) { +func (s *FeaturesTestSuite) TestGetFeatures() { w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} + store := &storage.MockStorage{} h := handler{ - storage: s, + storage: store, config: config.LoadConfig(""), } h.GetFeatures(c) - expected := `{"data":{"features":{"telegramEnabled":false}},"status":"success","error":{}}` - assert.Equal(t, expected, w.Body.String()) - assert.Equal(t, http.StatusOK, w.Code) + s.Equal(http.StatusOK, w.Code) + s.Equal(`{"data":{"features":{"telegramEnabled":false}},"status":"success","error":{}}`, w.Body.String()) +} + +func TestFeaturesTestSuite(t *testing.T) { + suite.Run(t, new(FeaturesTestSuite)) } diff --git a/internal/api/feed_test.go b/internal/api/feed_test.go index 69695a2c..c384d9f2 100644 --- a/internal/api/feed_test.go +++ b/internal/api/feed_test.go @@ -7,74 +7,85 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/api/response" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { +type FeedTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite +} + +func (s *FeedTestSuite) SetupTest() { gin.SetMode(gin.TestMode) + + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.store = &storage.MockStorage{} + s.cfg = config.LoadConfig("") } -func TestGetFeedTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } +func (s *FeedTestSuite) TestGetFeed() { + s.Run("when ticker not found", func() { + h := s.handler() + h.GetFeed(s.ctx) - h.GetFeed(c) + s.Equal(http.StatusOK, s.w.Code) + s.Contains(s.w.Body.String(), response.TickerNotFound) + s.store.AssertExpectations(s.T()) + }) - assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), response.TickerNotFound) -} + s.Run("when fetching messages fails", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything).Return([]storage.Message{}, errors.New("storage error")).Once() -func TestGetFeedMessageFetchError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything).Return([]storage.Message{}, errors.New("storage error")) + h := s.handler() + h.GetFeed(s.ctx) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } + s.Equal(http.StatusOK, s.w.Code) + s.Contains(s.w.Body.String(), response.MessageFetchError) + s.store.AssertExpectations(s.T()) + }) - h.GetFeed(c) + s.Run("when fetching messages succeeds", func() { + ticker := storage.Ticker{ + ID: 1, + Title: "Title", + Information: storage.TickerInformation{ + URL: "https://demoticker.org", + Author: "Author", + Email: "author@demoticker.org", + }, + } + s.ctx.Set("ticker", ticker) + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/feed?format=atom", nil) + message := storage.Message{ + TickerID: ticker.ID, + Text: "Text", + } + s.store.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything).Return([]storage.Message{message}, nil).Once() - assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), response.MessageFetchError) -} + h := s.handler() + h.GetFeed(s.ctx) -func TestGetFeed(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - ticker := storage.Ticker{ - ID: 1, - Title: "Title", - Information: storage.TickerInformation{ - URL: "https://demoticker.org", - Author: "Author", - Email: "author@demoticker.org", - }, - } - c.Set("ticker", ticker) - s := &storage.MockStorage{} - message := storage.Message{ - TickerID: ticker.ID, - Text: "Text", - } - s.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything).Return([]storage.Message{message}, nil) + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} - h := handler{ - storage: s, - config: config.LoadConfig(""), +func (s *FeedTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, } +} - h.GetFeed(c) +func TestFeedTestSuite(t *testing.T) { + suite.Run(t, new(FeedTestSuite)) } diff --git a/internal/api/helper/util_test.go b/internal/api/helper/util_test.go index 76ab59c8..ba3514b4 100644 --- a/internal/api/helper/util_test.go +++ b/internal/api/helper/util_test.go @@ -6,140 +6,143 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func TestGetDomainEmptyOrigin(t *testing.T) { - req := http.Request{ - URL: &url.URL{}, - } - - c := gin.Context{Request: &req} - - domain, err := GetDomain(&c) - assert.Equal(t, "", domain) - assert.Equal(t, "origin header not found", err.Error()) +type UtilTestSuite struct { + suite.Suite } -func TestGetDomainLocalhost(t *testing.T) { - req := http.Request{ - Header: http.Header{ - "Origin": []string{"http://localhost/"}, - }, - URL: &url.URL{}, - } - - c := gin.Context{Request: &req} - - domain, err := GetDomain(&c) - assert.Equal(t, "localhost", domain) - assert.Equal(t, nil, err) -} +func (s *UtilTestSuite) TestGetDomain() { + s.Run("when origin is empty", func() { + c := s.buildContext(url.URL{}, http.Header{}) + domain, err := GetDomain(c) + s.Equal("", domain) + s.Equal("origin header not found", err.Error()) + }) -func TestGetDomainLocalhostPort(t *testing.T) { - req := http.Request{ - Header: http.Header{ + s.Run("when origin is localhost", func() { + c := s.buildContext(url.URL{}, http.Header{ + "Origin": []string{"http://localhost/"}, + }) + domain, err := GetDomain(c) + s.Equal("localhost", domain) + s.NoError(err) + }) + + s.Run("when origin is localhost with port", func() { + c := s.buildContext(url.URL{}, http.Header{ "Origin": []string{"http://localhost:3000/"}, - }, - URL: &url.URL{}, - } - - c := gin.Context{Request: &req} - - domain, err := GetDomain(&c) - assert.Equal(t, "localhost", domain) - assert.Equal(t, nil, err) -} - -func TestGetDomainWWW(t *testing.T) { - req := http.Request{ - Header: http.Header{ + }) + domain, err := GetDomain(c) + s.Equal("localhost", domain) + s.NoError(err) + }) + + s.Run("when origin has subdomain", func() { + c := s.buildContext(url.URL{}, http.Header{ "Origin": []string{"http://www.demoticker.org/"}, - }, - URL: &url.URL{}, - } - - c := gin.Context{Request: &req} - - domain, err := GetDomain(&c) - assert.Equal(t, "demoticker.org", domain) - assert.Equal(t, nil, err) -} - -func TestGetDomainOriginQueryOverwrite(t *testing.T) { - req := http.Request{ - Header: http.Header{ + }) + domain, err := GetDomain(c) + s.Equal("demoticker.org", domain) + s.NoError(err) + }) + + s.Run("when query param is set", func() { + c := s.buildContext(url.URL{RawQuery: "origin=another.demoticker.org"}, http.Header{ "Origin": []string{"http://www.demoticker.org/"}, - }, - URL: &url.URL{RawQuery: "origin=another.demoticker.org"}, - } - - c := gin.Context{Request: &req} - - domain, err := GetDomain(&c) - assert.Equal(t, "another.demoticker.org", domain) - assert.Equal(t, nil, err) + }) + domain, err := GetDomain(c) + s.Equal("another.demoticker.org", domain) + s.NoError(err) + }) } -func TestMe(t *testing.T) { - c := &gin.Context{} - _, err := Me(c) - - assert.NotNil(t, err) - - c.Set("me", storage.User{}) - - _, err = Me(c) - - assert.Nil(t, err) +func (s *UtilTestSuite) TestMe() { + s.Run("when me is not set", func() { + c := &gin.Context{} + _, err := Me(c) + s.Equal("me not found", err.Error()) + }) + + s.Run("when me is set", func() { + c := &gin.Context{} + c.Set("me", storage.User{}) + _, err := Me(c) + s.NoError(err) + }) } -func TestIsAdmin(t *testing.T) { - c := &gin.Context{} - isAdmin := IsAdmin(c) - - assert.False(t, isAdmin) - - c.Set("me", storage.User{IsSuperAdmin: true}) - - isAdmin = IsAdmin(c) - - assert.True(t, isAdmin) +func (s *UtilTestSuite) TestIsAdmin() { + s.Run("when me is not set", func() { + c := &gin.Context{} + isAdmin := IsAdmin(c) + s.False(isAdmin) + }) + + s.Run("when me is set", func() { + c := &gin.Context{} + c.Set("me", storage.User{IsSuperAdmin: true}) + isAdmin := IsAdmin(c) + s.True(isAdmin) + }) } -func TestTicker(t *testing.T) { - c := &gin.Context{} - - _, err := Ticker(c) - assert.NotNil(t, err) - - c.Set("ticker", storage.Ticker{}) - - _, err = Ticker(c) - assert.Nil(t, err) +func (s *UtilTestSuite) TestTicker() { + s.Run("when ticker is not set", func() { + c := &gin.Context{} + _, err := Ticker(c) + s.Equal("ticker not found", err.Error()) + }) + + s.Run("when ticker is set", func() { + c := &gin.Context{} + c.Set("ticker", storage.Ticker{}) + _, err := Ticker(c) + s.NoError(err) + }) } -func TestMessage(t *testing.T) { - c := &gin.Context{} - - _, err := Message(c) - assert.NotNil(t, err) - - c.Set("message", storage.Message{}) - - _, err = Message(c) - assert.Nil(t, err) +func (s *UtilTestSuite) TestMessage() { + s.Run("when message is not set", func() { + c := &gin.Context{} + _, err := Message(c) + s.Equal("message not found", err.Error()) + }) + + s.Run("when message is set", func() { + c := &gin.Context{} + c.Set("message", storage.Message{}) + _, err := Message(c) + s.NoError(err) + }) } -func TestUser(t *testing.T) { - c := &gin.Context{} +func (s *UtilTestSuite) TestUser() { + s.Run("when user is not set", func() { + c := &gin.Context{} + _, err := User(c) + s.Equal("user not found", err.Error()) + }) + + s.Run("when user is set", func() { + c := &gin.Context{} + c.Set("user", storage.User{}) + _, err := User(c) + s.NoError(err) + }) +} - _, err := User(c) - assert.NotNil(t, err) +func (s *UtilTestSuite) buildContext(u url.URL, headers http.Header) *gin.Context { + req := http.Request{ + Header: headers, + URL: &u, + } - c.Set("user", storage.User{}) + return &gin.Context{Request: &req} +} - _, err = User(c) - assert.Nil(t, err) +func TestUtilTestSuite(t *testing.T) { + suite.Run(t, new(UtilTestSuite)) } diff --git a/internal/api/init_test.go b/internal/api/init_test.go index 6a074c05..1e52c4b4 100644 --- a/internal/api/init_test.go +++ b/internal/api/init_test.go @@ -1,73 +1,96 @@ package api import ( + "errors" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func TestGetInit(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v1/init?origin=demoticker.org", nil) - - ticker := storage.NewTicker() - ticker.Active = true - s := &storage.MockStorage{} - s.On("GetRefreshIntervalSettings").Return(storage.DefaultRefreshIntervalSettings()) - s.On("FindTickerByDomain", mock.AnythingOfType("string"), mock.Anything).Return(ticker, nil) - - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetInit(c) - - assert.Equal(t, http.StatusOK, w.Code) +type InitTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite } -func TestGetInitInvalidDomain(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v1/init", nil) - - s := &storage.MockStorage{} - s.On("GetRefreshIntervalSettings").Return(storage.DefaultRefreshIntervalSettings()) +func (s *InitTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetInit(c) - - assert.Equal(t, http.StatusOK, w.Code) + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.store = &storage.MockStorage{} + s.store.On("GetRefreshIntervalSettings").Return(storage.DefaultRefreshIntervalSettings()) + s.cfg = config.LoadConfig("") } -func TestGetInitInactiveTicker(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v1/init?origin=demoticker.org", nil) - - ticker := storage.NewTicker() - s := &storage.MockStorage{} - s.On("GetRefreshIntervalSettings").Return(storage.DefaultRefreshIntervalSettings()) - s.On("GetInactiveSettings").Return(storage.DefaultInactiveSettings()) - s.On("FindTickerByDomain", mock.AnythingOfType("string"), mock.Anything).Return(ticker, nil) +func (s *InitTestSuite) TestGetInit() { + s.Run("if neither header nor query is set", func() { + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/init", nil) + h := s.handler() + h.GetInit(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.Equal(`{"data":{"settings":{"refreshInterval":10000},"ticker":null},"status":"success","error":{}}`, s.w.Body.String()) + s.store.AssertNotCalled(s.T(), "FindTickerByDomain", mock.AnythingOfType("string"), mock.Anything) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when database returns error", func() { + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/init?origin=demoticker.org", nil) + s.store.On("FindTickerByDomain", mock.AnythingOfType("string"), mock.Anything).Return(storage.Ticker{}, errors.New("storage error")).Once() + s.store.On("GetInactiveSettings").Return(storage.DefaultInactiveSettings()).Once() + h := s.handler() + h.GetInit(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.Contains(s.w.Body.String(), `"ticker":null`) + s.store.AssertCalled(s.T(), "FindTickerByDomain", "demoticker.org", mock.Anything) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when database returns an inactive ticker", func() { + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/init?origin=demoticker.org", nil) + ticker := storage.NewTicker() + ticker.Active = false + s.store.On("FindTickerByDomain", mock.AnythingOfType("string"), mock.Anything).Return(ticker, nil).Once() + s.store.On("GetInactiveSettings").Return(storage.DefaultInactiveSettings()).Once() + h := s.handler() + h.GetInit(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.Contains(s.w.Body.String(), `"ticker":null`) + s.store.AssertCalled(s.T(), "FindTickerByDomain", "demoticker.org", mock.Anything) + }) + + s.Run("when database returns an active ticker", func() { + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/init?origin=demoticker.org", nil) + ticker := storage.NewTicker() + ticker.Active = true + s.store.On("FindTickerByDomain", mock.AnythingOfType("string"), mock.Anything).Return(ticker, nil).Once() + s.store.On("GetInactiveSettings").Return(storage.DefaultInactiveSettings()).Once() + h := s.handler() + h.GetInit(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertCalled(s.T(), "FindTickerByDomain", "demoticker.org", mock.Anything) + }) +} - h := handler{ - storage: s, - config: config.LoadConfig(""), +func (s *InitTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, } +} - h.GetInit(c) - - assert.Equal(t, http.StatusOK, w.Code) +func TestInitTestSuite(t *testing.T) { + suite.Run(t, new(InitTestSuite)) } diff --git a/internal/api/media_test.go b/internal/api/media_test.go index ccfff06e..82ed9be0 100644 --- a/internal/api/media_test.go +++ b/internal/api/media_test.go @@ -7,49 +7,62 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { - gin.SetMode(gin.TestMode) +type MediaTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite } -func TestGetMedia(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/media", nil) +func (s *MediaTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) - upload := storage.NewUpload("image.jpg", "image/jpeg", 1) - s := &storage.MockStorage{} - s.On("FindUploadByUUID", mock.Anything).Return(upload, nil) - s.On("UploadPath").Return("./uploads") + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/media", nil) + s.store = &storage.MockStorage{} + s.cfg = config.LoadConfig("") +} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } +func (s *MediaTestSuite) TestGetMedia() { + s.Run("when upload not found", func() { + s.store.On("FindUploadByUUID", mock.Anything).Return(storage.Upload{}, errors.New("not found")).Once() + h := s.handler() + h.GetMedia(s.ctx) - h.GetMedia(c) + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) - assert.NotEmpty(t, w.Header().Get("Cache-Control")) - assert.NotEmpty(t, w.Header().Get("Expires")) -} + s.Run("when upload found", func() { + upload := storage.NewUpload("image.jpg", "image/jpeg", 1) + s.store.On("FindUploadByUUID", mock.Anything).Return(upload, nil).Once() + s.store.On("UploadPath").Return("./uploads").Once() -func TestGetMediaNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) + h := s.handler() + h.GetMedia(s.ctx) - s := &storage.MockStorage{} - s.On("FindUploadByUUID", mock.Anything).Return(storage.Upload{}, errors.New("not found")) + s.Equal(http.StatusNotFound, s.w.Code) + s.NotEmpty(s.w.Header().Get("Cache-Control")) + s.NotEmpty(s.w.Header().Get("Expires")) + s.store.AssertExpectations(s.T()) + }) +} - h := handler{ - storage: s, - config: config.LoadConfig(""), +func (s *MediaTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, } - h.GetMedia(c) +} - assert.Equal(t, http.StatusNotFound, w.Code) +func TestMediaTestSuite(t *testing.T) { + suite.Run(t, new(MediaTestSuite)) } diff --git a/internal/api/messages_test.go b/internal/api/messages_test.go index 68956daf..ac7bf6e1 100644 --- a/internal/api/messages_test.go +++ b/internal/api/messages_test.go @@ -8,271 +8,207 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/systemli/ticker/internal/bridge" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { - gin.SetMode(gin.TestMode) -} - -func TestGetMessagesTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.AddParam("tickerID", "1") - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetMessages(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetMessagesStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Message{}, errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetMessages(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetMessagesEmptyResult(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Message{}, errors.New("not found")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetMessages(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetMessages(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Message{}, nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetMessages(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestGetMessageNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetMessage(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetMessage(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("message", storage.Message{}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetMessage(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPostMessageTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostMessage(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestPostMessageFormError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", nil) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostMessage(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostMessageUploadsNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - json := `{"text":"text","attachments":[1]}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - c.AddParam("tickerID", "1") - s := &storage.MockStorage{} - s.On("FindUploadsByIDs", mock.Anything).Return([]storage.Upload{}, errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostMessage(c) - - assert.Equal(t, http.StatusNotFound, w.Code) +type MessagesTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite } -func TestPostMessageStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - json := `{"text":"text","attachments":[1]}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - c.AddParam("tickerID", "1") - s := &storage.MockStorage{} - s.On("FindUploadsByIDs", mock.Anything).Return([]storage.Upload{}, nil) - s.On("SaveMessage", mock.Anything).Return(errors.New("storage error")) - b := &bridge.MockBridge{} - b.On("Send", mock.Anything, mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - bridges: bridge.Bridges{"mock": b}, - } - - h.PostMessage(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostMessage(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - json := `{"text":"text","attachments":[1]}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("FindUploadsByIDs", mock.Anything).Return([]storage.Upload{}, nil) - s.On("SaveMessage", mock.Anything).Return(nil) - b := &bridge.MockBridge{} - b.On("Send", mock.Anything, mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - bridges: bridge.Bridges{"mock": b}, - } - - h.PostMessage(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestDeleteMessageTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteMessage(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestDeleteMessageMessageNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteMessage(c) - - assert.Equal(t, http.StatusNotFound, w.Code) +func (s *MessagesTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) } -func TestDeleteMessageStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.Set("message", storage.Message{}) - s := &storage.MockStorage{} - s.On("DeleteMessage", mock.Anything).Return(errors.New("storage error")) - b := &bridge.MockBridge{} - b.On("Delete", mock.Anything, mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - bridges: bridge.Bridges{"mock": b}, +func (s *MessagesTestSuite) Run(name string, subtest func()) { + s.T().Run(name, func(t *testing.T) { + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.store = &storage.MockStorage{} + s.cfg = config.LoadConfig("") + + subtest() + }) +} + +func (s *MessagesTestSuite) TestGetMessages() { + s.Run("when ticker not found", func() { + h := s.handler() + h.GetMessages(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when database returns error", func() { + ticker := storage.Ticker{ID: 1} + s.ctx.Set("ticker", ticker) + s.store.On("FindMessagesByTickerAndPagination", ticker, mock.Anything, mock.Anything).Return([]storage.Message{}, errors.New("storage error")).Once() + h := s.handler() + h.GetMessages(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when database returns messages", func() { + ticker := storage.Ticker{ID: 1} + s.ctx.Set("ticker", ticker) + s.store.On("FindMessagesByTickerAndPagination", ticker, mock.Anything, mock.Anything).Return([]storage.Message{}, nil).Once() + h := s.handler() + h.GetMessages(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *MessagesTestSuite) TestGetMessage() { + s.Run("when message not found", func() { + h := s.handler() + h.GetMessage(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when message found", func() { + message := storage.Message{ID: 1} + s.ctx.Set("message", message) + h := s.handler() + h.GetMessage(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *MessagesTestSuite) TestPostMessage() { + s.Run("when ticker not found", func() { + h := s.handler() + h.PostMessage(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when form is invalid", func() { + ticker := storage.Ticker{ID: 1} + s.ctx.Set("ticker", ticker) + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", nil) + h := s.handler() + h.PostMessage(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when upload not found", func() { + ticker := storage.Ticker{ID: 1} + s.ctx.Set("ticker", ticker) + json := `{"text":"text","attachments":[1]}` + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", strings.NewReader(json)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("FindUploadsByIDs", []int{1}).Return([]storage.Upload{}, errors.New("storage error")).Once() + h := s.handler() + h.PostMessage(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when database returns error", func() { + ticker := storage.Ticker{ID: 1} + s.ctx.Set("ticker", ticker) + json := `{"text":"text","attachments":[1]}` + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", strings.NewReader(json)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.ctx.AddParam("tickerID", "1") + s.store.On("FindUploadsByIDs", []int{1}).Return([]storage.Upload{}, nil).Once() + s.store.On("SaveMessage", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PostMessage(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when database returns message", func() { + ticker := storage.Ticker{ID: 1} + s.ctx.Set("ticker", ticker) + json := `{"text":"text","attachments":[1]}` + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", strings.NewReader(json)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.ctx.AddParam("tickerID", "1") + s.store.On("FindUploadsByIDs", []int{1}).Return([]storage.Upload{}, nil).Once() + s.store.On("SaveMessage", mock.Anything).Return(nil).Once() + h := s.handler() + h.PostMessage(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) +} + +func (s *MessagesTestSuite) TestDeleteMessage() { + s.Run("when ticker not found", func() { + h := s.handler() + h.DeleteMessage(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when message not found", func() { + ticker := storage.Ticker{ID: 1} + s.ctx.Set("ticker", ticker) + h := s.handler() + h.DeleteMessage(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when database returns error", func() { + ticker := storage.Ticker{ID: 1} + message := storage.Message{ID: 1} + s.ctx.Set("ticker", ticker) + s.ctx.Set("message", message) + s.store.On("DeleteMessage", message).Return(errors.New("storage error")).Once() + h := s.handler() + h.DeleteMessage(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) + + s.Run("when database returns message", func() { + ticker := storage.Ticker{ID: 1} + message := storage.Message{ID: 1} + s.ctx.Set("ticker", ticker) + s.ctx.Set("message", message) + s.store.On("DeleteMessage", message).Return(nil).Once() + h := s.handler() + h.DeleteMessage(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.True(s.store.AssertExpectations(s.T())) + }) +} + +func (s *MessagesTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, } - - h.DeleteMessage(c) - - assert.Equal(t, http.StatusNotFound, w.Code) } -func TestDeleteMessage(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.Set("message", storage.Message{}) - s := &storage.MockStorage{} - s.On("DeleteMessage", mock.Anything).Return(nil) - b := &bridge.MockBridge{} - b.On("Delete", mock.Anything, mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - bridges: bridge.Bridges{"mock": b}, - } - - h.DeleteMessage(c) - - assert.Equal(t, http.StatusOK, w.Code) +func TestMessagesTestSuite(t *testing.T) { + suite.Run(t, new(MessagesTestSuite)) } diff --git a/internal/api/middleware/auth/auth_test.go b/internal/api/middleware/auth/auth_test.go index 52daaaa3..b11babd9 100644 --- a/internal/api/middleware/auth/auth_test.go +++ b/internal/api/middleware/auth/auth_test.go @@ -10,143 +10,127 @@ import ( jwt "github.com/appleboy/gin-jwt/v2" "github.com/gin-gonic/gin" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/api/response" "github.com/systemli/ticker/internal/storage" - "golang.org/x/crypto/bcrypt" ) -func TestStorage(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Auth Middleware Suite") +type AuthTestSuite struct { + suite.Suite } -var _ = Describe("Auth Middleware", func() { +func (s *AuthTestSuite) SetupTest() { gin.SetMode(gin.TestMode) +} - When("Authenticator", func() { - Context("empty form is sent", func() { - mockStorage := &storage.MockStorage{} +func (s *AuthTestSuite) TestAuthenticator() { + s.Run("when form is empty", func() { + mockStorage := &storage.MockStorage{} + authenticator := Authenticator(mockStorage) + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(`{}`)) - authenticator := Authenticator(mockStorage) - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Request = httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(`{}`)) + _, err := authenticator(c) + s.Error(err) + s.Equal("missing Username or Password", err.Error()) + }) - It("should return an error", func() { - _, err := authenticator(c) - Expect(err).NotTo(BeNil()) - Expect(err).To(Equal(errors.New("missing Username or Password"))) - }) - }) + s.Run("when user is not found", func() { + mockStorage := &storage.MockStorage{} + mockStorage.On("FindUserByEmail", mock.Anything).Return(storage.User{}, errors.New("not found")) + + authenticator := Authenticator(mockStorage) + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(`{"username": "user@systemli.org", "password": "password"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + _, err := authenticator(c) + s.Error(err) + s.Equal("not found", err.Error()) + }) - Context("user not found", func() { - mockStorage := &storage.MockStorage{} - mockStorage.On("FindUserByEmail", mock.Anything).Return(storage.User{}, errors.New("not found")) + s.Run("when user is found", func() { + user, err := storage.NewUser("user@systemli.org", "password") + s.NoError(err) - authenticator := Authenticator(mockStorage) + mockStorage := &storage.MockStorage{} + mockStorage.On("FindUserByEmail", mock.Anything).Return(user, nil) + authenticator := Authenticator(mockStorage) + + s.Run("with correct password", func() { c, _ := gin.CreateTestContext(httptest.NewRecorder()) c.Request = httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(`{"username": "user@systemli.org", "password": "password"}`)) c.Request.Header.Set("Content-Type", "application/json") + user, err := authenticator(c) - It("should return an error", func() { - _, err := authenticator(c) - Expect(err).NotTo(BeNil()) - Expect(err).To(Equal(errors.New("not found"))) - }) + s.NoError(err) + s.Equal("user@systemli.org", user.(storage.User).Email) }) - Context("user is found", func() { - hashedPassword, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost) - user := storage.User{ - ID: 1, - Email: "user@systemli.org", - EncryptedPassword: string(hashedPassword), - IsSuperAdmin: false, - } - mockStorage := &storage.MockStorage{} - mockStorage.On("FindUserByEmail", mock.Anything).Return(user, nil) - - authenticator := Authenticator(mockStorage) - - It("should return a user with correct password", func() { - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Request = httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(`{"username": "user@systemli.org", "password": "password"}`)) - c.Request.Header.Set("Content-Type", "application/json") - user, err := authenticator(c) - Expect(err).To(BeNil()) - Expect(user).To(BeAssignableToTypeOf(storage.User{})) - }) - - It("should return an error with incorrect password", func() { - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Request = httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(`{"username": "user@systemli.org", "password": "password1"}`)) - c.Request.Header.Set("Content-Type", "application/json") - - _, err := authenticator(c) - Expect(err).NotTo(BeNil()) - }) + s.Run("with incorrect password", func() { + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request = httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(`{"username": "user@systemli.org", "password": "password1"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + _, err := authenticator(c) + + s.Error(err) + s.Equal("authentication failed", err.Error()) }) }) +} - When("Authorizator", func() { - Context("storage returns no user", func() { - mockStorage := &storage.MockStorage{} - mockStorage.On("FindUserByID", mock.Anything).Return(storage.User{}, errors.New("user not found")) - authorizator := Authorizator(mockStorage) - rr := httptest.NewRecorder() - c, _ := gin.CreateTestContext(rr) - - It("should return false", func() { - found := authorizator(float64(1), c) - Expect(found).To(BeFalse()) - }) - }) +func (s *AuthTestSuite) TestAuthorizator() { + s.Run("when user is not found", func() { + mockStorage := &storage.MockStorage{} + mockStorage.On("FindUserByID", mock.Anything).Return(storage.User{}, errors.New("not found")) + authorizator := Authorizator(mockStorage) + c, _ := gin.CreateTestContext(httptest.NewRecorder()) - Context("storage returns a user", func() { - mockStorage := &storage.MockStorage{} - mockStorage.On("FindUserByID", mock.Anything).Return(storage.User{ID: 1}, nil) - authorizator := Authorizator(mockStorage) - rr := httptest.NewRecorder() - c, _ := gin.CreateTestContext(rr) - - It("should return true", func() { - found := authorizator(float64(1), c) - Expect(found).To(BeTrue()) - }) - }) + found := authorizator(float64(1), c) + s.False(found) }) - When("Unauthorized", func() { - It("should return a 403 with json payload", func() { - rr := httptest.NewRecorder() - c, _ := gin.CreateTestContext(rr) - c.Request = httptest.NewRequest(http.MethodGet, "/login", nil) + s.Run("when user is found", func() { + mockStorage := &storage.MockStorage{} + mockStorage.On("FindUserByID", mock.Anything).Return(storage.User{ID: 1}, nil) + authorizator := Authorizator(mockStorage) + c, _ := gin.CreateTestContext(httptest.NewRecorder()) - Unauthorized(c, 403, "unauthorized") + found := authorizator(float64(1), c) + s.True(found) + }) +} - err := json.Unmarshal(rr.Body.Bytes(), &response.Response{}) - Expect(rr.Code).To(Equal(403)) - Expect(err).To(BeNil()) - }) +func (s *AuthTestSuite) TestUnauthorized() { + s.Run("returns a 403 with json payload", func() { + rr := httptest.NewRecorder() + c, _ := gin.CreateTestContext(rr) + c.Request = httptest.NewRequest(http.MethodGet, "/login", nil) + + Unauthorized(c, 403, "unauthorized") + + err := json.Unmarshal(rr.Body.Bytes(), &response.Response{}) + s.NoError(err) + s.Equal(403, rr.Code) }) +} - When("FillClaims", func() { - Context("invalid user is given", func() { - It("should return empty claims", func() { - claims := FillClaim("empty") - Expect(claims).To(Equal(jwt.MapClaims{})) - }) - }) +func (s *AuthTestSuite) TestFillClaims() { + s.Run("when user is empty", func() { + claims := FillClaim("empty") + s.Equal(jwt.MapClaims{}, claims) + }) - Context("valid user is given", func() { - It("should return the claims", func() { - user := storage.User{ID: 1, Email: "user@systemli.org", IsSuperAdmin: true} - claims := FillClaim(user) + s.Run("when user is valid", func() { + user := storage.User{ID: 1, Email: "user@systemli.org", IsSuperAdmin: true} + claims := FillClaim(user) - Expect(claims).To(Equal(jwt.MapClaims{"id": 1, "email": "user@systemli.org", "roles": []string{"user", "admin"}})) - }) - }) + s.Equal(jwt.MapClaims{"id": 1, "email": "user@systemli.org", "roles": []string{"user", "admin"}}, claims) }) -}) +} + +func TestAuthTestSuite(t *testing.T) { + suite.Run(t, new(AuthTestSuite)) +} diff --git a/internal/api/middleware/me/me_test.go b/internal/api/middleware/me/me_test.go index a91fe767..8f26f92f 100644 --- a/internal/api/middleware/me/me_test.go +++ b/internal/api/middleware/me/me_test.go @@ -7,52 +7,49 @@ import ( "testing" "github.com/gin-gonic/gin" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func TestStorage(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Auth Middleware Suite") +type MeTestSuite struct { + suite.Suite } -var _ = Describe("Me Middleware", func() { - When("id is not present", func() { - It("should return an error", func() { - mockStorage := &storage.MockStorage{} - mw := MeMiddleware(mockStorage) - gin.SetMode(gin.ReleaseMode) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) +func (s *MeTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) +} - mw(c) +func (s *MeTestSuite) TestMeMiddleware() { + s.Run("when id is not present", func() { + mockStorage := &storage.MockStorage{} + mw := MeMiddleware(mockStorage) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) - Expect(w.Code).To(Equal(http.StatusBadRequest)) - }) + mw(c) + + s.Equal(http.StatusBadRequest, w.Code) }) - When("id is present", func() { - Context("user not found", func() { + s.Run("when id is present", func() { + s.Run("when user is not found", func() { mockStorage := &storage.MockStorage{} mockStorage.On("FindUserByID", mock.Anything).Return(storage.User{}, errors.New("not found")) mw := MeMiddleware(mockStorage) - gin.SetMode(gin.ReleaseMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("id", float64(1)) mw(c) - Expect(w.Code).To(Equal(http.StatusBadRequest)) + s.Equal(http.StatusBadRequest, w.Code) }) - Context("user found", func() { + s.Run("when user is found", func() { mockStorage := &storage.MockStorage{} mockStorage.On("FindUserByID", mock.Anything).Return(storage.User{ID: 1}, nil) mw := MeMiddleware(mockStorage) - gin.SetMode(gin.ReleaseMode) w := httptest.NewRecorder() c, _ := gin.CreateTestContext(w) c.Set("id", float64(1)) @@ -60,8 +57,12 @@ var _ = Describe("Me Middleware", func() { mw(c) user, exists := c.Get("me") - Expect(exists).To(BeTrue()) - Expect(user).To(BeAssignableToTypeOf(storage.User{})) + s.True(exists) + s.IsType(storage.User{}, user) }) }) -}) +} + +func TestMeTestSuite(t *testing.T) { + suite.Run(t, new(MeTestSuite)) +} diff --git a/internal/api/middleware/message/message_test.go b/internal/api/middleware/message/message_test.go index 320459ea..0398baed 100644 --- a/internal/api/middleware/message/message_test.go +++ b/internal/api/middleware/message/message_test.go @@ -7,55 +7,64 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func init() { +type MessageTestSuite struct { + suite.Suite +} + +func (s *MessageTestSuite) SetupTest() { gin.SetMode(gin.TestMode) } -func TestPrefetchMessageParamMissing(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - mw := PrefetchMessage(s) +func (s *MessageTestSuite) TestMessage() { + s.Run("when param is missing", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("ticker", storage.Ticker{}) + store := &storage.MockStorage{} + mw := PrefetchMessage(store) - mw(c) + mw(c) - assert.Equal(t, http.StatusBadRequest, w.Code) -} + s.Equal(http.StatusBadRequest, w.Code) + }) -func TestPrefetchMessageStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.AddParam("messageID", "1") - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("FindMessage", mock.Anything, mock.Anything, mock.Anything).Return(storage.Message{}, errors.New("storage error")) - mw := PrefetchMessage(s) + s.Run("storage returns error", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.AddParam("messageID", "1") + c.Set("ticker", storage.Ticker{}) + store := &storage.MockStorage{} + store.On("FindMessage", mock.Anything, mock.Anything, mock.Anything).Return(storage.Message{}, errors.New("storage error")) + mw := PrefetchMessage(store) - mw(c) + mw(c) - assert.Equal(t, http.StatusNotFound, w.Code) -} + s.Equal(http.StatusNotFound, w.Code) + }) -func TestPrefetchMessage(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.AddParam("messageID", "1") - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - message := storage.Message{ID: 1} - s.On("FindMessage", mock.Anything, mock.Anything, mock.Anything).Return(message, nil) - mw := PrefetchMessage(s) + s.Run("storage returns message", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.AddParam("messageID", "1") + c.Set("ticker", storage.Ticker{}) + store := &storage.MockStorage{} + message := storage.Message{ID: 1} + store.On("FindMessage", mock.Anything, mock.Anything, mock.Anything).Return(message, nil) + mw := PrefetchMessage(store) - mw(c) + mw(c) - me, e := c.Get("message") - assert.True(t, e) - assert.Equal(t, message, me.(storage.Message)) + me, e := c.Get("message") + s.True(e) + s.Equal(message, me.(storage.Message)) + }) +} +func TestMessageTestSuite(t *testing.T) { + suite.Run(t, new(MessageTestSuite)) } diff --git a/internal/api/middleware/response_cache/response_cache_test.go b/internal/api/middleware/response_cache/response_cache_test.go index 1df7e9d3..38d395b8 100644 --- a/internal/api/middleware/response_cache/response_cache_test.go +++ b/internal/api/middleware/response_cache/response_cache_test.go @@ -8,46 +8,100 @@ import ( "time" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/cache" ) -func TestCreateKey(t *testing.T) { - c := gin.Context{ - Request: &http.Request{ - Method: "GET", - URL: &url.URL{Path: "/api/v1/settings", RawQuery: "origin=localhost"}, - }, - } +type ResponseCacheTestSuite struct { + suite.Suite +} + +func (s *ResponseCacheTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) +} + +func (s *ResponseCacheTestSuite) TestCreateKey() { + s.Run("create cache key with origin", func() { + c := gin.Context{ + Request: &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/api/v1/settings", RawQuery: "origin=localhost"}, + }, + } - key := CreateKey(&c) - assert.Equal(t, "response:localhost::origin=localhost", key) + key := CreateKey(&c) + s.Equal("response:localhost::origin=localhost", key) + }) - c.Request.URL.RawQuery = "" + s.Run("create cache key without origin", func() { + c := gin.Context{ + Request: &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/api/v1/settings"}, + }, + } - key = CreateKey(&c) - assert.Equal(t, "response:unknown::", key) + key := CreateKey(&c) + s.Equal("response:unknown::", key) + }) } -func TestCachePage(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = &http.Request{ - Method: "GET", - URL: &url.URL{Path: "/ping", RawQuery: "origin=localhost"}, - } +func (s *ResponseCacheTestSuite) TestCachePage() { + s.Run("when cache is empty", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = &http.Request{ + Method: "GET", + URL: &url.URL{Path: "/ping", RawQuery: "origin=localhost"}, + } + + inMemoryCache := cache.NewCache(time.Minute) + defer inMemoryCache.Close() + CachePage(inMemoryCache, time.Minute, func(c *gin.Context) { + c.String(http.StatusOK, "pong") + })(c) + + s.Equal(http.StatusOK, w.Code) + s.Equal("pong", w.Body.String()) + + count := 0 + inMemoryCache.Range(func(key, value interface{}) bool { + count++ + return true + }) + s.Equal(1, count) + }) - inMemoryCache := cache.NewCache(time.Minute) - defer inMemoryCache.Close() - CachePage(inMemoryCache, time.Minute, func(c *gin.Context) { - c.String(http.StatusOK, "pong") - })(c) + s.Run("when cache is not empty", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = &http.Request{ + Method: "GET", + Header: http.Header{ + "Origin": []string{"http://localhost/"}, + }, + URL: &url.URL{Path: "/ping"}, + } + + inMemoryCache := cache.NewCache(time.Minute) + defer inMemoryCache.Close() + inMemoryCache.Set("response:localhost::", responseCache{ + Status: http.StatusOK, + Header: http.Header{ + "DNT": []string{"1"}, + }, + Body: []byte("cached"), + }, time.Minute) - assert.Equal(t, http.StatusOK, w.Code) + CachePage(inMemoryCache, time.Minute, func(c *gin.Context) { + c.String(http.StatusOK, "pong") + })(c) - CachePage(inMemoryCache, time.Minute, func(c *gin.Context) { - c.String(http.StatusOK, "pong") - })(c) + s.Equal(http.StatusOK, w.Code) + s.Equal("cached", w.Body.String()) + }) +} - assert.Equal(t, http.StatusOK, w.Code) +func TestResponseCacheTestSuite(t *testing.T) { + suite.Run(t, new(ResponseCacheTestSuite)) } diff --git a/internal/api/middleware/ticker/ticker_test.go b/internal/api/middleware/ticker/ticker_test.go index 2bc0db04..c4d2defb 100644 --- a/internal/api/middleware/ticker/ticker_test.go +++ b/internal/api/middleware/ticker/ticker_test.go @@ -7,104 +7,112 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func init() { - gin.SetMode(gin.TestMode) -} - -func TestPrefetchTickerParamMissing(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{}) - s := &storage.MockStorage{} - mw := PrefetchTicker(s) - - mw(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +type TickerTestSuite struct { + suite.Suite } -func TestPrefetchTickerStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.AddParam("tickerID", "1") - c.Set("me", storage.User{IsSuperAdmin: true}) - s := &storage.MockStorage{} - s.On("FindTickerByUserAndID", mock.Anything, mock.Anything, mock.Anything).Return(storage.Ticker{}, errors.New("storage error")) - mw := PrefetchTicker(s) - - mw(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestPrefetchTicker(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.AddParam("tickerID", "1") - c.Set("me", storage.User{IsSuperAdmin: true}) - s := &storage.MockStorage{} - ticker := storage.Ticker{ID: 1} - s.On("FindTickerByUserAndID", mock.Anything, mock.Anything, mock.Anything).Return(ticker, nil) - mw := PrefetchTicker(s) - - mw(c) - - ti, e := c.Get("ticker") - assert.True(t, e) - assert.Equal(t, ticker, ti.(storage.Ticker)) - +func (s *TickerTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) } -func TestPrefetchTickerFromRequestMissingOrigin(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) - s := &storage.MockStorage{} - mw := PrefetchTickerFromRequest(s) - - mw(c) - - assert.Equal(t, http.StatusOK, w.Code) - ticker, exists := c.Get("ticker") - assert.Equal(t, nil, ticker) - assert.False(t, exists) +func (s *TickerTestSuite) TestPrefetchTicker() { + s.Run("when param is missing", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + store := &storage.MockStorage{} + mw := PrefetchTicker(store) + + mw(c) + + s.Equal(http.StatusBadRequest, w.Code) + }) + + s.Run("storage returns error", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.AddParam("tickerID", "1") + store := &storage.MockStorage{} + store.On("FindTickerByUserAndID", mock.Anything, mock.Anything, mock.Anything).Return(storage.Ticker{}, errors.New("storage error")) + mw := PrefetchTicker(store) + + mw(c) + + s.Equal(http.StatusNotFound, w.Code) + }) + + s.Run("storage returns ticker", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.AddParam("tickerID", "1") + store := &storage.MockStorage{} + ticker := storage.Ticker{ID: 1} + store.On("FindTickerByUserAndID", mock.Anything, mock.Anything, mock.Anything).Return(ticker, nil) + mw := PrefetchTicker(store) + + mw(c) + + ti, e := c.Get("ticker") + s.True(e) + s.Equal(ticker, ti.(storage.Ticker)) + }) } -func TestPrefetchTickerFromRequestTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) - c.Request.Header.Set("Origin", "https://demoticker.org") - s := &storage.MockStorage{} - s.On("FindTickerByDomain", mock.Anything).Return(storage.Ticker{}, errors.New("not found")) - mw := PrefetchTickerFromRequest(s) - - mw(c) - - assert.Equal(t, http.StatusOK, w.Code) - ticker, exists := c.Get("ticker") - assert.Equal(t, nil, ticker) - assert.False(t, exists) +func (s *TickerTestSuite) TestPrefetchTickerFromRequest() { + s.Run("when origin is missing", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) + store := &storage.MockStorage{} + mw := PrefetchTickerFromRequest(store) + + mw(c) + + s.Equal(http.StatusOK, w.Code) + ticker, exists := c.Get("ticker") + s.Nil(ticker) + s.False(exists) + }) + + s.Run("when ticker is not found", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) + c.Request.Header.Set("Origin", "https://demoticker.org") + store := &storage.MockStorage{} + store.On("FindTickerByDomain", mock.Anything).Return(storage.Ticker{}, errors.New("not found")) + mw := PrefetchTickerFromRequest(store) + + mw(c) + + s.Equal(http.StatusOK, w.Code) + ticker, exists := c.Get("ticker") + s.Nil(ticker) + s.False(exists) + }) + + s.Run("when ticker is found", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) + c.Request.Header.Set("Origin", "https://demoticker.org") + store := &storage.MockStorage{} + store.On("FindTickerByDomain", mock.Anything).Return(storage.Ticker{}, nil) + mw := PrefetchTickerFromRequest(store) + + mw(c) + + s.Equal(http.StatusOK, w.Code) + ticker, exists := c.Get("ticker") + s.NotNil(ticker) + s.True(exists) + }) } -func TestPrefetchTickerFromRequest(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) - c.Request.Header.Set("Origin", "https://demoticker.org") - s := &storage.MockStorage{} - s.On("FindTickerByDomain", mock.Anything).Return(storage.Ticker{}, nil) - mw := PrefetchTickerFromRequest(s) - - mw(c) - - assert.Equal(t, http.StatusOK, w.Code) - ticker, exists := c.Get("ticker") - assert.NotNil(t, ticker) - assert.True(t, exists) +func TestTickerTestSuite(t *testing.T) { + suite.Run(t, new(TickerTestSuite)) } diff --git a/internal/api/middleware/user/admin_test.go b/internal/api/middleware/user/admin_test.go index 0690d39d..ab592882 100644 --- a/internal/api/middleware/user/admin_test.go +++ b/internal/api/middleware/user/admin_test.go @@ -6,31 +6,41 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func init() { +type AdminTestSuite struct { + suite.Suite +} + +func (s *AdminTestSuite) SetupTest() { gin.SetMode(gin.TestMode) } -func TestNeedAdminMissingUser(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - mw := NeedAdmin() +func (s *AdminTestSuite) TestNeedAdmin() { + s.Run("when user is missing", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + mw := NeedAdmin() - mw(c) + mw(c) - assert.Equal(t, http.StatusBadRequest, w.Code) -} + s.Equal(http.StatusBadRequest, w.Code) + }) -func TestNeedAdminNonAdmin(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{}) - mw := NeedAdmin() + s.Run("when user is not admin", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("me", storage.User{}) + mw := NeedAdmin() - mw(c) + mw(c) + + s.Equal(http.StatusForbidden, w.Code) + }) +} - assert.Equal(t, http.StatusForbidden, w.Code) +func TestAdminTestSuite(t *testing.T) { + suite.Run(t, new(AdminTestSuite)) } diff --git a/internal/api/middleware/user/user_test.go b/internal/api/middleware/user/user_test.go index b845c9b9..ed1f1155 100644 --- a/internal/api/middleware/user/user_test.go +++ b/internal/api/middleware/user/user_test.go @@ -7,51 +7,61 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func init() { +type UserTestSuite struct { + suite.Suite +} + +func (s *UserTestSuite) SetupTest() { gin.SetMode(gin.TestMode) } -func TestPrefetchUserMissingParam(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - mw := PrefetchUser(s) +func (s *UserTestSuite) TestPrefetchUser() { + s.Run("when param is missing", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + store := &storage.MockStorage{} + mw := PrefetchUser(store) - mw(c) + mw(c) - assert.Equal(t, http.StatusBadRequest, w.Code) -} + s.Equal(http.StatusBadRequest, w.Code) + }) -func TestPrefetchUserStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.AddParam("userID", "1") - s := &storage.MockStorage{} - s.On("FindUserByID", mock.Anything, mock.Anything).Return(storage.User{}, errors.New("storage error")) - mw := PrefetchUser(s) + s.Run("storage returns error", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.AddParam("userID", "1") + store := &storage.MockStorage{} + store.On("FindUserByID", mock.Anything, mock.Anything).Return(storage.User{}, errors.New("storage error")) + mw := PrefetchUser(store) - mw(c) + mw(c) - assert.Equal(t, http.StatusNotFound, w.Code) -} + s.Equal(http.StatusNotFound, w.Code) + }) + + s.Run("storage returns user", func() { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.AddParam("userID", "1") + store := &storage.MockStorage{} + user := storage.User{ID: 1} + store.On("FindUserByID", mock.Anything, mock.Anything).Return(user, nil) + mw := PrefetchUser(store) -func TestPrefetchUser(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.AddParam("userID", "1") - s := &storage.MockStorage{} - user := storage.User{ID: 1} - s.On("FindUserByID", mock.Anything, mock.Anything).Return(user, nil) - mw := PrefetchUser(s) + mw(c) - mw(c) + us, e := c.Get("user") + s.True(e) + s.Equal(user, us.(storage.User)) + }) +} - us, e := c.Get("user") - assert.True(t, e) - assert.Equal(t, user, us.(storage.User)) +func TestUserTestSuite(t *testing.T) { + suite.Run(t, new(UserTestSuite)) } diff --git a/internal/api/pagination/pagination_test.go b/internal/api/pagination/pagination_test.go index f3aeb204..2f8d1e5c 100644 --- a/internal/api/pagination/pagination_test.go +++ b/internal/api/pagination/pagination_test.go @@ -6,35 +6,45 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) -func TestDefaultPagination(t *testing.T) { - req := http.Request{ - URL: &url.URL{ - RawQuery: ``, - }, - } - - c := gin.Context{Request: &req} - p := NewPagination(&c) - - assert.Equal(t, p.GetLimit(), 10) - assert.Equal(t, p.GetBefore(), 0) - assert.Equal(t, p.GetAfter(), 0) +type PaginationTestSuite struct { + suite.Suite } -func TestCustomPagination(t *testing.T) { - req := http.Request{ - URL: &url.URL{ - RawQuery: `limit=20&before=1&after=1`, - }, - } - - c := gin.Context{Request: &req} - p := NewPagination(&c) +func (s *PaginationTestSuite) TestNewPagination() { + s.Run("with default values", func() { + req := http.Request{ + URL: &url.URL{ + RawQuery: ``, + }, + } + + c := gin.Context{Request: &req} + p := NewPagination(&c) + + s.Equal(10, p.GetLimit()) + s.Equal(0, p.GetBefore()) + s.Equal(0, p.GetAfter()) + }) + + s.Run("with custom values", func() { + req := http.Request{ + URL: &url.URL{ + RawQuery: `limit=20&before=1&after=1`, + }, + } + + c := gin.Context{Request: &req} + p := NewPagination(&c) + + s.Equal(20, p.GetLimit()) + s.Equal(1, p.GetBefore()) + s.Equal(1, p.GetAfter()) + }) +} - assert.Equal(t, p.GetLimit(), 20) - assert.Equal(t, p.GetBefore(), 1) - assert.Equal(t, p.GetAfter(), 1) +func TestPaginationTestSuite(t *testing.T) { + suite.Run(t, new(PaginationTestSuite)) } diff --git a/internal/api/renderer/rss_test.go b/internal/api/renderer/rss_test.go index 63f33654..e88be689 100644 --- a/internal/api/renderer/rss_test.go +++ b/internal/api/renderer/rss_test.go @@ -6,12 +6,31 @@ import ( "time" "github.com/gorilla/feeds" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) -func TestFeed(t *testing.T) { - w := httptest.NewRecorder() +type RendererTestSuite struct { + suite.Suite +} + +func (s *RendererTestSuite) TestFormatFromString() { + s.Run("when format is atom", func() { + format := FormatFromString("atom") + s.Equal(AtomFormat, format) + }) + + s.Run("when format is rss", func() { + format := FormatFromString("rss") + s.Equal(RSSFormat, format) + }) + + s.Run("when format is empty", func() { + format := FormatFromString("") + s.Equal(RSSFormat, format) + }) +} +func (s *RendererTestSuite) TestWriteFeed() { feed := &feeds.Feed{ Title: "Title", Author: &feeds.Author{ @@ -23,29 +42,30 @@ func TestFeed(t *testing.T) { }, Created: time.Now(), } - atom := Feed{Data: feed, Format: AtomFormat} - - err := atom.Render(w) - assert.Nil(t, err) - atom.WriteContentType(w) - assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) + s.Run("when format is atom", func() { + w := httptest.NewRecorder() + atom := Feed{Data: feed, Format: AtomFormat} - rss := Feed{Data: feed, Format: RSSFormat} + err := atom.Render(w) + s.NoError(err) - err = rss.Render(w) - assert.Nil(t, err) -} + atom.WriteContentType(w) + s.Equal("application/xml; charset=utf-8", w.Header().Get("Content-Type")) + }) -func TestFormatFromString(t *testing.T) { - var format Format + s.Run("when format is rss", func() { + w := httptest.NewRecorder() + rss := Feed{Data: feed, Format: RSSFormat} - format = FormatFromString("atom") - assert.Equal(t, AtomFormat, format) + err := rss.Render(w) + s.NoError(err) - format = FormatFromString("rss") - assert.Equal(t, RSSFormat, format) + rss.WriteContentType(w) + s.Equal("application/xml; charset=utf-8", w.Header().Get("Content-Type")) + }) +} - format = FormatFromString("") - assert.Equal(t, RSSFormat, format) +func TestRendererTestSuite(t *testing.T) { + suite.Run(t, new(RendererTestSuite)) } diff --git a/internal/api/response/init_test.go b/internal/api/response/init_test.go index b9c6bf86..54e019fb 100644 --- a/internal/api/response/init_test.go +++ b/internal/api/response/init_test.go @@ -4,11 +4,15 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func TestInitTickerResponse(t *testing.T) { +type InitTickerResponseTestSuite struct { + suite.Suite +} + +func (s *InitTickerResponseTestSuite) TestInitTickerResponse() { ticker := storage.Ticker{ ID: 1, CreatedAt: time.Now(), @@ -27,15 +31,19 @@ func TestInitTickerResponse(t *testing.T) { response := InitTickerResponse(ticker) - assert.Equal(t, ticker.ID, response.ID) - assert.Equal(t, ticker.CreatedAt, response.CreatedAt) - assert.Equal(t, ticker.Domain, response.Domain) - assert.Equal(t, ticker.Title, response.Title) - assert.Equal(t, ticker.Description, response.Description) - assert.Equal(t, ticker.Information.Author, response.Information.Author) - assert.Equal(t, ticker.Information.URL, response.Information.URL) - assert.Equal(t, ticker.Information.Email, response.Information.Email) - assert.Equal(t, ticker.Information.Twitter, response.Information.Twitter) - assert.Equal(t, ticker.Information.Facebook, response.Information.Facebook) - assert.Equal(t, ticker.Information.Telegram, response.Information.Telegram) + s.Equal(ticker.ID, response.ID) + s.Equal(ticker.CreatedAt, response.CreatedAt) + s.Equal(ticker.Domain, response.Domain) + s.Equal(ticker.Title, response.Title) + s.Equal(ticker.Description, response.Description) + s.Equal(ticker.Information.Author, response.Information.Author) + s.Equal(ticker.Information.URL, response.Information.URL) + s.Equal(ticker.Information.Email, response.Information.Email) + s.Equal(ticker.Information.Twitter, response.Information.Twitter) + s.Equal(ticker.Information.Facebook, response.Information.Facebook) + s.Equal(ticker.Information.Telegram, response.Information.Telegram) +} + +func TestInitTickerResponseTestSuite(t *testing.T) { + suite.Run(t, new(InitTickerResponseTestSuite)) } diff --git a/internal/api/response/message_test.go b/internal/api/response/message_test.go index 080d61d5..0eb1d2a4 100644 --- a/internal/api/response/message_test.go +++ b/internal/api/response/message_test.go @@ -3,25 +3,33 @@ package response import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func TestMessagesResponse(t *testing.T) { +type MessagesResponseTestSuite struct { + suite.Suite +} + +func (s *MessagesResponseTestSuite) TestMessagesResponse() { config := config.Config{Upload: config.Upload{URL: "https://upload.example.com"}} message := storage.NewMessage() message.Attachments = []storage.Attachment{{UUID: "uuid", Extension: "jpg"}} response := MessagesResponse([]storage.Message{message}, config) - assert.Equal(t, 1, len(response)) - assert.Empty(t, response[0].TelegramURL) - assert.Empty(t, response[0].MastodonURL) - assert.Equal(t, `{"type":"FeatureCollection","features":[]}`, response[0].GeoInformation) - assert.Equal(t, 1, len(response[0].Attachments)) + s.Equal(1, len(response)) + s.Empty(response[0].TelegramURL) + s.Empty(response[0].MastodonURL) + s.Equal(`{"type":"FeatureCollection","features":[]}`, response[0].GeoInformation) + s.Equal(1, len(response[0].Attachments)) attachments := response[0].Attachments - assert.Equal(t, "https://upload.example.com/media/uuid.jpg", attachments[0].URL) + s.Equal("https://upload.example.com/media/uuid.jpg", attachments[0].URL) +} + +func TestMessagesResponseTestSuite(t *testing.T) { + suite.Run(t, new(MessagesResponseTestSuite)) } diff --git a/internal/api/response/response_test.go b/internal/api/response/response_test.go index e2017c28..6f358773 100644 --- a/internal/api/response/response_test.go +++ b/internal/api/response/response_test.go @@ -3,21 +3,32 @@ package response import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) -func TestSuccessResponse(t *testing.T) { - d := []string{"value1", "value2"} - r := SuccessResponse(map[string]interface{}{"user": d}) - - assert.Equal(t, StatusSuccess, r.Status) - assert.Equal(t, Data{"user": d}, r.Data) - assert.Equal(t, Error{}, r.Error) +type ResponseTestSuite struct { + suite.Suite } -func TestErrorResponse(t *testing.T) { - r := ErrorResponse(CodeDefault, InsufficientPermissions) +func (s *ResponseTestSuite) TestResponse() { + s.Run("when status is success", func() { + d := []string{"value1", "value2"} + r := SuccessResponse(map[string]interface{}{"user": d}) + + s.Equal(StatusSuccess, r.Status) + s.Equal(Data(map[string]interface{}{"user": d}), r.Data) + s.Equal(Error{}, r.Error) + }) + + s.Run("when status is error", func() { + r := ErrorResponse(CodeDefault, InsufficientPermissions) + + s.Equal(StatusError, r.Status) + s.Equal(Data(nil), r.Data) + s.Equal(Error{Code: CodeDefault, Message: InsufficientPermissions}, r.Error) + }) +} - assert.Equal(t, StatusError, r.Status) - assert.Equal(t, Error{Code: CodeDefault, Message: InsufficientPermissions}, r.Error) +func TestResponseTestSuite(t *testing.T) { + suite.Run(t, new(ResponseTestSuite)) } diff --git a/internal/api/response/settings_test.go b/internal/api/response/settings_test.go index c29d8e12..be072a4f 100644 --- a/internal/api/response/settings_test.go +++ b/internal/api/response/settings_test.go @@ -3,24 +3,32 @@ package response import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func TestInactiveSettingsResponse(t *testing.T) { +type SettingsResponseTestSuite struct { + suite.Suite +} + +func (s *SettingsResponseTestSuite) TestInactiveSettingsResponse() { inactiveSettings := storage.DefaultInactiveSettings() setting := InactiveSettingsResponse(inactiveSettings) - assert.Equal(t, storage.SettingInactiveName, setting.Name) - assert.Equal(t, inactiveSettings, setting.Value) + s.Equal(storage.SettingInactiveName, setting.Name) + s.Equal(inactiveSettings, setting.Value) } -func TestRefreshIntervalSettingsResponse(t *testing.T) { +func (s *SettingsResponseTestSuite) TestRefreshIntervalSettingsResponse() { refreshIntervalSettings := storage.DefaultRefreshIntervalSettings() setting := RefreshIntervalSettingsResponse(refreshIntervalSettings) - assert.Equal(t, storage.SettingRefreshInterval, setting.Name) - assert.Equal(t, refreshIntervalSettings, setting.Value) + s.Equal(storage.SettingRefreshInterval, setting.Name) + s.Equal(refreshIntervalSettings, setting.Value) +} + +func TestSettingsResponseTestSuite(t *testing.T) { + suite.Run(t, new(SettingsResponseTestSuite)) } diff --git a/internal/api/response/ticker_test.go b/internal/api/response/ticker_test.go index de68b133..15884ffc 100644 --- a/internal/api/response/ticker_test.go +++ b/internal/api/response/ticker_test.go @@ -5,12 +5,16 @@ import ( "time" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func TestTickersResponse(t *testing.T) { +type TickersResponseTestSuite struct { + suite.Suite +} + +func (s *TickersResponseTestSuite) TestTickersResponse() { ticker := storage.Ticker{ ID: 1, CreatedAt: time.Now(), @@ -55,29 +59,33 @@ func TestTickersResponse(t *testing.T) { tickerResponse := TickersResponse([]storage.Ticker{ticker}, config) - assert.Equal(t, 1, len(tickerResponse)) - assert.Equal(t, ticker.ID, tickerResponse[0].ID) - assert.Equal(t, ticker.CreatedAt, tickerResponse[0].CreatedAt) - assert.Equal(t, ticker.Domain, tickerResponse[0].Domain) - assert.Equal(t, ticker.Title, tickerResponse[0].Title) - assert.Equal(t, ticker.Description, tickerResponse[0].Description) - assert.Equal(t, ticker.Active, tickerResponse[0].Active) - assert.Equal(t, ticker.Information.Author, tickerResponse[0].Information.Author) - assert.Equal(t, ticker.Information.URL, tickerResponse[0].Information.URL) - assert.Equal(t, ticker.Information.Email, tickerResponse[0].Information.Email) - assert.Equal(t, ticker.Information.Twitter, tickerResponse[0].Information.Twitter) - assert.Equal(t, ticker.Information.Facebook, tickerResponse[0].Information.Facebook) - assert.Equal(t, ticker.Information.Telegram, tickerResponse[0].Information.Telegram) - assert.Equal(t, ticker.Telegram.Active, tickerResponse[0].Telegram.Active) - assert.Equal(t, ticker.Telegram.Connected(), tickerResponse[0].Telegram.Connected) - assert.Equal(t, config.Telegram.User.UserName, tickerResponse[0].Telegram.BotUsername) - assert.Equal(t, ticker.Telegram.ChannelName, tickerResponse[0].Telegram.ChannelName) - assert.Equal(t, ticker.Mastodon.Active, tickerResponse[0].Mastodon.Active) - assert.Equal(t, ticker.Mastodon.Connected(), tickerResponse[0].Mastodon.Connected) - assert.Equal(t, ticker.Mastodon.User.Username, tickerResponse[0].Mastodon.Name) - assert.Equal(t, ticker.Mastodon.Server, tickerResponse[0].Mastodon.Server) - assert.Equal(t, ticker.Mastodon.User.DisplayName, tickerResponse[0].Mastodon.ScreenName) - assert.Equal(t, ticker.Mastodon.User.Avatar, tickerResponse[0].Mastodon.ImageURL) - assert.Equal(t, ticker.Location.Lat, tickerResponse[0].Location.Lat) - assert.Equal(t, ticker.Location.Lon, tickerResponse[0].Location.Lon) + s.Equal(1, len(tickerResponse)) + s.Equal(ticker.ID, tickerResponse[0].ID) + s.Equal(ticker.CreatedAt, tickerResponse[0].CreatedAt) + s.Equal(ticker.Domain, tickerResponse[0].Domain) + s.Equal(ticker.Title, tickerResponse[0].Title) + s.Equal(ticker.Description, tickerResponse[0].Description) + s.Equal(ticker.Active, tickerResponse[0].Active) + s.Equal(ticker.Information.Author, tickerResponse[0].Information.Author) + s.Equal(ticker.Information.URL, tickerResponse[0].Information.URL) + s.Equal(ticker.Information.Email, tickerResponse[0].Information.Email) + s.Equal(ticker.Information.Twitter, tickerResponse[0].Information.Twitter) + s.Equal(ticker.Information.Facebook, tickerResponse[0].Information.Facebook) + s.Equal(ticker.Information.Telegram, tickerResponse[0].Information.Telegram) + s.Equal(ticker.Telegram.Active, tickerResponse[0].Telegram.Active) + s.Equal(ticker.Telegram.Connected(), tickerResponse[0].Telegram.Connected) + s.Equal(config.Telegram.User.UserName, tickerResponse[0].Telegram.BotUsername) + s.Equal(ticker.Telegram.ChannelName, tickerResponse[0].Telegram.ChannelName) + s.Equal(ticker.Mastodon.Active, tickerResponse[0].Mastodon.Active) + s.Equal(ticker.Mastodon.Connected(), tickerResponse[0].Mastodon.Connected) + s.Equal(ticker.Mastodon.User.Username, tickerResponse[0].Mastodon.Name) + s.Equal(ticker.Mastodon.Server, tickerResponse[0].Mastodon.Server) + s.Equal(ticker.Mastodon.User.DisplayName, tickerResponse[0].Mastodon.ScreenName) + s.Equal(ticker.Mastodon.User.Avatar, tickerResponse[0].Mastodon.ImageURL) + s.Equal(ticker.Location.Lat, tickerResponse[0].Location.Lat) + s.Equal(ticker.Location.Lon, tickerResponse[0].Location.Lon) +} + +func TestTickersResponseTestSuite(t *testing.T) { + suite.Run(t, new(TickersResponseTestSuite)) } diff --git a/internal/api/response/timeline_test.go b/internal/api/response/timeline_test.go index 1f98e277..e77392b0 100644 --- a/internal/api/response/timeline_test.go +++ b/internal/api/response/timeline_test.go @@ -3,23 +3,31 @@ package response import ( "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func TestTimelineResponse(t *testing.T) { +type TimelineTestSuite struct { + suite.Suite +} + +func (s *TimelineTestSuite) TestTimelineResponse() { config := config.Config{Upload: config.Upload{URL: "https://upload.example.com"}} message := storage.NewMessage() message.Attachments = []storage.Attachment{{UUID: "uuid", Extension: "jpg"}} response := TimelineResponse([]storage.Message{message}, config) - assert.Equal(t, 1, len(response)) - assert.Equal(t, `{"type":"FeatureCollection","features":[]}`, response[0].GeoInformation) - assert.Equal(t, 1, len(response[0].Attachments)) + s.Equal(1, len(response)) + s.Equal(`{"type":"FeatureCollection","features":[]}`, response[0].GeoInformation) + s.Equal(1, len(response[0].Attachments)) attachments := response[0].Attachments - assert.Equal(t, "https://upload.example.com/media/uuid.jpg", attachments[0].URL) + s.Equal("https://upload.example.com/media/uuid.jpg", attachments[0].URL) +} + +func TestTimelineTestSuite(t *testing.T) { + suite.Run(t, new(TimelineTestSuite)) } diff --git a/internal/api/response/upload_test.go b/internal/api/response/upload_test.go index 3ff7e7b1..30c20bdc 100644 --- a/internal/api/response/upload_test.go +++ b/internal/api/response/upload_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) @@ -16,14 +16,22 @@ var ( } ) -func TestUploadResponse(t *testing.T) { +type UploadResponseTestSuite struct { + suite.Suite +} + +func (s *UploadResponseTestSuite) TestUploadResponse() { response := UploadResponse(u, c) - assert.Equal(t, fmt.Sprintf("%s/media/%s", c.Upload.URL, u.FileName()), response.URL) + s.Equal(fmt.Sprintf("%s/media/%s", c.Upload.URL, u.FileName()), response.URL) } -func TestUploadsResponse(t *testing.T) { +func (s *UploadResponseTestSuite) TestUploadsResponse() { response := UploadsResponse([]storage.Upload{u}, c) - assert.Equal(t, 1, len(response)) + s.Equal(1, len(response)) +} + +func TestUploadResponseTestSuite(t *testing.T) { + suite.Run(t, new(UploadResponseTestSuite)) } diff --git a/internal/api/response/user_test.go b/internal/api/response/user_test.go index 4db9faff..7f773320 100644 --- a/internal/api/response/user_test.go +++ b/internal/api/response/user_test.go @@ -4,11 +4,15 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" ) -func TestUsersResponse(t *testing.T) { +type UsersResponseTestSuite struct { + suite.Suite +} + +func (s *UsersResponseTestSuite) TestUsersResponse() { users := []storage.User{ { ID: 1, @@ -26,13 +30,17 @@ func TestUsersResponse(t *testing.T) { } usersResponse := UsersResponse(users) - assert.Equal(t, 1, len(usersResponse)) - assert.Equal(t, users[0].ID, usersResponse[0].ID) - assert.Equal(t, users[0].CreatedAt, usersResponse[0].CreatedAt) - assert.Equal(t, users[0].Email, usersResponse[0].Email) - assert.Equal(t, users[0].IsSuperAdmin, usersResponse[0].IsSuperAdmin) - assert.Equal(t, 1, len(usersResponse[0].Tickers)) - assert.Equal(t, users[0].Tickers[0].ID, usersResponse[0].Tickers[0].ID) - assert.Equal(t, users[0].Tickers[0].Domain, usersResponse[0].Tickers[0].Domain) - assert.Equal(t, users[0].Tickers[0].Title, usersResponse[0].Tickers[0].Title) + s.Equal(1, len(usersResponse)) + s.Equal(users[0].ID, usersResponse[0].ID) + s.Equal(users[0].CreatedAt, usersResponse[0].CreatedAt) + s.Equal(users[0].Email, usersResponse[0].Email) + s.Equal(users[0].IsSuperAdmin, usersResponse[0].IsSuperAdmin) + s.Equal(1, len(usersResponse[0].Tickers)) + s.Equal(users[0].Tickers[0].ID, usersResponse[0].Tickers[0].ID) + s.Equal(users[0].Tickers[0].Domain, usersResponse[0].Tickers[0].Domain) + s.Equal(users[0].Tickers[0].Title, usersResponse[0].Tickers[0].Title) +} + +func TestUsersResponseTestSuite(t *testing.T) { + suite.Run(t, new(UsersResponseTestSuite)) } diff --git a/internal/api/settings_test.go b/internal/api/settings_test.go index 5f307144..87feda39 100644 --- a/internal/api/settings_test.go +++ b/internal/api/settings_test.go @@ -10,182 +10,159 @@ import ( "github.com/gin-gonic/gin" "github.com/goccy/go-json" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { - gin.SetMode(gin.TestMode) +type SettingsTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite } -func TestGetSettingWithoutParam(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetSetting(c) - - assert.Equal(t, http.StatusNotFound, w.Code) +func (s *SettingsTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) } -func TestGetSettingInactiveSetting(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - c.AddParam("name", storage.SettingInactiveName) - s := &storage.MockStorage{} - s.On("GetInactiveSettings").Return(storage.InactiveSettings{}) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetSetting(c) +func (s *SettingsTestSuite) Run(name string, subtest func()) { + s.T().Run(name, func(t *testing.T) { + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.ctx.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) + s.store = &storage.MockStorage{} + s.cfg = config.LoadConfig("") - assert.Equal(t, http.StatusOK, w.Code) + subtest() + }) } -func TestGetSettingRefreshIntervalSetting(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - c.AddParam("name", storage.SettingRefreshInterval) - s := &storage.MockStorage{} - s.On("GetRefreshIntervalSettings").Return(storage.RefreshIntervalSettings{}) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetSetting(c) - - assert.Equal(t, http.StatusOK, w.Code) +func (s *SettingsTestSuite) TestGetSetting() { + s.Run("when url param is missing", func() { + h := s.handler() + h.GetSetting(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("get inactive settings", func() { + s.ctx.AddParam("name", storage.SettingInactiveName) + s.store.On("GetInactiveSettings").Return(storage.InactiveSettings{}).Once() + h := s.handler() + h.GetSetting(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("get refresh interval settings", func() { + s.ctx.AddParam("name", storage.SettingRefreshInterval) + s.store.On("GetRefreshIntervalSettings").Return(storage.RefreshIntervalSettings{}).Once() + h := s.handler() + h.GetSetting(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when user is no admin", func() { + s.ctx.Set("me", storage.User{ID: 1, IsSuperAdmin: false}) + s.ctx.AddParam("name", storage.SettingRefreshInterval) + h := s.handler() + h.GetSetting(s.ctx) + + s.Equal(http.StatusForbidden, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestPutInactiveSettingsMissingBody(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - body := `broken_json` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", strings.NewReader(body)) - - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutInactiveSettings(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +func (s *SettingsTestSuite) TestPutInactiveSetting() { + s.Run("when body is invalid", func() { + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", strings.NewReader(`broken_json`)) + h := s.handler() + h.PutInactiveSettings(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + setting := storage.DefaultInactiveSettings() + body, _ := json.Marshal(setting) + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", bytes.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveInactiveSettings", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PutInactiveSettings(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns settings", func() { + setting := storage.DefaultInactiveSettings() + body, _ := json.Marshal(setting) + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", bytes.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveInactiveSettings", mock.Anything).Return(nil).Once() + s.store.On("GetInactiveSettings").Return(setting) + h := s.handler() + h.PutInactiveSettings(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestPutInactiveSettingsStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - setting := storage.DefaultInactiveSettings() - body, _ := json.Marshal(setting) - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", bytes.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - - s := &storage.MockStorage{} - s.On("SaveInactiveSettings", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutInactiveSettings(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +func (s *SettingsTestSuite) TestPutRefreshIntervalSetting() { + s.Run("when body is invalid", func() { + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", strings.NewReader(`broken_json`)) + h := s.handler() + h.PutRefreshInterval(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + setting := storage.DefaultRefreshIntervalSettings() + body, _ := json.Marshal(setting) + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", bytes.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveRefreshIntervalSettings", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PutRefreshInterval(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns settings", func() { + setting := storage.DefaultRefreshIntervalSettings() + body, _ := json.Marshal(setting) + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", bytes.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveRefreshIntervalSettings", mock.Anything).Return(nil).Once() + s.store.On("GetRefreshIntervalSettings").Return(setting) + h := s.handler() + h.PutRefreshInterval(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestPutInactiveSettings(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - setting := storage.DefaultInactiveSettings() - body, _ := json.Marshal(setting) - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", bytes.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - - s := &storage.MockStorage{} - s.On("SaveInactiveSettings", mock.Anything).Return(nil) - s.On("GetInactiveSettings").Return(setting) - h := handler{ - storage: s, - config: config.LoadConfig(""), +func (s *SettingsTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, } - - h.PutInactiveSettings(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPutRefreshIntervalSettingsMissingBody(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - body := `broken_json` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", strings.NewReader(body)) - - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutRefreshInterval(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) } -func TestPutRefreshIntervalSettingsStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - body := `{"refreshInterval": 10000}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - - s := &storage.MockStorage{} - s.On("SaveRefreshIntervalSettings", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutRefreshInterval(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutRefreshIntervalSettings(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - setting := storage.DefaultRefreshIntervalSettings() - body := `{"refreshInterval": 10000}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/settings", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - - s := &storage.MockStorage{} - s.On("SaveRefreshIntervalSettings", mock.Anything).Return(nil) - s.On("GetRefreshIntervalSettings").Return(setting) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutRefreshInterval(c) - - assert.Equal(t, http.StatusOK, w.Code) +func TestSettingsTestSuite(t *testing.T) { + suite.Run(t, new(SettingsTestSuite)) } diff --git a/internal/api/tickers_test.go b/internal/api/tickers_test.go index 3c9dea46..8ae296a1 100644 --- a/internal/api/tickers_test.go +++ b/internal/api/tickers_test.go @@ -11,776 +11,554 @@ import ( "github.com/gin-gonic/gin" "github.com/mattn/go-mastodon" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { - gin.SetMode(gin.TestMode) -} - -func TestGetTickersForbidden(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTickers(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetTickersStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - s := &storage.MockStorage{} - s.On("FindTickersByUser", mock.Anything, mock.Anything).Return([]storage.Ticker{}, errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTickers(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetTickers(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: false, Tickers: []storage.Ticker{{ID: 2}}}) - s := &storage.MockStorage{} - s.On("FindTickersByUser", mock.Anything, mock.Anything).Return([]storage.Ticker{}, nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTickers(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestGetTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTicker(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetTicker(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTicker(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestGetTickerUsersTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTickerUsers(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetTickerUsers(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("FindUsersByTicker", mock.Anything, mock.Anything).Return([]storage.User{}, nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTickerUsers(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPostTickerFormError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers", nil) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostTicker(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostTickerStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := `{"domain":"localhost","title":"title","description":"description"}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostTicker(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostTicker(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := `{"domain":"localhost","title":"title","description":"description"}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostTicker(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPutTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTicker(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestPutTickerFormError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", nil) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTicker(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutTickerStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := `{"domain":"localhost","title":"title","description":"description"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTicker(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutTicker(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := `{"domain":"localhost","title":"title","description":"description"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTicker(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPutTickerUsersNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerUsers(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestPutTickerUsersFormError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/user", nil) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerUsers(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutTickerUsersStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := `{"users":[{"id":1},{"id":2},{"id":3}]}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/user", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("FindUsersByTicker", mock.Anything).Return([]storage.User{}, nil) - s.On("SaveTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerUsers(c) - - assert.Equal(t, http.StatusInternalServerError, w.Code) -} - -func TestPutTickerUsers(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := `{"users":[{"id":1},{"id":2},{"id":3}]}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/user", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("FindUsersByIDs", mock.Anything).Return([]storage.User{}, nil) - s.On("FindUsersByTicker", mock.Anything).Return([]storage.User{}, nil) - s.On("SaveTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerUsers(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPutTickerTelegramTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerTelegram(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestPutTickerTelegramFormError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/telegram", nil) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerTelegram(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutTickerTelegramStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := `{"active":true,"channelName":"@channel_name"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/telegram", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerTelegram(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutTickerTelegram(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := `{"active":true,"channelName":"@channel_name"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/telegram", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerTelegram(c) - - assert.Equal(t, http.StatusOK, w.Code) +type TickerTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite } -func TestDeleteTickerTelegramTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerTelegram(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestDeleteTickerTelegramStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerTelegram(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestDeleteTickerTelegram(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerTelegram(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPutTickerMastodonTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerMastodon(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestPutTickerMastodonFormError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/mastodon", nil) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerMastodon(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutTickerMastodonConnectError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := `{"active":true,"server":"http://localhost","secret":"secret","token":"token","accessToken":"access_token"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/mastodon", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerMastodon(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutTickerMastodonStorageError(t *testing.T) { - w := httptest.NewRecorder() - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - account := mastodon.Account{} - json, _ := json.Marshal(account) - w.Write(json) - })) - defer server.Close() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := fmt.Sprintf(`{"server":"%s","token":"token","secret":"secret","accessToken":"access_toklen"}`, server.URL) - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/mastodon", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerMastodon(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutTickerMastodon(t *testing.T) { - w := httptest.NewRecorder() - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - account := mastodon.Account{} - json, _ := json.Marshal(account) - w.Write(json) - })) - defer server.Close() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - body := fmt.Sprintf(`{"server":"%s","token":"token","secret":"secret","accessToken":"access_toklen"}`, server.URL) - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/mastodon", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutTickerMastodon(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestDeleteTickerMastodonTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerMastodon(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestDeleteTickerMastodonStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerMastodon(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestDeleteTickerMastodon(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("SaveTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerMastodon(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestDeleteTickerTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTicker(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestDeleteTickerStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("DeleteMessages", mock.Anything).Return(errors.New("storage error")) - s.On("DeleteUploadsByTicker", mock.Anything).Return(errors.New("storage error")) - s.On("DeleteTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTicker(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestDeleteTicker(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("DeleteMessages", mock.Anything).Return(nil) - s.On("DeleteUploadsByTicker", mock.Anything).Return(nil) - s.On("DeleteTicker", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTicker(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestDeleteTickerUserTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerUser(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestDeleteTickerUserMissingUserParam(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerUser(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestDeleteTickerUserUserNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.AddParam("userID", "1") - s := &storage.MockStorage{} - s.On("FindUserByID", mock.Anything).Return(storage.User{}, errors.New("not found")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerUser(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestDeleteTickerUserStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.AddParam("userID", "1") - s := &storage.MockStorage{} - s.On("FindUserByID", mock.Anything).Return(storage.User{}, nil) - s.On("DeleteTickerUser", mock.Anything, mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerUser(c) - - assert.Equal(t, http.StatusInternalServerError, w.Code) -} - -func TestDeleteTickerUser(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - c.AddParam("userID", "1") - s := &storage.MockStorage{} - s.On("FindUserByID", mock.Anything).Return(storage.User{}, nil) - s.On("DeleteTickerUser", mock.Anything, mock.Anything).Return(nil) - s.On("FindUsersByTicker", mock.Anything).Return([]storage.User{}, nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteTickerUser(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestResetTickerUserTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.ResetTicker(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestResetTickerStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("DeleteMessages", mock.Anything).Return(errors.New("storage error")) - s.On("DeleteUploadsByTicker", mock.Anything).Return(errors.New("storage error")) - s.On("SaveTicker", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.ResetTicker(c) - - assert.Equal(t, http.StatusInternalServerError, w.Code) -} - -func TestResetTickerStorageError2(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("DeleteMessages", mock.Anything).Return(errors.New("storage error")) - s.On("DeleteUploadsByTicker", mock.Anything).Return(errors.New("storage error")) - s.On("SaveTicker", mock.Anything).Return(nil) - s.On("DeleteTickerUsers", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.ResetTicker(c) - - assert.Equal(t, http.StatusInternalServerError, w.Code) +func (s *TickerTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) } -func TestResetTicker(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("DeleteMessages", mock.Anything).Return(nil) - s.On("DeleteUploadsByTicker", mock.Anything).Return(nil) - s.On("SaveTicker", mock.Anything).Return(nil) - s.On("DeleteTickerUsers", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } +func (s *TickerTestSuite) Run(name string, subtest func()) { + s.T().Run(name, func(t *testing.T) { + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.store = &storage.MockStorage{} + s.cfg = config.LoadConfig("") + + subtest() + }) +} + +func (s *TickerTestSuite) TestGetTickers() { + s.Run("when not authorized", func() { + h := s.handler() + h.GetTickers(s.ctx) - h.ResetTicker(c) + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) - assert.Equal(t, http.StatusOK, w.Code) + s.Run("when storage returns an error", func() { + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickersByUser", mock.Anything, mock.Anything).Return([]storage.Ticker{}, errors.New("storage error")).Once() + h := s.handler() + h.GetTickers(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns tickers", func() { + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickersByUser", mock.Anything, mock.Anything).Return([]storage.Ticker{}, nil).Once() + h := s.handler() + h.GetTickers(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestGetTicker() { + s.Run("when ticker not found", func() { + h := s.handler() + h.GetTicker(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when ticker found", func() { + s.ctx.Set("ticker", storage.Ticker{}) + h := s.handler() + h.GetTicker(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestGetTickerUsers() { + s.Run("when ticker not found", func() { + h := s.handler() + h.GetTickerUsers(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when ticker found", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("FindUsersByTicker", mock.Anything, mock.Anything).Return([]storage.User{}, nil).Once() + h := s.handler() + h.GetTickerUsers(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestPostTicker() { + s.Run("when body is invalid", func() { + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers", strings.NewReader(`broken_json`)) + h := s.handler() + h.PostTicker(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + body := `{"domain":"localhost","title":"title","description":"description"}` + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PostTicker(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + body := `{"domain":"localhost","title":"title","description":"description"}` + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/tickers", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + h := s.handler() + h.PostTicker(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestPutTicker() { + s.Run("when ticker not found", func() { + h := s.handler() + h.PutTicker(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when body is invalid", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", nil) + s.ctx.Request.Header.Add("Content-Type", "application/json") + h := s.handler() + h.PutTicker(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + body := `{"domain":"localhost","title":"title","description":"description"}` + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PutTicker(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + body := `{"domain":"localhost","title":"title","description":"description"}` + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + h := s.handler() + h.PutTicker(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestPutTickerUsers() { + s.Run("when ticker not found", func() { + h := s.handler() + h.PutTickerUsers(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when body is invalid", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/user", nil) + s.ctx.Request.Header.Add("Content-Type", "application/json") + h := s.handler() + h.PutTickerUsers(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + body := `{"users":[{"id":1},{"id":2},{"id":3}]}` + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/user", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PutTickerUsers(s.ctx) + + s.Equal(http.StatusInternalServerError, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + body := `{"users":[{"id":1},{"id":2},{"id":3}]}` + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/user", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + h := s.handler() + h.PutTickerUsers(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestPutTickerTelegram() { + s.Run("when ticker not found", func() { + h := s.handler() + h.PutTickerTelegram(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when body is invalid", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/telegram", nil) + s.ctx.Request.Header.Add("Content-Type", "application/json") + h := s.handler() + h.PutTickerTelegram(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + body := `{"active":true,"channelName":"@channel_name"}` + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/telegram", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PutTickerTelegram(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + body := `{"active":true,"channelName":"@channel_name"}` + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/telegram", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + h := s.handler() + h.PutTickerTelegram(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestDeleteTickerTelegram() { + s.Run("when ticker not found", func() { + h := s.handler() + h.DeleteTickerTelegram(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.DeleteTickerTelegram(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + h := s.handler() + h.DeleteTickerTelegram(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestPutTickerMastodon() { + s.Run("when ticker not found", func() { + h := s.handler() + h.PutTickerMastodon(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when body is invalid", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/mastodon", nil) + s.ctx.Request.Header.Add("Content-Type", "application/json") + h := s.handler() + h.PutTickerMastodon(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when mastodon server is not reachable", func() { + s.ctx.Set("ticker", storage.Ticker{}) + body := `{"active":true,"server":"http://localhost","secret":"secret","token":"token","accessToken":"access_token"}` + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/mastodon", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + h := s.handler() + h.PutTickerMastodon(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + account := mastodon.Account{} + json, _ := json.Marshal(account) + w.Write(json) + })) + defer server.Close() + body := fmt.Sprintf(`{"server":"%s","token":"token","secret":"secret","accessToken":"access_toklen"}`, server.URL) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/mastodon", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PutTickerMastodon(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + account := mastodon.Account{} + json, _ := json.Marshal(account) + w.Write(json) + })) + defer server.Close() + body := fmt.Sprintf(`{"server":"%s","token":"token","secret":"secret","accessToken":"access_toklen"}`, server.URL) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/tickers/1/mastodon", strings.NewReader(body)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + h := s.handler() + h.PutTickerMastodon(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestDeleteTickerMastodon() { + s.Run("when ticker not found", func() { + h := s.handler() + h.DeleteTickerMastodon(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.DeleteTickerMastodon(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + h := s.handler() + h.DeleteTickerMastodon(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestDeleteTicker() { + s.Run("when ticker not found", func() { + h := s.handler() + h.DeleteTicker(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("DeleteMessages", mock.Anything).Return(errors.New("storage error")) + s.store.On("DeleteUploadsByTicker", mock.Anything).Return(errors.New("storage error")) + s.store.On("DeleteTicker", mock.Anything).Return(errors.New("storage error")) + h := s.handler() + h.DeleteTicker(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("DeleteMessages", mock.Anything).Return(nil) + s.store.On("DeleteUploadsByTicker", mock.Anything).Return(nil) + s.store.On("DeleteTicker", mock.Anything).Return(nil) + h := s.handler() + h.DeleteTicker(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestDeleteTickerUser() { + s.Run("when ticker not found", func() { + h := s.handler() + h.DeleteTickerUser(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when user param is missing", func() { + s.ctx.Set("ticker", storage.Ticker{}) + h := s.handler() + h.DeleteTickerUser(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when user not found", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.ctx.AddParam("userID", "1") + s.store.On("FindUserByID", mock.Anything).Return(storage.User{}, errors.New("not found")).Once() + h := s.handler() + h.DeleteTickerUser(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.ctx.AddParam("userID", "1") + s.store.On("FindUserByID", mock.Anything).Return(storage.User{}, nil).Once() + s.store.On("DeleteTickerUser", mock.Anything, mock.Anything).Return(errors.New("storage error")) + h := s.handler() + h.DeleteTickerUser(s.ctx) + + s.Equal(http.StatusInternalServerError, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.ctx.AddParam("userID", "1") + s.store.On("FindUserByID", mock.Anything).Return(storage.User{}, nil).Once() + s.store.On("DeleteTickerUser", mock.Anything, mock.Anything).Return(nil) + h := s.handler() + h.DeleteTickerUser(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) TestResetTicker() { + s.Run("when ticker not found", func() { + h := s.handler() + h.ResetTicker(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns error", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("DeleteMessages", mock.Anything).Return(errors.New("storage error")).Once() + s.store.On("DeleteUploadsByTicker", mock.Anything).Return(errors.New("storage error")).Once() + s.store.On("SaveTicker", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.ResetTicker(s.ctx) + + s.Equal(http.StatusInternalServerError, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when deleting users fails", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("DeleteMessages", mock.Anything).Return(nil).Once() + s.store.On("DeleteUploadsByTicker", mock.Anything).Return(nil).Once() + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + s.store.On("DeleteTickerUsers", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.ResetTicker(s.ctx) + + s.Equal(http.StatusInternalServerError, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns ticker", func() { + s.ctx.Set("ticker", storage.Ticker{}) + s.store.On("DeleteMessages", mock.Anything).Return(nil).Once() + s.store.On("DeleteUploadsByTicker", mock.Anything).Return(nil).Once() + s.store.On("SaveTicker", mock.Anything).Return(nil).Once() + s.store.On("DeleteTickerUsers", mock.Anything).Return(nil).Once() + h := s.handler() + h.ResetTicker(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) +} + +func (s *TickerTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, + } +} + +func TestTickerTestSuite(t *testing.T) { + suite.Run(t, new(TickerTestSuite)) } diff --git a/internal/api/timeline_test.go b/internal/api/timeline_test.go index d70b0b42..f017a6af 100644 --- a/internal/api/timeline_test.go +++ b/internal/api/timeline_test.go @@ -7,99 +7,78 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/api/response" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { - gin.SetMode(gin.TestMode) +type TimelineTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite } -func TestGetTimelineMissingDomain(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTimeline(c) - - assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), response.TickerNotFound) +func (s *TimelineTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) } -func TestGetTimelineTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) - c.Request.Header.Add("Origin", "https://demoticker.org") +func (s *TimelineTestSuite) Run(name string, subtest func()) { + s.T().Run(name, func(t *testing.T) { + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.store = &storage.MockStorage{} + s.cfg = config.LoadConfig("") - s := &storage.MockStorage{} - s.On("FindTickerByDomain", mock.Anything).Return(storage.Ticker{}, errors.New("not found")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTimeline(c) - - assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), response.TickerNotFound) + subtest() + }) } -func TestGetTimelineTickerInactive(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{Active: false}) - s := &storage.MockStorage{} - - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTimeline(c) - - assert.Equal(t, http.StatusOK, w.Code) +func (s *TimelineTestSuite) TestGetTimeline() { + s.Run("when ticker is missing", func() { + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) + h := s.handler() + h.GetTimeline(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.Contains(s.w.Body.String(), response.TickerNotFound) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns an error", func() { + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) + s.ctx.Set("ticker", storage.Ticker{Active: true}) + s.store.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Message{}, errors.New("storage error")).Once() + h := s.handler() + h.GetTimeline(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.Contains(s.w.Body.String(), response.MessageFetchError) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns messages", func() { + s.ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/timeline", nil) + s.ctx.Set("ticker", storage.Ticker{Active: true}) + s.store.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Message{}, nil).Once() + h := s.handler() + h.GetTimeline(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestGetTimelineMessageFetchError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{Active: true}) - s := &storage.MockStorage{} - s.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Message{}, errors.New("storage error")) - - h := handler{ - storage: s, - config: config.LoadConfig(""), +func (s *TimelineTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, } - - h.GetTimeline(c) - - assert.Equal(t, http.StatusOK, w.Code) - assert.Contains(t, w.Body.String(), response.MessageFetchError) } -func TestGetTimeline(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("ticker", storage.Ticker{}) - s := &storage.MockStorage{} - s.On("FindMessagesByTickerAndPagination", mock.Anything, mock.Anything, mock.Anything).Return([]storage.Message{}, nil) - - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetTimeline(c) - - assert.Equal(t, http.StatusOK, w.Code) +func TestTimelineTestSuite(t *testing.T) { + suite.Run(t, new(TimelineTestSuite)) } diff --git a/internal/api/upload_test.go b/internal/api/upload_test.go index 39a76159..18574c4e 100644 --- a/internal/api/upload_test.go +++ b/internal/api/upload_test.go @@ -11,261 +11,234 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { - gin.SetMode(gin.TestMode) -} - -func TestPostUploadForbidden(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestPostUploadMultipartError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - c.Request = httptest.NewRequest(http.MethodPost, "/upload", nil) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostUploadMissingTickerValue(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - writer.CreateFormField("field") - _ = writer.Close() - c.Request = httptest.NewRequest(http.MethodPost, "/upload", body) - c.Request.Header.Add("Content-Type", writer.FormDataContentType()) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostUploadTickerValueWrong(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - writer.CreateFormField("ticker") - _ = writer.Close() - c.Request = httptest.NewRequest(http.MethodPost, "/upload", body) - c.Request.Header.Add("Content-Type", writer.FormDataContentType()) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +type UploadTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite } -func TestPostUploadTickerNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - writer.WriteField("ticker", "1") - _ = writer.Close() - c.Request = httptest.NewRequest(http.MethodPost, "/upload", body) - c.Request.Header.Add("Content-Type", writer.FormDataContentType()) - s := &storage.MockStorage{} - s.On("FindTickerByUserAndID", mock.Anything, mock.Anything).Return(storage.Ticker{}, errors.New("not found")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +func (s *UploadTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) } -func TestPostUploadMissingFiles(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - writer.WriteField("ticker", "1") - _ = writer.Close() - c.Request = httptest.NewRequest(http.MethodPost, "/upload", body) - c.Request.Header.Add("Content-Type", writer.FormDataContentType()) - s := &storage.MockStorage{} - s.On("FindTickerByUserAndID", mock.Anything, mock.Anything).Return(storage.Ticker{}, nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) +func (s *UploadTestSuite) Run(name string, subtest func()) { + s.T().Run(name, func(t *testing.T) { + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.store = &storage.MockStorage{} + s.cfg = config.LoadConfig("") - assert.Equal(t, http.StatusBadRequest, w.Code) + subtest() + }) } -func TestPostUpload(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - writer.WriteField("ticker", "1") - path := "../../testdata/gopher.jpg" - part, err := writer.CreateFormFile("files", filepath.Base(path)) - if err != nil { - t.Error(err) - } - b, err := os.ReadFile(path) - if err != nil { - t.Error(err) - } - part.Write(b) - _ = writer.Close() - c.Request = httptest.NewRequest(http.MethodPost, "/upload", body) - c.Request.Header.Add("Content-Type", writer.FormDataContentType()) - s := &storage.MockStorage{} - s.On("FindTickerByUserAndID", mock.Anything, mock.Anything).Return(storage.Ticker{}, nil) - s.On("SaveUpload", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusOK, w.Code) +func (s *UploadTestSuite) TestPostUpload() { + s.Run("when no user is set", func() { + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when form is invalid", func() { + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", nil) + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when ticker is missing", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.CreateFormField("field") + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when ticker value is invalid", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.CreateFormField("ticker") + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when ticker is not found", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("ticker", "1") + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickerByUserAndID", mock.Anything, 1).Return(storage.Ticker{}, errors.New("not found")).Once() + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when form files are missing", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("ticker", "1") + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickerByUserAndID", mock.Anything, 1).Return(storage.Ticker{}, nil).Once() + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when too much files are uploaded", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("ticker", "1") + path := "../../testdata/gopher.jpg" + part1, _ := writer.CreateFormFile("files", filepath.Base(path)) + part2, _ := writer.CreateFormFile("files", filepath.Base(path)) + part3, _ := writer.CreateFormFile("files", filepath.Base(path)) + part4, _ := writer.CreateFormFile("files", filepath.Base(path)) + b, _ := os.ReadFile(path) + + part1.Write(b) + part2.Write(b) + part3.Write(b) + part4.Write(b) + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickerByUserAndID", mock.Anything, 1).Return(storage.Ticker{}, nil).Once() + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when file type is not allowed", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("ticker", "1") + path := "./api.go" + part, _ := writer.CreateFormFile("files", filepath.Base(path)) + b, _ := os.ReadFile(path) + part.Write(b) + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickerByUserAndID", mock.Anything, 1).Return(storage.Ticker{}, nil).Once() + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when file is gif", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("ticker", "1") + path := "../../testdata/gopher-dance.gif" + part, _ := writer.CreateFormFile("files", filepath.Base(path)) + b, _ := os.ReadFile(path) + part.Write(b) + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickerByUserAndID", mock.Anything, 1).Return(storage.Ticker{}, nil).Once() + s.store.On("SaveUpload", mock.Anything).Return(nil).Once() + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when save returns an error", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("ticker", "1") + path := "../../testdata/gopher.jpg" + part, _ := writer.CreateFormFile("files", filepath.Base(path)) + b, _ := os.ReadFile(path) + part.Write(b) + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickerByUserAndID", mock.Anything, 1).Return(storage.Ticker{}, nil).Once() + s.store.On("SaveUpload", mock.Anything).Return(errors.New("save error")).Once() + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when save is successful", func() { + body := new(bytes.Buffer) + writer := multipart.NewWriter(body) + writer.WriteField("ticker", "1") + path := "../../testdata/gopher.jpg" + part, _ := writer.CreateFormFile("files", filepath.Base(path)) + b, _ := os.ReadFile(path) + part.Write(b) + _ = writer.Close() + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/upload", body) + s.ctx.Request.Header.Add("Content-Type", writer.FormDataContentType()) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindTickerByUserAndID", mock.Anything, 1).Return(storage.Ticker{}, nil).Once() + s.store.On("SaveUpload", mock.Anything).Return(nil).Once() + h := s.handler() + h.PostUpload(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestPostUploadGIF(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - writer.WriteField("ticker", "1") - path := "../../testdata/gopher-dance.gif" - part, err := writer.CreateFormFile("files", filepath.Base(path)) - if err != nil { - t.Error(err) - } - b, err := os.ReadFile(path) - if err != nil { - t.Error(err) +func (s *UploadTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, } - part.Write(b) - _ = writer.Close() - c.Request = httptest.NewRequest(http.MethodPost, "/upload", body) - c.Request.Header.Add("Content-Type", writer.FormDataContentType()) - s := &storage.MockStorage{} - s.On("FindTickerByUserAndID", mock.Anything, mock.Anything).Return(storage.Ticker{}, nil) - s.On("SaveUpload", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusOK, w.Code) } -func TestPostUploadTooMuchFiles(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - writer.WriteField("ticker", "1") - path := "../../testdata/gopher.jpg" - part1, _ := writer.CreateFormFile("files", filepath.Base(path)) - part2, _ := writer.CreateFormFile("files", filepath.Base(path)) - part3, _ := writer.CreateFormFile("files", filepath.Base(path)) - part4, _ := writer.CreateFormFile("files", filepath.Base(path)) - b, _ := os.ReadFile(path) - - part1.Write(b) - part2.Write(b) - part3.Write(b) - part4.Write(b) - _ = writer.Close() - c.Request = httptest.NewRequest(http.MethodPost, "/upload", body) - c.Request.Header.Add("Content-Type", writer.FormDataContentType()) - s := &storage.MockStorage{} - s.On("FindTickerByUserAndID", mock.Anything, mock.Anything).Return(storage.Ticker{}, nil) - s.On("SaveUpload", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostUploadForbiddenFileType(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - body := new(bytes.Buffer) - writer := multipart.NewWriter(body) - writer.WriteField("ticker", "1") - path := "./api.go" - part, err := writer.CreateFormFile("files", filepath.Base(path)) - if err != nil { - t.Error(err) - } - b, err := os.ReadFile(path) - if err != nil { - t.Error(err) - } - part.Write(b) - _ = writer.Close() - c.Request = httptest.NewRequest(http.MethodPost, "/upload", body) - c.Request.Header.Add("Content-Type", writer.FormDataContentType()) - s := &storage.MockStorage{} - s.On("FindTickerByUserAndID", mock.Anything, mock.Anything).Return(storage.Ticker{}, nil) - s.On("SaveUpload", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUpload(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +func TestUploadTestSuite(t *testing.T) { + suite.Run(t, new(UploadTestSuite)) } diff --git a/internal/api/user_test.go b/internal/api/user_test.go index 2d16fbcc..935dab0e 100644 --- a/internal/api/user_test.go +++ b/internal/api/user_test.go @@ -8,446 +8,304 @@ import ( "testing" "github.com/gin-gonic/gin" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/config" "github.com/systemli/ticker/internal/storage" ) -func init() { - gin.SetMode(gin.TestMode) -} - -func TestGetUsersStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - s := &storage.MockStorage{} - s.On("FindUsers", mock.Anything).Return([]storage.User{}, errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetUsers(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetUsers(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{IsSuperAdmin: true}) - s := &storage.MockStorage{} - s.On("FindUsers", mock.Anything).Return([]storage.User{}, nil) - - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetUsers(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestGetUserMissingParam(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetUser(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetUserInsufficentPermission(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("user", storage.User{ID: 1}) - c.Set("me", storage.User{ID: 2, IsSuperAdmin: false}) - - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetUser(c) - - assert.Equal(t, http.StatusForbidden, w.Code) -} - -func TestGetUserStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.AddParam("userID", "1") - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - - s := &storage.MockStorage{} - s.On("FindUserByID", mock.Anything, mock.Anything).Return(storage.User{}, errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetUser(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestGetUserMissingPermission(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("user", storage.User{ID: 2}) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: false}) - - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetUser(c) - - assert.Equal(t, http.StatusForbidden, w.Code) -} - -func TestGetUser(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("user", storage.User{ID: 1}) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: false}) - - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.GetUser(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPostUserMissingBody(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Request = &http.Request{} - - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUser(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostUserTooLongPassword(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - json := `{"email":"louis@systemli.org","password":"swusp-dud-gust-grong-yuz-swuft-plaft-glact-skast-swem-yen-kom-tut-prisp-gont"}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUser(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostUserStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - json := `{"email":"louis@systemli.org","password":"password1234"}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - s := &storage.MockStorage{} - s.On("SaveUser", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUser(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPostUser(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - json := `{"email":"louis@systemli.org","password":"password1234"}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - s := &storage.MockStorage{} - s.On("SaveUser", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PostUser(c) - - assert.Equal(t, http.StatusOK, w.Code) -} - -func TestPutUserNotFound(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutUser(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestPutUserMissingBody(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - c.Set("user", storage.User{}) - body := `broken_json` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(body)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutUser(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) -} - -func TestPutUserStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - c.Set("user", storage.User{}) - json := `{"email":"louis@systemli.org","password":"password1234","isSuperAdmin":true,"tickers":[{"id":1}]}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("FindTickersByIDs", mock.Anything).Return([]storage.Ticker{}, nil) - s.On("SaveUser", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutUser(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +type UserTestSuite struct { + w *httptest.ResponseRecorder + ctx *gin.Context + store *storage.MockStorage + cfg config.Config + suite.Suite } -func TestPutUserStorageError2(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - c.Set("user", storage.User{}) - json := `{"email":"louis@systemli.org","password":"password1234","isSuperAdmin":true,"tickers":[{"id":1}]}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("FindTickersByIDs", mock.Anything).Return([]storage.Ticker{}, errors.New("storage error")) - s.On("SaveUser", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutUser(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +func (s *UserTestSuite) SetupTest() { + gin.SetMode(gin.TestMode) } -func TestPutUser(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - c.Set("user", storage.User{}) - json := `{"email":"louis@systemli.org","password":"password1234","isSuperAdmin":true,"tickers":[{"id":1}]}` - c.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("FindTickersByIDs", mock.Anything).Return([]storage.Ticker{{ID: 1}}, nil) - s.On("SaveUser", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.PutUser(c) +func (s *UserTestSuite) Run(name string, subtest func()) { + s.T().Run(name, func(t *testing.T) { + s.w = httptest.NewRecorder() + s.ctx, _ = gin.CreateTestContext(s.w) + s.store = &storage.MockStorage{} + s.cfg = config.LoadConfig("") - assert.Equal(t, http.StatusOK, w.Code) + subtest() + }) } -func TestDeleteUserMissingParam(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteUser(c) - - assert.Equal(t, http.StatusNotFound, w.Code) +func (s *UserTestSuite) TestGetUsers() { + s.Run("when storage returns an error", func() { + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindUsers", mock.Anything).Return([]storage.User{}, errors.New("storage error")).Once() + h := s.handler() + h.GetUsers(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns users", func() { + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("FindUsers", mock.Anything).Return([]storage.User{}, nil).Once() + h := s.handler() + h.GetUsers(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestDeleteUserSelfUser(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - c.Set("user", storage.User{ID: 1}) - s := &storage.MockStorage{} - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteUser(c) - - assert.Equal(t, http.StatusBadRequest, w.Code) +func (s *UserTestSuite) TestGetUser() { + s.Run("when user is missing", func() { + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + h := s.handler() + h.GetUser(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when insufficient permissions", func() { + s.ctx.Set("user", storage.User{ID: 1}) + s.ctx.Set("me", storage.User{ID: 2, IsSuperAdmin: false}) + h := s.handler() + h.GetUser(s.ctx) + + s.Equal(http.StatusForbidden, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("returns user", func() { + s.ctx.Set("user", storage.User{ID: 1}) + s.ctx.Set("me", storage.User{ID: 1, IsSuperAdmin: false}) + h := s.handler() + h.GetUser(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestDeleteUserStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - c.Set("user", storage.User{ID: 2}) - s := &storage.MockStorage{} - s.On("DeleteUser", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteUser(c) - - assert.Equal(t, http.StatusNotFound, w.Code) -} - -func TestDeleteUser(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) - c.Set("user", storage.User{ID: 2}) - s := &storage.MockStorage{} - s.On("DeleteUser", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - - h.DeleteUser(c) - - assert.Equal(t, http.StatusOK, w.Code) +func (s *UserTestSuite) TestPostUser() { + s.Run("when body is missing", func() { + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", nil) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + h := s.handler() + h.PostUser(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when body is invalid", func() { + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader("invalid")) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + h := s.handler() + h.PostUser(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when password is too long", func() { + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(`{"email":"user@systemli.org","password":"swusp-dud-gust-grong-yuz-swuft-plaft-glact-skast-swem-yen-kom-tut-prisp-gont"}`)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + h := s.handler() + h.PostUser(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns an error", func() { + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(`{"email":"user@systemli.org","password":"password1234"}`)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("SaveUser", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PostUser(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when save is successful", func() { + s.ctx.Request = httptest.NewRequest(http.MethodPost, "/v1/admin/users", strings.NewReader(`{"email":"user@systemli.org","password":"password1234"}`)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.store.On("SaveUser", mock.Anything).Return(nil).Once() + h := s.handler() + h.PostUser(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestPutMeUnauthenticated(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - json := `{"password":"password1234","newPassword":"password5678"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveUser", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - h.PutMe(c) - assert.Equal(t, http.StatusForbidden, w.Code) +func (s *UserTestSuite) TestPutUser() { + s.Run("when user is missing", func() { + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + h := s.handler() + h.PutUser(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when body is invalid", func() { + s.ctx.Set("user", storage.User{ID: 1}) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users", strings.NewReader("invalid")) + s.ctx.Request.Header.Add("Content-Type", "application/json") + h := s.handler() + h.PutUser(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when tickers are missing", func() { + s.ctx.Set("user", storage.User{ID: 1}) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users", strings.NewReader(`{"email":"louis@systemli.org","password":"password1234","isSuperAdmin":true,"tickers":[{"id":1}]}`)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveUser", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PutUser(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when save is successful", func() { + s.ctx.Set("user", storage.User{ID: 1}) + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users", strings.NewReader(`{"email":"louis@systemli.org","password":"password1234","isSuperAdmin":true,"tickers":[{"id":1}]}`)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveUser", mock.Anything).Return(nil).Once() + h := s.handler() + h.PutUser(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestPutMeFormError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, EncryptedPassword: "$2a$10$3rj/kzMI7gKPoBtJFG55tuzA.RQGYqbYQdM69LPyU.2YkGbkRu.T2"}) - json := `{"wrongparameter":"password1234","newPassword":"password5678"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveUser", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - h.PutMe(c) - assert.Equal(t, http.StatusBadRequest, w.Code) +func (s *UserTestSuite) TestDeleteUser() { + s.Run("when user is missing", func() { + s.ctx.Set("me", storage.User{IsSuperAdmin: true}) + h := s.handler() + h.DeleteUser(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when user is self", func() { + s.ctx.Set("user", storage.User{ID: 1}) + s.ctx.Set("me", storage.User{ID: 1, IsSuperAdmin: true}) + h := s.handler() + h.DeleteUser(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns an error", func() { + s.ctx.Set("user", storage.User{ID: 1}) + s.ctx.Set("me", storage.User{ID: 2, IsSuperAdmin: true}) + s.store.On("DeleteUser", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.DeleteUser(s.ctx) + + s.Equal(http.StatusNotFound, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when delete is successful", func() { + s.ctx.Set("user", storage.User{ID: 1}) + s.ctx.Set("me", storage.User{ID: 2, IsSuperAdmin: true}) + s.store.On("DeleteUser", mock.Anything).Return(nil).Once() + h := s.handler() + h.DeleteUser(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestPutMeWrongPassword(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, EncryptedPassword: "$2a$10$3rj/kzMI7gKPoBtJFG55tuzA.RQGYqbYQdM69LPyU.2YkGbkRu.T2"}) - json := `{"password":"wrongpassword","newPassword":"password5678"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveUser", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - h.PutMe(c) - assert.Equal(t, http.StatusBadRequest, w.Code) +func (s *UserTestSuite) TestPutMe() { + s.Run("when user is missing", func() { + h := s.handler() + h.PutMe(s.ctx) + + s.Equal(http.StatusForbidden, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when form is invalid", func() { + user, _ := storage.NewUser("user@systemli.org", "password1234") + s.ctx.Set("me", user) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader("invalid")) + s.ctx.Request.Header.Add("Content-Type", "application/json") + h := s.handler() + h.PutMe(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when password is wrong", func() { + user, _ := storage.NewUser("user@systemli.org", "password1234") + s.ctx.Set("me", user) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader(`{"password":"wrongpassword","newPassword":"password5678"}`)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + h := s.handler() + h.PutMe(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when storage returns an error", func() { + user, _ := storage.NewUser("user@systemli.org", "password1234") + s.ctx.Set("me", user) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader(`{"password":"password1234","newPassword":"password5678"}`)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveUser", mock.Anything).Return(errors.New("storage error")).Once() + h := s.handler() + h.PutMe(s.ctx) + + s.Equal(http.StatusBadRequest, s.w.Code) + s.store.AssertExpectations(s.T()) + }) + + s.Run("when save is successful", func() { + user, _ := storage.NewUser("user@systemli.org", "password1234") + s.ctx.Set("me", user) + s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader(`{"password":"password1234","newPassword":"password5678"}`)) + s.ctx.Request.Header.Add("Content-Type", "application/json") + s.store.On("SaveUser", mock.Anything).Return(nil).Once() + h := s.handler() + h.PutMe(s.ctx) + + s.Equal(http.StatusOK, s.w.Code) + s.store.AssertExpectations(s.T()) + }) } -func TestPutMeStorageError(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, EncryptedPassword: "$2a$10$3rj/kzMI7gKPoBtJFG55tuzA.RQGYqbYQdM69LPyU.2YkGbkRu.T2"}) - json := `{"password":"password1234","newPassword":"password5678"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveUser", mock.Anything).Return(errors.New("storage error")) - h := handler{ - storage: s, - config: config.LoadConfig(""), +func (s *UserTestSuite) handler() handler { + return handler{ + storage: s.store, + config: s.cfg, } - h.PutMe(c) - assert.Equal(t, http.StatusBadRequest, w.Code) } -func TestPutMeOk(t *testing.T) { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("me", storage.User{ID: 1, EncryptedPassword: "$2a$10$3rj/kzMI7gKPoBtJFG55tuzA.RQGYqbYQdM69LPyU.2YkGbkRu.T2"}) - json := `{"password":"password1234","newPassword":"password5678"}` - c.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/users/me", strings.NewReader(json)) - c.Request.Header.Add("Content-Type", "application/json") - s := &storage.MockStorage{} - s.On("SaveUser", mock.Anything).Return(nil) - h := handler{ - storage: s, - config: config.LoadConfig(""), - } - h.PutMe(c) - assert.Equal(t, http.StatusOK, w.Code) +func TestUserTestSuite(t *testing.T) { + suite.Run(t, new(UserTestSuite)) } diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go index 1d758828..2c49da47 100644 --- a/internal/cache/cache_test.go +++ b/internal/cache/cache_test.go @@ -4,72 +4,107 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) -func TestCache(t *testing.T) { +type CacheTestSuite struct { + suite.Suite +} + +func TestCacheTestSuite(t *testing.T) { + suite.Run(t, new(CacheTestSuite)) +} + +func (s *CacheTestSuite) TestCache() { interval := 100 * time.Microsecond c := NewCache(interval) defer c.Close() - c.Set("foo", "bar", 0) - c.Set("baz", "qux", interval/2) - - baz, found := c.Get("baz") - assert.True(t, found) - assert.Equal(t, "qux", baz) - - time.Sleep(interval / 2) - - _, found = c.Get("baz") - assert.False(t, found) - - time.Sleep(interval) - - _, found = c.Get("404") - assert.False(t, found) + s.Run("Set", func() { + s.Run("adds value to cache", func() { + c.Set("foo", "bar", 0) + foo, found := c.Get("foo") + s.True(found) + s.Equal("bar", foo) + }) + + s.Run("add value to cache with expiration", func() { + c.Set("foo", "bar", interval/2) + foo, found := c.Get("foo") + s.True(found) + s.Equal("bar", foo) + + time.Sleep(interval) + + foo, found = c.Get("foo") + s.False(found) + s.Empty(foo) + }) + }) - foo, found := c.Get("foo") - assert.True(t, found) - assert.Equal(t, "bar", foo) -} + s.Run("Get", func() { + s.Run("returns empty value if not found", func() { + foo, found := c.Get("foo") + s.False(found) + s.Empty(foo) + }) + + s.Run("returns empty value if expired", func() { + c.Set("foo", "bar", interval/2) + foo, found := c.Get("foo") + s.True(found) + s.Equal("bar", foo) + + time.Sleep(interval) + + foo, found = c.Get("foo") + s.False(found) + s.Empty(foo) + }) + }) -func TestDelete(t *testing.T) { - c := NewCache(time.Minute) - c.Set("foo", "bar", time.Hour) + s.Run("Delete", func() { + s.Run("removes value from cache", func() { + c.Set("foo", "bar", 0) + foo, found := c.Get("foo") + s.True(found) + s.Equal("bar", foo) - _, found := c.Get("foo") - assert.True(t, found) + c.Delete("foo") + foo, found = c.Get("foo") + s.False(found) + s.Empty(foo) + }) + }) - c.Delete("foo") + s.Run("Range", func() { + s.Run("iterates over all values in cache", func() { + c.Set("foo", "bar", 0) + c.Set("bar", "baz", 0) - _, found = c.Get("foo") - assert.False(t, found) -} + count := 0 + c.Range(func(key, value interface{}) bool { + count++ + return true + }) -func TestRange(t *testing.T) { - c := NewCache(time.Minute) - c.Set("foo", "bar", time.Hour) - c.Set("baz", "qux", time.Hour) + s.Equal(2, count) + }) - count := 0 - c.Range(func(key, value interface{}) bool { - count++ - return true - }) - assert.Equal(t, 2, count) -} + s.Run("iterates not over expired values", func() { + c.Set("foo", "bar", interval/2) + c.Set("bar", "baz", 0) -func TestRangeTimer(t *testing.T) { - c := NewCache(time.Minute) - c.Set("foo", "bar", time.Nanosecond) - c.Set("baz", "qux", time.Nanosecond) + time.Sleep(interval) - time.Sleep(time.Microsecond) + count := 0 + c.Range(func(key, value interface{}) bool { + count++ + return true + }) - c.Range(func(key, value interface{}) bool { - assert.Fail(t, "should not be called") - return true + s.Equal(1, count) + }) }) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 3341e41a..6285376e 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,23 +1,23 @@ package config import ( + "io" "os" "path/filepath" "testing" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" ) -func TestConfig(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Config Suite") +type ConfigTestSuite struct { + envs map[string]string + suite.Suite } -var _ = Describe("Config", func() { - log.Logger.SetOutput(GinkgoWriter) +func (s *ConfigTestSuite) SetupTest() { + log.Logger.SetOutput(io.Discard) - var envs map[string]string = map[string]string{ + s.envs = map[string]string{ "TICKER_LISTEN": ":7070", "TICKER_LOG_LEVEL": "trace", "TICKER_LOG_FORMAT": "text", @@ -29,81 +29,85 @@ var _ = Describe("Config", func() { "TICKER_UPLOAD_URL": "https://example.com", "TICKER_TELEGRAM_TOKEN": "token", } +} - Describe("LoadConfig", func() { - BeforeEach(func() { - for key := range envs { - os.Unsetenv(key) - } - }) - - Context("when path is empty", func() { - It("loads config with default values", func() { +func (s *ConfigTestSuite) TestConfig() { + s.Run("LoadConfig", func() { + s.Run("when path is empty", func() { + s.Run("loads config with default values", func() { c := LoadConfig("") - Expect(c.Listen).To(Equal(":8080")) - Expect(c.LogLevel).To(Equal("debug")) - Expect(c.LogFormat).To(Equal("json")) - Expect(c.Secret).ToNot(BeEmpty()) - Expect(c.Database.Type).To(Equal("sqlite")) - Expect(c.Database.DSN).To(Equal("ticker.db")) - Expect(c.MetricsListen).To(Equal(":8181")) - Expect(c.Upload.Path).To(Equal("uploads")) - Expect(c.Upload.URL).To(Equal("http://localhost:8080")) - Expect(c.Telegram.Token).To(BeEmpty()) - Expect(c.Telegram.Enabled()).To(BeFalse()) + s.Equal(":8080", c.Listen) + s.Equal("debug", c.LogLevel) + s.Equal("json", c.LogFormat) + s.NotEmpty(c.Secret) + s.Equal("sqlite", c.Database.Type) + s.Equal("ticker.db", c.Database.DSN) + s.Equal(":8181", c.MetricsListen) + s.Equal("uploads", c.Upload.Path) + s.Equal("http://localhost:8080", c.Upload.URL) + s.Empty(c.Telegram.Token) + s.False(c.Telegram.Enabled()) }) - It("loads config from env", func() { - for key, value := range envs { + s.Run("loads config from env", func() { + for key, value := range s.envs { err := os.Setenv(key, value) - Expect(err).ToNot(HaveOccurred()) + s.NoError(err) } c := LoadConfig("") - Expect(c.Listen).To(Equal(envs["TICKER_LISTEN"])) - Expect(c.LogLevel).To(Equal(envs["TICKER_LOG_LEVEL"])) - Expect(c.LogFormat).To(Equal(envs["TICKER_LOG_FORMAT"])) - Expect(c.Secret).To(Equal(envs["TICKER_SECRET"])) - Expect(c.Database.Type).To(Equal(envs["TICKER_DATABASE_TYPE"])) - Expect(c.Database.DSN).To(Equal(envs["TICKER_DATABASE_DSN"])) - Expect(c.MetricsListen).To(Equal(envs["TICKER_METRICS_LISTEN"])) - Expect(c.Upload.Path).To(Equal(envs["TICKER_UPLOAD_PATH"])) - Expect(c.Upload.URL).To(Equal(envs["TICKER_UPLOAD_URL"])) - Expect(c.Telegram.Token).To(Equal(envs["TICKER_TELEGRAM_TOKEN"])) - Expect(c.Telegram.Enabled()).To(BeTrue()) + s.Equal(s.envs["TICKER_LISTEN"], c.Listen) + s.Equal(s.envs["TICKER_LOG_LEVEL"], c.LogLevel) + s.Equal(s.envs["TICKER_LOG_FORMAT"], c.LogFormat) + s.Equal(s.envs["TICKER_SECRET"], c.Secret) + s.Equal(s.envs["TICKER_DATABASE_TYPE"], c.Database.Type) + s.Equal(s.envs["TICKER_DATABASE_DSN"], c.Database.DSN) + s.Equal(s.envs["TICKER_METRICS_LISTEN"], c.MetricsListen) + s.Equal(s.envs["TICKER_UPLOAD_PATH"], c.Upload.Path) + s.Equal(s.envs["TICKER_UPLOAD_URL"], c.Upload.URL) + s.Equal(s.envs["TICKER_TELEGRAM_TOKEN"], c.Telegram.Token) + s.True(c.Telegram.Enabled()) + + for key := range s.envs { + os.Unsetenv(key) + } }) }) - Context("when path is not empty", func() { - Context("when path is absolute", func() { - It("loads config from file", func() { + s.Run("when path is not empty", func() { + s.Run("when path is absolute", func() { + s.Run("loads config from file", func() { path, err := filepath.Abs("../../testdata/config_valid.yml") - Expect(err).ToNot(HaveOccurred()) + s.NoError(err) c := LoadConfig(path) - Expect(c.Listen).To(Equal("127.0.0.1:8888")) + s.Equal("127.0.0.1:8888", c.Listen) }) }) - Context("when path is relative", func() { - It("loads config from file", func() { + s.Run("when path is relative", func() { + s.Run("loads config from file", func() { c := LoadConfig("../../testdata/config_valid.yml") - Expect(c.Listen).To(Equal("127.0.0.1:8888")) + s.Equal("127.0.0.1:8888", c.Listen) }) }) - Context("when file does not exist", func() { - It("loads config with default values", func() { + s.Run("when file does not exist", func() { + s.Run("loads config with default values", func() { c := LoadConfig("config_notfound.yml") - Expect(c.Listen).To(Equal(":8080")) + s.Equal(":8080", c.Listen) }) }) - Context("when file is invalid", func() { - It("loads config with default values", func() { + s.Run("when file is invalid", func() { + s.Run("loads config with default values", func() { c := LoadConfig("../../testdata/config_invalid.txt") - Expect(c.Listen).To(Equal(":8080")) + s.Equal(":8080", c.Listen) }) }) }) }) -}) +} + +func TestConfig(t *testing.T) { + suite.Run(t, new(ConfigTestSuite)) +} diff --git a/internal/legacy/migration_test.go b/internal/legacy/migration_test.go index a195d6a6..c56f6ef4 100644 --- a/internal/legacy/migration_test.go +++ b/internal/legacy/migration_test.go @@ -6,36 +6,43 @@ import ( "time" "github.com/asdine/storm" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" "github.com/systemli/ticker/internal/storage" "gorm.io/driver/sqlite" "gorm.io/gorm" ) -func TestMigration(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "Migration Suite") +type MigrationTestSuite struct { + oldDb *storm.DB + newDb *gorm.DB + migration *Migration + err error + + ticker Ticker + message Message + attachment Attachment + adminUser User + user User + upload Upload + refreshIntervalSetting Setting + inactiveSettingsSetting Setting + + suite.Suite } -var _ = Describe("Migration", func() { - var ( - oldDb *storm.DB - newDb *gorm.DB - migration *Migration - err error - - ticker Ticker - message Message - attachment Attachment - adminUser User - user User - upload Upload - refreshIntervalSetting Setting - inactiveSettingsSetting Setting - ) - - ticker = Ticker{ +func (s *MigrationTestSuite) SetupTest() { + s.oldDb, s.err = storm.Open("storm.db", storm.BoltOptions(0600, nil)) + s.NoError(s.err) + + s.newDb, s.err = gorm.Open(sqlite.Open("file:testdatabase?mode=memory&cache=shared"), &gorm.Config{}) + s.NoError(s.err) + s.NoError(storage.MigrateDB(s.newDb)) + + oldStorage := NewLegacyStorage(s.oldDb) + newStorage := storage.NewSqlStorage(s.newDb, "testdata/uploads") + s.migration = NewMigration(oldStorage, newStorage) + + s.ticker = Ticker{ ID: 161, CreationDate: time.Now(), Active: true, @@ -53,28 +60,28 @@ var _ = Describe("Migration", func() { }, } - attachment = Attachment{ + s.attachment = Attachment{ UUID: "uuid", Extension: "png", ContentType: "image/png", } - message = Message{ + s.message = Message{ ID: 1, CreationDate: time.Now(), Ticker: 161, Text: "Message", - Attachments: []Attachment{attachment}, + Attachments: []Attachment{s.attachment}, } - adminUser = User{ + s.adminUser = User{ ID: 1, Email: "admin@systemli.org", CreationDate: time.Now(), IsSuperAdmin: true, } - user = User{ + s.user = User{ ID: 2, Email: "user@systemli.org", CreationDate: time.Now(), @@ -82,7 +89,7 @@ var _ = Describe("Migration", func() { Tickers: []int{161}, } - upload = Upload{ + s.upload = Upload{ ID: 1, CreationDate: time.Now(), TickerID: 161, @@ -92,13 +99,13 @@ var _ = Describe("Migration", func() { Path: "testdata/uploads", } - refreshIntervalSetting = Setting{ + s.refreshIntervalSetting = Setting{ ID: 1, Name: "refresh_interval", Value: 10000, } - inactiveSettingsSetting = Setting{ + s.inactiveSettingsSetting = Setting{ ID: 2, Name: "inactive_settings", Value: map[string]string{ @@ -111,100 +118,84 @@ var _ = Describe("Migration", func() { }, } - BeforeEach(func() { - oldDb, err = storm.Open("storm.db", storm.BoltOptions(0600, nil)) - Expect(err).ToNot(HaveOccurred()) - - newDb, err = gorm.Open(sqlite.Open("file:testdatabase?mode=memory&cache=shared"), &gorm.Config{}) - Expect(err).ToNot(HaveOccurred()) - Expect(storage.MigrateDB(newDb)).To(Succeed()) - - oldStorage := NewLegacyStorage(oldDb) - newStorage := storage.NewSqlStorage(newDb, "testdata/uploads") - migration = NewMigration(oldStorage, newStorage) - - Expect(oldDb.Save(&ticker)).To(Succeed()) - Expect(oldDb.Save(&message)).To(Succeed()) - Expect(oldDb.Save(&adminUser)).To(Succeed()) - Expect(oldDb.Save(&user)).To(Succeed()) - Expect(oldDb.Save(&upload)).To(Succeed()) - Expect(oldDb.Save(&refreshIntervalSetting)).To(Succeed()) - Expect(oldDb.Save(&inactiveSettingsSetting)).To(Succeed()) - }) - - AfterEach(func() { - Expect(oldDb.Close()).To(Succeed()) - Expect(os.Remove("storm.db")).To(Succeed()) - }) - - Describe("Do", func() { - It("migrates all the data successfully", func() { - err = migration.Do() - Expect(err).ToNot(HaveOccurred()) - - var tickers []storage.Ticker - Expect(newDb.Find(&tickers).Error).ToNot(HaveOccurred()) - Expect(tickers).To(HaveLen(1)) - Expect(tickers[0].ID).To(Equal(ticker.ID)) - Expect(tickers[0].CreatedAt).Should(BeTemporally("~", ticker.CreationDate, time.Second)) - Expect(tickers[0].UpdatedAt).Should(BeTemporally("~", ticker.CreationDate, time.Second)) - Expect(tickers[0].Active).To(BeTrue()) - - var telegram storage.TickerTelegram - Expect(newDb.First(&telegram).Error).ToNot(HaveOccurred()) - Expect(telegram.Active).To(BeTrue()) - - var mastodon storage.TickerMastodon - Expect(newDb.First(&mastodon).Error).ToNot(HaveOccurred()) - Expect(mastodon.Active).To(BeTrue()) - - var users []storage.User - Expect(newDb.Find(&users).Error).ToNot(HaveOccurred()) - Expect(users).To(HaveLen(2)) - Expect(users[0].ID).To(Equal(adminUser.ID)) - Expect(users[0].CreatedAt).Should(BeTemporally("~", adminUser.CreationDate, time.Second)) - Expect(users[0].Email).To(Equal(adminUser.Email)) - Expect(users[0].IsSuperAdmin).To(BeTrue()) - Expect(users[1].ID).To(Equal(user.ID)) - Expect(users[1].CreatedAt).Should(BeTemporally("~", user.CreationDate, time.Second)) - Expect(users[1].Email).To(Equal(user.Email)) - Expect(users[1].IsSuperAdmin).To(BeFalse()) - - var tickersUsers []storage.User - Expect(newDb.Model(&tickers[0]).Association("Users").Find(&tickersUsers)).To(Succeed()) - Expect(tickersUsers).To(HaveLen(1)) - Expect(tickersUsers[0].Email).To(Equal(user.Email)) - - var messages []storage.Message - Expect(newDb.Find(&messages).Error).ToNot(HaveOccurred()) - Expect(messages).To(HaveLen(1)) - Expect(messages[0].ID).To(Equal(message.ID)) - Expect(messages[0].TickerID).To(Equal(message.Ticker)) - Expect(messages[0].Text).To(Equal(message.Text)) - Expect(messages[0].CreatedAt).Should(BeTemporally("~", message.CreationDate, time.Second)) - - var attachments []storage.Attachment - Expect(newDb.Find(&attachments).Error).ToNot(HaveOccurred()) - Expect(attachments).To(HaveLen(1)) - Expect(attachments[0].MessageID).To(Equal(message.ID)) - Expect(attachments[0].UUID).To(Equal(attachment.UUID)) - Expect(attachments[0].CreatedAt).Should(BeTemporally("~", message.CreationDate, time.Second)) - - var uploads []storage.Upload - Expect(newDb.Find(&uploads).Error).ToNot(HaveOccurred()) - Expect(uploads).To(HaveLen(1)) - Expect(uploads[0].ID).To(Equal(upload.ID)) - Expect(uploads[0].CreatedAt).Should(BeTemporally("~", upload.CreationDate, time.Second)) - Expect(uploads[0].TickerID).To(Equal(upload.TickerID)) - Expect(uploads[0].UUID).To(Equal(upload.UUID)) - - var settings []storage.Setting - Expect(newDb.Find(&settings).Error).ToNot(HaveOccurred()) - Expect(settings).To(HaveLen(2)) - Expect(settings[0].Name).To(Equal(refreshIntervalSetting.Name)) - Expect(settings[0].Value).To(Equal(`{"refreshInterval":10000}`)) - Expect(settings[1].Name).To(Equal(inactiveSettingsSetting.Name)) - Expect(settings[1].Value).To(Equal(`{"headline":"Headline","subHeadline":"Subheadline","description":"Description","author":"Author","email":"Email","homepage":"","twitter":"Twitter"}`)) - }) - }) -}) + s.NoError(s.oldDb.Save(&s.ticker)) + s.NoError(s.oldDb.Save(&s.message)) + s.NoError(s.oldDb.Save(&s.adminUser)) + s.NoError(s.oldDb.Save(&s.user)) + s.NoError(s.oldDb.Save(&s.upload)) + s.NoError(s.oldDb.Save(&s.refreshIntervalSetting)) + s.NoError(s.oldDb.Save(&s.inactiveSettingsSetting)) +} + +func (s *MigrationTestSuite) TearDownTest() { + s.NoError(s.oldDb.Close()) + s.NoError(os.Remove("storm.db")) +} + +func (s *MigrationTestSuite) TestDo() { + s.err = s.migration.Do() + s.NoError(s.err) + + var tickers []storage.Ticker + s.NoError(s.newDb.Find(&tickers).Error) + s.Equal(1, len(tickers)) + s.Equal(s.ticker.ID, tickers[0].ID) + s.Equal(s.ticker.Active, tickers[0].Active) + + var telegram storage.TickerTelegram + s.NoError(s.newDb.First(&telegram).Error) + s.Equal(s.ticker.Telegram.Active, telegram.Active) + + var mastodon storage.TickerMastodon + s.NoError(s.newDb.First(&mastodon).Error) + s.Equal(s.ticker.Mastodon.Active, mastodon.Active) + + var users []storage.User + s.NoError(s.newDb.Find(&users).Error) + s.Equal(2, len(users)) + s.Equal(s.adminUser.ID, users[0].ID) + s.Equal(s.adminUser.Email, users[0].Email) + s.Equal(s.adminUser.IsSuperAdmin, users[0].IsSuperAdmin) + s.Equal(s.user.ID, users[1].ID) + s.Equal(s.user.Email, users[1].Email) + s.Equal(s.user.IsSuperAdmin, users[1].IsSuperAdmin) + + var tickersUsers []storage.User + s.NoError(s.newDb.Model(&tickers[0]).Association("Users").Find(&tickersUsers)) + s.Equal(1, len(tickersUsers)) + s.Equal(s.user.ID, tickersUsers[0].ID) + s.Equal(s.user.Email, tickersUsers[0].Email) + + var messages []storage.Message + s.NoError(s.newDb.Find(&messages).Error) + s.Equal(1, len(messages)) + s.Equal(s.message.ID, messages[0].ID) + s.Equal(s.message.Ticker, messages[0].TickerID) + s.Equal(s.message.Text, messages[0].Text) + + var attachments []storage.Attachment + s.NoError(s.newDb.Find(&attachments).Error) + s.Equal(1, len(attachments)) + s.Equal(s.attachment.UUID, attachments[0].UUID) + s.Equal(s.attachment.Extension, attachments[0].Extension) + s.Equal(s.attachment.ContentType, attachments[0].ContentType) + + var uploads []storage.Upload + s.NoError(s.newDb.Find(&uploads).Error) + s.Equal(1, len(uploads)) + s.Equal(s.upload.ID, uploads[0].ID) + s.Equal(s.upload.TickerID, uploads[0].TickerID) + s.Equal(s.upload.UUID, uploads[0].UUID) + + var settings []storage.Setting + s.NoError(s.newDb.Find(&settings).Error) + s.Equal(2, len(settings)) + s.Equal(s.refreshIntervalSetting.Name, settings[0].Name) + s.Equal(`{"refreshInterval":10000}`, settings[0].Value) + s.Equal(s.inactiveSettingsSetting.Name, settings[1].Name) + s.Equal(`{"headline":"Headline","subHeadline":"Subheadline","description":"Description","author":"Author","email":"Email","homepage":"","twitter":"Twitter"}`, settings[1].Value) +} + +func TestMigrationTestSuite(t *testing.T) { + suite.Run(t, new(MigrationTestSuite)) +} diff --git a/internal/storage/sql_storage_test.go b/internal/storage/sql_storage_test.go index 79025e97..86cf442f 100644 --- a/internal/storage/sql_storage_test.go +++ b/internal/storage/sql_storage_test.go @@ -6,28 +6,27 @@ import ( "testing" "github.com/gin-gonic/gin" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" pagination "github.com/systemli/ticker/internal/api/pagination" "gorm.io/driver/sqlite" "gorm.io/gorm" ) -func TestSqlStorage(t *testing.T) { - RegisterFailHandler(Fail) - RunSpecs(t, "SqlStorage Suite") +type SqlStorageTestSuite struct { + db *gorm.DB + store *SqlStorage + suite.Suite } -var _ = Describe("SqlStorage", func() { - log.Logger.SetOutput(GinkgoWriter) +func (s *SqlStorageTestSuite) SetupTest() { db, err := gorm.Open(sqlite.Open("file:testdatabase?mode=memory&cache=shared"), &gorm.Config{}) - Expect(err).ToNot(HaveOccurred()) + s.NoError(err) - var store = NewSqlStorage(db, "/uploads") + s.db = db + s.store = NewSqlStorage(db, "/uploads") err = db.AutoMigrate( &Ticker{}, - &TickerInformation{}, &TickerTelegram{}, &TickerMastodon{}, &User{}, @@ -36,1007 +35,988 @@ var _ = Describe("SqlStorage", func() { &Attachment{}, &Setting{}, ) - Expect(err).ToNot(HaveOccurred()) + s.NoError(err) +} - BeforeEach(func() { - db.Exec("DELETE FROM users") - db.Exec("DELETE FROM messages") - db.Exec("DELETE FROM attachments") - db.Exec("DELETE FROM tickers") - db.Exec("DELETE FROM settings") - db.Exec("DELETE FROM uploads") - }) +func (s *SqlStorageTestSuite) BeforeTest(suiteName, testName string) { + s.NoError(s.db.Exec("DELETE FROM users").Error) + s.NoError(s.db.Exec("DELETE FROM messages").Error) + s.NoError(s.db.Exec("DELETE FROM attachments").Error) + s.NoError(s.db.Exec("DELETE FROM tickers").Error) + s.NoError(s.db.Exec("DELETE FROM ticker_mastodons").Error) + s.NoError(s.db.Exec("DELETE FROM ticker_telegrams").Error) + s.NoError(s.db.Exec("DELETE FROM settings").Error) + s.NoError(s.db.Exec("DELETE FROM uploads").Error) +} - Describe("FindUsers", func() { - It("returns all users", func() { - users, err := store.FindUsers() - Expect(err).ToNot(HaveOccurred()) - Expect(users).To(BeEmpty()) +func (s *SqlStorageTestSuite) TestFindUsers() { + s.Run("when no users exist", func() { + users, err := s.store.FindUsers() + s.NoError(err) + s.Empty(users) + }) - err = db.Create(&User{}).Error - Expect(err).ToNot(HaveOccurred()) + s.Run("when users exist", func() { + err := s.db.Create(&User{Tickers: []Ticker{{ID: 1}}}).Error + s.NoError(err) - users, err = store.FindUsers() - Expect(err).ToNot(HaveOccurred()) - Expect(users).To(HaveLen(1)) + s.Run("without preload", func() { + users, err := s.store.FindUsers() + s.NoError(err) + s.Len(users, 1) + s.Empty(users[0].Tickers) }) - It("returns all users with preloaded tickers", func() { - var ticker Ticker - err = db.Create(&ticker).Error - Expect(err).ToNot(HaveOccurred()) - - err = db.Create(&User{ - Tickers: []Ticker{ticker}, - }).Error - Expect(err).ToNot(HaveOccurred()) - - users, err := store.FindUsers(WithTickers()) - Expect(err).ToNot(HaveOccurred()) - Expect(users).To(HaveLen(1)) - Expect(users[0].Tickers).To(HaveLen(1)) + s.Run("with preload", func() { + users, err := s.store.FindUsers(WithTickers()) + s.NoError(err) + s.Len(users, 1) + s.Len(users[0].Tickers, 1) }) }) +} - Describe("FindUserByID", func() { - It("returns the user with the given id", func() { - user, err := store.FindUserByID(1) - Expect(err).To(HaveOccurred()) - Expect(user).To(BeZero()) - - err = db.Create(&User{}).Error - Expect(err).ToNot(HaveOccurred()) - - user, err = store.FindUserByID(1) - Expect(err).ToNot(HaveOccurred()) - Expect(user).ToNot(BeZero()) - }) +func (s *SqlStorageTestSuite) TestFindUserByID() { + s.Run("when user does not exist", func() { + _, err := s.store.FindUserByID(1) + s.Error(err) }) - Describe("FindUsersByIDs", func() { - It("returns the users with the given ids", func() { - users, err := store.FindUsersByIDs([]int{1, 2}) - Expect(err).ToNot(HaveOccurred()) - Expect(users).To(BeEmpty()) + s.Run("when user exists", func() { + err := s.db.Create(&User{ID: 1, Tickers: []Ticker{{ID: 1}}}).Error + s.NoError(err) - err = db.Create(&User{}).Error - Expect(err).ToNot(HaveOccurred()) + s.Run("without preload", func() { + user, err := s.store.FindUserByID(1) + s.NoError(err) + s.NotNil(user) + s.Empty(user.Tickers) + }) - users, err = store.FindUsersByIDs([]int{1, 2}) - Expect(err).ToNot(HaveOccurred()) - Expect(users).To(HaveLen(1)) + s.Run("with preload", func() { + user, err := s.store.FindUserByID(1, WithTickers()) + s.NoError(err) + s.NotNil(user) + s.Len(user.Tickers, 1) }) }) +} - Describe("FindUserByEmail", func() { - It("returns the user with the given email", func() { - user, err := store.FindUserByEmail("user@systemli.org") - Expect(err).To(HaveOccurred()) - Expect(user).To(BeZero()) +func (s *SqlStorageTestSuite) TestFindUsersByIDs() { + s.Run("when no users exist", func() { + users, err := s.store.FindUsersByIDs([]int{1, 2}) + s.NoError(err) + s.Empty(users) + }) - err = db.Create(&User{Email: "user@systemli.org"}).Error - Expect(err).ToNot(HaveOccurred()) + s.Run("when users exist", func() { + err := s.db.Create(&User{ID: 1, Tickers: []Ticker{{ID: 1}}}).Error + s.NoError(err) - user, err = store.FindUserByEmail("user@systemli.org") - Expect(err).ToNot(HaveOccurred()) - Expect(user).ToNot(BeZero()) + s.Run("without preload", func() { + users, err := s.store.FindUsersByIDs([]int{1}) + s.NoError(err) + s.Len(users, 1) + s.Empty(users[0].Tickers) }) - }) - Describe("FindUsersByTicker", func() { - It("returns the users with the given ticker", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + s.Run("with preload", func() { + users, err := s.store.FindUsersByIDs([]int{1}, WithTickers()) + s.NoError(err) + s.Len(users, 1) + s.Len(users[0].Tickers, 1) + }) + }) +} - users, err := store.FindUsersByTicker(ticker) - Expect(err).ToNot(HaveOccurred()) - Expect(users).To(BeEmpty()) +func (s *SqlStorageTestSuite) TestFindUserByEmail() { + s.Run("when user does not exist", func() { + _, err := s.store.FindUserByEmail("user@systemli.org") + s.Error(err) + }) - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + s.Run("when user exists", func() { + err := s.db.Create(&User{Email: "user@systemli.org", Tickers: []Ticker{{ID: 1}}}).Error + s.NoError(err) - ticker.Users = append(ticker.Users, user) - err = store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + s.Run("without preload", func() { + user, err := s.store.FindUserByEmail("user@systemli.org") + s.NoError(err) + s.NotNil(user) + s.Empty(user.Tickers) + }) - users, err = store.FindUsersByTicker(ticker) - Expect(err).ToNot(HaveOccurred()) - Expect(users).To(HaveLen(1)) + s.Run("with preload", func() { + user, err := s.store.FindUserByEmail("user@systemli.org", WithTickers()) + s.NoError(err) + s.NotNil(user) + s.Len(user.Tickers, 1) }) }) +} - Describe("SaveUser", func() { - It("persists the user", func() { - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestFindUsersByTicker() { + s.Run("when no users exist", func() { + users, err := s.store.FindUsersByTicker(Ticker{ID: 1}) + s.NoError(err) + s.Empty(users) + }) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + s.Run("when users exist", func() { + err := s.db.Create(&User{Tickers: []Ticker{{ID: 1}}}).Error + s.NoError(err) - var count int64 - err = db.Model(&User{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + s.Run("without preload", func() { + users, err := s.store.FindUsersByTicker(Ticker{ID: 1}) + s.NoError(err) + s.Len(users, 1) + s.Empty(users[0].Tickers) }) - It("persists the user with tickers", func() { - ticker := &Ticker{ - Title: "Title", - Domain: "systemli.org", - } - err = store.SaveTicker(ticker) - Expect(err).ToNot(HaveOccurred()) + s.Run("with preload", func() { + users, err := s.store.FindUsersByTicker(Ticker{ID: 1}, WithTickers()) + s.NoError(err) + s.Len(users, 1) + s.Len(users[0].Tickers, 1) + }) + }) +} - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) - user.Tickers = append(user.Tickers, *ticker) +func (s *SqlStorageTestSuite) TestSaveUser() { + user, err := NewUser("user@systemli.org", "password") + s.NoError(err) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + s.Run("when user is new", func() { + err = s.store.SaveUser(&user) + s.NoError(err) - user, err = store.FindUserByID(user.ID, WithTickers()) - Expect(err).ToNot(HaveOccurred()) - Expect(user.Tickers).To(HaveLen(1)) - }) + var count int64 + err = s.db.Model(&User{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + }) - It("updates existing user", func() { - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) + s.Run("when user is existing", func() { + user.Email = "update@systemli.org" - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + err = s.store.SaveUser(&user) + s.NoError(err) + }) - user.Email = "new@systemli.org" - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + s.Run("when user is existing with tickers", func() { + ticker := Ticker{} + err = s.store.SaveTicker(&ticker) + s.NoError(err) - user, err = store.FindUserByID(user.ID) - Expect(err).ToNot(HaveOccurred()) - Expect(user.Email).To(Equal("new@systemli.org")) + user.Tickers = append(user.Tickers, ticker) + err = s.store.SaveUser(&user) + s.NoError(err) - ticker := &Ticker{ - Title: "Title", - Domain: "systemli.org", - } - err = store.SaveTicker(ticker) - Expect(err).ToNot(HaveOccurred()) + user, err = s.store.FindUserByID(user.ID, WithTickers()) + s.NoError(err) + s.Len(user.Tickers, 1) + }) - user.Tickers = append(user.Tickers, *ticker) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + s.Run("when user removes tickers", func() { + user.Tickers = []Ticker{} + err = s.store.SaveUser(&user) + s.NoError(err) - user, err = store.FindUserByID(user.ID, WithTickers()) - Expect(err).ToNot(HaveOccurred()) - Expect(user.Tickers).To(HaveLen(1)) - }) + user, err = s.store.FindUserByID(user.ID, WithTickers()) + s.NoError(err) + s.Empty(user.Tickers) + }) +} - It("updates existing user with less tickers", func() { - ticker := &Ticker{ - Title: "Title", - Domain: "systemli.org", - } - err = store.SaveTicker(ticker) - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestDeleteUser() { + s.Run("when user does not exist", func() { + user := User{ID: 1} + err := s.store.DeleteUser(user) + s.NoError(err) + }) - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) + s.Run("when user exists", func() { + user := User{ID: 1} + err := s.db.Create(&user).Error + s.NoError(err) - user.Tickers = append(user.Tickers, *ticker) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + err = s.store.DeleteUser(user) + s.NoError(err) - user.Tickers = []Ticker{} - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + var count int64 + err = s.db.Model(&User{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) + }) +} - user, err = store.FindUserByID(user.ID, WithTickers()) - Expect(err).ToNot(HaveOccurred()) - Expect(user.Tickers).To(BeEmpty()) - }) +func (s *SqlStorageTestSuite) TestDeleteTickerUsers() { + s.Run("when ticker does not exist", func() { + ticker := &Ticker{ID: 1} + err := s.store.DeleteTickerUsers(ticker) + s.NoError(err) }) - Describe("DeleteUser", func() { - It("deletes the user", func() { - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) + s.Run("when ticker exists", func() { + ticker := &Ticker{ID: 1} + err := s.db.Create(&ticker).Error + s.NoError(err) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + count := s.db.Model(&ticker).Association("Users").Count() + s.Equal(int64(0), count) - var count int64 - err = db.Model(&User{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + user := User{ID: 1} + err = s.db.Create(&user).Error + s.NoError(err) - err = store.DeleteUser(user) - Expect(err).ToNot(HaveOccurred()) + err = s.db.Model(&ticker).Association("Users").Append(&user) + s.NoError(err) - err = db.Model(&User{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) - }) + count = s.db.Model(&ticker).Association("Users").Count() + s.Equal(int64(1), count) + + err = s.store.DeleteTickerUsers(ticker) + s.NoError(err) + + count = s.db.Model(&ticker).Association("Users").Count() + s.Equal(int64(0), count) }) +} - Describe("DeleteTickerUsers", func() { - It("deletes the users from the ticker", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestDeleteTickerUser() { + s.Run("when ticker does not exist", func() { + ticker := &Ticker{ID: 1} + user := &User{ID: 1} + err := s.store.DeleteTickerUser(ticker, user) + s.NoError(err) + }) - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + s.Run("when ticker exists", func() { + ticker := &Ticker{ID: 1} + err := s.db.Create(&ticker).Error + s.NoError(err) - ticker.Users = append(ticker.Users, user) - err = store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + user := User{ID: 1} + err = s.db.Create(&user).Error + s.NoError(err) - var count int64 - err = db.Model(&User{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + err = s.db.Model(&ticker).Association("Users").Append(&user) + s.NoError(err) - err = store.DeleteTickerUsers(&ticker) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker.Users).To(BeEmpty()) + count := s.db.Model(&ticker).Association("Users").Count() + s.Equal(int64(1), count) - count = db.Model(ticker).Association("Users").Count() - Expect(count).To(Equal(int64(0))) - }) + err = s.store.DeleteTickerUser(ticker, &user) + s.NoError(err) + + count = s.db.Model(&ticker).Association("Users").Count() + s.Equal(int64(0), count) }) +} - Describe("DeleteTickerUser", func() { - It("deletes the user from the ticker", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestAddTickerUser() { + ticker := &Ticker{} + err := s.db.Create(&ticker).Error + s.NoError(err) - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + user := User{Email: "user@systemli.org"} + err = s.db.Create(&user).Error + s.NoError(err) - ticker.Users = append(ticker.Users, user) - err = store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + err = s.store.AddTickerUser(ticker, &user) + s.NoError(err) - var count int64 - err = db.Model(&User{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + count := s.db.Model(&ticker).Association("Users").Count() + s.Equal(int64(1), count) +} - err = store.DeleteTickerUser(&ticker, &user) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker.Users).To(BeEmpty()) - }) +func (s *SqlStorageTestSuite) TestFindTickers() { + s.Run("when no tickers exist", func() { + tickers, err := s.store.FindTickers() + s.NoError(err) + s.Empty(tickers) }) - Describe("AddTickerUser", func() { - It("adds the user to the ticker", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + s.Run("when tickers exist", func() { + err := s.db.Create(&Ticker{ID: 1}).Error + s.NoError(err) - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + tickers, err := s.store.FindTickers() + s.NoError(err) + s.Len(tickers, 1) + }) +} - err = store.AddTickerUser(&ticker, &user) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker.Users).To(HaveLen(1)) - }) +func (s *SqlStorageTestSuite) TestFindTickerByID() { + s.Run("when ticker does not exist", func() { + _, err := s.store.FindTickerByID(1) + s.Error(err) }) - Describe("FindTickers", func() { - It("returns all tickers", func() { - tickers, err := store.FindTickers() - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(BeEmpty()) + err := s.db.Create(&Ticker{ID: 1}).Error + s.NoError(err) - err = db.Create(&Ticker{}).Error - Expect(err).ToNot(HaveOccurred()) + s.Run("when ticker exists", func() { + ticker, err := s.store.FindTickerByID(1) + s.NoError(err) + s.NotNil(ticker) + }) - tickers, err = store.FindTickers() - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(HaveLen(1)) - }) + s.Run("when ticker exists with users", func() { + user := User{Email: "user@systemli.org"} + err = s.db.Create(&user).Error + s.NoError(err) - It("returns all tickers with preload", func() { - err = db.Create(&Ticker{ - Information: TickerInformation{ - Author: "Author", - }, - }).Error - Expect(err).ToNot(HaveOccurred()) + err = s.db.Model(&Ticker{ID: 1}).Association("Users").Append(&user) + s.NoError(err) - tickers, err := store.FindTickers(WithPreload()) - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(HaveLen(1)) + ticker, err := s.store.FindTickerByID(1, WithPreload()) + s.NoError(err) + s.NotNil(ticker) + s.Len(ticker.Users, 1) + }) +} - Expect(tickers[0].Information.Author).To(Equal("Author")) - }) +func (s *SqlStorageTestSuite) TestFindTickersByIDs() { + s.Run("when no tickers exist", func() { + tickers, err := s.store.FindTickersByIDs([]int{1, 2}) + s.NoError(err) + s.Empty(tickers) }) + err := s.db.Create(&Ticker{ID: 1}).Error + s.NoError(err) - Describe("FindTickersByUser", func() { - var user = User{ - Email: "user@systemli.org", - IsSuperAdmin: false, - } - var admin User = User{ - Email: "admin@systemli.org", - IsSuperAdmin: true, - } - var ticker Ticker = Ticker{ - Users: []User{user}, - } + s.Run("when tickers exist", func() { + tickers, err := s.store.FindTickersByIDs([]int{1}) + s.NoError(err) + s.Len(tickers, 1) + }) - BeforeEach(func() { - Expect(db.Create(&user).Error).ToNot(HaveOccurred()) - Expect(db.Create(&admin).Error).ToNot(HaveOccurred()) - Expect(db.Create(&ticker).Error).ToNot(HaveOccurred()) - }) + s.Run("when tickers exist with users", func() { + user := User{Email: "user@systemli.org"} + err = s.db.Create(&user).Error - It("returns all tickers for admins", func() { - tickers, err := store.FindTickersByUser(admin) - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(HaveLen(1)) - }) + err = s.db.Model(&Ticker{ID: 1}).Association("Users").Append(&user) + s.NoError(err) - It("returns all tickers for users", func() { - tickers, err := store.FindTickersByUser(user) - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(HaveLen(1)) - }) + tickers, err := s.store.FindTickersByIDs([]int{1}, WithPreload()) + s.NoError(err) + s.Len(tickers, 1) + s.Len(tickers[0].Users, 1) + }) +} - It("returns no tickers for users", func() { - tickers, err := store.FindTickersByUser(User{ID: 2}) - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(BeEmpty()) - }) +func (s *SqlStorageTestSuite) TestFindTickerByDomain() { + s.Run("when ticker does not exist", func() { + _, err := s.store.FindTickerByDomain("systemli.org") + s.Error(err) }) - Describe("FindTickerByUserAndID", func() { - var user = User{ - Email: "user@systemli.org", - IsSuperAdmin: false, - } - var admin = User{ - Email: "admin@systemli.org", - IsSuperAdmin: true, - } - var ticker = Ticker{ - Users: []User{user}, - } + ticker := Ticker{Domain: "systemli.org"} + err := s.db.Create(&ticker).Error + s.NoError(err) - BeforeEach(func() { - Expect(db.Create(&user).Error).ToNot(HaveOccurred()) - Expect(db.Create(&admin).Error).ToNot(HaveOccurred()) - Expect(db.Create(&ticker).Error).ToNot(HaveOccurred()) - }) + s.Run("when ticker exists", func() { + ticker, err := s.store.FindTickerByDomain("systemli.org") + s.NoError(err) + s.NotNil(ticker) + }) - It("returns the ticker for admins", func() { - ticker, err := store.FindTickerByUserAndID(admin, ticker.ID) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker).ToNot(BeZero()) - }) + s.Run("when ticker exists with preload", func() { + ticker.Mastodon = TickerMastodon{Active: true} + ticker.Telegram = TickerTelegram{Active: true} - It("returns the ticker for users", func() { - ticker, err := store.FindTickerByUserAndID(user, ticker.ID) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker).ToNot(BeZero()) - }) + err = s.db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&ticker).Error + s.NoError(err) - It("returns no ticker for users", func() { - ticker, err := store.FindTickerByUserAndID(User{ID: 2}, ticker.ID) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker).To(BeZero()) - }) + ticker, err := s.store.FindTickerByDomain("systemli.org", WithPreload()) + s.NoError(err) + s.NotNil(ticker) + s.True(ticker.Mastodon.Active) + s.True(ticker.Telegram.Active) }) +} - Describe("FindTickersByIDs", func() { - It("returns the tickers with the given ids", func() { - tickers, err := store.FindTickersByIDs([]int{1, 2}) - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(BeEmpty()) - - err = db.Create(&Ticker{}).Error - Expect(err).ToNot(HaveOccurred()) - - tickers, err = store.FindTickersByIDs([]int{1, 2}) - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(HaveLen(1)) - }) +func (s *SqlStorageTestSuite) TestFindTickersByUser() { + s.Run("when no tickers exist", func() { + tickers, err := s.store.FindTickersByUser(User{ID: 1}) + s.NoError(err) + s.Empty(tickers) + }) - It("returns the tickers with the given ids and preload", func() { - err = db.Create(&Ticker{ - Information: TickerInformation{ - Author: "Author", - }, - }).Error - Expect(err).ToNot(HaveOccurred()) + user := User{Email: "user@systemli.org"} + err := s.db.Create(&user).Error + s.NoError(err) - tickers, err := store.FindTickersByIDs([]int{1, 2}, WithPreload()) - Expect(err).ToNot(HaveOccurred()) - Expect(tickers).To(HaveLen(1)) + ticker := Ticker{Users: []User{user}} + err = s.db.Create(&ticker).Error + s.NoError(err) - Expect(tickers[0].Information.Author).To(Equal("Author")) - }) + s.Run("when tickers exist", func() { + tickers, err := s.store.FindTickersByUser(user) + s.NoError(err) + s.Len(tickers, 1) }) - Describe("FindTickerByID", func() { - It("returns the ticker with the given id", func() { - ticker, err := store.FindTickerByID(1) - Expect(err).To(HaveOccurred()) - Expect(ticker).To(BeZero()) - - err = db.Create(&Ticker{}).Error - Expect(err).ToNot(HaveOccurred()) + s.Run("when tickers exist with preload", func() { + tickers, err := s.store.FindTickersByUser(user, WithPreload()) + s.NoError(err) + s.Len(tickers, 1) + s.Len(tickers[0].Users, 1) + }) - ticker, err = store.FindTickerByID(1) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker).ToNot(BeZero()) - }) + s.Run("when super admin", func() { + tickers, err := s.store.FindTickersByUser(User{IsSuperAdmin: true}) + s.NoError(err) + s.Len(tickers, 1) + }) +} - It("returns the ticker with the given id and preload", func() { - err = db.Create(&Ticker{ - Information: TickerInformation{ - Author: "Author", - }, - }).Error - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestFindTickerByUserAndID() { + user := User{Email: "user@systemli.org"} + err := s.db.Create(&user).Error + s.NoError(err) - ticker, err := store.FindTickerByID(1, WithPreload()) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker).ToNot(BeZero()) + ticker := Ticker{Users: []User{user}} + err = s.db.Create(&ticker).Error + s.NoError(err) - Expect(ticker.Information.Author).To(Equal("Author")) - }) + s.Run("when ticker exists", func() { + ticker, err := s.store.FindTickerByUserAndID(user, ticker.ID) + s.NoError(err) + s.NotNil(ticker) }) - Describe("FindTickerByDomain", func() { - It("returns the ticker with the given domain", func() { - ticker, err := store.FindTickerByDomain("systemli.org") - Expect(err).To(HaveOccurred()) - Expect(ticker).To(BeZero()) - - ticker = Ticker{ - Domain: "systemli.org", - Information: TickerInformation{ - Author: "Author", - }, - } - err = db.Create(&ticker).Error - Expect(err).ToNot(HaveOccurred()) - - ticker, err = store.FindTickerByDomain("systemli.org") - Expect(err).ToNot(HaveOccurred()) - Expect(ticker).ToNot(BeZero()) - Expect(ticker.Information.Author).To(Equal("Author")) - }) - - It("returns the ticker for the given domain with preload all associations", func() { - ticker = Ticker{ - Domain: "systemli.org", - Mastodon: TickerMastodon{ - Active: true, - }, - Telegram: TickerTelegram{ - Active: true, - }, - } - err = db.Create(&ticker).Error - Expect(err).ToNot(HaveOccurred()) - - ticker, err = store.FindTickerByDomain("systemli.org", WithPreload()) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker).ToNot(BeZero()) - Expect(ticker.Mastodon.Active).To(BeTrue()) - Expect(ticker.Telegram.Active).To(BeTrue()) - }) + s.Run("when ticker exists with preload", func() { + ticker, err := s.store.FindTickerByUserAndID(user, ticker.ID, WithPreload()) + s.NoError(err) + s.NotNil(ticker) + s.Len(ticker.Users, 1) }) - Describe("SaveTicker", func() { - It("persists the ticker", func() { - ticker := NewTicker() - - err = store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + s.Run("when super admin", func() { + ticker, err := s.store.FindTickerByUserAndID(User{IsSuperAdmin: true}, ticker.ID) + s.NoError(err) + s.NotNil(ticker) + }) +} - var count int64 - err = db.Model(&Ticker{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) - }) +func (s *SqlStorageTestSuite) TestSaveTicker() { + ticker := Ticker{} - It("persists the ticker with users", func() { - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + s.Run("when ticker is new", func() { + err := s.store.SaveTicker(&ticker) + s.NoError(err) - ticker := NewTicker() - ticker.Users = append(ticker.Users, user) + var count int64 + err = s.db.Model(&Ticker{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + }) - err = store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + s.Run("when ticker is existing", func() { + ticker.Domain = "systemli.org" + err := s.store.SaveTicker(&ticker) + s.NoError(err) - ticker, err = store.FindTickerByID(ticker.ID, WithPreload()) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker.Users).To(HaveLen(1)) - }) + var count int64 + err = s.db.Model(&Ticker{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + }) - It("removes users from ticker", func() { - user, err := NewUser("user@systemli.org", "password") - Expect(err).ToNot(HaveOccurred()) - err = store.SaveUser(&user) - Expect(err).ToNot(HaveOccurred()) + s.Run("when ticker is existing with users", func() { + user := User{Email: "user@systemli.org"} + err := s.db.Create(&user).Error + s.NoError(err) - ticker := NewTicker() - ticker.Users = append(ticker.Users, user) + ticker.Users = append(ticker.Users, user) + err = s.store.SaveTicker(&ticker) + s.NoError(err) - err = store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + ticker, err = s.store.FindTickerByID(ticker.ID, WithPreload()) + s.NoError(err) + s.Len(ticker.Users, 1) + }) - ticker.Users = []User{} - err = store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + s.Run("when ticker removes users", func() { + ticker.Users = []User{} + err := s.store.SaveTicker(&ticker) + s.NoError(err) - ticker, err = store.FindTickerByID(ticker.ID, WithPreload()) - Expect(err).ToNot(HaveOccurred()) - Expect(ticker.Users).To(BeEmpty()) - }) + ticker, err = s.store.FindTickerByID(ticker.ID, WithPreload()) + s.NoError(err) + s.Empty(ticker.Users) }) +} - Describe("DeleteTicker", func() { - It("deletes the ticker", func() { - ticker := NewTicker() - - err = store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestDeleteTicker() { + s.Run("when ticker does not exist", func() { + ticker := Ticker{ID: 1} + err := s.store.DeleteTicker(ticker) + s.NoError(err) + }) - var count int64 - err = db.Model(&Ticker{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + s.Run("when ticker exists", func() { + ticker := Ticker{ID: 1} + err := s.db.Create(&ticker).Error + s.NoError(err) - err = store.DeleteTicker(ticker) - Expect(err).ToNot(HaveOccurred()) + err = s.store.DeleteTicker(ticker) + s.NoError(err) - err = db.Model(&Ticker{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) - }) + var count int64 + err = s.db.Model(&Ticker{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) }) +} - Describe("FindUploadByUUID", func() { - It("returns the upload with the given uuid", func() { - upload, err := store.FindUploadByUUID("uuid") - Expect(err).To(HaveOccurred()) - Expect(upload).To(BeZero()) +func (s *SqlStorageTestSuite) TestFindUploadByUUID() { + s.Run("when upload does not exist", func() { + _, err := s.store.FindUploadByUUID("uuid") + s.Error(err) + }) - err = db.Create(&Upload{UUID: "uuid"}).Error - Expect(err).ToNot(HaveOccurred()) + s.Run("when upload exists", func() { + err := s.db.Create(&Upload{UUID: "uuid"}).Error + s.NoError(err) - upload, err = store.FindUploadByUUID("uuid") - Expect(err).ToNot(HaveOccurred()) - Expect(upload).ToNot(BeZero()) - }) + upload, err := s.store.FindUploadByUUID("uuid") + s.NoError(err) + s.NotNil(upload) }) +} - Describe("FindUploadsByIDs", func() { - It("returns the uploads with the given ids", func() { - uploads, err := store.FindUploadsByIDs([]int{1, 2}) - Expect(err).ToNot(HaveOccurred()) - Expect(uploads).To(BeEmpty()) +func (s *SqlStorageTestSuite) TestFindUploadsByIDs() { + s.Run("when no uploads exist", func() { + uploads, err := s.store.FindUploadsByIDs([]int{1, 2}) + s.NoError(err) + s.Empty(uploads) + }) - err = db.Create(&Upload{}).Error - Expect(err).ToNot(HaveOccurred()) + s.Run("when uploads exist", func() { + err := s.db.Create(&Upload{ID: 1}).Error + s.NoError(err) - uploads, err = store.FindUploadsByIDs([]int{1, 2}) - Expect(err).ToNot(HaveOccurred()) - Expect(uploads).To(HaveLen(1)) - }) + uploads, err := s.store.FindUploadsByIDs([]int{1}) + s.NoError(err) + s.Len(uploads, 1) }) +} - Describe("SaveUpload", func() { - It("persists the upload", func() { - upload := NewUpload("image.jpg", "content-type", 1) +func (s *SqlStorageTestSuite) TestSaveUpload() { + upload := Upload{} - err = store.SaveUpload(&upload) - Expect(err).ToNot(HaveOccurred()) + s.Run("when upload is new", func() { + err := s.store.SaveUpload(&upload) + s.NoError(err) - var count int64 - err = db.Model(&Upload{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) - }) + var count int64 + err = s.db.Model(&Upload{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) }) - Describe("DeleteUpload", func() { - It("deletes the upload", func() { - upload := NewUpload("image.jpg", "content-type", 1) - - err = store.SaveUpload(&upload) - Expect(err).ToNot(HaveOccurred()) - - var count int64 - err = db.Model(&Upload{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + s.Run("when upload is existing", func() { + upload.UUID = "uuid" + err := s.store.SaveUpload(&upload) + s.NoError(err) - err = store.DeleteUpload(upload) - Expect(err).ToNot(HaveOccurred()) + var count int64 + err = s.db.Model(&Upload{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + s.Equal("uuid", upload.UUID) + }) +} - err = db.Model(&Upload{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) - }) +func (s *SqlStorageTestSuite) TestDeleteUpload() { + s.Run("when upload does not exist", func() { + upload := Upload{ID: 1} + err := s.store.DeleteUpload(upload) + s.NoError(err) }) - Describe("DeleteUploads", func() { - It("deletes the uploads", func() { - upload := NewUpload("image.jpg", "content-type", 1) + s.Run("when upload exists", func() { + upload := Upload{ID: 1} + err := s.db.Create(&upload).Error + s.NoError(err) - err = store.SaveUpload(&upload) - Expect(err).ToNot(HaveOccurred()) + err = s.store.DeleteUpload(upload) + s.NoError(err) - var count int64 - err = db.Model(&Upload{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + var count int64 + err = s.db.Model(&Upload{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) + }) +} - uploads := []Upload{upload} - store.DeleteUploads(uploads) +func (s *SqlStorageTestSuite) TestDeleteUploads() { + s.Run("when uploads do not exist", func() { + uploads := []Upload{{ID: 1}} + s.store.DeleteUploads(uploads) - err = db.Model(&Upload{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) - }) + var count int64 + err := s.db.Model(&Upload{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) }) - Describe("DeleteUploadsByTicker", func() { - It("deletes the uploads", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) + s.Run("when uploads exist", func() { + var count int64 + uploads := []Upload{{ID: 1}} + err := s.db.Create(&uploads).Error + s.NoError(err) - upload := NewUpload("image.jpg", "content-type", ticker.ID) - err = store.SaveUpload(&upload) - Expect(err).ToNot(HaveOccurred()) + err = s.db.Model(&Upload{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) - var count int64 - err = db.Model(&Upload{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + s.store.DeleteUploads(uploads) + s.NoError(err) - err = store.DeleteUploadsByTicker(ticker) - Expect(err).ToNot(HaveOccurred()) + err = s.db.Model(&Upload{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) + }) +} - err = db.Model(&Upload{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) - }) +func (s *SqlStorageTestSuite) TestDeleteUploadsByTicker() { + s.Run("when uploads do not exist", func() { + ticker := Ticker{ID: 1} + err := s.store.DeleteUploadsByTicker(ticker) + s.NoError(err) }) - Describe("FindMessage", func() { - It("returns the message with the given id", func() { - message, err := store.FindMessage(1, 1) - Expect(err).To(HaveOccurred()) - Expect(message).To(BeZero()) + s.Run("when uploads exist", func() { + ticker := Ticker{ID: 1} + err := s.db.Create(&ticker).Error + s.NoError(err) - err = db.Create(&Message{ID: 1, TickerID: 1}).Error - Expect(err).ToNot(HaveOccurred()) + upload := Upload{TickerID: ticker.ID} + err = s.db.Create(&upload).Error + s.NoError(err) - message, err = store.FindMessage(1, 1) - Expect(err).ToNot(HaveOccurred()) - Expect(message).ToNot(BeZero()) - }) + var count int64 + err = s.db.Model(&Upload{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) - It("returns the message with the given id and attachments", func() { - err = db.Create(&Message{ - ID: 1, - TickerID: 1, - Text: "Text", - Attachments: []Attachment{ - {ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}, - }, - }).Error - Expect(err).ToNot(HaveOccurred()) - - message, err := store.FindMessage(1, 1, WithAttachments()) - Expect(err).ToNot(HaveOccurred()) - Expect(message).ToNot(BeZero()) - - Expect(message.Attachments).To(HaveLen(1)) - Expect(message.Attachments[0].UUID).To(Equal("uuid")) - }) + err = s.store.DeleteUploadsByTicker(ticker) + s.NoError(err) + + err = s.db.Model(&Upload{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) }) +} - Describe("FindMessagesByTicker", func() { - It("returns the messages with the given ticker", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestFindMessage() { + s.Run("when message does not exist", func() { + _, err := s.store.FindMessage(1, 1) + s.Error(err) + }) - messages, err := store.FindMessagesByTicker(ticker) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(BeEmpty()) + message := Message{ID: 1, TickerID: 1} + err := s.db.Create(&message).Error + s.NoError(err) - err = db.Create(&Message{TickerID: ticker.ID}).Error - Expect(err).ToNot(HaveOccurred()) + s.Run("when message exists", func() { + message, err := s.store.FindMessage(1, 1) + s.NoError(err) + s.NotNil(message) + }) - messages, err = store.FindMessagesByTicker(ticker) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(HaveLen(1)) - }) + s.Run("when message exists with attachments", func() { + message.Attachments = []Attachment{{ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}} + err := s.db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(&message).Error + s.NoError(err) - It("returns the messages with the given ticker and attachments", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) - - err = db.Create(&Message{ - TickerID: ticker.ID, - Text: "Text", - Attachments: []Attachment{ - {ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}, - }, - }).Error - Expect(err).ToNot(HaveOccurred()) - - messages, err := store.FindMessagesByTicker(ticker, WithAttachments()) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(HaveLen(1)) - - Expect(messages[0].Attachments).To(HaveLen(1)) - Expect(messages[0].Attachments[0].UUID).To(Equal("uuid")) - }) + message, err := s.store.FindMessage(1, 1, WithAttachments()) + s.NoError(err) + s.NotNil(message) + s.Len(message.Attachments, 1) + s.Equal("uuid", message.Attachments[0].UUID) }) +} - Describe("FindMessagesByTickerAndPagination", func() { - It("returns the messages with the given ticker and pagination", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) - - c := &gin.Context{} - p := pagination.NewPagination(c) - messages, err := store.FindMessagesByTickerAndPagination(ticker, *p) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(BeEmpty()) - - err = db.Create(&Message{TickerID: ticker.ID}).Error - Expect(err).ToNot(HaveOccurred()) - - messages, err = store.FindMessagesByTickerAndPagination(ticker, *p) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(HaveLen(1)) - - err = db.Create([]Message{ - {TickerID: ticker.ID, ID: 2}, - {TickerID: ticker.ID, ID: 3}, - {TickerID: ticker.ID, ID: 4}, - }).Error - Expect(err).ToNot(HaveOccurred()) - - c = &gin.Context{} - c.Request = &http.Request{URL: &url.URL{RawQuery: "limit=2"}} - p = pagination.NewPagination(c) - messages, err = store.FindMessagesByTickerAndPagination(ticker, *p) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(HaveLen(2)) - - c = &gin.Context{} - c.Request = &http.Request{URL: &url.URL{RawQuery: "limit=2&after=2"}} - p = pagination.NewPagination(c) - messages, err = store.FindMessagesByTickerAndPagination(ticker, *p) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(HaveLen(2)) - - c = &gin.Context{} - c.Request = &http.Request{URL: &url.URL{RawQuery: "limit=2&before=4"}} - p = pagination.NewPagination(c) - messages, err = store.FindMessagesByTickerAndPagination(ticker, *p) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(HaveLen(2)) - }) +func (s *SqlStorageTestSuite) TestFindMessagesByTicker() { + ticker := Ticker{ID: 1} + err := s.db.Create(&ticker).Error + s.NoError(err) - It("returns the messages with the given ticker, pagination and attachments", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) - - err = db.Create(&Message{ - TickerID: ticker.ID, - Text: "Text", - Attachments: []Attachment{ - {ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}, - }, - }).Error - Expect(err).ToNot(HaveOccurred()) - - c := &gin.Context{} - p := pagination.NewPagination(c) - messages, err := store.FindMessagesByTickerAndPagination(ticker, *p, WithAttachments()) - Expect(err).ToNot(HaveOccurred()) - Expect(messages).To(HaveLen(1)) - - Expect(messages[0].Attachments).To(HaveLen(1)) - Expect(messages[0].Attachments[0].UUID).To(Equal("uuid")) - }) + s.Run("when no messages exist", func() { + messages, err := s.store.FindMessagesByTicker(ticker) + s.NoError(err) + s.Empty(messages) }) - Describe("SaveMessage", func() { - It("persists the message", func() { - Expect(store.SaveMessage(&Message{})).ToNot(HaveOccurred()) - - var count int64 - err = db.Model(&Message{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) - }) + s.Run("when messages exist", func() { + messages, err := s.store.FindMessagesByTicker(ticker) + s.NoError(err) + s.Empty(messages) + }) - It("persists the message with attachments", func() { - message := &Message{} - message.Attachments = []Attachment{ + s.Run("when messages exist with attachments", func() { + message := Message{ + TickerID: ticker.ID, + Text: "Text", + Attachments: []Attachment{ {ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}, - } + }, + } + err := s.db.Create(&message).Error + s.NoError(err) - err = store.SaveMessage(message) - Expect(err).ToNot(HaveOccurred()) + messages, err := s.store.FindMessagesByTicker(ticker, WithAttachments()) + s.NoError(err) + s.Len(messages, 1) - var count int64 - err = db.Model(&Message{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + s.Len(messages[0].Attachments, 1) + s.Equal("uuid", messages[0].Attachments[0].UUID) + }) +} - err = db.Model(&Attachment{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) - }) +func (s *SqlStorageTestSuite) TestFindMessagesByTickerAndPagination() { + ticker := Ticker{ID: 1} + err := s.db.Create(&ticker).Error + s.NoError(err) + + s.Run("when no messages exist", func() { + p := pagination.NewPagination(&gin.Context{}) + messages, err := s.store.FindMessagesByTickerAndPagination(ticker, *p) + s.NoError(err) + s.Empty(messages) + }) + + err = s.db.Create(&[]Message{ + {TickerID: ticker.ID, ID: 1, Attachments: []Attachment{{ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}}}, + {TickerID: ticker.ID, ID: 2, Attachments: []Attachment{{ID: 2, MessageID: 2, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}}}, + {TickerID: ticker.ID, ID: 3, Attachments: []Attachment{{ID: 3, MessageID: 3, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}}}, + {TickerID: ticker.ID, ID: 4, Attachments: []Attachment{{ID: 4, MessageID: 4, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}}}, + }).Error + s.NoError(err) + + s.Run("when messages exist with attachments", func() { + p := pagination.NewPagination(&gin.Context{}) + messages, err := s.store.FindMessagesByTickerAndPagination(ticker, *p, WithAttachments()) + s.NoError(err) + s.Len(messages, 4) + s.Equal("uuid", messages[0].Attachments[0].UUID) + s.Equal("uuid", messages[1].Attachments[0].UUID) + s.Equal("uuid", messages[2].Attachments[0].UUID) + s.Equal("uuid", messages[3].Attachments[0].UUID) + }) + + s.Run("when messages exist with limit set", func() { + c := &gin.Context{} + c.Request = &http.Request{URL: &url.URL{RawQuery: "limit=2"}} + p := pagination.NewPagination(c) + messages, err := s.store.FindMessagesByTickerAndPagination(ticker, *p) + s.NoError(err) + s.Len(messages, 2) + s.Equal(4, messages[0].ID) + s.Equal(3, messages[1].ID) + }) + + s.Run("when messages exist with limit and after set", func() { + c := &gin.Context{} + c.Request = &http.Request{URL: &url.URL{RawQuery: "limit=2&after=2"}} + p := pagination.NewPagination(c) + messages, err := s.store.FindMessagesByTickerAndPagination(ticker, *p) + s.NoError(err) + s.Len(messages, 2) + s.Equal(4, messages[0].ID) + s.Equal(3, messages[1].ID) + }) + + s.Run("when messages exist with limit and before set", func() { + c := &gin.Context{} + c.Request = &http.Request{URL: &url.URL{RawQuery: "limit=2&before=4"}} + p := pagination.NewPagination(c) + messages, err := s.store.FindMessagesByTickerAndPagination(ticker, *p) + s.NoError(err) + s.Len(messages, 2) + s.Equal(3, messages[0].ID) + s.Equal(2, messages[1].ID) + }) +} - It("updates the message with attachments", func() { - message := &Message{} - Expect(store.SaveMessage(message)).ToNot(HaveOccurred()) - Expect(message.Attachments).To(BeEmpty()) +func (s *SqlStorageTestSuite) TestSaveMessage() { + message := Message{Attachments: []Attachment{{ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}}} - message.Attachments = []Attachment{ - {ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}, - } + s.Run("when message is new", func() { + err := s.store.SaveMessage(&message) + s.NoError(err) - Expect(store.SaveMessage(message)).ToNot(HaveOccurred()) + var count int64 + err = s.db.Model(&Message{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) - var count int64 - err = db.Model(&Message{}).Count(&count).Error + err = s.db.Model(&Attachment{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + }) - Expect(err).ToNot(HaveOccurred()) + s.Run("when message is existing", func() { + message.TickerID = 1 + message.Attachments = append(message.Attachments, Attachment{ID: 2, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}) + err := s.store.SaveMessage(&message) + s.NoError(err) - err = db.Model(&Attachment{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) - }) + var count int64 + err = s.db.Model(&Message{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + err = s.db.Model(&Attachment{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(2), count) }) +} - Describe("DeleteMessage", func() { - It("deletes the message", func() { - message := NewMessage() +func (s *SqlStorageTestSuite) TestDeleteMessage() { + s.Run("when message does not exist", func() { + message := Message{ID: 1} + err := s.store.DeleteMessage(message) + s.NoError(err) + }) - err = store.SaveMessage(&message) - Expect(err).ToNot(HaveOccurred()) + s.Run("when message exists", func() { + message := Message{ID: 1, Attachments: []Attachment{{ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}}} + err := s.db.Create(&message).Error + s.NoError(err) - var count int64 - err = db.Model(&Message{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + err = s.store.DeleteMessage(message) + s.NoError(err) - err = store.DeleteMessage(message) - Expect(err).ToNot(HaveOccurred()) + var count int64 + err = s.db.Model(&Message{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) - err = db.Model(&Message{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) - }) + err = s.db.Model(&Attachment{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) + }) +} - It("deletes the message with attachments", func() { - message := NewMessage() - message.Attachments = []Attachment{ - {ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}, - } +func (s *SqlStorageTestSuite) TestDeleteMessages() { + ticker := Ticker{ID: 1} + err := s.db.Create(&ticker).Error + s.NoError(err) - err = store.SaveMessage(&message) - Expect(err).ToNot(HaveOccurred()) + message := Message{ID: 1, TickerID: ticker.ID, Attachments: []Attachment{{ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}}} + err = s.db.Create(&message).Error + s.NoError(err) - var count int64 - err = db.Model(&Message{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + s.Run("when messages do not exist", func() { + err := s.store.DeleteMessages(Ticker{ID: 2}) + s.NoError(err) + }) - err = store.DeleteMessage(message) - Expect(err).ToNot(HaveOccurred()) + s.Run("when messages exist", func() { + err := s.store.DeleteMessages(ticker) + s.NoError(err) - err = db.Model(&Message{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) + var count int64 + err = s.db.Model(&Message{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) - err = db.Model(&Attachment{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) - }) + err = s.db.Model(&Attachment{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(0), count) }) +} - Describe("DeleteMessages", func() { - It("deletes the messages", func() { - ticker := NewTicker() - err := store.SaveTicker(&ticker) - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestGetInactiveSettings() { + s.Run("when no settings exist", func() { + settings := s.store.GetInactiveSettings() + s.Equal(DefaultInactiveSettings().Author, settings.Author) + }) - message := NewMessage() - message.TickerID = ticker.ID - message.Attachments = []Attachment{ - {ID: 1, MessageID: 1, UUID: "uuid", ContentType: "image/jpg", Extension: "jpg"}, - } - err = store.SaveMessage(&message) - Expect(err).ToNot(HaveOccurred()) + s.Run("when settings exist", func() { + setting := Setting{Name: SettingInactiveName, Value: `{"author":"test"}`} + err := s.db.Create(&setting).Error + s.NoError(err) - var count int64 - err = db.Model(&Message{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(1))) + settings := s.store.GetInactiveSettings() + s.Equal("test", settings.Author) + }) +} - err = store.DeleteMessages(ticker) - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestSaveInactiveSettings() { + settings := InactiveSettings{Author: "test"} - err = db.Model(&Message{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) + s.Run("when settings are new", func() { + err := s.store.SaveInactiveSettings(settings) + s.NoError(err) - err = db.Model(&Attachment{}).Count(&count).Error - Expect(err).ToNot(HaveOccurred()) - Expect(count).To(Equal(int64(0))) - }) + var count int64 + err = s.db.Model(&Setting{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) }) - Describe("GetInactiveSettings", func() { - It("returns the default inactive setting", func() { - setting := store.GetInactiveSettings() - Expect(setting.Author).To(Equal(DefaultInactiveSettings().Author)) - }) + s.Run("when settings are existing", func() { + settings.Author = "test2" + err := s.store.SaveInactiveSettings(settings) + s.NoError(err) - It("returns the inactive setting", func() { - settings := InactiveSettings{ - Author: "author", - } + var count int64 + err = s.db.Model(&Setting{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + s.Equal("test2", settings.Author) + }) +} - err = store.SaveInactiveSettings(settings) - Expect(err).ToNot(HaveOccurred()) +func (s *SqlStorageTestSuite) TestGetRefreshIntervalSettings() { + s.Run("when no settings exist", func() { + settings := s.store.GetRefreshIntervalSettings() + s.Equal(DefaultRefreshIntervalSettings().RefreshInterval, settings.RefreshInterval) + }) - setting := store.GetInactiveSettings() - Expect(setting.Author).To(Equal(settings.Author)) - }) + s.Run("when settings exist", func() { + setting := Setting{Name: SettingRefreshInterval, Value: `{"refresh_interval":1000}`} + err := s.db.Create(&setting).Error + s.NoError(err) + + settings := s.store.GetRefreshIntervalSettings() + s.Equal(1000, settings.RefreshInterval) }) +} - Describe("GetRefreshIntervalSetting", func() { - It("returns the default refresh interval setting", func() { - setting := store.GetRefreshIntervalSettings() - Expect(setting.RefreshInterval).To(Equal(DefaultRefreshIntervalSettings().RefreshInterval)) - }) +func (s *SqlStorageTestSuite) TestSaveRefreshIntervalSettings() { + settings := RefreshIntervalSettings{RefreshInterval: 1000} + + s.Run("when settings are new", func() { + err := s.store.SaveRefreshIntervalSettings(settings) + s.NoError(err) - It("returns the refresh interval setting", func() { - settings := RefreshIntervalSettings{ - RefreshInterval: 1000, - } + var count int64 + err = s.db.Model(&Setting{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + }) - err = store.SaveRefreshIntervalSettings(settings) - Expect(err).ToNot(HaveOccurred()) + s.Run("when settings are existing", func() { + settings.RefreshInterval = 2000 + err := s.store.SaveRefreshIntervalSettings(settings) + s.NoError(err) - setting := store.GetRefreshIntervalSettings() - Expect(setting.RefreshInterval).To(Equal(settings.RefreshInterval)) - }) + var count int64 + err = s.db.Model(&Setting{}).Count(&count).Error + s.NoError(err) + s.Equal(int64(1), count) + s.Equal(2000, settings.RefreshInterval) }) -}) +} + +func TestSqlStorageTestSuite(t *testing.T) { + suite.Run(t, new(SqlStorageTestSuite)) +}