emvoicer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem "em-http-request", "~> 0.3.0"
6
+ gem "nokogiri", "~> 1.4.6"
7
+
8
+ # Add dependencies to develop your gem here.
9
+ # Include everything needed to run rake, tests, features, etc.
10
+ group :development do
11
+ gem "rspec", "~> 2.3.0"
12
+ gem "bundler", "~> 1.0.0"
13
+ gem "jeweler", "~> 1.6.2"
14
+ gem "simplecov", ">= 0"
15
+ end
16
+
17
+ group :test do
18
+ gem "mocha", "~> 0.9.12"
19
+ gem "webmock", "~> 1.6.4"
20
+
21
+ gem "eventmachine"
22
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.6)
5
+ crack (0.1.8)
6
+ diff-lcs (1.1.2)
7
+ em-http-request (0.3.0)
8
+ addressable (>= 2.0.0)
9
+ escape_utils
10
+ eventmachine (>= 0.12.9)
11
+ escape_utils (0.2.3)
12
+ eventmachine (0.12.10)
13
+ git (1.2.5)
14
+ jeweler (1.6.4)
15
+ bundler (~> 1.0)
16
+ git (>= 1.2.5)
17
+ rake
18
+ mocha (0.9.12)
19
+ nokogiri (1.4.7)
20
+ rake (0.9.2)
21
+ rspec (2.3.0)
22
+ rspec-core (~> 2.3.0)
23
+ rspec-expectations (~> 2.3.0)
24
+ rspec-mocks (~> 2.3.0)
25
+ rspec-core (2.3.1)
26
+ rspec-expectations (2.3.0)
27
+ diff-lcs (~> 1.1.2)
28
+ rspec-mocks (2.3.0)
29
+ simplecov (0.4.2)
30
+ simplecov-html (~> 0.4.4)
31
+ simplecov-html (0.4.5)
32
+ webmock (1.6.4)
33
+ addressable (~> 2.2, > 2.2.5)
34
+ crack (>= 0.1.7)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ bundler (~> 1.0.0)
41
+ em-http-request (~> 0.3.0)
42
+ eventmachine
43
+ jeweler (~> 1.6.2)
44
+ mocha (~> 0.9.12)
45
+ nokogiri (~> 1.4.6)
46
+ rspec (~> 2.3.0)
47
+ simplecov
48
+ webmock (~> 1.6.4)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Tomasz Werbicki
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = emvoicer
2
+
3
+ Description goes here.
4
+
5
+ == Contributing to emvoicer
6
+
7
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
8
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
9
+ * Fork the project
10
+ * Start a feature/bugfix branch
11
+ * Commit and push until you are happy with your contribution
12
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
13
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2011 Tomasz Werbicki. See LICENSE.txt for
18
+ further details.
19
+
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "emvoicer"
18
+ gem.homepage = "http://github.com/neaf/emvoicer"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Invoice data fetcher to use within EventMachine reactor.}
21
+ # gem.description = %Q{TODO: longer description of your gem}
22
+ gem.email = "tomasz@werbicki.net"
23
+ gem.authors = ["Tomasz Werbicki"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/emvoicer.gemspec ADDED
@@ -0,0 +1,76 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{emvoicer}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Tomasz Werbicki"]
12
+ s.date = %q{2011-07-14}
13
+ s.email = %q{tomasz@werbicki.net}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE.txt",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".rspec",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "emvoicer.gemspec",
28
+ "lib/emvoicer.rb",
29
+ "lib/emvoicer/client.rb",
30
+ "lib/emvoicer/invoice.rb",
31
+ "lib/emvoicer/services/base.rb",
32
+ "lib/emvoicer/services/blinksale.rb",
33
+ "lib/emvoicer/services/blinksale/parsing.rb",
34
+ "lib/emvoicer/services/http_auth.rb",
35
+ "spec/client_spec.rb",
36
+ "spec/fixtures/invoice_collection.xml",
37
+ "spec/invoice_spec.rb",
38
+ "spec/services/base_spec.rb",
39
+ "spec/services/blinksale_spec.rb",
40
+ "spec/services/http_auth_spec.rb",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/neaf/emvoicer}
44
+ s.licenses = ["MIT"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = %q{1.6.2}
47
+ s.summary = %q{Invoice data fetcher to use within EventMachine reactor.}
48
+
49
+ if s.respond_to? :specification_version then
50
+ s.specification_version = 3
51
+
52
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
53
+ s.add_runtime_dependency(%q<em-http-request>, ["~> 0.3.0"])
54
+ s.add_runtime_dependency(%q<nokogiri>, ["~> 1.4.6"])
55
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
56
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
57
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
58
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
59
+ else
60
+ s.add_dependency(%q<em-http-request>, ["~> 0.3.0"])
61
+ s.add_dependency(%q<nokogiri>, ["~> 1.4.6"])
62
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
63
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
64
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
65
+ s.add_dependency(%q<simplecov>, [">= 0"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<em-http-request>, ["~> 0.3.0"])
69
+ s.add_dependency(%q<nokogiri>, ["~> 1.4.6"])
70
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
71
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
72
+ s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
73
+ s.add_dependency(%q<simplecov>, [">= 0"])
74
+ end
75
+ end
76
+
data/lib/emvoicer.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "emvoicer/invoice"
2
+ require "emvoicer/client"
3
+ require "emvoicer/services/base"
4
+ require "emvoicer/services/http_auth"
5
+ require "emvoicer/services/blinksale"
@@ -0,0 +1,27 @@
1
+ module Emvoicer
2
+ class Client
3
+ ATTRIBUTES = [
4
+ :id,
5
+ :name,
6
+ :address_1,
7
+ :address_2,
8
+ :city,
9
+ :zip_code,
10
+ :country,
11
+ :phone
12
+ ]
13
+
14
+ attr_reader :service, :attributes
15
+
16
+ def initialize(service, attributes)
17
+ @service = service
18
+ @attributes = attributes
19
+ end
20
+
21
+ ATTRIBUTES.each do |attr|
22
+ define_method(attr) do
23
+ attributes[attr]
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,31 @@
1
+ module Emvoicer
2
+ class Invoice
3
+ ATTRIBUTES = [
4
+ :id,
5
+ :number,
6
+ :total_amount,
7
+ :due_amount,
8
+ :issued_on,
9
+ :due_on,
10
+ :currency,
11
+ :client_id
12
+ ]
13
+
14
+ attr_reader :service, :attributes
15
+
16
+ def initialize(service, attributes)
17
+ @service = service
18
+ @attributes = attributes
19
+ end
20
+
21
+ ATTRIBUTES.each do |attr|
22
+ define_method(attr) do
23
+ attributes[attr]
24
+ end
25
+ end
26
+
27
+ def client(callback)
28
+ service.client(client_id, callback)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,83 @@
1
+ require "em-http-request"
2
+
3
+ module Emvoicer
4
+ module Services
5
+ class Base
6
+ def request(url, params, callback)
7
+ http = EM::HttpRequest.new(url).get(:query => params, :head => request_headers)
8
+ http.callback { |http| callback.call(http) }
9
+ http.errback { raise Emvoicer::RequestError }
10
+ end
11
+
12
+ def invoices(params, callback)
13
+ request(invoices_url, params, proc { |http|
14
+ invoices = handle_invoices_source(http.response)
15
+ callback.call(invoices)
16
+ })
17
+ end
18
+
19
+ def handle_invoices_source(source)
20
+ nodes = self.class.extract_invoice_nodes(source)
21
+ nodes.map { |n| generate_invoice(n) }
22
+ end
23
+
24
+ def generate_invoice(node)
25
+ attributes = self.class.parse_invoice(node)
26
+ Emvoicer::Invoice.new(self, attributes)
27
+ end
28
+
29
+ def client(id, callback)
30
+ request(client_url(id), {}, proc { |http|
31
+ client = handle_client_source(http.response)
32
+ callback.call(client)
33
+ })
34
+ end
35
+
36
+ def handle_client_source(source)
37
+ node = self.class.extract_client_node(source)
38
+ generate_client(node)
39
+ end
40
+
41
+ def generate_client(node)
42
+ attributes = self.class.parse_client(node)
43
+ Emvoicer::Client.new(self, attributes)
44
+ end
45
+
46
+ def request_headers
47
+ headers
48
+ end
49
+
50
+ # Override methods below to comform speific API
51
+ def headers
52
+ {}
53
+ end
54
+
55
+ def invoices_url
56
+ ""
57
+ end
58
+
59
+ def client_url(id)
60
+ ""
61
+ end
62
+
63
+ def self.extract_invoice_nodes(source)
64
+ []
65
+ end
66
+
67
+ def self.parse_invoice(node)
68
+ {}
69
+ end
70
+
71
+ def self.extract_client_node(source)
72
+ []
73
+ end
74
+
75
+ def self.parse_client(node)
76
+ {}
77
+ end
78
+ end
79
+ end
80
+
81
+ class RequestError < StandardError
82
+ end
83
+ end
@@ -0,0 +1,48 @@
1
+ require "emvoicer/services/blinksale/parsing"
2
+
3
+ module Emvoicer
4
+ module Services
5
+ class Blinksale < Emvoicer::Services::HttpAuth
6
+ extend BlinksaleParsing
7
+
8
+ attr_reader :subdomain, :username, :password
9
+
10
+ def initialize(subdomain, username, password)
11
+ @subdomain = subdomain
12
+ @username = username
13
+ @password = password
14
+ end
15
+
16
+ def auth_params
17
+ [username, password]
18
+ end
19
+
20
+ def headers
21
+ {
22
+ "Content-Type" => "application/vnd.blinksale+xml",
23
+ "Accept" => "application/vnd.blinksale+xml"
24
+ }
25
+ end
26
+
27
+ def base_url
28
+ "https://#{ subdomain }.blinksale.com"
29
+ end
30
+
31
+ def invoices_url
32
+ base_url + "/invoices"
33
+ end
34
+
35
+ def client_url(id)
36
+ base_url + "/clients/#{ id }"
37
+ end
38
+
39
+ def self.test_authentication_response(response)
40
+ case response
41
+ when Net::HTTPSuccess then true
42
+ else
43
+ false
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,44 @@
1
+ require "nokogiri"
2
+
3
+ module Emvoicer
4
+ module Services
5
+ module BlinksaleParsing
6
+ def extract_invoice_nodes(source)
7
+ doc = Nokogiri::XML(source)
8
+ doc.xpath("//xmlns:invoice")
9
+ end
10
+
11
+ def parse_invoice(nokogiri_element)
12
+ {
13
+ :id => nokogiri_element.attributes["uri"].content[/\d+$/].to_i,
14
+ :number => nokogiri_element.xpath("xmlns:number").first.content,
15
+ :total_amount => nokogiri_element.attributes["total"].content.to_f,
16
+ :due_amount => nokogiri_element.attributes["total_due"].content.to_f,
17
+ :issued_on => Date.strptime(nokogiri_element.xpath("xmlns:date").first.content, "%Y-%m-%d"),
18
+ :due_on => Date.strptime(nokogiri_element.xpath("xmlns:terms").first.attributes["due_date"].content, "%Y-%m-%d"),
19
+ :currency => nokogiri_element.xpath("xmlns:currency").first.content,
20
+ :client_id => nokogiri_element.xpath("xmlns:client").first.content[/\d+$/].to_i
21
+ }
22
+ end
23
+
24
+ def extract_client_node(source)
25
+ doc = Nokogiri::XML(source)
26
+ doc.xpath("//xmlns:client").first
27
+ end
28
+
29
+ def parse_client(nokogiri_element)
30
+ {
31
+ :id => nokogiri_element.attributes["uri"].content[/\d+$/].to_i,
32
+ :name => nokogiri_element.xpath('xmlns:name').first.content,
33
+ :address1 => nokogiri_element.xpath("xmlns:address1").first.content,
34
+ :address2 => nokogiri_element.xpath("xmlns:address2").first.content,
35
+ :city => nokogiri_element.xpath("xmlns:city").first.content,
36
+ :state => nokogiri_element.xpath("xmlns:state").first.content,
37
+ :zip_code => nokogiri_element.xpath("xmlns:zip").first.content,
38
+ :country => nokogiri_element.xpath("xmlns:country").first.content,
39
+ :phone => nokogiri_element.xpath("xmlns:phone").first.content
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,25 @@
1
+ require "net/http"
2
+ require "uri"
3
+
4
+ module Emvoicer
5
+ module Services
6
+ class HttpAuth < Emvoicer::Services::Base
7
+ def request_headers
8
+ super.merge("Authorization" => auth_params)
9
+ end
10
+
11
+ def authentication_valid?
12
+ url = URI.parse(invoices_url)
13
+ request = Net::HTTP::Get.new(url.path)
14
+ request.basic_auth *auth_params
15
+ headers.each do |k, v|
16
+ request[k] = v
17
+ end
18
+ response = Net::HTTP.new(url.host, url.port).start do |http|
19
+ http.request(request)
20
+ end
21
+ self.class.test_authentication_response(response)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,47 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Emvoicer::Client do
4
+ context "in general" do
5
+ let(:service) do
6
+ Emvoicer::Services::Base.new
7
+ end
8
+
9
+ let(:client) do
10
+ attributes = {
11
+ :id => 1,
12
+ :name => "Haute Haute Heat",
13
+ :address_1 => "1234 Main Street",
14
+ :address_2 => "Suite 123",
15
+ :city => "Anytown",
16
+ :zip_code => "70123",
17
+ :country => "US",
18
+ :phone => "123-555-1212"
19
+ }
20
+
21
+ Emvoicer::Client.new(service, attributes)
22
+ end
23
+
24
+ describe ".service" do
25
+ it "returns service which client was created with" do
26
+ client.service.should eq(service)
27
+ end
28
+ end
29
+
30
+ [
31
+ :id,
32
+ :name,
33
+ :address_1,
34
+ :address_2,
35
+ :city,
36
+ :zip_code,
37
+ :country,
38
+ :phone
39
+ ].each do |attr|
40
+ describe ".#{ attr }" do
41
+ it "returns value of #{ attr } attribute" do
42
+ client.send(attr).should eq(client.attributes[attr])
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <invoices xmlns="http://www.blinksale.com/api">
3
+ <invoice status="open" uri="http://example.blinksale.com/invoices/1" total="20.00" total_due="10.00">
4
+ <client name="Acme">http://example.blinksale.com/clients/1</client>
5
+
6
+ <number>100001</number>
7
+ <date>2011-03-13</date>
8
+ <terms due_date="2011-03-28">15</terms>
9
+ <currency>USD</currency>
10
+
11
+ <tags>brian, consulting</tags>
12
+ </invoice>
13
+ <invoice status="open" uri="http://example.blinksale.com/invoices/1" total="20.00" total_due="10.00">
14
+ <client name="Acme">http://example.blinksale.com/clients/2</client>
15
+
16
+ <number>100002</number>
17
+ <date>2011-03-13</date>
18
+ <terms due_date="2011-03-28">15</terms>
19
+ <currency>USD</currency>
20
+
21
+ <tags>brian, consulting</tags>
22
+ </invoice>
23
+ </invoices>
@@ -0,0 +1,55 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Emvoicer::Invoice do
4
+ context "in general" do
5
+ let(:service) do
6
+ Emvoicer::Services::Base.new
7
+ end
8
+
9
+ let(:invoice) do
10
+ attributes = {
11
+ :id => 1,
12
+ :number => "100001",
13
+ :total_amount => 20.0,
14
+ :due_amount => 10.0,
15
+ :issued_on => Date.new(2011, 3, 13),
16
+ :due_on => Date.new(2011, 3, 28),
17
+ :currency => "USD",
18
+ :client_id => 1
19
+ }
20
+
21
+ Emvoicer::Invoice.new(service, attributes)
22
+ end
23
+
24
+ describe ".service" do
25
+ it "returns service which invoice was created with" do
26
+ invoice.service.should eq(service)
27
+ end
28
+ end
29
+
30
+ [
31
+ :id,
32
+ :number,
33
+ :total_amount,
34
+ :due_amount,
35
+ :issued_on,
36
+ :due_on,
37
+ :currency,
38
+ :client_id
39
+ ].each do |attr|
40
+ describe ".#{ attr }" do
41
+ it "returns value of #{ attr } attribute" do
42
+ invoice.send(attr).should eq(invoice.attributes[attr])
43
+ end
44
+ end
45
+ end
46
+
47
+ describe ".client(callback)" do
48
+ it "fetches client and calls callback" do
49
+ callback = proc {}
50
+ service.expects(:client).with(invoice.client_id, callback)
51
+ invoice.client(callback)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,192 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+
3
+ describe Emvoicer::Services::Base do
4
+ let(:service) do
5
+ Emvoicer::Services::Base.new
6
+ end
7
+
8
+ before(:each) do
9
+ service.stubs(:headers).returns({ "Accept" => "application/xml" })
10
+ service.stubs(:invoices_url).returns("http://example.com/invoices")
11
+ service.stubs(:client_url).with(1).returns("http://example.com/clients/1")
12
+ end
13
+
14
+ describe ".request(url, params, callback)" do
15
+ it "issues GET request wih given parameters" do
16
+ stub_request(:get, "http://example.com/?status=open").
17
+ with(:headers => { "Accept" => "application/xml"}).
18
+ to_return(:status => 200, :body => "", :headers => {})
19
+
20
+ EM.run_block do
21
+ service.request("http://example.com",
22
+ { :status => "open" },
23
+ proc {})
24
+ end
25
+
26
+ WebMock.should have_requested(:get, "http://example.com").with(
27
+ :query => { :status => "open" },
28
+ :headers => { "Accept" => "application/xml" }
29
+ )
30
+ end
31
+
32
+ context "on successfull request" do
33
+ it "calls provided callback" do
34
+ stub_request(:get, "http://example.com/?status=open").
35
+ with(:headers => { "Accept" => "application/xml"}).
36
+ to_return(:status => 200, :body => "", :headers => {})
37
+
38
+ called = false
39
+
40
+ EM.run_block do
41
+ service.request("http://example.com",
42
+ { :status => "open" },
43
+ proc { called = true })
44
+ end
45
+
46
+ called.should be_true
47
+ end
48
+ end
49
+ end
50
+
51
+ describe ".invoices(params, callback)" do
52
+ let(:response) { "Response" }
53
+ let(:invoice) { "Invoice" }
54
+
55
+ it "issues a get request with invoices_url and provided params" do
56
+ service.expects(:request)
57
+
58
+ service.invoices({ :status => "open" }, proc {})
59
+ end
60
+
61
+ it "calls callback with invoices collection" do
62
+ stub_request(:get, "http://example.com/invoices?status=open").
63
+ with(:headers => {"Accept"=>"application/xml"}).
64
+ to_return(:status => 200, :body => response, :headers => {})
65
+
66
+ service.expects(:handle_invoices_source).with(response).returns([invoice])
67
+
68
+ called = false
69
+
70
+ EM.run_block do
71
+ service.invoices({ :status => "open" }, proc { |invoices| called = true; invoices.should eq([invoice]) })
72
+ end
73
+
74
+ called.should be_true
75
+ end
76
+ end
77
+
78
+ describe ".handle_invoices_source(source)" do
79
+ let(:source) { "Source" }
80
+ let(:node) { "Node" }
81
+ let(:invoice) { "Invoice" }
82
+
83
+ it "generates nodes from source" do
84
+ service.class.expects(:extract_invoice_nodes).with(source).returns([node])
85
+ service.handle_invoices_source(source)
86
+ end
87
+
88
+ it "returns generated invoices from nodes" do
89
+ service.class.stubs(:extract_invoice_nodes).with(source).returns([node])
90
+ service.expects(:generate_invoice).with(node).returns(invoice)
91
+ service.handle_invoices_source(source).should eq([invoice])
92
+ end
93
+ end
94
+
95
+ describe ".generate_invoice(node)" do
96
+ let(:node) { "Node" }
97
+ let(:attributes) do
98
+ {
99
+ :id => 1,
100
+ :number => "100001",
101
+ :total_amount => 20.0,
102
+ :due_amount => 10.0,
103
+ :issued_on => Date.new(2011, 3, 13),
104
+ :due_on => Date.new(2011, 3, 28),
105
+ :currency => "USD",
106
+ :client_id => 1
107
+ }
108
+ end
109
+
110
+ it "parses given node" do
111
+ service.class.expects(:parse_invoice).with(node).returns(attributes)
112
+ service.generate_invoice(node)
113
+ end
114
+
115
+ it "creates Invoice instance with service and node attributes" do
116
+ service.class.stubs(:parse_invoice).with(node).returns(attributes)
117
+ Emvoicer::Invoice.expects(:new).with(service, attributes)
118
+ service.generate_invoice(node)
119
+ end
120
+ end
121
+
122
+ describe ".client(id, callback)" do
123
+ let(:response) { "Response" }
124
+ let(:client) { "Client" }
125
+
126
+ it "issues a get request with client_url and provided id" do
127
+ service.expects(:request)
128
+
129
+ service.client(1, proc {})
130
+ end
131
+
132
+ it "calls callback with client" do
133
+ stub_request(:get, "http://example.com/clients/1").
134
+ with(:headers => {"Accept"=>"application/xml"}).
135
+ to_return(:status => 200, :body => response, :headers => {})
136
+
137
+ service.expects(:handle_client_source).with(response).returns([client])
138
+
139
+ called = false
140
+
141
+ EM.run_block do
142
+ service.client(1, proc { |invoices| called = true; client.should eq(client) })
143
+ end
144
+
145
+ called.should be_true
146
+ end
147
+ end
148
+
149
+ describe ".handle_client_source(source)" do
150
+ let(:source) { "Source" }
151
+ let(:node) { "Node" }
152
+ let(:client) { "Client" }
153
+
154
+ it "generates node from source" do
155
+ service.class.expects(:extract_client_node).with(source).returns(node)
156
+ service.handle_client_source(source)
157
+ end
158
+
159
+ it "returns generated client from node" do
160
+ service.class.stubs(:extract_client_node).with(source).returns(node)
161
+ service.expects(:generate_client).with(node).returns(client)
162
+ service.handle_client_source(source).should eq(client)
163
+ end
164
+ end
165
+
166
+ describe ".generate_client(node)" do
167
+ let(:node) { "Node" }
168
+ let(:attributes) do
169
+ {
170
+ :id => 1,
171
+ :name => "Haute Haute Heat",
172
+ :address_1 => "1234 Main Street",
173
+ :address_2 => "Suite 123",
174
+ :city => "Anytown",
175
+ :zip_code => "70123",
176
+ :country => "US",
177
+ :phone => "123-555-1212"
178
+ }
179
+ end
180
+
181
+ it "parses given node" do
182
+ service.class.expects(:parse_client).with(node).returns(attributes)
183
+ service.generate_client(node)
184
+ end
185
+
186
+ it "creates Client instance with service and node attributes" do
187
+ service.class.stubs(:parse_client).with(node).returns(attributes)
188
+ Emvoicer::Client.expects(:new).with(service, attributes)
189
+ service.generate_client(node)
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,82 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../spec_helper")
2
+
3
+ collection_xml = File.read(File.expand_path(File.dirname(__FILE__) + "/../fixtures/invoice_collection.xml"))
4
+
5
+ describe Emvoicer::Services::Blinksale do
6
+ let(:blinksale) do
7
+ Emvoicer::Services::Blinksale.new("test-host", "test-username", "test-password")
8
+ end
9
+
10
+ describe ".auth_params" do
11
+ it "returns proper API credentials array" do
12
+ blinksale.auth_params.should eq(["test-username", "test-password"])
13
+ end
14
+ end
15
+
16
+ describe ".headers" do
17
+ it "returns proper Accept and ContentType headers" do
18
+ blinksale.headers.should eq({
19
+ "Content-Type" => "application/vnd.blinksale+xml",
20
+ "Accept" => "application/vnd.blinksale+xml"
21
+ })
22
+ end
23
+ end
24
+
25
+ describe "#test_authentication_response" do
26
+ context "when response is successful" do
27
+ it "returns true" do
28
+ stub_request(:get, "http://test-username:test-password@test-host.blinksale.com:443/invoices").
29
+ with(:headers => {"Accept"=>"application/vnd.blinksale+xml", "Content-Type"=>"application/vnd.blinksale+xml", "User-Agent"=>"Ruby"}).
30
+ to_return(:status => 200, :body => "", :headers => {})
31
+
32
+ blinksale.authentication_valid?.should be_true
33
+ end
34
+ end
35
+
36
+ context "when response is not successful" do
37
+ it "returns false" do
38
+ stub_request(:get, "http://test-username:test-password@test-host.blinksale.com:443/invoices").
39
+ with(:headers => {"Accept"=>"application/vnd.blinksale+xml", "Content-Type"=>"application/vnd.blinksale+xml", "User-Agent"=>"Ruby"}).
40
+ to_return(:status => 401, :body => "", :headers => {})
41
+
42
+ blinksale.authentication_valid?.should be_false
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "invoice handling" do
48
+ describe ".invoices_url" do
49
+ it "returns proper API invoices url" do
50
+ blinksale.invoices_url.should eq("https://test-host.blinksale.com/invoices")
51
+ end
52
+ end
53
+
54
+ describe "#extract_invoice_nodes(source)" do
55
+ it "returns array of nodes to parse" do
56
+ nodes = Emvoicer::Services::Blinksale.extract_invoice_nodes(collection_xml)
57
+ nodes.should have(2).items
58
+ nodes.each { |n| n.should be_an_instance_of(Nokogiri::XML::Element) }
59
+ end
60
+ end
61
+
62
+ describe "#parse_invoice(nokogiri_element)" do
63
+ let(:nokogiri_element) do
64
+ Nokogiri::XML(collection_xml).xpath("//xmlns:invoice").first
65
+ end
66
+
67
+ it "returns hash with proper invoice attributes" do
68
+ hash = Emvoicer::Services::Blinksale.parse_invoice(nokogiri_element)
69
+
70
+ hash[:id].should eql(1)
71
+ hash[:number].should eql("100001")
72
+ hash[:total_amount].should eql(20.0)
73
+ hash[:due_amount].should eql(10.0)
74
+ hash[:issued_on].should eql(Date.new(2011, 3, 13))
75
+ hash[:due_on].should eql(Date.new(2011, 3, 28))
76
+ hash[:currency].should eql("USD")
77
+
78
+ hash[:client_id].should eql(1)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,34 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Emvoicer::Services::HttpAuth do
4
+ let(:service) do
5
+ Emvoicer::Services::HttpAuth.new
6
+ end
7
+
8
+ describe ".request_headers" do
9
+ it "it returns headers with authorization credentials" do
10
+ service.stubs(:headers).returns({ "Accept" => "application/xml" })
11
+ service.stubs(:auth_params).returns(["username", "password"])
12
+
13
+ service.request_headers.should eq({
14
+ "Accept" => "application/xml",
15
+ "Authorization" => ["username", "password"]
16
+ })
17
+ end
18
+ end
19
+
20
+ describe ".authentication_valid?" do
21
+ let(:response) { "Response" }
22
+
23
+ it "passess invoices response to service test_authentication_response method" do
24
+ service.stubs(:invoices_url).returns("http://example.com/invoices")
25
+ service.stubs(:auth_params).returns(["user", "pass"])
26
+ stub_request(:get, "http://user:pass@example.com/invoices").
27
+ with(:headers => {"Accept"=>"*/*", "User-Agent"=>"Ruby"}).
28
+ to_return(:status => 200, :body => "", :headers => {})
29
+
30
+ service.class.expects(:test_authentication_response)
31
+ service.authentication_valid?
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+
4
+ require 'rspec'
5
+ require 'emvoicer'
6
+
7
+ require 'webmock/rspec'
8
+ WebMock.disable_net_connect!
9
+
10
+ # Requires supporting files with custom matchers and macros, etc,
11
+ # in ./support/ and its subdirectories.
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
13
+
14
+ RSpec.configure do |config|
15
+ config.mock_with :mocha
16
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: emvoicer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tomasz Werbicki
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-07-14 00:00:00.000000000 +02:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: em-http-request
17
+ requirement: &2166161560 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.3.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2166161560
26
+ - !ruby/object:Gem::Dependency
27
+ name: nokogiri
28
+ requirement: &2166161080 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.4.6
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *2166161080
37
+ - !ruby/object:Gem::Dependency
38
+ name: rspec
39
+ requirement: &2166160600 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 2.3.0
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *2166160600
48
+ - !ruby/object:Gem::Dependency
49
+ name: bundler
50
+ requirement: &2166160120 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 1.0.0
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *2166160120
59
+ - !ruby/object:Gem::Dependency
60
+ name: jeweler
61
+ requirement: &2166159640 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ~>
65
+ - !ruby/object:Gem::Version
66
+ version: 1.6.2
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *2166159640
70
+ - !ruby/object:Gem::Dependency
71
+ name: simplecov
72
+ requirement: &2166159160 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: *2166159160
81
+ description:
82
+ email: tomasz@werbicki.net
83
+ executables: []
84
+ extensions: []
85
+ extra_rdoc_files:
86
+ - LICENSE.txt
87
+ - README.rdoc
88
+ files:
89
+ - .document
90
+ - .rspec
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - LICENSE.txt
94
+ - README.rdoc
95
+ - Rakefile
96
+ - VERSION
97
+ - emvoicer.gemspec
98
+ - lib/emvoicer.rb
99
+ - lib/emvoicer/client.rb
100
+ - lib/emvoicer/invoice.rb
101
+ - lib/emvoicer/services/base.rb
102
+ - lib/emvoicer/services/blinksale.rb
103
+ - lib/emvoicer/services/blinksale/parsing.rb
104
+ - lib/emvoicer/services/http_auth.rb
105
+ - spec/client_spec.rb
106
+ - spec/fixtures/invoice_collection.xml
107
+ - spec/invoice_spec.rb
108
+ - spec/services/base_spec.rb
109
+ - spec/services/blinksale_spec.rb
110
+ - spec/services/http_auth_spec.rb
111
+ - spec/spec_helper.rb
112
+ has_rdoc: true
113
+ homepage: http://github.com/neaf/emvoicer
114
+ licenses:
115
+ - MIT
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ segments:
127
+ - 0
128
+ hash: 474791924770760535
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 1.6.2
138
+ signing_key:
139
+ specification_version: 3
140
+ summary: Invoice data fetcher to use within EventMachine reactor.
141
+ test_files: []