diff --git a/cmd/go-mtg-vk/main.go b/cmd/go-mtg-vk/main.go index 909a40a..0c16b19 100644 --- a/cmd/go-mtg-vk/main.go +++ b/cmd/go-mtg-vk/main.go @@ -2,7 +2,6 @@ package main import ( "encoding/json" - "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" "io/ioutil" "log" "math/rand" @@ -10,6 +9,9 @@ import ( "strconv" "time" + "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" + "gitlab.com/flygrounder/go-mtg-vk/internal/scenario" + "github.com/gin-gonic/gin" "gitlab.com/flygrounder/go-mtg-vk/internal/caching" "gitlab.com/flygrounder/go-mtg-vk/internal/vk" @@ -26,17 +28,19 @@ func main() { var dictMap map[string]string _ = json.Unmarshal(dictBytes, &dictMap) handler := vk.Handler{ + Scenario: &scenario.Scenario{ Sender: &vk.ApiSender{ - Token: os.Getenv("VK_TOKEN"), + Token: os.Getenv("VK_TOKEN"), }, Logger: log.New(os.Stdout, "", 0), + Cache: caching.NewClient("redis:6379", "", time.Hour*24, 0), + InfoFetcher: &cardsinfo.Fetcher{ + Dict: dictMap, + }, + }, SecretKey: os.Getenv("VK_SECRET_KEY"), GroupId: groupId, ConfirmationString: os.Getenv("VK_CONFIRMATION_STRING"), - Cache: caching.NewClient("redis:6379", "", time.Hour*24, 0), - InfoFetcher: &cardsinfo.Fetcher{ - Dict: dictMap, - }, } r.POST("callback/message", handler.HandleMessage) diff --git a/internal/caching/client.go b/internal/caching/cache.go similarity index 99% rename from internal/caching/client.go rename to internal/caching/cache.go index d701d9a..6804779 100644 --- a/internal/caching/client.go +++ b/internal/caching/cache.go @@ -1,8 +1,9 @@ package caching import ( - "github.com/go-redis/redis" "time" + + "github.com/go-redis/redis" ) type CacheClient struct { diff --git a/internal/scenario/scenario.go b/internal/scenario/scenario.go new file mode 100644 index 0000000..f09d052 --- /dev/null +++ b/internal/scenario/scenario.go @@ -0,0 +1,89 @@ +package scenario + +import ( + "errors" + "log" + "strings" +) + +const ( + incorrectMessage = "Некорректная команда" + cardNotFoundMessage = "Карта не найдена" + pricesUnavailableMessage = "Цены временно недоступны, попробуйте позже" +) + +type Scenario struct { + Sender Sender + Logger *log.Logger + InfoFetcher CardInfoFetcher + Cache CardCache +} + +type UserMessage struct { + Body string + UserId int64 +} + +type CardCache interface { + Get(cardName string) (string, error) + Set(cardName string, message string) +} + +type CardInfoFetcher interface { + GetFormattedCardPrices(name string) (string, error) + GetNameByCardId(set string, number string) string + GetOriginalName(name string) string +} + +type Sender interface { + Send(userId int64, message string) +} + +func (s *Scenario) HandleSearch(msg *UserMessage) { + cardName, err := s.getCardNameByCommand(msg.Body) + if err != nil { + s.Sender.Send(msg.UserId, incorrectMessage) + s.Logger.Printf("[info] Not correct command. Message: %s user input: %s", err.Error(), msg.Body) + } else if cardName == "" { + s.Sender.Send(msg.UserId, cardNotFoundMessage) + s.Logger.Printf("[info] Could not find card. User input: %s", msg.Body) + } else { + message, err := s.getMessage(cardName) + if err != nil { + s.Sender.Send(msg.UserId, pricesUnavailableMessage) + s.Logger.Printf("[error] Could not find SCG prices. Message: %s card name: %s", err.Error(), cardName) + return + } + s.Sender.Send(msg.UserId, message) + } +} + +func (s *Scenario) getMessage(cardName string) (string, error) { + val, err := s.Cache.Get(cardName) + if err != nil { + message, err := s.InfoFetcher.GetFormattedCardPrices(cardName) + if err != nil { + return "", err + } + s.Cache.Set(cardName, message) + return message, nil + } + return val, nil +} + +func (s *Scenario) getCardNameByCommand(command string) (string, error) { + var name string + switch { + case strings.HasPrefix(command, "!s"): + split := strings.Split(command, " ") + if len(split) < 3 { + return "", errors.New("wrong command") + } + set := split[1] + number := split[2] + name = s.InfoFetcher.GetNameByCardId(set, number) + default: + name = s.InfoFetcher.GetOriginalName(command) + } + return name, nil +} diff --git a/internal/scenario/scenario_test.go b/internal/scenario/scenario_test.go new file mode 100644 index 0000000..195d3b9 --- /dev/null +++ b/internal/scenario/scenario_test.go @@ -0,0 +1,82 @@ +package scenario + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestScenario_HandleSearch_BadCommand(t *testing.T) { + testCtx := GetTestScenarioCtx() + testCtx.Scenario.HandleSearch(&UserMessage{ + Body: "!s", + UserId: 1, + }) + assert.Equal(t, []testMessage{ + { + userId: 1, + message: incorrectMessage, + }, + }, testCtx.Sender.sent) + assert.True(t, strings.Contains(testCtx.LogBuf.String(), "[info]")) +} + +func TestScenario_HandleSearch_GoodCommand(t *testing.T) { + testCtx := GetTestScenarioCtx() + testCtx.Scenario.HandleSearch(&UserMessage{ + Body: "!s grn 228", + UserId: 1, + }) + assert.Equal(t, []testMessage{ + { + userId: 1, + message: "good", + }, + }, testCtx.Sender.sent) +} + +func TestScenario_HandleSearch_NotFoundCard(t *testing.T) { + testCtx := GetTestScenarioCtx() + testCtx.Scenario.HandleSearch(&UserMessage{ + Body: "absolutely_random_card", + UserId: 1, + }) + assert.Equal(t, []testMessage{ + { + userId: 1, + message: cardNotFoundMessage, + }, + }, testCtx.Sender.sent) + assert.True(t, strings.Contains(testCtx.LogBuf.String(), "[info]")) +} + +func TestScenario_HandleSearch_BadCard(t *testing.T) { + testCtx := GetTestScenarioCtx() + testCtx.Scenario.HandleSearch(&UserMessage{ + Body: "bad", + UserId: 1, + }) + assert.Equal(t, []testMessage{ + { + userId: 1, + message: pricesUnavailableMessage, + }, + }, testCtx.Sender.sent) + assert.True(t, strings.Contains(testCtx.LogBuf.String(), "[error]")) +} +func TestScenario_HandleSearch_Uncached(t *testing.T) { + testCtx := GetTestScenarioCtx() + testCtx.Scenario.HandleSearch(&UserMessage{ + Body: "uncached", + UserId: 1, + }) + assert.Equal(t, []testMessage{ + { + userId: 1, + message: "uncached", + }, + }, testCtx.Sender.sent) + msg, _ := testCtx.Scenario.Cache.Get("uncached") + assert.Equal(t, "uncached", msg) +} diff --git a/internal/vk/test_cache.go b/internal/scenario/test_cache.go similarity index 94% rename from internal/vk/test_cache.go rename to internal/scenario/test_cache.go index 6cf799a..9646096 100644 --- a/internal/vk/test_cache.go +++ b/internal/scenario/test_cache.go @@ -1,4 +1,4 @@ -package vk +package scenario import "errors" diff --git a/internal/scenario/test_ctx.go b/internal/scenario/test_ctx.go new file mode 100644 index 0000000..95c6818 --- /dev/null +++ b/internal/scenario/test_ctx.go @@ -0,0 +1,31 @@ +package scenario + +import ( + "bytes" + "log" +) + +type TestScenarioCtx struct { + Scenario *Scenario + Sender *testSender + LogBuf *bytes.Buffer +} + +func GetTestScenarioCtx() TestScenarioCtx { + sender := &testSender{} + buf := &bytes.Buffer{} + return TestScenarioCtx{ + LogBuf: buf, + Scenario: &Scenario{ + Sender: sender, + Logger: log.New(buf, "", 0), + InfoFetcher: &testInfoFetcher{}, + Cache: &testCache{ + table: map[string]string{ + "good": "good", + }, + }, + }, + Sender: sender, + } +} diff --git a/internal/vk/test_info_fetcher.go b/internal/scenario/test_info_fetcher.go similarity index 96% rename from internal/vk/test_info_fetcher.go rename to internal/scenario/test_info_fetcher.go index bb75e19..0241d3f 100644 --- a/internal/vk/test_info_fetcher.go +++ b/internal/scenario/test_info_fetcher.go @@ -1,4 +1,4 @@ -package vk +package scenario import ( "errors" diff --git a/internal/vk/test_sender.go b/internal/scenario/test_sender.go similarity index 71% rename from internal/vk/test_sender.go rename to internal/scenario/test_sender.go index cf06c1f..64daf7a 100644 --- a/internal/vk/test_sender.go +++ b/internal/scenario/test_sender.go @@ -1,4 +1,4 @@ -package vk +package scenario type testSender struct { sent []testMessage @@ -9,7 +9,7 @@ type testMessage struct { message string } -func (s *testSender) send(userId int64, message string) { +func (s *testSender) Send(userId int64, message string) { s.sent = append(s.sent, testMessage{ userId: userId, message: message, diff --git a/internal/vk/handler.go b/internal/vk/handler.go index 1cca8c7..f8184df 100644 --- a/internal/vk/handler.go +++ b/internal/vk/handler.go @@ -1,23 +1,18 @@ package vk import ( - "errors" - "log" "net/http" - "strings" "github.com/gin-gonic/gin" + "gitlab.com/flygrounder/go-mtg-vk/internal/scenario" ) type Handler struct { - Sender sender - Logger *log.Logger + Scenario *scenario.Scenario SecretKey string GroupId int64 ConfirmationString string DictPath string - Cache cardCache - InfoFetcher cardInfoFetcher } type cardInfoFetcher interface { @@ -59,62 +54,16 @@ func (h *Handler) HandleMessage(c *gin.Context) { case "confirmation": h.handleConfirmation(c, &req) case "message_new": - go h.handleSearch(&req) + go h.Scenario.HandleSearch(&scenario.UserMessage{ + Body: req.Object.Body, + UserId: req.Object.UserId, + }) c.String(http.StatusOK, "ok") } } -func (h *Handler) handleSearch(req *messageRequest) { - cardName, err := h.getCardNameByCommand(req.Object.Body) - if err != nil { - h.Sender.send(req.Object.UserId, incorrectMessage) - h.Logger.Printf("[info] Not correct command. Message: %s user input: %s", err.Error(), req.Object.Body) - } else if cardName == "" { - h.Sender.send(req.Object.UserId, cardNotFoundMessage) - h.Logger.Printf("[info] Could not find card. User input: %s", req.Object.Body) - } else { - message, err := h.getMessage(cardName) - if err != nil { - h.Sender.send(req.Object.UserId, pricesUnavailableMessage) - h.Logger.Printf("[error] Could not find SCG prices. Message: %s card name: %s", err.Error(), cardName) - return - } - h.Sender.send(req.Object.UserId, message) - } -} - func (h *Handler) handleConfirmation(c *gin.Context, req *messageRequest) { if (req.Type == "confirmation") && (req.GroupId == h.GroupId) { c.String(http.StatusOK, h.ConfirmationString) } } - -func (h *Handler) getMessage(cardName string) (string, error) { - val, err := h.Cache.Get(cardName) - if err != nil { - message, err := h.InfoFetcher.GetFormattedCardPrices(cardName) - if err != nil { - return "", err - } - h.Cache.Set(cardName, message) - return message, nil - } - return val, nil -} - -func (h *Handler) getCardNameByCommand(command string) (string, error) { - var name string - switch { - case strings.HasPrefix(command, "!s"): - split := strings.Split(command, " ") - if len(split) < 3 { - return "", errors.New("wrong command") - } - set := split[1] - number := split[2] - name = h.InfoFetcher.GetNameByCardId(set, number) - default: - name = h.InfoFetcher.GetOriginalName(command) - } - return name, nil -} diff --git a/internal/vk/handler_test.go b/internal/vk/handler_test.go index c59560b..acc9395 100644 --- a/internal/vk/handler_test.go +++ b/internal/vk/handler_test.go @@ -1,9 +1,9 @@ package vk import ( - "github.com/stretchr/testify/assert" - "strings" "testing" + + "github.com/stretchr/testify/assert" ) func TestHandler_HandleMessage_Confirm(t *testing.T) { @@ -36,86 +36,3 @@ func TestHandler_HandleMessage_NoSecretKey(t *testing.T) { assert.Equal(t, "", testCtx.recorder.Body.String()) } -func TestHandler_handleSearch_BadCommand(t *testing.T) { - testCtx := getTestHandlerCtx() - testCtx.handler.handleSearch(&messageRequest{ - Object: userMessage{ - Body: "!s", - UserId: 1, - }, - }) - assert.Equal(t, []testMessage{ - { - userId: 1, - message: incorrectMessage, - }, - }, testCtx.sender.sent) - assert.True(t, strings.Contains(testCtx.logBuf.String(), "[info]")) -} - -func TestHandler_handleSearch_GoodCommand(t *testing.T) { - testCtx := getTestHandlerCtx() - testCtx.handler.handleSearch(&messageRequest{ - Object: userMessage{ - Body: "!s grn 228", - UserId: 1, - }, - }) - assert.Equal(t, []testMessage{ - { - userId: 1, - message: "good", - }, - }, testCtx.sender.sent) -} - -func TestHandler_handleSearch_NotFoundCard(t *testing.T) { - testCtx := getTestHandlerCtx() - testCtx.handler.handleSearch(&messageRequest{ - Object: userMessage{ - Body: "absolutely_random_card", - UserId: 1, - }, - }) - assert.Equal(t, []testMessage{ - { - userId: 1, - message: cardNotFoundMessage, - }, - }, testCtx.sender.sent) - assert.True(t, strings.Contains(testCtx.logBuf.String(), "[info]")) -} - -func TestHandler_handleSearch_BadCard(t *testing.T) { - testCtx := getTestHandlerCtx() - testCtx.handler.handleSearch(&messageRequest{ - Object: userMessage{ - Body: "bad", - UserId: 1, - }, - }) - assert.Equal(t, []testMessage{ - { - userId: 1, - message: pricesUnavailableMessage, - }, - }, testCtx.sender.sent) - assert.True(t, strings.Contains(testCtx.logBuf.String(), "[error]")) -} -func TestHandler_handleSearch_Uncached(t *testing.T) { - testCtx := getTestHandlerCtx() - testCtx.handler.handleSearch(&messageRequest{ - Object: userMessage{ - Body: "uncached", - UserId: 1, - }, - }) - assert.Equal(t, []testMessage{ - { - userId: 1, - message: "uncached", - }, - }, testCtx.sender.sent) - msg, _ := testCtx.handler.Cache.Get("uncached") - assert.Equal(t, "uncached", msg) -} diff --git a/internal/vk/sender.go b/internal/vk/sender.go index 2ec5ee5..27a7665 100644 --- a/internal/vk/sender.go +++ b/internal/vk/sender.go @@ -31,7 +31,7 @@ type errorResponse struct { ErrorMsg string `json:"error_msg"` } -func (s *ApiSender) send(userId int64, message string) { +func (s *ApiSender) Send(userId int64, message string) { randomId := rand.Int63() params := []string{ "access_token=" + s.Token, diff --git a/internal/vk/sender_test.go b/internal/vk/sender_test.go index d97241b..d7f5876 100644 --- a/internal/vk/sender_test.go +++ b/internal/vk/sender_test.go @@ -25,7 +25,7 @@ func TestApiSender_Send_OK(t *testing.T) { ).ParamPresent("random_id").Reply(http.StatusOK) sender := ApiSender{Token: "token"} - sender.send(1, "msg") + sender.Send(1, "msg") assert.False(t, gock.HasUnmatchedRequest()) } @@ -39,7 +39,7 @@ func TestApiSender_Send_NotOK(t *testing.T) { Token: "token", Logger: log.New(b, "", 0), } - sender.send(1, "msg") + sender.Send(1, "msg") assert.True(t, strings.Contains(b.String(), "[error]")) } @@ -60,6 +60,6 @@ func TestApiSender_Send_ErrorCode(t *testing.T) { Token: "token", Logger: log.New(b, "", 0), } - sender.send(1, "msg") + sender.Send(1, "msg") assert.True(t, strings.Contains(b.String(), "[error]")) } diff --git a/internal/vk/test_ctx.go b/internal/vk/test_ctx.go index c704707..9c0e261 100644 --- a/internal/vk/test_ctx.go +++ b/internal/vk/test_ctx.go @@ -3,37 +3,26 @@ package vk import ( "bytes" "encoding/json" - "github.com/gin-gonic/gin" - "log" "net/http/httptest" + + "github.com/gin-gonic/gin" + "gitlab.com/flygrounder/go-mtg-vk/internal/scenario" ) type testCtx struct { handler *Handler recorder *httptest.ResponseRecorder - sender *testSender - logBuf *bytes.Buffer } func getTestHandlerCtx() testCtx { - sender := &testSender{} - buf := &bytes.Buffer{} + s := scenario.GetTestScenarioCtx() return testCtx{ - logBuf: buf, handler: &Handler{ SecretKey: "sec", GroupId: 10, ConfirmationString: "con", - Sender: sender, - Logger: log.New(buf, "", 0), - InfoFetcher: &testInfoFetcher{}, - Cache: &testCache{ - table: map[string]string{ - "good": "good", - }, - }, + Scenario: s.Scenario, }, - sender: sender, recorder: httptest.NewRecorder(), } }