diff --git a/internal/cardsinfo/card.go b/internal/cardsinfo/card.go index b6b2f80..a8c6543 100644 --- a/internal/cardsinfo/card.go +++ b/internal/cardsinfo/card.go @@ -5,26 +5,22 @@ import ( "strings" ) -type CardPrice interface { - Format() string +type scgCardPrice struct { + price string + edition string + link string } -type ScgCardPrice struct { - Price string - Edition string - Link string +func (s *scgCardPrice) format() string { + return fmt.Sprintf("%v: %v\n%v\n", s.edition, s.price, s.link) } -func (s *ScgCardPrice) Format() string { - return fmt.Sprintf("%v: %v\n%v\n", s.Edition, s.Price, s.Link) -} - -type Card struct { +type card struct { Name string `json:"name"` Layout string `json:"layout"` } -func (c *Card) getName() string { +func (c *card) getName() string { if c.Layout == "transform" { return strings.Replace(c.Name, "//", "|", 1) } diff --git a/internal/cardsinfo/format.go b/internal/cardsinfo/format.go index 5def049..3147322 100644 --- a/internal/cardsinfo/format.go +++ b/internal/cardsinfo/format.go @@ -4,11 +4,19 @@ import ( "fmt" ) -func (f *Fetcher) FormatCardPrices(name string, prices []CardPrice) string { +func (f *Fetcher) GetFormattedCardPrices(name string) (string, error) { + prices, err := f.getPrices(name) + if err != nil { + return "", err + } + return f.formatCardPrices(name, prices), nil +} + +func (f *Fetcher) formatCardPrices(name string, prices []scgCardPrice) string { message := fmt.Sprintf("Оригинальное название: %v\n", name) message += fmt.Sprintf("Результатов: %v\n", len(prices)) for i, v := range prices { - message += fmt.Sprintf("%v. %v", i+1, v.Format()) + message += fmt.Sprintf("%v. %v", i+1, v.format()) } return message } diff --git a/internal/cardsinfo/format_test.go b/internal/cardsinfo/format_test.go index 26d0e1c..68b94cb 100644 --- a/internal/cardsinfo/format_test.go +++ b/internal/cardsinfo/format_test.go @@ -2,16 +2,39 @@ package cardsinfo import ( "github.com/stretchr/testify/assert" + "gopkg.in/h2non/gock.v1" + "net/http" + "os" "testing" ) +func TestFetcher_GetFormattedCardPrices_Error(t *testing.T) { + defer gock.Off() + + gock.New(scgSearchUrlTemplate + "card").Reply(http.StatusInternalServerError) + f := &Fetcher{} + _, err := f.GetFormattedCardPrices("card") + assert.NotNil(t, err) +} + +func TestFetcher_GetFormattedCardPrices_Empty(t *testing.T) { + defer gock.Off() + + resp, _ := os.Open("test_data/EmptyTest.html") + gock.New(scgSearchUrlTemplate + "card").Reply(http.StatusOK).Body(resp) + f := &Fetcher{} + msg, err := f.GetFormattedCardPrices("card") + assert.Nil(t, err) + assert.Equal(t, "Оригинальное название: card\nРезультатов: 0\n", msg) +} + func TestFormatCardPrices(t *testing.T) { f := &Fetcher{} - formatted := f.FormatCardPrices("card", []CardPrice{ - &ScgCardPrice{ - Price: "1.5$", - Edition: "ED", - Link: "scg.com", + formatted := f.formatCardPrices("card", []scgCardPrice{ + { + price: "1.5$", + edition: "ED", + link: "scg.com", }, }) assert.Equal(t, "Оригинальное название: card\nРезультатов: 1\n1. ED: 1.5$\nscg.com\n", formatted) diff --git a/internal/cardsinfo/names.go b/internal/cardsinfo/names.go index d2a7ec8..4d3a629 100644 --- a/internal/cardsinfo/names.go +++ b/internal/cardsinfo/names.go @@ -9,26 +9,26 @@ import ( "strings" ) -const ScryfallUrl = "https://api.scryfall.com" +const scryfallUrl = "https://api.scryfall.com" func (f *Fetcher) GetNameByCardId(set string, number string) string { /* Note: number is string because some cards contain letters in their numbers. */ - path := ScryfallUrl + "/cards/" + strings.ToLower(set) + "/" + number - return GetCardByUrl(path) + path := scryfallUrl + "/cards/" + strings.ToLower(set) + "/" + number + return getCardByUrl(path) } func (f *Fetcher) GetOriginalName(name string) string { - path := ScryfallUrl + "/cards/named?fuzzy=" + ApplyFilters(name) - result := GetCardByUrl(path) + path := scryfallUrl + "/cards/named?fuzzy=" + applyFilters(name) + result := getCardByUrl(path) if result == "" && f.Dict != nil { result, _ = dicttranslate.FindFromReader(name, f.Dict, 5) } return result } -func ApplyFilters(name string) string { +func applyFilters(name string) string { /* Despite of the rules of Russian language, letter ё is replaced with e on cards Sometimes it leads to wrong search results @@ -37,13 +37,13 @@ func ApplyFilters(name string) string { return url.QueryEscape(name) } -func GetCardByUrl(path string) string { +func getCardByUrl(path string) string { response, _ := http.Get(path) defer func() { _ = response.Body.Close() }() data, _ := ioutil.ReadAll(response.Body) - var v Card + var v card err := json.Unmarshal(data, &v) if err != nil { return "" diff --git a/internal/cardsinfo/names_test.go b/internal/cardsinfo/names_test.go index f94db60..5208430 100644 --- a/internal/cardsinfo/names_test.go +++ b/internal/cardsinfo/names_test.go @@ -12,7 +12,7 @@ import ( func TestGetNameByCardId(t *testing.T) { defer gock.Off() - gock.New(ScryfallUrl + "/set/1").Reply(http.StatusOK).JSON(Card{ + gock.New(scryfallUrl + "/set/1").Reply(http.StatusOK).JSON(card{ Name: "card", }) f := &Fetcher{} @@ -23,7 +23,7 @@ func TestGetNameByCardId(t *testing.T) { func TestGetOriginalName_Scryfall(t *testing.T) { defer gock.Off() - gock.New(ScryfallUrl + "/cards/named?fuzzy=card").Reply(http.StatusOK).JSON(Card{ + gock.New(scryfallUrl + "/cards/named?fuzzy=card").Reply(http.StatusOK).JSON(card{ Name: "Result Card", }) f := &Fetcher{} @@ -34,7 +34,7 @@ func TestGetOriginalName_Scryfall(t *testing.T) { func TestGetOriginalName_Dict(t *testing.T) { defer gock.Off() - gock.New(ScryfallUrl + "/cards/named?fuzzy=card").Reply(http.StatusOK).JSON(Card{}) + gock.New(scryfallUrl + "/cards/named?fuzzy=card").Reply(http.StatusOK).JSON(card{}) serialized, _ := json.Marshal(map[string]string{ "card": "Card", }) @@ -49,7 +49,7 @@ func TestGetOriginalName_Dict(t *testing.T) { func TestGetOriginalName_BadJson(t *testing.T) { defer gock.Off() - gock.New(ScryfallUrl + "/cards/named?fuzzy=card").Reply(http.StatusOK).BodyString("}") + gock.New(scryfallUrl + "/cards/named?fuzzy=card").Reply(http.StatusOK).BodyString("}") f := &Fetcher{} name := f.GetOriginalName("card") assert.Equal(t, "", name) @@ -58,7 +58,7 @@ func TestGetOriginalName_BadJson(t *testing.T) { func TestGetOriginalName_DoubleSide(t *testing.T) { defer gock.Off() - gock.New(ScryfallUrl + "/cards/named?fuzzy=card").Reply(http.StatusOK).JSON(Card{ + gock.New(scryfallUrl + "/cards/named?fuzzy=card").Reply(http.StatusOK).JSON(card{ Name: "Legion's Landing // Adanto, the First Fort", Layout: "transform", }) diff --git a/internal/cardsinfo/prices.go b/internal/cardsinfo/prices.go index 24300d6..3aefa1d 100644 --- a/internal/cardsinfo/prices.go +++ b/internal/cardsinfo/prices.go @@ -10,8 +10,8 @@ import ( const scgDomain = "https://starcitygames.com" const scgSearchUrlTemplate = "https://starcitygames.hawksearch.com/sites/starcitygames/?search_query=" -func (f *Fetcher) GetPrices(name string) ([]CardPrice, error) { - prices, err := GetPricesScg(name) +func (f *Fetcher) getPrices(name string) ([]scgCardPrice, error) { + prices, err := getPricesScg(name) if err != nil { return nil, err } @@ -21,7 +21,7 @@ func (f *Fetcher) GetPrices(name string) ([]CardPrice, error) { return prices, nil } -func GetPricesScg(name string) ([]CardPrice, error) { +func getPricesScg(name string) ([]scgCardPrice, error) { escapedName := url.QueryEscape(name) searchUrl := scgSearchUrlTemplate + escapedName node, err := htmlquery.LoadURL(searchUrl) @@ -29,23 +29,23 @@ func GetPricesScg(name string) ([]CardPrice, error) { return nil, errors.Wrap(err, "cannot load url") } blocks := htmlquery.Find(node, "//div[@class=\"hawk-results-item\"]") - var results []CardPrice + var results []scgCardPrice for _, block := range blocks { - price := &ScgCardPrice{} + price := scgCardPrice{} linkNode := htmlquery.FindOne(block, "//h2/a") for _, attr := range linkNode.Attr { if attr.Key == "href" { - price.Link = scgDomain + attr.Val + price.link = scgDomain + attr.Val break } } editionNode := htmlquery.FindOne(block, "//p[@class=\"hawk-results-item__category\"]/a") if editionNode.FirstChild != nil { - price.Edition = editionNode.FirstChild.Data + price.edition = editionNode.FirstChild.Data } priceNode := htmlquery.FindOne(block, "//span[@class='hawk-old-price']|//div[contains(concat(' ',normalize-space(@class),' '),' hawk-results-item__options-table-cell--price ')]") if priceNode.FirstChild != nil { - price.Price = priceNode.FirstChild.Data + price.price = priceNode.FirstChild.Data } results = append(results, price) } diff --git a/internal/cardsinfo/prices_test.go b/internal/cardsinfo/prices_test.go index bb6ccfc..3aa2782 100644 --- a/internal/cardsinfo/prices_test.go +++ b/internal/cardsinfo/prices_test.go @@ -15,33 +15,33 @@ func TestGetPrices_Ok(t *testing.T) { file, _ := os.Open("test_data/AcademyRuinsTest.html") gock.New(scgSearchUrlTemplate + "card").Reply(http.StatusOK).Body(file) f := &Fetcher{} - prices, err := f.GetPrices("card") + prices, err := f.getPrices("card") assert.Nil(t, err) - assert.Equal(t, []CardPrice{ - &ScgCardPrice{ - Price: "$6.99", - Edition: "Double Masters", - Link: "https://starcitygames.com/academy-ruins-sgl-mtg-2xm-309-enn/?sku=SGL-MTG-2XM-309-ENN1", + assert.Equal(t, []scgCardPrice{ + { + price: "$6.99", + edition: "Double Masters", + link: "https://starcitygames.com/academy-ruins-sgl-mtg-2xm-309-enn/?sku=SGL-MTG-2XM-309-ENN1", }, - &ScgCardPrice{ - Price: "$9.99", - Edition: "Double Masters (Foil)", - Link: "https://starcitygames.com/academy-ruins-sgl-mtg-2xm-309-enf/?sku=SGL-MTG-2XM-309-ENF1", + { + price: "$9.99", + edition: "Double Masters (Foil)", + link: "https://starcitygames.com/academy-ruins-sgl-mtg-2xm-309-enf/?sku=SGL-MTG-2XM-309-ENF1", }, - &ScgCardPrice{ - Price: "$11.99", - Edition: "Double Masters - Variants", - Link: "https://starcitygames.com/academy-ruins-sgl-mtg-2xm2-369-enn/?sku=SGL-MTG-2XM2-369-ENN1", + { + price: "$11.99", + edition: "Double Masters - Variants", + link: "https://starcitygames.com/academy-ruins-sgl-mtg-2xm2-369-enn/?sku=SGL-MTG-2XM2-369-ENN1", }, - &ScgCardPrice{ - Price: "$14.99", - Edition: "Double Masters - Variants (Foil)", - Link: "https://starcitygames.com/academy-ruins-sgl-mtg-2xm2-369-enf/?sku=SGL-MTG-2XM2-369-ENF1", + { + price: "$14.99", + edition: "Double Masters - Variants (Foil)", + link: "https://starcitygames.com/academy-ruins-sgl-mtg-2xm2-369-enf/?sku=SGL-MTG-2XM2-369-ENF1", }, - &ScgCardPrice{ - Price: "$7.99", - Edition: "Modern Masters: 2013 Edition", - Link: "https://starcitygames.com/academy-ruins-sgl-mtg-mma-219-enn/?sku=SGL-MTG-MMA-219-ENN1", + { + price: "$7.99", + edition: "Modern Masters: 2013 Edition", + link: "https://starcitygames.com/academy-ruins-sgl-mtg-mma-219-enn/?sku=SGL-MTG-MMA-219-ENN1", }, }, prices) } @@ -51,7 +51,7 @@ func TestGetPrices_Unavailable(t *testing.T) { gock.New(scgSearchUrlTemplate + "card").Reply(http.StatusBadGateway) f := &Fetcher{} - _, err := f.GetPrices("card") + _, err := f.getPrices("card") assert.NotNil(t, err) } @@ -61,7 +61,7 @@ func TestGetPrices_Empty(t *testing.T) { file, _ := os.Open("test_data/EmptyTest.html") gock.New(scgSearchUrlTemplate + "card").Reply(http.StatusOK).Body(file) f := &Fetcher{} - prices, err := f.GetPrices("card") + prices, err := f.getPrices("card") assert.Nil(t, err) assert.Nil(t, prices) } diff --git a/internal/cardsinfo/test_data/AcademyRuinsTest.html b/internal/cardsinfo/test_data/AcademyRuinsTest.html index 16102be..83be959 100644 --- a/internal/cardsinfo/test_data/AcademyRuinsTest.html +++ b/internal/cardsinfo/test_data/AcademyRuinsTest.html @@ -119,7 +119,7 @@
@@ -248,7 +248,7 @@
@@ -377,7 +377,7 @@
@@ -506,7 +506,7 @@
@@ -578,7 +578,7 @@
@@ -789,7 +789,7 @@
@@ -918,7 +918,7 @@
@@ -2115,7 +2115,7 @@
diff --git a/internal/cardsinfo/test_data/EmptyTest.html b/internal/cardsinfo/test_data/EmptyTest.html index 755aec3..556eeb8 100644 --- a/internal/cardsinfo/test_data/EmptyTest.html +++ b/internal/cardsinfo/test_data/EmptyTest.html @@ -225,12 +225,12 @@
- +

- Rathi Dragon + Rathi Dragon

SGL-MTG-9ED-210-JAF

@@ -244,7 +244,7 @@
@@ -257,12 +257,12 @@
- +

- Rathi Dragon + Rathi Dragon

SGL-MTG-9ED-210-ZSF

@@ -276,7 +276,7 @@
@@ -289,12 +289,12 @@
- +

- Rathi Dragon + Rathi Dragon

SGL-MTG-9ED-210-ENN

@@ -308,7 +308,7 @@
diff --git a/internal/vk/handler.go b/internal/vk/handler.go index 05db6d3..1cca8c7 100644 --- a/internal/vk/handler.go +++ b/internal/vk/handler.go @@ -7,28 +7,26 @@ import ( "strings" "github.com/gin-gonic/gin" - "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" ) type Handler struct { - Sender Sender + Sender sender Logger *log.Logger SecretKey string GroupId int64 ConfirmationString string DictPath string - Cache CardCache - InfoFetcher CardInfoFetcher + Cache cardCache + InfoFetcher cardInfoFetcher } -type CardInfoFetcher interface { - GetPrices(name string) ([]cardsinfo.CardPrice, error) - FormatCardPrices(name string, prices []cardsinfo.CardPrice) string +type cardInfoFetcher interface { + GetFormattedCardPrices(name string) (string, error) GetNameByCardId(set string, number string) string GetOriginalName(name string) string } -type CardCache interface { +type cardCache interface { Get(cardName string) (string, error) Set(cardName string, message string) } @@ -69,19 +67,19 @@ func (h *Handler) HandleMessage(c *gin.Context) { func (h *Handler) handleSearch(req *messageRequest) { cardName, err := h.getCardNameByCommand(req.Object.Body) if err != nil { - h.Sender.Send(req.Object.UserId, incorrectMessage) + 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.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.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) + h.Sender.send(req.Object.UserId, message) } } @@ -94,11 +92,10 @@ func (h *Handler) handleConfirmation(c *gin.Context, req *messageRequest) { func (h *Handler) getMessage(cardName string) (string, error) { val, err := h.Cache.Get(cardName) if err != nil { - prices, err := h.InfoFetcher.GetPrices(cardName) + message, err := h.InfoFetcher.GetFormattedCardPrices(cardName) if err != nil { return "", err } - message := h.InfoFetcher.FormatCardPrices(cardName, prices) h.Cache.Set(cardName, message) return message, nil } diff --git a/internal/vk/sender.go b/internal/vk/sender.go index 114f68f..10e49e0 100644 --- a/internal/vk/sender.go +++ b/internal/vk/sender.go @@ -11,10 +11,10 @@ import ( "strings" ) -const SendMessageUrl = "https://api.vk.com/method/messages.send" +const sendMessageUrl = "https://api.vk.com/method/messages.send" -type Sender interface { - Send(userId int64, message string) +type sender interface { + send(userId int64, message string) } type ApiSender struct { @@ -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, @@ -41,7 +41,7 @@ func (s *ApiSender) Send(userId int64, message string) { "random_id=" + strconv.FormatInt(randomId, 10), } joined := strings.Join(params, "&") - reqUrl := SendMessageUrl + "?" + joined + reqUrl := sendMessageUrl + "?" + joined resp, err := http.Get(reqUrl) if err != nil || resp.StatusCode != http.StatusOK { s.Logger.Printf("[error] Could not Send message. User: %d", userId) diff --git a/internal/vk/sender_test.go b/internal/vk/sender_test.go index 5e2c5c1..a7fe8d1 100644 --- a/internal/vk/sender_test.go +++ b/internal/vk/sender_test.go @@ -13,7 +13,7 @@ import ( func TestApiSender_Send_OK(t *testing.T) { defer gock.Off() - gock.New(SendMessageUrl).MatchParams( + gock.New(sendMessageUrl).MatchParams( map[string]string{ "access_token": "token", "peer_id": "1", @@ -23,28 +23,28 @@ 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()) } func TestApiSender_Send_NotOK(t *testing.T) { defer gock.Off() - gock.New(SendMessageUrl).Reply(http.StatusInternalServerError) + gock.New(sendMessageUrl).Reply(http.StatusInternalServerError) b := &bytes.Buffer{} sender := ApiSender{ Token: "token", Logger: log.New(b, "", 0), } - sender.Send(1, "msg") + sender.send(1, "msg") assert.True(t, strings.Contains(b.String(), "[error]")) } func TestApiSender_Send_ErrorCode(t *testing.T) { defer gock.Off() - gock.New(SendMessageUrl).Reply(http.StatusOK).JSON( + gock.New(sendMessageUrl).Reply(http.StatusOK).JSON( map[string]interface{}{ "error": map[string]interface{}{ "error_code": 100, @@ -58,6 +58,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_info_fetcher.go b/internal/vk/test_info_fetcher.go index bcba44b..bb75e19 100644 --- a/internal/vk/test_info_fetcher.go +++ b/internal/vk/test_info_fetcher.go @@ -2,20 +2,15 @@ package vk import ( "errors" - "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" ) type testInfoFetcher struct{} -func (t *testInfoFetcher) GetPrices(name string) ([]cardsinfo.CardPrice, error) { +func (t *testInfoFetcher) GetFormattedCardPrices(name string) (string, error) { if name == "good" || name == "uncached" { - return nil, nil + return name, nil } - return nil, errors.New("test") -} - -func (t *testInfoFetcher) FormatCardPrices(name string, _ []cardsinfo.CardPrice) string { - return name + return "", errors.New("test") } func (t *testInfoFetcher) GetNameByCardId(_ string, _ string) string { diff --git a/internal/vk/test_sender.go b/internal/vk/test_sender.go index fb68251..cf06c1f 100644 --- a/internal/vk/test_sender.go +++ b/internal/vk/test_sender.go @@ -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,