badbill 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/badbill.rb ADDED
@@ -0,0 +1,152 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yajl/json_gem'
4
+ require 'faraday_middleware'
5
+ require 'hashie/mash'
6
+
7
+ require_relative 'badbill/resource'
8
+ require_relative 'badbill/forward_methods'
9
+ require_relative 'badbill/base_resource'
10
+
11
+ require_relative 'badbill/client'
12
+ require_relative 'badbill/invoice'
13
+
14
+ # Handles the connection and requests to the Billomat API.
15
+ #
16
+ # This class can be used for direct API access and is used for connections from
17
+ # resource classes.
18
+ #
19
+ # If a API resource is not yet implemented as a Ruby class, easy access is possible here.
20
+ #
21
+ # Examples:
22
+ #
23
+ # billo = BadBill.new 'ruby', '1234568'
24
+ # # => #<BadBill:0x00000002825710 ...>
25
+ # billo.get 'clients'
26
+ # # => {"clients"=>{"client"=>[...]}}
27
+ class BadBill
28
+ VERSION = '0.0.1'
29
+
30
+ # Reject any not allowed HTTP method.
31
+ class NotAllowedException < Exception; end
32
+ # Fail if no global connection is set.
33
+ class NoConnection < Exception; end
34
+
35
+ # The API url used for all connections.
36
+ API_URL = 'http%s://%s.billomat.net/'
37
+ # Allowed HTTP methods.
38
+ ALLOWED_METHODS = [:get, :post, :put, :delete]
39
+
40
+ # Create new Billomat connection.
41
+ #
42
+ # @param [String] billomat_id Used as your BillomatID
43
+ # @param [String] api_key API Key used to authenticate to the API.
44
+ # @param [Boolean] ssl Wether to use SSL or not (only possible for paying customers)
45
+ def initialize billomat_id, api_key, ssl=false
46
+ @billomat_id = billomat_id
47
+ @api_key = api_key
48
+ @ssl = ssl
49
+ @http_adapter = connection
50
+
51
+ BadBill.connection = self
52
+ end
53
+
54
+ # Assign global BadBill connection object.
55
+ #
56
+ # @param [BadBill] connection The connection object.
57
+ def self.connection= connection
58
+ @connection = connection
59
+ end
60
+
61
+ # Get the global connection object.
62
+ #
63
+ # @return [BadBill, nil] The global connection object or nil if not set.
64
+ def self.connection
65
+ @connection
66
+ end
67
+
68
+ # Call the specified resource.
69
+ #
70
+ # It sets the X-BillomatApiKey header, the Content-Type header and the Accept header.
71
+ #
72
+ # @param [String] resource The String resource name (gets prepended with _/api/_).
73
+ # @param [String,Integer] id The ID for the resource.
74
+ # @param [Hash] options All parameters for this request.
75
+ # Exact parameters depend on the resource.
76
+ # @param [Symbol] method One of ALLOWED_METHODS.
77
+ #
78
+ # @return [Hashie::Mash] The response body.
79
+ # On error the return value only includes the key :error.
80
+ def call resource, id='', options=nil, method=:get
81
+ raise NotAllowedException.new("#{method.inspect} is not allowed. Use one of [:#{ALLOWED_METHODS*', :'}]") unless ALLOWED_METHODS.include?(method)
82
+
83
+ if id.kind_of? Hash
84
+ options = id
85
+ id = ''
86
+ end
87
+
88
+ #no_accept = options.delete :no_accept
89
+ @http_adapter.__send__(method) { |req|
90
+ if method == :get && options && !options.empty?
91
+ req.url "/api/#{resource}/#{id}", options
92
+ else
93
+ req.url "/api/#{resource}/#{id}"
94
+ end
95
+ req.headers['X-BillomatApiKey'] = @api_key
96
+ req.headers['Accept'] = 'application/json'
97
+ req.headers['Content-Type'] = 'application/json' if [:post, :put].include?(method)
98
+ req.body = options if method != :get && options && !options.empty?
99
+ }.body
100
+ rescue Faraday::Error::ClientError => error
101
+ Hashie::Mash.new :error => error
102
+ end
103
+
104
+ # Send a GET request.
105
+ #
106
+ # @param (see #call)
107
+ # @return (see #call)
108
+ def get resource, id='', options=nil
109
+ call resource, id, options, :get
110
+ end
111
+
112
+ # Send a POST request.
113
+ #
114
+ # @param (see #call)
115
+ # @return (see #call)
116
+ def post resource, id='', options=nil
117
+ call resource, id, options, :post
118
+ end
119
+
120
+ # Send a PUT request.
121
+ #
122
+ # @param (see #call)
123
+ # @return (see #call)
124
+ def put resource, id='', options=nil
125
+ call resource, id, options, :put
126
+ end
127
+
128
+ # Send a DELETE request.
129
+ #
130
+ # @param (see #call)
131
+ # @return (see #call)
132
+ def delete resource, id='', options=nil
133
+ call resource, id, options, :delete
134
+ end
135
+
136
+ private
137
+ # Creates a new Faraday connection object.
138
+ #
139
+ # @return [Faraday] new connection object with url and response handling set.
140
+ def connection
141
+ @connection ||= Faraday.new(API_URL % [@ssl ? 's' : '', @billomat_id]) do |conn|
142
+ conn.request :json
143
+
144
+ conn.response :mashify
145
+ conn.response :json, :content_type => /\bjson$/
146
+ #conn.response :logger
147
+ conn.response :raise_error
148
+ conn.adapter :net_http
149
+ conn.options[:timeout] = 2
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../helper'
4
+
5
+ describe BadBill::BaseResource do
6
+ it "can't set a new ID" do
7
+ b = BadBill::BaseResource.new 42, {}
8
+ b.id.should == 42
9
+ lambda{ b.id = 23 }.should raise_error(NoMethodError)
10
+ b.id.should == 42
11
+ end
12
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../helper'
4
+
5
+ describe BadBill::Client do
6
+ before :all do
7
+ @badbill = BadBill.new "ruby", "12345"
8
+ end
9
+
10
+ it "fetches all clients" do
11
+ stub = stub_request(:get, "ruby.billomat.net/api/clients/").
12
+ with(:headers => {'Accept' => 'application/json'}).
13
+ to_return(:body => '{"clients":{"client":[{"id":1}]}}',
14
+ :headers => {'Content-Type' => 'application/json'})
15
+
16
+ resp = BadBill::Client.all
17
+ stub.should have_been_requested
18
+
19
+ resp.size.should == 1
20
+ resp.first.id.should == 1
21
+ end
22
+
23
+ it "fetches info about myself" do
24
+ stub = stub_request(:get, "ruby.billomat.net/api/clients/myself").
25
+ with(:headers => {'Accept' => 'application/json'}).
26
+ to_return(:body => '{"client":{"id":1}}',
27
+ :headers => {'Content-Type' => 'application/json'})
28
+
29
+ resp = BadBill::Client.myself
30
+ stub.should have_been_requested
31
+
32
+ resp.id.should == 1
33
+ resp.myself?.should == true
34
+ end
35
+
36
+ it "fetches info about another client" do
37
+ stub = stub_request(:get, "ruby.billomat.net/api/clients/1").
38
+ with(:headers => {'Accept' => 'application/json'}).
39
+ to_return(:body => '{"client":{"id":1}}',
40
+ :headers => {'Content-Type' => 'application/json'})
41
+
42
+ resp = BadBill::Client.find 1
43
+ stub.should have_been_requested
44
+
45
+ resp.id.should == 1
46
+ resp.myself?.should == false
47
+ end
48
+
49
+ it "saves changes to client data" do
50
+ body = { client: {name: "old name", "id" => 1} }
51
+ stub_request(:get, "ruby.billomat.net/api/clients/1").
52
+ with(:headers => {'Accept' => 'application/json'}).
53
+ to_return(:body => body,
54
+ :headers => {'Content-Type' => 'application/json'})
55
+
56
+ client = BadBill::Client.find 1
57
+
58
+ body = { client: {name: "new name", "id" => 1} }
59
+ stub = stub_request(:put, "ruby.billomat.net/api/clients/1").
60
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
61
+ to_return(:status => 200)
62
+
63
+ client.name.should == "old name"
64
+ client.name = "new name"
65
+ client.save.should == true
66
+ stub.should have_been_requested
67
+ end
68
+
69
+ it "creates a new client with given data" do
70
+ body = {
71
+ "client" => {
72
+ "name" => "Musterfirma",
73
+ "salutation" => "Herr",
74
+ "first_name" => "Max",
75
+ "last_name" => "Muster",
76
+ "street" => "Musterstraße 123",
77
+ "zip" => "12345",
78
+ "city" => "Musterstadt",
79
+ "state" => "Bundesland",
80
+ "country_code" => "DE",
81
+ }
82
+ }
83
+
84
+ body_return = { "client" => body["client"].dup }
85
+ body_return["client"]["id"] = 42
86
+ body_return["client"]["created"] = "2007-12-13T12:12:00+01:00"
87
+
88
+ stub = stub_request(:post, "ruby.billomat.net/api/clients/").
89
+ with(:body => body.to_json, :headers => {'Accept' => 'application/json',
90
+ 'Content-Type'=>'application/json'}).
91
+ to_return(:status => 201, :body => body_return,
92
+ :headers => {'Content-Type' => 'application/json'})
93
+
94
+ client = BadBill::Client.create body["client"]
95
+ stub.should have_been_requested
96
+ client.id.should == 42
97
+ end
98
+ end
@@ -0,0 +1,228 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../helper'
4
+
5
+ describe BadBill::Invoice do
6
+ before :all do
7
+ @badbill = BadBill.new "ruby", "12345"
8
+ end
9
+
10
+ it "fetches all invoices" do
11
+ stub = stub_request(:get, "ruby.billomat.net/api/invoices/").
12
+ with(:headers => {'Accept' => 'application/json'}).
13
+ to_return(:body => '{"invoices":{"invoice":[{"id":1}]}}',
14
+ :headers => {'Content-Type' => 'application/json'})
15
+
16
+ resp = BadBill::Invoice.all
17
+ stub.should have_been_requested
18
+
19
+ resp.size.should == 1
20
+ resp.first.id.should == 1
21
+ end
22
+
23
+ it "fetches info about an invoice" do
24
+ stub = stub_request(:get, "ruby.billomat.net/api/invoices/1").
25
+ with(:headers => {'Accept' => 'application/json'}).
26
+ to_return(:body => '{"invoice":{"id":1}}',
27
+ :headers => {'Content-Type' => 'application/json'})
28
+
29
+ resp = BadBill::Invoice.find 1
30
+ stub.should have_been_requested
31
+
32
+ resp.id.should == 1
33
+ end
34
+
35
+ context "existing invoice" do
36
+ before :each do
37
+ stub_request(:get, "ruby.billomat.net/api/invoices/1").
38
+ with(:headers => {'Accept' => 'application/json'}).
39
+ to_return(:body => '{"invoice":{"id":1,"status":"DRAFT"}}',
40
+ :headers => {'Content-Type' => 'application/json'})
41
+
42
+ @invoice = BadBill::Invoice.find 1
43
+ end
44
+
45
+ it "marks invoice as complete" do
46
+ stub = stub_request(:put, "ruby.billomat.net/api/invoices/1/complete").
47
+ with(:headers => {'Accept' => 'application/json'}).
48
+ to_return(:status => 200)
49
+
50
+ @invoice.complete.should == true
51
+ stub.should have_been_requested
52
+ end
53
+
54
+ it "cancels an invoice" do
55
+ stub = stub_request(:put, "ruby.billomat.net/api/invoices/1/cancel").
56
+ with(:headers => {'Accept' => 'application/json'}).
57
+ to_return(:status => 200)
58
+
59
+ @invoice.cancel.should == true
60
+ stub.should have_been_requested
61
+ end
62
+
63
+ it "deletes an invoice" do
64
+ stub = stub_request(:delete, "ruby.billomat.net/api/invoices/1").
65
+ with(:headers => {'Accept' => 'application/json'}).
66
+ to_return(:status => 200)
67
+
68
+ @invoice.delete.should == true
69
+ stub.should have_been_requested
70
+ end
71
+
72
+ it "fetches the pdf" do
73
+ body = {
74
+ "pdf" => {
75
+ "id" => 1,
76
+ "created" => "2009-09-02T12 =>04 =>15+02 =>00",
77
+ "invoice_id" => 1,
78
+ "filename" => "invoice_1.pdf",
79
+ "mimetype" => "application/pdf",
80
+ "filesize" => "1",
81
+ "base64file" => "foobar"
82
+ }
83
+ }
84
+
85
+ stub = stub_request(:get, "ruby.billomat.net/api/invoices/1/pdf").
86
+ with(:headers => {'Accept' => 'application/json'}).
87
+ to_return(:body => body, :headers => {
88
+ 'Content-Type' => 'application/json'
89
+ })
90
+
91
+ pdf = @invoice.pdf
92
+
93
+ stub.should have_been_requested
94
+ pdf.id.should == 1
95
+ pdf.invoice_id == 1
96
+ end
97
+
98
+ it "uploads a signature" do
99
+ body = {"signature" => {"base64file" => "cnVieQ==\n"}}
100
+ stub = stub_request(:put, "ruby.billomat.net/api/invoices/1/upload-signature").
101
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
102
+ to_return(:status => 200)
103
+
104
+ @invoice.status = "OPEN"
105
+ file_content = StringIO.new "ruby"
106
+ resp = @invoice.upload_signature file_content
107
+ resp.should == true
108
+
109
+ stub.should have_been_requested
110
+ end
111
+
112
+ context "sending invoice as mail" do
113
+ it "sends an invoice via mail" do
114
+ body = {
115
+ "email" => {
116
+ "from" => "sender@test.example",
117
+ "recipients" => {
118
+ "to" => "recv@test.example",
119
+ "cc" => "mail@test.example"
120
+ },
121
+ "subject" => "subject",
122
+ "body" => "body",
123
+ "filename" => "invoice",
124
+ "attachments" => {
125
+ "attachment" => {
126
+ "filename" => "more.pdf",
127
+ "mimetype" => "application/pdf",
128
+ "base64file" => "foobar"
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ stub = stub_request(:post, "ruby.billomat.net/api/invoices/1/email").
135
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
136
+ to_return(:headers => {
137
+ 'Content-Type' => 'application/json'
138
+ })
139
+
140
+ resp = @invoice.email "recv@test.example", "sender@test.example",
141
+ "subject", "body", {
142
+ recipients: { cc: "mail@test.example" },
143
+ filename: "invoice",
144
+ attachments: {
145
+ attachment: {
146
+ filename: "more.pdf",
147
+ mimetype: "application/pdf",
148
+ base64file: "foobar"
149
+ }
150
+ }
151
+ }
152
+
153
+ stub.should have_been_requested
154
+ resp.should == true
155
+ end
156
+
157
+ it "sends an invoice with only basic information" do
158
+ body = {
159
+ "email" => {
160
+ "recipients" => {
161
+ "to" => "recv@test.example",
162
+ "cc" => "mail@test.example",
163
+ }
164
+ }
165
+ }
166
+
167
+ stub = stub_request(:post, "ruby.billomat.net/api/invoices/1/email").
168
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
169
+ to_return(:headers => {
170
+ 'Content-Type' => 'application/json'
171
+ })
172
+
173
+ resp = @invoice.email "recv@test.example", {recipients: { cc: "mail@test.example" }}
174
+
175
+ stub.should have_been_requested
176
+ resp.should == true
177
+ end
178
+
179
+ it "sends an invoice with basic information and from" do
180
+ body = {
181
+ "email" => {
182
+ "recipients" => {
183
+ "to" => "recv@test.example",
184
+ "cc" => "mail@test.example",
185
+ },
186
+ "from" => "sender@test.example"
187
+ }
188
+ }
189
+
190
+ stub = stub_request(:post, "ruby.billomat.net/api/invoices/1/email").
191
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
192
+ to_return(:headers => {
193
+ 'Content-Type' => 'application/json'
194
+ })
195
+
196
+ resp = @invoice.email "recv@test.example", "sender@test.example", {recipients: { cc: "mail@test.example" }}
197
+
198
+ stub.should have_been_requested
199
+ resp.should == true
200
+ end
201
+
202
+ it "sends an invoice with basic information, from and subject" do
203
+ body = {
204
+ "email" => {
205
+ "recipients" => {
206
+ "to" => "recv@test.example",
207
+ "cc" => "mail@test.example",
208
+ },
209
+ "from" => "sender@test.example",
210
+ "subject" => "subject"
211
+ }
212
+ }
213
+
214
+ stub = stub_request(:post, "ruby.billomat.net/api/invoices/1/email").
215
+ with(:headers => {'Accept' => 'application/json'}, :body => body).
216
+ to_return(:headers => {
217
+ 'Content-Type' => 'application/json'
218
+ })
219
+
220
+ resp = @invoice.email "recv@test.example", "sender@test.example", "subject",
221
+ {recipients: { cc: "mail@test.example" }}
222
+
223
+ stub.should have_been_requested
224
+ resp.should == true
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,89 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'helper'
4
+
5
+ describe BadBill do
6
+ it "creates new global connection object" do
7
+ BadBill.connection.should be_nil
8
+ b = BadBill.new "ruby", "12345"
9
+ BadBill.connection.should_not be_nil
10
+ end
11
+
12
+ context "sending requests" do
13
+ before :each do
14
+ @badbill = BadBill.new "ruby", "12345"
15
+ end
16
+
17
+ it "sends a specified request to the API" do
18
+ stub = stub_request(:get, "ruby.billomat.net/api/").
19
+ with(:headers => {'Accept' => /application\/json/}).
20
+ to_return(:body => "{}")
21
+
22
+ @badbill.call ''
23
+ stub.should have_been_requested
24
+ end
25
+
26
+ it "sends a GET request to the API" do
27
+ stub = stub_request(:get, "ruby.billomat.net/api/").
28
+ with(:headers => {'X-BillomatApiKey' => '12345',
29
+ 'Accept' => /application\/json/}).
30
+ to_return(:body => "{}")
31
+
32
+ @badbill.get ''
33
+ stub.should have_been_requested
34
+ end
35
+
36
+ it "sends a GET request with parameters to the API" do
37
+ stub = stub_request(:get, "ruby.billomat.net/api/resource/?option=foobar").
38
+ with(:headers => {'X-BillomatApiKey' => '12345',
39
+ 'Accept' => /application\/json/}).
40
+ to_return(:body => "{}")
41
+
42
+ @badbill.get 'resource', option: "foobar"
43
+ stub.should have_been_requested
44
+ end
45
+
46
+ it "sends a POST request to the API" do
47
+ stub = stub_request(:post, "ruby.billomat.net/api/").
48
+ with(:headers => {'X-BillomatApiKey' => '12345',
49
+ 'Accept' => /application\/json/,
50
+ 'Content-Type' => /application\/json/}).
51
+ to_return(:body => "{}")
52
+
53
+ @badbill.post ''
54
+ stub.should have_been_requested
55
+ end
56
+
57
+ it "sends a PUT request to the API" do
58
+ stub = stub_request(:put, "ruby.billomat.net/api/").
59
+ with(:headers => {'X-BillomatApiKey' => '12345',
60
+ 'Accept' => /application\/json/,
61
+ 'Content-Type' => /application\/json/}).
62
+ to_return(:body => "{}")
63
+
64
+ @badbill.put ''
65
+ stub.should have_been_requested
66
+ end
67
+
68
+ it "sends a DELETE request to the API" do
69
+ stub = stub_request(:delete, "ruby.billomat.net/api/").
70
+ with(:headers => {'X-BillomatApiKey' => '12345',
71
+ 'Accept' => /application\/json/}).
72
+ to_return(:body => "{}")
73
+
74
+ @badbill.delete ''
75
+ stub.should have_been_requested
76
+ end
77
+
78
+ it "returns an error object on HTTP errors" do
79
+ stub = stub_request(:get, "ruby.billomat.net/api/resource/").
80
+ with(:headers => {'X-BillomatApiKey' => '12345',
81
+ 'Accept' => /application\/json/}).
82
+ to_return(:status => 404)
83
+
84
+ resp = @badbill.get 'resource'
85
+ resp.error.kind_of?(Faraday::Error::ClientError).should == true
86
+ stub.should have_been_requested
87
+ end
88
+ end
89
+ end
data/spec/helper.rb ADDED
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ if ENV['COVERAGE']
4
+ require 'simplecov'
5
+
6
+ SimpleCov.formatter = Class.new do
7
+ def format(result)
8
+ SimpleCov::Formatter::HTMLFormatter.new.format(result) unless ENV['CI']
9
+ File.open('coverage/covered_percent', 'w') do |f|
10
+ f.puts result.source_files.covered_percent.to_i
11
+ end
12
+ end
13
+ end
14
+
15
+ SimpleCov.start
16
+ end
17
+
18
+ require 'rspec'
19
+ require 'webmock/rspec'
20
+
21
+ here = File.expand_path(File.dirname(__FILE__) + '/..')
22
+ $LOAD_PATH.unshift here+'/lib'
23
+
24
+ require 'badbill'