goods 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +114 -0
- data/Rakefile +1 -0
- data/goods.gemspec +31 -0
- data/lib/goods/catalog.rb +32 -0
- data/lib/goods/categories_list.rb +26 -0
- data/lib/goods/category.rb +54 -0
- data/lib/goods/containable.rb +54 -0
- data/lib/goods/container.rb +58 -0
- data/lib/goods/currencies_list.rb +12 -0
- data/lib/goods/currency.rb +29 -0
- data/lib/goods/offer.rb +43 -0
- data/lib/goods/offers_list.rb +38 -0
- data/lib/goods/version.rb +3 -0
- data/lib/goods/xml/validator.rb +66 -0
- data/lib/goods/xml.rb +129 -0
- data/lib/goods.rb +42 -0
- data/lib/support/shops.dtd +149 -0
- data/spec/fixtures/empty_catalog.xml +15 -0
- data/spec/fixtures/simple_catalog.xml +74 -0
- data/spec/goods/catalog_spec.rb +56 -0
- data/spec/goods/categories_list_spec.rb +42 -0
- data/spec/goods/category_spec.rb +90 -0
- data/spec/goods/currencies_list_spec.rb +5 -0
- data/spec/goods/currency_spec.rb +39 -0
- data/spec/goods/offer_spec.rb +87 -0
- data/spec/goods/offers_list_spec.rb +80 -0
- data/spec/goods/xml/validator_spec.rb +25 -0
- data/spec/goods/xml_spec.rb +178 -0
- data/spec/goods_spec.rb +44 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/shared_examples_for_containable.rb +16 -0
- data/spec/support/shared_examples_for_container.rb +43 -0
- metadata +168 -0
data/lib/goods/xml.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require "nokogiri"
|
2
|
+
|
3
|
+
module Goods
|
4
|
+
class XML
|
5
|
+
class InvalidFormatError < StandardError; end
|
6
|
+
|
7
|
+
def initialize(string, url = nil, encoding = nil)
|
8
|
+
@xml_source = Nokogiri::XML::Document.parse(string, url, encoding)
|
9
|
+
end
|
10
|
+
|
11
|
+
def categories
|
12
|
+
@categories ||= extract_categories
|
13
|
+
end
|
14
|
+
|
15
|
+
def currencies
|
16
|
+
@currencies ||= extract_currencies
|
17
|
+
end
|
18
|
+
|
19
|
+
def offers
|
20
|
+
@offers ||= extract_offers
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
|
27
|
+
def catalog_node
|
28
|
+
@xml_source / "yml_catalog"
|
29
|
+
end
|
30
|
+
|
31
|
+
def shop_node
|
32
|
+
catalog_node / "shop"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Categories part
|
36
|
+
def categories_node
|
37
|
+
shop_node / "categories" / "category"
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_categories
|
41
|
+
categories_node.map do |category|
|
42
|
+
category_node_to_hash(category)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def category_node_to_hash(category)
|
47
|
+
category_hash = {
|
48
|
+
id: category.attribute("id").value,
|
49
|
+
name: category.text
|
50
|
+
}
|
51
|
+
category_hash[:parent_id] = if category.attribute("parentId")
|
52
|
+
category.attribute("parentId").value
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
category_hash
|
57
|
+
end
|
58
|
+
|
59
|
+
# Currencies part
|
60
|
+
def currencies_node
|
61
|
+
shop_node / "currencies" / "currency"
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_currencies
|
65
|
+
currencies_node.map do |currency|
|
66
|
+
currency_node_to_hash(currency)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def currency_node_to_hash(currency)
|
71
|
+
currency_hash = {
|
72
|
+
id: currency.attribute("id").value
|
73
|
+
}
|
74
|
+
|
75
|
+
attributes_with_defaults = {
|
76
|
+
rate: "1",
|
77
|
+
plus: "0"
|
78
|
+
}
|
79
|
+
attributes_with_defaults.each do |attr, default|
|
80
|
+
currency_hash[attr] = if currency.attribute(attr.to_s)
|
81
|
+
currency.attribute(attr.to_s).value
|
82
|
+
else
|
83
|
+
default
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
currency_hash
|
88
|
+
end
|
89
|
+
|
90
|
+
#Offers part
|
91
|
+
def offers_node
|
92
|
+
shop_node / "offers" / "offer"
|
93
|
+
end
|
94
|
+
|
95
|
+
def extract_offers
|
96
|
+
offers_node.map do |offer|
|
97
|
+
offer_node_to_hash(offer)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def offer_node_to_hash(offer)
|
102
|
+
offer_hash = {
|
103
|
+
id: offer.attribute("id").value
|
104
|
+
}
|
105
|
+
|
106
|
+
offer_hash[:available] = if attr = offer.attribute("available")
|
107
|
+
!! (attr.value =~ /true/)
|
108
|
+
else
|
109
|
+
true
|
110
|
+
end
|
111
|
+
{
|
112
|
+
url: "url",
|
113
|
+
currency_id: "currencyId",
|
114
|
+
category_id: "categoryId",
|
115
|
+
picture: "picture",
|
116
|
+
description: "description",
|
117
|
+
name: "name",
|
118
|
+
vendor: "vendor",
|
119
|
+
model: "model"
|
120
|
+
}.each do |property, node|
|
121
|
+
offer_hash[property] = (el = offer.xpath(node).first) ? el.text.strip : nil
|
122
|
+
end
|
123
|
+
|
124
|
+
offer_hash[:price] = offer.xpath("price").first.text.to_f
|
125
|
+
|
126
|
+
offer_hash
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/goods.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require "goods/version"
|
3
|
+
require "goods/xml/validator"
|
4
|
+
require "goods/xml"
|
5
|
+
require "goods/containable"
|
6
|
+
require "goods/container"
|
7
|
+
require "goods/category"
|
8
|
+
require "goods/categories_list"
|
9
|
+
require "goods/offer"
|
10
|
+
require "goods/offers_list"
|
11
|
+
require "goods/currency"
|
12
|
+
require "goods/currencies_list"
|
13
|
+
require "goods/catalog"
|
14
|
+
|
15
|
+
module Goods
|
16
|
+
def self.from_string(xml_string, url=nil, encoding=nil)
|
17
|
+
validator = XML::Validator.new
|
18
|
+
if validator.valid? xml_string
|
19
|
+
Catalog.new(string: xml_string, url: url, encoding: encoding)
|
20
|
+
else
|
21
|
+
raise XML::InvalidFormatError, validator.error
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.from_url(url, encoding=nil)
|
26
|
+
xml_string = self.load url
|
27
|
+
from_string(xml_string, url, encoding)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.from_file(file, encoding=nil)
|
31
|
+
xml_string = self.load file
|
32
|
+
from_string(xml_string, nil, encoding)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def self.load(source)
|
38
|
+
open(source) do |f|
|
39
|
+
f.read
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
<!ELEMENT yml_catalog (shop)>
|
2
|
+
<!ATTLIST yml_catalog
|
3
|
+
date CDATA #REQUIRED
|
4
|
+
count CDATA #IMPLIED>
|
5
|
+
|
6
|
+
<!ELEMENT shop (name, company, url, phone?, platform?, version?, agency?, email*, currencies, categories, store?, pickup?, delivery?, deliveryIncluded?, local_delivery_cost?, adult?, offers)>
|
7
|
+
<!ELEMENT company (#PCDATA)>
|
8
|
+
<!ELEMENT phone (#PCDATA)>
|
9
|
+
|
10
|
+
<!ELEMENT currencies (currency+)>
|
11
|
+
<!ELEMENT currency EMPTY>
|
12
|
+
<!ATTLIST currency
|
13
|
+
id (RUR|RUB|USD|BYR|KZT|EUR|UAH) #REQUIRED
|
14
|
+
rate CDATA "1"
|
15
|
+
plus CDATA "0">
|
16
|
+
|
17
|
+
|
18
|
+
<!ELEMENT categories (category+)>
|
19
|
+
<!ELEMENT category (#PCDATA)>
|
20
|
+
<!ATTLIST category
|
21
|
+
id CDATA #REQUIRED
|
22
|
+
parentId CDATA #IMPLIED
|
23
|
+
tid CDATA #IMPLIED
|
24
|
+
yid CDATA #IMPLIED>
|
25
|
+
|
26
|
+
<!ELEMENT offers (offer+)>
|
27
|
+
<!ELEMENT offer (url?, buyurl?, price, wprice?, currencyId, xCategory?, categoryId+, market_category?,
|
28
|
+
picture*, store?, pickup?, delivery?, deliveryIncluded?, local_delivery_cost?, orderingTime?,
|
29
|
+
((typePrefix?, vendor, vendorCode?, model, (provider, tarifplan?)?) |
|
30
|
+
(author?, name, publisher?, series?, year?, ISBN?, volume?, part?, language?, binding?, page_extent?, table_of_contents?) |
|
31
|
+
(author?, name, publisher?, series?, year?, ISBN?, volume?, part?, language?, table_of_contents?, performed_by?, performance_type?, storage?, format?, recording_length?) |
|
32
|
+
(artist?, title, year?, media?, starring?, director?, originalName?, country?) |
|
33
|
+
(worldRegion?, country?, region?, days, dataTour*, name, hotel_stars?, room?, meal?, included, transport, price_min?, price_max?, options?) |
|
34
|
+
(name, place, hall?, hall_part?, date, is_premiere?, is_kids?) |
|
35
|
+
(name, vendor?, vendorCode?)
|
36
|
+
),
|
37
|
+
aliases?, additional*, description?, sales_notes?, promo?,
|
38
|
+
manufacturer_warranty?, country_of_origin?, downloadable?, adult?,
|
39
|
+
age?,
|
40
|
+
barcode*,
|
41
|
+
param*,
|
42
|
+
related_offer*
|
43
|
+
)>
|
44
|
+
<!ATTLIST offer
|
45
|
+
id CDATA #IMPLIED
|
46
|
+
group_id CDATA #IMPLIED
|
47
|
+
type (vendor.model | book | audiobook | artist.title | tour | ticket | event-ticket) #IMPLIED
|
48
|
+
available (true | false) #IMPLIED
|
49
|
+
bid CDATA #IMPLIED
|
50
|
+
cbid CDATA #IMPLIED>
|
51
|
+
|
52
|
+
<!ELEMENT url (#PCDATA)>
|
53
|
+
<!ELEMENT store (#PCDATA)>
|
54
|
+
<!ELEMENT email (#PCDATA)>
|
55
|
+
<!ELEMENT platform (#PCDATA)>
|
56
|
+
<!ELEMENT version (#PCDATA)>
|
57
|
+
<!ELEMENT agency (#PCDATA)>
|
58
|
+
<!ELEMENT buyurl (#PCDATA)>
|
59
|
+
<!ELEMENT picture (#PCDATA)>
|
60
|
+
<!ELEMENT pickup (#PCDATA)>
|
61
|
+
<!ELEMENT delivery (#PCDATA)>
|
62
|
+
<!ELEMENT deliveryIncluded EMPTY>
|
63
|
+
<!ELEMENT local_delivery_cost (#PCDATA)>
|
64
|
+
<!ELEMENT orderingTime (onstock?, ordering, deliveryTime?)>
|
65
|
+
<!ELEMENT onstock EMPTY>
|
66
|
+
<!ELEMENT ordering (#PCDATA)>
|
67
|
+
<!ATTLIST ordering
|
68
|
+
hours CDATA #IMPLIED>
|
69
|
+
|
70
|
+
<!ELEMENT deliveryTime EMPTY>
|
71
|
+
<!ELEMENT price (#PCDATA)>
|
72
|
+
<!ELEMENT wprice (#PCDATA)>
|
73
|
+
<!ELEMENT currencyId (#PCDATA)>
|
74
|
+
<!ELEMENT categoryId (#PCDATA)>
|
75
|
+
<!ATTLIST categoryId
|
76
|
+
type (Yandex | Torg | Own) "Own" >
|
77
|
+
|
78
|
+
<!ELEMENT market_category (#PCDATA)>
|
79
|
+
<!ELEMENT typePrefix (#PCDATA)>
|
80
|
+
<!ELEMENT vendor (#PCDATA)>
|
81
|
+
<!ELEMENT vendorCode (#PCDATA)>
|
82
|
+
<!ELEMENT model (#PCDATA)>
|
83
|
+
<!ELEMENT author (#PCDATA)>
|
84
|
+
<!ELEMENT name (#PCDATA)>
|
85
|
+
<!ELEMENT publisher (#PCDATA)>
|
86
|
+
<!ELEMENT ISBN (#PCDATA)>
|
87
|
+
<!ELEMENT volume (#PCDATA)>
|
88
|
+
<!ELEMENT part (#PCDATA)>
|
89
|
+
<!ELEMENT language (#PCDATA)>
|
90
|
+
<!ELEMENT binding (#PCDATA)>
|
91
|
+
<!ELEMENT page_extent (#PCDATA)>
|
92
|
+
<!ELEMENT table_of_contents (#PCDATA)>
|
93
|
+
<!ELEMENT performed_by (#PCDATA)>
|
94
|
+
<!ELEMENT performance_type (#PCDATA)>
|
95
|
+
<!ELEMENT storage (#PCDATA)>
|
96
|
+
<!ELEMENT format (#PCDATA)>
|
97
|
+
<!ELEMENT recording_length (#PCDATA)>
|
98
|
+
<!ELEMENT series (#PCDATA)>
|
99
|
+
<!ELEMENT year (#PCDATA)>
|
100
|
+
<!ELEMENT artist (#PCDATA)>
|
101
|
+
<!ELEMENT title (#PCDATA)>
|
102
|
+
<!ELEMENT media (#PCDATA)>
|
103
|
+
<!ELEMENT starring (#PCDATA)>
|
104
|
+
<!ELEMENT director (#PCDATA)>
|
105
|
+
<!ELEMENT originalName (#PCDATA)>
|
106
|
+
<!ELEMENT country (#PCDATA)>
|
107
|
+
<!ELEMENT description (#PCDATA)>
|
108
|
+
<!ELEMENT sales_notes (#PCDATA)>
|
109
|
+
<!ELEMENT promo (#PCDATA)>
|
110
|
+
<!ELEMENT aliases (#PCDATA)>
|
111
|
+
<!ELEMENT provider (#PCDATA)>
|
112
|
+
<!ELEMENT tarifplan (#PCDATA)>
|
113
|
+
<!ELEMENT xCategory (#PCDATA)>
|
114
|
+
<!ELEMENT additional (#PCDATA)>
|
115
|
+
<!ELEMENT worldRegion (#PCDATA)>
|
116
|
+
<!ELEMENT region (#PCDATA)>
|
117
|
+
<!ELEMENT days (#PCDATA)>
|
118
|
+
<!ELEMENT dataTour (#PCDATA)>
|
119
|
+
<!ELEMENT hotel_stars (#PCDATA)>
|
120
|
+
<!ELEMENT room (#PCDATA)>
|
121
|
+
<!ELEMENT meal (#PCDATA)>
|
122
|
+
<!ELEMENT included (#PCDATA)>
|
123
|
+
<!ELEMENT transport (#PCDATA)>
|
124
|
+
<!ELEMENT price_min (#PCDATA)>
|
125
|
+
<!ELEMENT price_max (#PCDATA)>
|
126
|
+
<!ELEMENT options (#PCDATA)>
|
127
|
+
<!ELEMENT manufacturer_warranty (#PCDATA)>
|
128
|
+
<!ELEMENT country_of_origin (#PCDATA)>
|
129
|
+
<!ELEMENT downloadable (#PCDATA)>
|
130
|
+
<!ELEMENT adult (#PCDATA)>
|
131
|
+
<!ELEMENT age (#PCDATA)>
|
132
|
+
<!ELEMENT barcode (#PCDATA)>
|
133
|
+
<!ELEMENT param (#PCDATA)>
|
134
|
+
<!ATTLIST param
|
135
|
+
name CDATA #REQUIRED
|
136
|
+
unit CDATA #IMPLIED>
|
137
|
+
<!ELEMENT related_offer (#PCDATA)>
|
138
|
+
|
139
|
+
<!ELEMENT place (#PCDATA)>
|
140
|
+
<!ELEMENT hall (#PCDATA)>
|
141
|
+
<!ATTLIST hall
|
142
|
+
plan CDATA #IMPLIED>
|
143
|
+
|
144
|
+
<!ELEMENT hall_part (#PCDATA)>
|
145
|
+
<!ELEMENT is_premiere (#PCDATA)>
|
146
|
+
<!ELEMENT is_kids (#PCDATA)>
|
147
|
+
<!ELEMENT date (#PCDATA)>
|
148
|
+
|
149
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!DOCTYPE yml_catalog SYSTEM "/Users/rainx/WebstormProjects/partner/pages/help/shops.dtd">
|
3
|
+
<yml_catalog date="2010-04-01 17:00">
|
4
|
+
<shop>
|
5
|
+
<currencies>
|
6
|
+
</currencies>
|
7
|
+
|
8
|
+
<categories>
|
9
|
+
</categories>
|
10
|
+
|
11
|
+
<offers>
|
12
|
+
</offers>
|
13
|
+
|
14
|
+
</shop>
|
15
|
+
</yml_catalog>
|
@@ -0,0 +1,74 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<!DOCTYPE yml_catalog SYSTEM "/Users/rainx/WebstormProjects/partner/pages/help/shops.dtd">
|
3
|
+
<yml_catalog date="2010-04-01 17:00">
|
4
|
+
<shop>
|
5
|
+
<name>Magazin</name>
|
6
|
+
<company>Magazin</company>
|
7
|
+
<url>http://www.magazin.ru/</url>
|
8
|
+
|
9
|
+
<currencies>
|
10
|
+
<currency id="RUR" rate="1" plus="0"/>
|
11
|
+
<currency id="USD" rate="30"/>
|
12
|
+
<currency id="KZT"/>
|
13
|
+
</currencies>
|
14
|
+
|
15
|
+
<categories>
|
16
|
+
<category id="1">Оргтехника</category>
|
17
|
+
<category id="10" parentId="1">Принтеры</category>
|
18
|
+
<category id="100" parentId="10">Струйные принтеры</category>
|
19
|
+
<category id="101" parentId="10">Лазерные принтеры</category>
|
20
|
+
|
21
|
+
<category id="2">Фототехника</category>
|
22
|
+
<category id="11" parentId="2">Фотоаппараты</category>
|
23
|
+
<category id="12" parentId="2">Объективы</category>
|
24
|
+
|
25
|
+
<category id="3">Книги</category>
|
26
|
+
<category id="13" parentId="3">Детективы</category>
|
27
|
+
</categories>
|
28
|
+
|
29
|
+
<local_delivery_cost>300</local_delivery_cost>
|
30
|
+
|
31
|
+
<offers>
|
32
|
+
<offer id="123" type="vendor.model" bid="13" cbid="20">
|
33
|
+
<url>http://magazin.ru/product_page.asp?pid=14344</url>
|
34
|
+
<price>15000</price>
|
35
|
+
<currencyId>RUR</currencyId>
|
36
|
+
<categoryId type="Own">100</categoryId>
|
37
|
+
<categoryId type="Own">101</categoryId>
|
38
|
+
<picture>http://magazin.ru/img/device1.jpg</picture>
|
39
|
+
<picture>http://magazin.ru/img/device2.jpg</picture>
|
40
|
+
<delivery>true</delivery>
|
41
|
+
<local_delivery_cost>300</local_delivery_cost>
|
42
|
+
<typePrefix>Принтер</typePrefix>
|
43
|
+
<vendor>НP</vendor>
|
44
|
+
<vendorCode>Q7533A</vendorCode>
|
45
|
+
<model>Color LaserJet 3000</model>
|
46
|
+
<description>A4, 64Mb, 600x600 dpi, USB 2.0, 29стр/мин ч/б / 15стр/мин цв, лотки на 100л и 250л, плотность до 175г/м, до 60000 стр/месяц </description>
|
47
|
+
<manufacturer_warranty>true</manufacturer_warranty>
|
48
|
+
<country_of_origin>Япония</country_of_origin>
|
49
|
+
</offer>
|
50
|
+
|
51
|
+
<offer id="1234" type="book" bid="17" available="false">
|
52
|
+
<price>100</price>
|
53
|
+
<currencyId>RUR</currencyId>
|
54
|
+
<categoryId type="Own">13</categoryId>
|
55
|
+
<delivery>true</delivery>
|
56
|
+
<local_delivery_cost>300</local_delivery_cost>
|
57
|
+
<author>Александра Маринина</author>
|
58
|
+
<name>Все не так. В 2 томах. Том 1</name>
|
59
|
+
<publisher>ЭКСМО - Пресс</publisher>
|
60
|
+
<series>А. Маринина - королева детектива</series>
|
61
|
+
<year>2009</year>
|
62
|
+
<ISBN>978-5-699-23647-3</ISBN>
|
63
|
+
<volume>2</volume>
|
64
|
+
<part>1</part>
|
65
|
+
<language>rus</language>
|
66
|
+
<binding>70x90/32</binding>
|
67
|
+
<page_extent>288</page_extent>
|
68
|
+
<description>Все прекрасно в большом патриархальном семействе Руденко. Но — увы! — впечатление это обманчиво: каждого из многочисленных представителей семьи обуревают свои потаенные страсти и запретные желания.</description>
|
69
|
+
<downloadable>false</downloadable>
|
70
|
+
</offer>
|
71
|
+
</offers>
|
72
|
+
|
73
|
+
</shop>
|
74
|
+
</yml_catalog>
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Goods::Catalog do
|
4
|
+
describe "#initialize" do
|
5
|
+
it "should call #from_string when string is passed" do
|
6
|
+
expect_any_instance_of(Goods::Catalog).to receive(:from_string).
|
7
|
+
with("string", "url", "UTF-8").once
|
8
|
+
Goods::Catalog.new(string: "string", url: "url", encoding: "UTF-8")
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should raise error when none of 'string', 'url', 'file' params is passed" do
|
12
|
+
expect{ Goods::Catalog.new({}) }.to raise_error(ArgumentError)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#from_string" do
|
17
|
+
class NullObject
|
18
|
+
def method_missing(name, *args)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
[Goods::XML, Goods::CategoriesList, Goods::CurrenciesList, Goods::OffersList].each do |part|
|
24
|
+
it "should create #{part}" do
|
25
|
+
expect(part).to receive(:new).and_return(NullObject.new).once
|
26
|
+
Goods::Catalog.new(string: "xml")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#prune" do
|
32
|
+
let(:xml) {
|
33
|
+
File.read(File.expand_path("../../fixtures/simple_catalog.xml", __FILE__))
|
34
|
+
}
|
35
|
+
let(:catalog) { Goods::Catalog.new string: xml}
|
36
|
+
|
37
|
+
it "should prune offers and categories" do
|
38
|
+
level = 2
|
39
|
+
expect(catalog.offers).to receive(:prune_categories).with(level)
|
40
|
+
expect(catalog.categories).to receive(:prune).with(level)
|
41
|
+
catalog.prune(level)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should replace categories of offers" do
|
45
|
+
catalog.prune(0)
|
46
|
+
expect(catalog.offers.find("123").category).to be(
|
47
|
+
catalog.categories.find("1")
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should remove categories affected by prunning" do
|
52
|
+
catalog.prune(0)
|
53
|
+
expect(catalog.categories.size).to eql(3)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Goods::CategoriesList do
|
4
|
+
it_should_behave_like "a container", Goods::Category, Goods::Category.new(id: "1", name: "Name")
|
5
|
+
|
6
|
+
describe "#add" do
|
7
|
+
it "should setup parent for category" do
|
8
|
+
parent = Goods::Category.new(id: "1", name: "Parent")
|
9
|
+
child = Goods::Category.new(id: "2", name: "Child", parent_id: "1")
|
10
|
+
subject.add(parent); subject.add(child)
|
11
|
+
|
12
|
+
expect(child.parent).to be(parent)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#prune" do
|
17
|
+
let(:list) do
|
18
|
+
list = Goods::CategoriesList.new [
|
19
|
+
{id: "1", name: "root"},
|
20
|
+
{id: "11", name: "root", parent_id: "1"},
|
21
|
+
{id: "12", name: "root", parent_id: "2"}
|
22
|
+
]
|
23
|
+
list
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should raise error if prune level < 0" do
|
27
|
+
expect{ list.prune(-1) }.to raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not make any changes to nodes with level lesser than target level" do
|
31
|
+
orig = list.dup
|
32
|
+
list.prune(3)
|
33
|
+
expect(list.size).to eq(orig.size)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should remove nodes with level greater than target level" do
|
37
|
+
list.prune(1)
|
38
|
+
expect(list.size).to eq(2)
|
39
|
+
list.each { |item| expect(item.level < 2).to eql(true) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Goods::Category do
|
4
|
+
let(:valid_description) { {id: "1", name: "Name"} }
|
5
|
+
let(:root) { Goods::Category.new valid_description }
|
6
|
+
|
7
|
+
it_should_behave_like "containable" do
|
8
|
+
let(:element) { Goods::Category.new(valid_description) }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#valid?" do
|
12
|
+
it "should reject categories with no root when one should be" do
|
13
|
+
invalid_child = Goods::Category.new(id: "3", name: "Invalid child", parent_id: "2")
|
14
|
+
expect(invalid_child).not_to be_valid
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should reject categories with incorrect parrent" do
|
18
|
+
invalid_child = Goods::Category.new(id: "3", name: "Invalid child", parent_id: "2")
|
19
|
+
invalid_child.parent = root
|
20
|
+
expect(invalid_child).not_to be_valid
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should accept categories with no root" do
|
24
|
+
child = Goods::Category.new(valid_description)
|
25
|
+
child.valid?
|
26
|
+
expect(child).to be_valid
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should accept categories with correct root" do
|
30
|
+
valid_child = Goods::Category.new(id: "3", name: "Invalid child", parent_id: "1")
|
31
|
+
valid_child.parent = root
|
32
|
+
expect(valid_child).to be_valid
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "on graph properties" do
|
37
|
+
let(:first_level) do
|
38
|
+
c = Goods::Category.new valid_description.merge parent_id: root.id
|
39
|
+
c.parent = root
|
40
|
+
c
|
41
|
+
end
|
42
|
+
let(:second_level) do
|
43
|
+
c = Goods::Category.new valid_description.merge parent_id: first_level.id
|
44
|
+
c.parent = first_level
|
45
|
+
c
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#level" do
|
49
|
+
it "should be 0 for root nodes" do
|
50
|
+
expect(root.level).to eq(0)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should be parent level + for for child nodes" do
|
54
|
+
expect(first_level.level).to eq(root.level + 1)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "#root" do
|
59
|
+
it "should be root itself if called on root" do
|
60
|
+
expect(root.root).to be(root)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should be root of a branch if called on child" do
|
64
|
+
expect(second_level.root).to be(root)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#parent_at_level" do
|
69
|
+
|
70
|
+
it "should raise error if level < 0" do
|
71
|
+
expect { root.parent_at_level(-1) }.to raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return self if called for object's level" do
|
75
|
+
expect(first_level.parent_at_level(first_level.level)).to be(first_level)
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should raise error if level > object level" do
|
79
|
+
expect { first_level.parent_at_level first_level.level + 1 }.
|
80
|
+
to raise_error(ArgumentError)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should return parent at specified level from the 'root'" do
|
84
|
+
expect(second_level.parent_at_level(1)).to be(first_level)
|
85
|
+
expect(second_level.parent_at_level(0)).to be(root)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Goods::Currency do
|
4
|
+
let(:valid_description) { {id: "VAL_CUR", rate: 1, plus: 0} }
|
5
|
+
let(:valid_currency) { Goods::Currency.new(valid_description) }
|
6
|
+
|
7
|
+
it_should_behave_like "containable" do
|
8
|
+
let(:element) { valid_currency }
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "#valid?" do
|
12
|
+
let(:invalid_description) { {id: "", rate: 0, plus: -1} }
|
13
|
+
|
14
|
+
it "should return true for valid currency" do
|
15
|
+
expect(Goods::Currency.new(valid_description).valid?).to be true
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return false for invalid currency" do
|
19
|
+
expect(Goods::Currency.new(invalid_description).valid?).to be false
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should remember invalid fields" do
|
23
|
+
invalid_currency = Goods::Currency.new(invalid_description)
|
24
|
+
invalid_currency.valid?
|
25
|
+
[:id, :rate, :plus].each do |field|
|
26
|
+
expect(invalid_currency.invalid_fields).to include(field)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#in" do
|
32
|
+
let(:rur) { Goods::Currency.new(id: "RUR", rate: 1, plus: 0) }
|
33
|
+
let(:usd) { Goods::Currency.new(id: "USD", rate: 30, plus: 0) }
|
34
|
+
|
35
|
+
it "should convert to another currency" do
|
36
|
+
expect(usd.in(rur)).to eql(30.0)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|