goods 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|