fassbinder 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.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ pkg/*
2
+ *.gem
3
+ .bundle
4
+ .rvmrc
5
+ .rspec
6
+ Gemfile.lock
7
+ amazon.yml
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2
+ Version 2, December 2004
3
+
4
+ Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
5
+
6
+ Everyone is permitted to copy and distribute verbatim or modified
7
+ copies of this license document, and changing it is allowed as long
8
+ as the name is changed.
9
+
10
+ DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12
+
13
+ 0. You just DO WHAT THE FUCK YOU WANT TO.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ Fassbinder
2
+ ==========
3
+
4
+ ![Fassbinder](http://www.ejournal.at/artsite/fassbinder.jpg)
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc 'Run all specs in spec directory'
7
+ RSpec::Core::RakeTask.new(:spec) do |spec|
8
+ spec.pattern = 'spec/**/*_spec.rb'
9
+ end
10
+
11
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path('../lib', __FILE__)
3
+ require 'fassbinder/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'fassbinder'
7
+ s.version = Fassbinder::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Paper Cavalier']
10
+ s.email = 'code@papercavalier.com'
11
+ s.homepage = 'https://rubygems.org/gems/fassbinder'
12
+ s.summary = %q{Wraps Amazon in a loving embrace.}
13
+ s.description = %q{Fassbinder wraps Amazon in a loving embrace.}
14
+
15
+ s.rubyforge_project = 'fassbinder'
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ['lib']
21
+
22
+ s.add_dependency('kosher', '~> 0.2.4')
23
+ s.add_dependency('sucker', '~> 1.3.1')
24
+ s.add_development_dependency('fabrication', '~> 0.9.5')
25
+ s.add_development_dependency('rspec', '~> 2.5.0')
26
+ s.add_development_dependency('ruby-debug19', '~> 0.11.6')
27
+ s.add_development_dependency('vcr', '~> 1.7.0')
28
+ s.add_development_dependency('webmock', '~> 1.6.2')
29
+ end
@@ -0,0 +1,25 @@
1
+ module Fassbinder
2
+ class Algorithm
3
+ def initialize(response)
4
+ raise InvalidResponseError unless response.valid?
5
+
6
+ @response = response
7
+ end
8
+
9
+ def books
10
+ @response.map('Item') do |item|
11
+ Book.build(item)
12
+ end
13
+ end
14
+
15
+ def errors
16
+ @response.errors.map do |error|
17
+ error['Message'].scan(/[0-9A-Z]{10}/).first rescue nil
18
+ end.compact
19
+ end
20
+
21
+ def response
22
+ @response
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Kosher
2
+ class Book < Struct.new(:asin, :offers, :offers_count, :sales_rank)
3
+
4
+ class << self
5
+ def build(doc)
6
+ asin = doc['ASIN']
7
+ sales_rank = doc['SalesRank'].to_i
8
+ offers_count = doc['Offers']['TotalOffers'].to_i
9
+ offers = build_offers(doc['Offers']['Offer'])
10
+
11
+ new(asin, offers, offers_count, sales_rank)
12
+ end
13
+
14
+ private
15
+
16
+ def build_offers(offers)
17
+ [offers].flatten.compact.map do |offer|
18
+
19
+ # Senify Yen because Ruby Money says so
20
+ if offer['OfferListing']['Price']['CurrencyCode'] == 'JPY'
21
+ offer['OfferListing']['Price']['Amount'] = offer['OfferListing']['Price']['Amount'].to_i * 100
22
+ end
23
+
24
+ Offer.build(offer)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module Kosher
2
+ class Condition < Struct.new(:grade)
3
+
4
+ alias_method :to_i, :grade
5
+
6
+ CONDITIONS = {
7
+ 'new' => 1,
8
+ 'mint' => 2,
9
+ 'verygood' => 3,
10
+ 'good' => 4,
11
+ 'acceptable' => 5 }
12
+
13
+ def initialize(string = '')
14
+ self.grade = CONDITIONS[string] || 6
15
+ end
16
+
17
+ def kosher?
18
+ grade <= 4
19
+ end
20
+
21
+ def new?
22
+ grade == 1
23
+ end
24
+
25
+ def used?
26
+ !new?
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,59 @@
1
+ module Kosher
2
+ class Description < String
3
+ DAMAGED = "\\b(?:missing|torn|broken|split|discard|withdrawn|rent|stain|school|damaged|water)"
4
+ EXLIB = "(?:e?x|discarded|retired|former|has|have)[\\s._-]*lib"
5
+ MARKED = "(highlight|hilit|underlin)"
6
+ MISSING_VOL = "(vols?|volume) only"
7
+ REVIEW_COPY = "\\b(?:uncorrected|advanced?\\sreview|arc)\\b"
8
+
9
+ def kosher?
10
+ !(present? && bad?)
11
+ end
12
+
13
+ private
14
+
15
+ def bad?
16
+ damaged? ||
17
+ ex_library? ||
18
+ marked? ||
19
+ missing_volume? ||
20
+ review_copy?
21
+ end
22
+
23
+ def damaged?
24
+ matches(DAMAGED)
25
+ end
26
+
27
+ def does_not_match(value)
28
+ !match(Regexp.new(value, true))
29
+ end
30
+
31
+ def ex_library?
32
+ matches(EXLIB) && does_not_match(negation_of(EXLIB))
33
+ end
34
+
35
+ def marked?
36
+ matches(MARKED) && does_not_match(negation_of(MARKED))
37
+ end
38
+
39
+ def matches(value)
40
+ !does_not_match(value)
41
+ end
42
+
43
+ def missing_volume?
44
+ matches(MISSING_VOL)
45
+ end
46
+
47
+ def negation_of(value)
48
+ "(?:no|not an?)\\s+#{value}"
49
+ end
50
+
51
+ def present?
52
+ self != ''
53
+ end
54
+
55
+ def review_copy?
56
+ matches(REVIEW_COPY)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module Fassbinder
2
+ class InvalidResponseError < StandardError; end
3
+ end
@@ -0,0 +1,38 @@
1
+ module Kosher
2
+ class Offer < Struct.new(
3
+ :seller,
4
+ :condition,
5
+ :description,
6
+ :ships_in,
7
+ :ships_free,
8
+ :cents,
9
+ :exchange_id,
10
+ :listing_id)
11
+
12
+ def self.build(doc)
13
+ offer = new
14
+ offer.seller = Seller.build(doc['Merchant'])
15
+
16
+ attributes = doc['OfferAttributes']
17
+ offer.condition = Condition.new(attributes['SubCondition'])
18
+ offer.description = Description.new(attributes['ConditionNote'].to_s)
19
+
20
+ listing = doc['OfferListing']
21
+ offer.ships_in = listing['AvailabilityAttributes']['MaximumHours'].to_i
22
+ offer.ships_free = listing['IsEligibleForSuperSaverShipping'] == '1'
23
+ offer.cents = listing['Price']['Amount'].to_i
24
+ offer.exchange_id = listing['ExchangeId']
25
+ offer.listing_id = listing['OfferListingId']
26
+
27
+ offer
28
+ end
29
+
30
+ def kosher?
31
+ condition.kosher? && seller.kosher? && description.kosher? && ships_now?
32
+ end
33
+
34
+ def ships_now?
35
+ ships_in.to_i <= 48
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,22 @@
1
+ module Fassbinder
2
+ class Request < Sucker::Request
3
+ def initialize(args = {})
4
+ super
5
+ self.<<({
6
+ 'Operation' => 'ItemLookup',
7
+ 'ItemLookup.Shared.IdType' => 'ASIN',
8
+ 'ItemLookup.Shared.Condition' => 'All',
9
+ 'ItemLookup.Shared.MerchantId' => 'All',
10
+ 'ItemLookup.Shared.ResponseGroup' => ['OfferFull', 'SalesRank'] })
11
+ end
12
+
13
+ def batchify(asins)
14
+ self.<<({ 'ItemLookup.1.ItemId' => asins[0, 10] })
15
+ self.<<({ 'ItemLookup.2.ItemId' => asins[10, 10] }) if asins.size > 10
16
+ end
17
+
18
+ def get
19
+ Algorithm.new(super)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ module Kosher
2
+ class Seller < Struct.new(:merchant_id, :name, :average_rating)
3
+ class << self
4
+ attr_accessor :blacklist
5
+
6
+ def build(doc)
7
+ merchant_id = doc['MerchantId']
8
+ name = doc['Name']
9
+ average_rating = doc['AverageFeedbackRating'].to_f
10
+
11
+ new(merchant_id, name, average_rating)
12
+ end
13
+ end
14
+
15
+ def blacklist
16
+ self.class.blacklist
17
+ end
18
+
19
+ def blacklisted?
20
+ blacklist.include?(merchant_id) rescue false
21
+ end
22
+
23
+ def kosher?
24
+ return false if blacklisted?
25
+
26
+ average_rating == 0.0 || average_rating > 4.7
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+
3
+ module Kosher
4
+ class Struct < ::Struct
5
+ def to_map
6
+ map = Hash.new
7
+ self.members.each { |m| map[m] = self[m] }
8
+ map
9
+ end
10
+
11
+ def to_json(*a)
12
+ to_map.to_json(*a)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Fassbinder
2
+ VERSION = '0.0.1'
3
+ end
data/lib/fassbinder.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'sucker'
2
+
3
+ require 'fassbinder/algorithm'
4
+ require 'fassbinder/errors'
5
+ require 'fassbinder/item'
6
+ require 'fassbinder/offer'
7
+ require 'fassbinder/request'
@@ -0,0 +1,7 @@
1
+ Fabricator(:kosher_condition, :class_name => 'kosher/condition') do
2
+ grade 1
3
+ end
4
+
5
+ Fabricator(:unkosher_condition, :class_name => 'kosher/condition') do
6
+ grade 5
7
+ end
@@ -0,0 +1,8 @@
1
+ Fabricator(:offer, :class_name => 'kosher/offer') do
2
+ seller { Fabricate(:good_seller) }
3
+ description Kosher::Description.new('')
4
+ condition { Fabricate(:kosher_condition) }
5
+ ships_free false
6
+ ships_in 48
7
+ cents 100
8
+ end
@@ -0,0 +1,15 @@
1
+ Fabricator(:seller, :class_name => 'kosher/seller') do
2
+ merchant_id Faker::Amazon.merchant_id
3
+ end
4
+
5
+ Fabricator(:new_seller, :from => :seller) do
6
+ average_rating 0.0
7
+ end
8
+
9
+ Fabricator(:good_seller, :from => :seller) do
10
+ average_rating 5.0
11
+ end
12
+
13
+ Fabricator(:bad_seller, :from => :seller) do
14
+ average_rating 4.5
15
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ module Kosher
4
+ describe Algorithm do
5
+ use_vcr_cassette 'batch-request'
6
+
7
+ let(:asins) do
8
+
9
+ # The last ASIN does not exist.
10
+ %w{
11
+ 0816614024 0143105825 0485113600 0816616779 0942299078
12
+ 0816614008 144006654X 0486400360 0486417670 087220474X
13
+ 0486454398 0268018359 1604246014 184467598X 0312427182
14
+ 1844674282 0745640974 0745646441 0826489540 2081232191 }
15
+ end
16
+
17
+ let(:algorithm) do
18
+ request = Request.new(credentials)
19
+ request.locale = :us
20
+ request.batchify(asins)
21
+ request.get
22
+ end
23
+
24
+ describe ".new" do
25
+ it "raises an error if response is not valid" do
26
+ response = mock('Response')
27
+ response.stub!(:valid?).and_return(false)
28
+
29
+ expect do
30
+ Algorithm.new(response)
31
+ end.to raise_error InvalidResponseError
32
+ end
33
+ end
34
+
35
+ describe "#books" do
36
+ it "should return found books" do
37
+ algorithm.items.count.should eql 19
38
+ algorithm.items.first.should be_a Book
39
+ end
40
+ end
41
+
42
+ describe "#errors" do
43
+ it "should return ASINs that are not found" do
44
+ algorithm.errors.count.should eql 1
45
+ algorithm.errors.first.should eql '2081232191'
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ module Kosher
4
+ describe Item do
5
+ it "should descend from Kosher::Struct" do
6
+ Item.ancestors.should include Kosher::Struct
7
+ end
8
+
9
+ describe ".build" do
10
+ use_vcr_cassette '0143105825'
11
+
12
+ let(:asin) { '0143105825' }
13
+
14
+ it "should build an item" do
15
+ request = Request.new(credentials)
16
+ request.locale = :us
17
+ request.batchify([asin])
18
+ algorithm = request.get
19
+ response = algorithm.instance_variable_get(:@response)
20
+ doc = response.find('Item').first
21
+ item = Item.build(doc)
22
+
23
+ item.should be_a Item
24
+ item.asin.should eql asin
25
+ item.offers_count.should be > 0
26
+ item.sales_rank.should be > 0
27
+ item.offers.count.should > 0
28
+ item.offers.first.should be_a Offer
29
+ end
30
+
31
+ it "should senify Yen in Japanese requests" do
32
+ request = Request.new(credentials)
33
+ request.locale = :jp
34
+ request.batchify([asin])
35
+ algorithm = request.get
36
+ response = algorithm.instance_variable_get(:@response)
37
+ doc = response.find('Item').first
38
+
39
+ doc['Offers']['Offer'].each do |offer|
40
+ offer['OfferListing']['Price']['Amount'] = 1
41
+ end
42
+ item = Item.build(doc)
43
+
44
+ item.offers.each do |offer|
45
+ offer.cents.should eql 100
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ module Kosher
4
+ describe Condition do
5
+ describe "#to_i" do
6
+ def this(str)
7
+ Condition.new(str).to_i
8
+ end
9
+
10
+ it "casts as integer" do
11
+ this('new').should eql 1
12
+ this('mint').should eql 2
13
+ this('verygood').should eql 3
14
+ this('good').should eql 4
15
+ this('acceptable').should eql 5
16
+ end
17
+
18
+ it "casts unrecognized conditions as 6" do
19
+ this('refurbished').should eql 6
20
+ end
21
+ end
22
+
23
+ describe "#kosher?" do
24
+ it "returns true if condition is good or better" do
25
+ Condition.new('verygood').should be_kosher
26
+ Condition.new('good').should be_kosher
27
+ Condition.new('acceptable').should_not be_kosher
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ module Kosher
4
+ describe Description do
5
+ def this(value)
6
+ Description.new(value)
7
+ end
8
+
9
+ it "inherits from String" do
10
+ Description.ancestors.should include String
11
+ end
12
+
13
+ it "validates a blank description" do
14
+ this('').should be_kosher
15
+ end
16
+
17
+ it "validates a non-blank description" do
18
+ this('foo').should be_kosher
19
+ end
20
+
21
+ it "does not validate advance review copies" do
22
+ this('Uncorrected review copy').should_not be_kosher
23
+ this('arc').should_not be_kosher
24
+ this('arc.').should_not be_kosher
25
+
26
+ this('marc').should be_kosher
27
+ end
28
+
29
+ it "does not validate marked books" do
30
+ this('Some highlighting').should_not be_kosher
31
+ this('Underlining.').should_not be_kosher
32
+ this('Good. Hiliting.').should_not be_kosher
33
+
34
+ this('No highlighting.').should be_kosher
35
+ end
36
+
37
+ it "does not validate books with missing volumes" do
38
+ this('First vol only.').should_not be_kosher
39
+ end
40
+
41
+ it "does not validate damaged or worn books" do
42
+ this('Different').should be_kosher
43
+ this('Rental').should_not be_kosher
44
+ this('Torn pages').should_not be_kosher
45
+ end
46
+
47
+ it "does not validate withdrawn library copies" do
48
+ this('xlib').should_not be_kosher
49
+ this('ex-library').should_not be_kosher
50
+ this('retired library copy').should_not be_kosher
51
+
52
+ this('Not an ex-library').should be_kosher
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,133 @@
1
+ require 'spec_helper'
2
+
3
+ module Kosher
4
+ describe Offer do
5
+ let(:offer) { Fabricate(:offer) }
6
+
7
+ describe ".build" do
8
+ use_vcr_cassette '0143105825'
9
+
10
+ let(:asin) { '0143105825' }
11
+
12
+ let(:doc) do
13
+ request = Request.new(credentials)
14
+ request.locale = :us
15
+ request.batchify([asin])
16
+ algorithm = request.get
17
+
18
+ response = algorithm.instance_variable_get(:@response)
19
+ response.find('Offer').first
20
+ end
21
+
22
+ it "should build an offer" do
23
+ offer = Offer.build(doc)
24
+
25
+ offer.should be_a Offer
26
+ expect do
27
+ offer.kosher?
28
+ end.should_not raise_error
29
+ end
30
+
31
+ it "populates the offer listing ID" do
32
+ offer = Offer.build(doc)
33
+ offer.listing_id.should_not be_nil
34
+ end
35
+
36
+ it "populates the exchange ID" do
37
+ offer = Offer.build(doc)
38
+ offer.exchange_id.should_not be_nil
39
+ end
40
+
41
+ it "should handle blank descriptions" do
42
+ doc['OfferAttributes']['ConditionNote'] = ''
43
+ offer = Offer.build(doc)
44
+
45
+ offer.description.should eql ''
46
+ end
47
+ end
48
+
49
+ describe "#kosher?" do
50
+ context "when condition is kosher" do
51
+ before do
52
+ offer.condition = Fabricate(:kosher_condition, :grade => 1)
53
+ end
54
+
55
+ context "when seller is kosher" do
56
+ before do
57
+ offer.seller = Fabricate(:good_seller)
58
+ end
59
+
60
+ context "when description is kosher" do
61
+ context "when offer ships now" do
62
+ it "returns true" do
63
+ offer.should be_kosher
64
+ end
65
+ end
66
+
67
+ context "when offer does not ship now" do
68
+ before do
69
+ offer.ships_in = 96
70
+ end
71
+
72
+ it "returns false" do
73
+ offer.should_not be_kosher
74
+ end
75
+ end
76
+ end
77
+
78
+ context "when description is not kosher" do
79
+ before do
80
+ offer.description = Kosher::Description.new("Withdrawn library book")
81
+ end
82
+
83
+ it "returns false" do
84
+ offer.should_not be_kosher
85
+ end
86
+ end
87
+ end
88
+
89
+ context "when seller is not kosher" do
90
+ before do
91
+ offer.seller = Fabricate(:bad_seller)
92
+ end
93
+
94
+ it "returns false" do
95
+ offer.should_not be_kosher
96
+ end
97
+ end
98
+ end
99
+
100
+ context "when condition is not kosher" do
101
+ before do
102
+ offer.condition = Fabricate(:kosher_condition, :grade => 5)
103
+ end
104
+
105
+ it "returns false" do
106
+ offer.should_not be_kosher
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "#ships_now?" do
112
+ context "when offer ships within 48 hours" do
113
+ before do
114
+ offer.ships_in = 48
115
+ end
116
+
117
+ it "returns true" do
118
+ offer.send(:ships_now?).should be_true
119
+ end
120
+ end
121
+
122
+ context "when offer ships in over 48 hours" do
123
+ before do
124
+ offer.ships_in = 96
125
+ end
126
+
127
+ it "returns false" do
128
+ offer.send(:ships_now?).should be_false
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ module Fassbinder
4
+ describe Request do
5
+ let(:request) { Request.new(credentials) }
6
+
7
+ describe ".new" do
8
+ it "defines a batch request" do
9
+ request.parameters['Operation'].should eql 'ItemLookup'
10
+ request.parameters['ItemLookup.Shared.IdType'].should eql 'ASIN'
11
+ request.parameters['ItemLookup.Shared.Condition'].should eql 'All'
12
+ request.parameters['ItemLookup.Shared.MerchantId'].should eql 'All'
13
+ request.parameters['ItemLookup.Shared.ResponseGroup'].should eql ['OfferFull', 'SalesRank']
14
+ end
15
+ end
16
+
17
+ describe "#batchify" do
18
+ it "adds up to 20 ASINs to the worker's parameters" do
19
+ asins = (0..19).to_a
20
+ request.batchify(asins)
21
+
22
+ request.parameters['ItemLookup.1.ItemId'].should eql (0..9).to_a
23
+ request.parameters['ItemLookup.2.ItemId'].should eql (10..19).to_a
24
+ end
25
+ end
26
+
27
+ describe "#get" do
28
+ before do
29
+ VCR.http_stubbing_adapter.http_connections_allowed = true
30
+ end
31
+
32
+ it "returns an algorithm" do
33
+ Request.stub!(:get)
34
+
35
+ request.locale = :us
36
+ request.batchify('foo')
37
+
38
+ request.get.should be_a Algorithm
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ module Kosher
4
+ describe Seller do
5
+ describe ".build" do
6
+ use_vcr_cassette '0143105825'
7
+
8
+ let(:asin) { '0143105825' }
9
+
10
+ let(:response) do
11
+ request = Request.new(credentials)
12
+ request.locale = :us
13
+ request.batchify([asin])
14
+ algorithm = request.get
15
+
16
+ algorithm.instance_variable_get(:@response)
17
+ end
18
+
19
+ let(:seller) do
20
+ docs = response.find('Merchant')
21
+ Seller.build(docs.first)
22
+ end
23
+
24
+ it "populates the merchant ID of a seller" do
25
+ seller.merchant_id.should match /^[0-9A-Z]{13,14}$/
26
+ end
27
+
28
+ it "populates the name of a seller" do
29
+ seller.name.should_not be_nil
30
+ end
31
+
32
+ it "populates the average rating of a seller" do
33
+ seller.average_rating.should be_an_instance_of Float
34
+ end
35
+
36
+ it "builds a newly-launched seller" do
37
+ doc = response.find('Merchant').detect { |doc| doc["AverageFeedbackRating"] == "0.0" }
38
+ new_seller = Seller.build(doc)
39
+
40
+ new_seller.average_rating.should eql 0.0
41
+ end
42
+ end
43
+
44
+ describe "#kosher?" do
45
+ it "returns true if seller's average rating is 0.0" do
46
+ seller = Fabricate(:new_seller)
47
+ seller.should be_kosher
48
+ end
49
+
50
+ it "returns true if seller's average rating is above 4.7" do
51
+ seller = Fabricate(:good_seller)
52
+ seller.should be_kosher
53
+ end
54
+
55
+ it "returns false if sellers' average rating is 4.7 or below" do
56
+ seller = Fabricate(:bad_seller)
57
+ seller.should_not be_kosher
58
+ end
59
+
60
+ it "returns false if seller is blacklisted" do
61
+ seller = Fabricate(:good_seller)
62
+ seller.should be_kosher
63
+
64
+ seller.merchant_id = Faker::Amazon.merchant_id
65
+ Seller.blacklist = [seller.merchant_id]
66
+
67
+ seller.should_not be_kosher
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ module Kosher
4
+ describe Struct do
5
+ before(:all) do
6
+ class Foo < Struct.new(:bar); end
7
+ end
8
+
9
+ describe "to_json" do
10
+ it "converts to JSON" do
11
+ foo = Foo.new
12
+ foo.bar = 1
13
+
14
+ foo.to_json.should eql "{\"bar\":1}"
15
+ end
16
+
17
+ it "handles nested structs" do
18
+ foo_1 = Foo.new
19
+ foo_2 = Foo.new
20
+
21
+ foo_2.bar = 1
22
+ foo_1.bar = foo_2
23
+
24
+ foo_1.to_json.should eql "{\"bar\":{\"bar\":1}}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rspec'
4
+ require 'fabrication'
5
+
6
+ require File.expand_path('../../lib/fassbinder', __FILE__)
7
+
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
@@ -0,0 +1,3 @@
1
+ def credentials
2
+ @credentials ||= YAML::load_file(File.dirname(__FILE__) + '/amazon.yml')
3
+ end
@@ -0,0 +1,19 @@
1
+ module Faker
2
+ module Amazon
3
+ class << self
4
+ def asin
5
+ raw = 9.times.map { rand(10).to_s }
6
+ (raw << Bookland::ISBN.new.send(:check_digit_10, raw)).join
7
+ end
8
+
9
+ def ean
10
+ Bookland::ISBN.to_13(asin)
11
+ end
12
+
13
+ def merchant_id
14
+ seed = ('A'..'Z').to_a + (0..9).to_a
15
+ 14.times.map { seed[rand(seed.size)] }.join
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ require 'vcr'
2
+
3
+ VCR.config do |c|
4
+ c.cassette_library_dir = "#{File.dirname(__FILE__)}/../fixtures/cassette_library"
5
+ c.stub_with :webmock
6
+ c.ignore_localhost = true
7
+ c.default_cassette_options = { :record => :new_episodes, :match_requests_on => [:host] }
8
+ end
9
+
10
+ RSpec.configure do |config|
11
+ config.extend VCR::RSpec::Macros
12
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fassbinder
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Paper Cavalier
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-03-09 00:00:00 +00:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: kosher
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 0.2.4
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: sucker
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 1.3.1
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: fabrication
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 0.9.5
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: 2.5.0
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: ruby-debug19
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.11.6
69
+ type: :development
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: vcr
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ version: 1.7.0
80
+ type: :development
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: webmock
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ version: 1.6.2
91
+ type: :development
92
+ version_requirements: *id007
93
+ description: Fassbinder wraps Amazon in a loving embrace.
94
+ email: code@papercavalier.com
95
+ executables: []
96
+
97
+ extensions: []
98
+
99
+ extra_rdoc_files: []
100
+
101
+ files:
102
+ - .gitignore
103
+ - Gemfile
104
+ - LICENSE
105
+ - README.md
106
+ - Rakefile
107
+ - fassbinder.gemspec
108
+ - lib/fassbinder.rb
109
+ - lib/fassbinder/algorithm.rb
110
+ - lib/fassbinder/book.rb
111
+ - lib/fassbinder/condition.rb
112
+ - lib/fassbinder/description.rb
113
+ - lib/fassbinder/errors.rb
114
+ - lib/fassbinder/offer.rb
115
+ - lib/fassbinder/request.rb
116
+ - lib/fassbinder/seller.rb
117
+ - lib/fassbinder/struct.rb
118
+ - lib/fassbinder/version.rb
119
+ - spec/fabricators/condition_fabricator.rb
120
+ - spec/fabricators/offer_fabricator.rb
121
+ - spec/fabricators/seller_fabricator.rb
122
+ - spec/fassbinder/algorithm_spec.rb
123
+ - spec/fassbinder/book_spec.rb
124
+ - spec/fassbinder/condition_spec.rb
125
+ - spec/fassbinder/description_spec.rb
126
+ - spec/fassbinder/offer_spec.rb
127
+ - spec/fassbinder/request_spec.rb
128
+ - spec/fassbinder/seller_spec.rb
129
+ - spec/fassbinder/struct_spec.rb
130
+ - spec/spec_helper.rb
131
+ - spec/support/credentials.rb
132
+ - spec/support/faker.rb
133
+ - spec/support/vcr.rb
134
+ has_rdoc: true
135
+ homepage: https://rubygems.org/gems/fassbinder
136
+ licenses: []
137
+
138
+ post_install_message:
139
+ rdoc_options: []
140
+
141
+ require_paths:
142
+ - lib
143
+ required_ruby_version: !ruby/object:Gem::Requirement
144
+ none: false
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: "0"
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ none: false
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: "0"
155
+ requirements: []
156
+
157
+ rubyforge_project: fassbinder
158
+ rubygems_version: 1.5.2
159
+ signing_key:
160
+ specification_version: 3
161
+ summary: Wraps Amazon in a loving embrace.
162
+ test_files:
163
+ - spec/fabricators/condition_fabricator.rb
164
+ - spec/fabricators/offer_fabricator.rb
165
+ - spec/fabricators/seller_fabricator.rb
166
+ - spec/fassbinder/algorithm_spec.rb
167
+ - spec/fassbinder/book_spec.rb
168
+ - spec/fassbinder/condition_spec.rb
169
+ - spec/fassbinder/description_spec.rb
170
+ - spec/fassbinder/offer_spec.rb
171
+ - spec/fassbinder/request_spec.rb
172
+ - spec/fassbinder/seller_spec.rb
173
+ - spec/fassbinder/struct_spec.rb
174
+ - spec/spec_helper.rb
175
+ - spec/support/credentials.rb
176
+ - spec/support/faker.rb
177
+ - spec/support/vcr.rb