From d55c36c0e27656415f714a05846134d1574fbb7a Mon Sep 17 00:00:00 2001 From: Felipe Martin Garcia Date: Sat, 6 Aug 2022 10:12:45 +0200 Subject: [PATCH] test: added basic testing to casadellibro * added composition for shops, which now require a ShopOptions with a client * added a mock client that returns data from a file in order to test the scrapper * updated all shops --- go.mod | 4 + go.sum | 15 ++ pkg/clients/clients.go | 7 + pkg/clients/http.go | 30 +++ pkg/clients/mock.go | 29 +++ pkg/clients/mockdata/embed.go | 6 + .../mockdata/www.casadellibro.com.html | 230 ++++++++++++++++++ pkg/manager/main.go | 4 +- pkg/models/shop.go | 14 +- pkg/shop/akiracomics/akira.go | 6 +- pkg/shop/amazon/amazon.go | 6 +- pkg/shop/casadellibro/casadellibro.go | 16 +- pkg/shop/casadellibro/casadellibro_test.go | 31 +++ pkg/shop/gtmstore/gtmstore.go | 6 +- pkg/shop/heroesdepapel/heroesdepapel.go | 6 +- pkg/shop/steam/steam.go | 6 +- 16 files changed, 395 insertions(+), 21 deletions(-) create mode 100644 pkg/clients/clients.go create mode 100644 pkg/clients/http.go create mode 100644 pkg/clients/mock.go create mode 100644 pkg/clients/mockdata/embed.go create mode 100644 pkg/clients/mockdata/www.casadellibro.com.html create mode 100644 pkg/shop/casadellibro/casadellibro_test.go diff --git a/go.mod b/go.mod index d075e59..5937287 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,13 @@ go 1.18 require ( github.com/PuerkitoBio/goquery v1.8.0 github.com/goodsign/monday v1.0.0 + github.com/stretchr/testify v1.8.0 ) require ( github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1f05e1d..c3f1a97 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,18 @@ github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0g github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc= github.com/goodsign/monday v1.0.0/go.mod h1:r4T4breXpoFwspQNM+u2sLxJb2zyTaxVGqUfTBjWOu8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 h1:N9Vc/rorQUDes6B9CNdIxAn5jODGj2wzfrei2x4wNj4= golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= @@ -12,3 +22,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/clients/clients.go b/pkg/clients/clients.go new file mode 100644 index 0000000..d61a4ae --- /dev/null +++ b/pkg/clients/clients.go @@ -0,0 +1,7 @@ +package clients + +import "io" + +type Client interface { + Get(url string) (io.Reader, error) +} diff --git a/pkg/clients/http.go b/pkg/clients/http.go new file mode 100644 index 0000000..cb64313 --- /dev/null +++ b/pkg/clients/http.go @@ -0,0 +1,30 @@ +package clients + +import ( + "fmt" + "io" + "net/http" +) + +type HttpClient struct { + http http.Client +} + +func (c HttpClient) Get(url string) (io.Reader, error) { + res, err := c.http.Get(url) + if err != nil { + return nil, fmt.Errorf("error retrieving url: %s", err) + } + + if res.StatusCode != 200 { + return nil, fmt.Errorf("error retrieving url: %d %s", res.StatusCode, res.Status) + } + + return res.Body, nil +} + +func NewBasicHttpClient() HttpClient { + return HttpClient{ + http: http.Client{}, + } +} diff --git a/pkg/clients/mock.go b/pkg/clients/mock.go new file mode 100644 index 0000000..91db6a5 --- /dev/null +++ b/pkg/clients/mock.go @@ -0,0 +1,29 @@ +package clients + +import ( + "fmt" + "io" + "net/url" + + "github.com/fmartingr/bazaar/pkg/clients/mockdata" +) + +type MockClient struct{} + +func (c MockClient) Get(urlString string) (io.Reader, error) { + parsedUrl, err := url.Parse(urlString) + if err != nil { + return nil, fmt.Errorf("error parsing url: %s", urlString) + } + + f, err := mockdata.Data.Open(parsedUrl.Host + ".html") + if err != nil { + return nil, fmt.Errorf("can't open mock data for %s", parsedUrl.Host) + } + + return f, nil +} + +func NewMockClient() MockClient { + return MockClient{} +} diff --git a/pkg/clients/mockdata/embed.go b/pkg/clients/mockdata/embed.go new file mode 100644 index 0000000..cf2ed6f --- /dev/null +++ b/pkg/clients/mockdata/embed.go @@ -0,0 +1,6 @@ +package mockdata + +import "embed" + +//go:embed *.html +var Data embed.FS diff --git a/pkg/clients/mockdata/www.casadellibro.com.html b/pkg/clients/mockdata/www.casadellibro.com.html new file mode 100644 index 0000000..4971cf1 --- /dev/null +++ b/pkg/clients/mockdata/www.casadellibro.com.html @@ -0,0 +1,230 @@ + + + + LA DEPENDIENTA | SAYAKA MURATA | Casa del Libro + + +

+ +

DUOMO EDITORIAL - 9788416634620
Escribe tu opinión

+ Resumen de +

Tras murakami, la nueva voz de la literatura japonesa. +«Una novela extraordinaria sobre lo difícil que resulta a veces formar parte del mundo.» The New York Times
Keiko Furukura tiene 36 años y está soltera. De hecho, nunca ha tenido pareja. Desde que abandonó a su tradicional familia para mudarse a Tokio, trabaja a tiempo parcial como dependienta de una konbini, un supermercado japonés abierto las 24 horas del día. Siempre ha sentido que no encajaba en la sociedad, pero en la tienda ha encontrado un mundo predecible, gobernado por un manual que dicta a los trabajadores cómo actuar y qué decir. Ha conseguido lograr esa normalidad que la sociedad le reclama: todos quieren ver a Keiko formar un hogar, seguir un camino convencional que la convierta, a sus ojos, en una adulta. +Con esta visión hilarante de las expectativas de la sociedad hacia las mujeres solteras, Sayaka Murata se ha consagrado como la nueva voz de la literatura japonesa.
«Un retrato entrañable y fresco que recuerda a Banana Yoshimoto, Han Kang y Amélie.» Grove Atlantic +«Una explosión de literatura, de las mejores que he visto por parte de una escritora tan joven.» John Freeman, Literary Hub«Absurda, cómica, audaz y precisa. Asombrosa.» Hiromi Kawakami
+ 16.80 EUR +
+ En Stock +
Escribe tu opinión
Información extra

+ Sinopsis de LA DEPENDIENTA +

Tras murakami, la nueva voz de la literatura japonesa. +«Una novela extraordinaria sobre lo difícil que resulta a veces formar parte del mundo.» The New York Times
Keiko Furukura tiene 36 años y está soltera. De hecho, nunca ha tenido pareja. Desde que abandonó a su tradicional familia para mudarse a Tokio, trabaja a tiempo parcial como dependienta de una konbini, un supermercado japonés abierto las 24 horas del día. Siempre ha sentido que no encajaba en la sociedad, pero en la tienda ha encontrado un mundo predecible, gobernado por un manual que dicta a los trabajadores cómo actuar y qué decir. Ha conseguido lograr esa normalidad que la sociedad le reclama: todos quieren ver a Keiko formar un hogar, seguir un camino convencional que la convierta, a sus ojos, en una adulta. +Con esta visión hilarante de las expectativas de la sociedad hacia las mujeres solteras, Sayaka Murata se ha consagrado como la nueva voz de la literatura japonesa.
«Un retrato entrañable y fresco que recuerda a Banana Yoshimoto, Han Kang y Amélie.» Grove Atlantic +«Una explosión de literatura, de las mejores que he visto por parte de una escritora tan joven.» John Freeman, Literary Hub«Absurda, cómica, audaz y precisa. Asombrosa.» Hiromi Kawakami

+ Ficha técnica de LA DEPENDIENTA +

Nº de páginas:
176
Editorial:
DUOMO EDITORIAL
Idioma:
CASTELLANO
Encuadernación:
Tapa blanda
ISBN:
9788416634620
Año de edición:
2019
Plaza de edición:
BARCELONA
Traductor:
MARINA BORNAS MONTAÑA
Fecha de lanzamiento:
01/01/2019
Alto:
21.5 cm
Ancho:
14 cm
Grueso:
1.5 cm
Escrito por...

+ Sayaka Murata +

+ Ver ficha del autor +

+ Compra segura +

+ +

+ Recogida en librería gratis +

+ Devoluciones gratis hasta 14 días +

+ Recibe nuestras novedades en libros en tu email +

Descuentos en libros, últimos títulos publicados y mucho más.

plan avanza gobierno logo
+ Copyright © 2022 Casa del Libro. Todos los derechos reservados +
+ España +
+ + diff --git a/pkg/manager/main.go b/pkg/manager/main.go index a91d488..4b4e51a 100644 --- a/pkg/manager/main.go +++ b/pkg/manager/main.go @@ -4,6 +4,7 @@ import ( "fmt" "net/url" + "github.com/fmartingr/bazaar/pkg/clients" "github.com/fmartingr/bazaar/pkg/models" ) @@ -14,7 +15,8 @@ type Manager struct { } func (m *Manager) Register(domains []string, shopFactory models.ShopFactory) error { - shop := shopFactory() + baseShop := models.NewShopOptions(clients.NewBasicHttpClient()) + shop := shopFactory(baseShop) for _, domain := range domains { if _, exists := m.domains[domain]; exists { diff --git a/pkg/models/shop.go b/pkg/models/shop.go index aee340b..5937c55 100644 --- a/pkg/models/shop.go +++ b/pkg/models/shop.go @@ -1,7 +1,19 @@ package models -type ShopFactory func() Shop +import "github.com/fmartingr/bazaar/pkg/clients" + +type ShopFactory func(baseShop ShopOptions) Shop type Shop interface { Get(url string) (*Product, error) } + +type ShopOptions struct { + Client clients.Client +} + +func NewShopOptions(client clients.Client) ShopOptions { + return ShopOptions{ + Client: client, + } +} diff --git a/pkg/shop/akiracomics/akira.go b/pkg/shop/akiracomics/akira.go index 751fda6..27b33f0 100644 --- a/pkg/shop/akiracomics/akira.go +++ b/pkg/shop/akiracomics/akira.go @@ -14,6 +14,7 @@ import ( var Domains = []string{"www.akiracomics.com", "akiracomics.com"} type AkiraShop struct { + models.ShopOptions domains []string } @@ -63,9 +64,10 @@ func (s *AkiraShop) Get(url string) (*models.Product, error) { } func NewAkiraShopFactory() models.ShopFactory { - return func() models.Shop { + return func(shopOptions models.ShopOptions) models.Shop { shop := AkiraShop{ - domains: Domains, + ShopOptions: shopOptions, + domains: Domains, } return &shop } diff --git a/pkg/shop/amazon/amazon.go b/pkg/shop/amazon/amazon.go index ddc32a0..62c17fc 100644 --- a/pkg/shop/amazon/amazon.go +++ b/pkg/shop/amazon/amazon.go @@ -17,6 +17,7 @@ import ( var Domains = []string{"www.amazon.es", "www.amazon.com"} type AmazonShop struct { + models.ShopOptions domains []string } @@ -97,9 +98,10 @@ func (s *AmazonShop) Get(url string) (*models.Product, error) { } func NewAmazonShopFactory() models.ShopFactory { - return func() models.Shop { + return func(shopOptions models.ShopOptions) models.Shop { shop := AmazonShop{ - domains: Domains, + ShopOptions: shopOptions, + domains: Domains, } return &shop } diff --git a/pkg/shop/casadellibro/casadellibro.go b/pkg/shop/casadellibro/casadellibro.go index 8225bf7..890a2a8 100644 --- a/pkg/shop/casadellibro/casadellibro.go +++ b/pkg/shop/casadellibro/casadellibro.go @@ -3,7 +3,6 @@ package casadellibro import ( "fmt" "log" - "net/http" "regexp" "strconv" "strings" @@ -21,21 +20,19 @@ const ( var Domains = []string{"www.casadellibro.com"} type CasaDelLibroShop struct { + models.ShopOptions + domains []string priceRegexp *regexp.Regexp } func (s *CasaDelLibroShop) Get(url string) (*models.Product, error) { - res, err := http.Get(url) + body, err := s.ShopOptions.Client.Get(url) if err != nil { - return nil, fmt.Errorf("error retrieving url: %s", err) + return nil, fmt.Errorf("error during request: %s", err) } - if res.StatusCode != 200 { - return nil, fmt.Errorf("error retrieving url: %d %s", res.StatusCode, res.Status) - } - - doc, err := goquery.NewDocumentFromReader(res.Body) + doc, err := goquery.NewDocumentFromReader(body) if err != nil { return nil, fmt.Errorf("error parsing body: %s", err) } @@ -88,13 +85,14 @@ func (s *CasaDelLibroShop) Get(url string) (*models.Product, error) { } func NewCasaDelLibroShopFactory() models.ShopFactory { - return func() models.Shop { + return func(shopOptions models.ShopOptions) models.Shop { r, err := regexp.Compile(`Price\"\:\"([\d+\.]+)`) if err != nil { log.Println(err) } shop := CasaDelLibroShop{ + ShopOptions: shopOptions, domains: Domains, priceRegexp: r, } diff --git a/pkg/shop/casadellibro/casadellibro_test.go b/pkg/shop/casadellibro/casadellibro_test.go new file mode 100644 index 0000000..bda2cbb --- /dev/null +++ b/pkg/shop/casadellibro/casadellibro_test.go @@ -0,0 +1,31 @@ +package casadellibro_test + +import ( + "testing" + "time" + + "github.com/fmartingr/bazaar/pkg/clients" + "github.com/fmartingr/bazaar/pkg/models" + "github.com/fmartingr/bazaar/pkg/shop/casadellibro" + "github.com/stretchr/testify/assert" +) + +func TestCasaDelLibro_Ok(t *testing.T) { + shop := casadellibro.NewCasaDelLibroShopFactory()(models.NewShopOptions(clients.NewMockClient())) + + testUrl := "https://www.casadellibro.com/test/" + + product, err := shop.Get(testUrl) + if err != nil { + t.Error(err) + return + } + + assert.Greater(t, len(product.Description), 100) + assert.Equal(t, product.Name, "LA DEPENDIENTA") + assert.Equal(t, product.ImageURL, "https://imagessl0.casadellibro.com/a/l/t5/20/9788416634620.jpg") + assert.Equal(t, product.Price, 15.96) + assert.Equal(t, product.PriceText, "15.96") + assert.Equal(t, product.ReleaseDate.Format(time.RFC3339), "2019-01-01T00:00:00Z") + assert.Equal(t, product.URL, testUrl) +} diff --git a/pkg/shop/gtmstore/gtmstore.go b/pkg/shop/gtmstore/gtmstore.go index 48f0c5c..58e016c 100644 --- a/pkg/shop/gtmstore/gtmstore.go +++ b/pkg/shop/gtmstore/gtmstore.go @@ -15,6 +15,7 @@ import ( var Domains = []string{"www.gtm-store.com"} type GTMStoreShop struct { + models.ShopOptions domains []string } @@ -62,9 +63,10 @@ func (s *GTMStoreShop) Get(url string) (*models.Product, error) { } func NewGTMStoreShopFactory() models.ShopFactory { - return func() models.Shop { + return func(shopOptions models.ShopOptions) models.Shop { shop := GTMStoreShop{ - domains: Domains, + ShopOptions: shopOptions, + domains: Domains, } return &shop } diff --git a/pkg/shop/heroesdepapel/heroesdepapel.go b/pkg/shop/heroesdepapel/heroesdepapel.go index a41525f..ddb83f9 100644 --- a/pkg/shop/heroesdepapel/heroesdepapel.go +++ b/pkg/shop/heroesdepapel/heroesdepapel.go @@ -13,6 +13,7 @@ import ( var Domains = []string{"www.heroesdepapel.es"} type HeroesDePapelShop struct { + models.ShopOptions domains []string } @@ -60,9 +61,10 @@ func (s *HeroesDePapelShop) Get(url string) (*models.Product, error) { } func NewHeroesDePapelShopFactory() models.ShopFactory { - return func() models.Shop { + return func(shopOptions models.ShopOptions) models.Shop { shop := HeroesDePapelShop{ - domains: Domains, + ShopOptions: shopOptions, + domains: Domains, } return &shop } diff --git a/pkg/shop/steam/steam.go b/pkg/shop/steam/steam.go index 0b70790..3ec0953 100644 --- a/pkg/shop/steam/steam.go +++ b/pkg/shop/steam/steam.go @@ -14,6 +14,7 @@ import ( var Domains = []string{"store.steampowered.com"} type SteamShop struct { + models.ShopOptions domains []string } @@ -60,9 +61,10 @@ func (s *SteamShop) Get(url string) (*models.Product, error) { } func NewSteamShopFactory() models.ShopFactory { - return func() models.Shop { + return func(shopOptions models.ShopOptions) models.Shop { shop := SteamShop{ - domains: Domains, + ShopOptions: shopOptions, + domains: Domains, } return &shop }