jbr 1.0.3 → 1.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c00c0f3ead7d9db2518cc70474af4639c1b6aa407a0ee9012abcf456d86095bc
4
- data.tar.gz: '06924dba14bdaebb1e309ca824eb3cef19c4cbd51715033f0add17ea6917c3a3'
3
+ metadata.gz: 1acfc5eb08362100da536df933e8fdbde9edb2c3857970258731087766ebc9b1
4
+ data.tar.gz: 101d507d0b42e8c5f4f775db7e23ebbe4842ce5df46ced706dc7e4b261c41cac
5
5
  SHA512:
6
- metadata.gz: 7925cb707094cae2a6007534bf1c4e7833cdacbc0651b4c0a37e20311405d8c8376c387f70fca14657799e77a339027c8da229f0c356c00080f308eb2a8333d1
7
- data.tar.gz: f7545884b57cf5feb495d03853c0aa0a748ea05b02841bf612f30432da7561368deb331e7c9e954e7282ff5058d376a9677cddc7611a8085d8bc6985bd362548
6
+ metadata.gz: b39be9586f32d4e4729999690569fbd806c96aa095ee899e5fcb3232308ee82376bf9e45bf1627dcc7822416698258b923e4c694fa4fe2b1c35be0e6e1087bde
7
+ data.tar.gz: c8904ce193f0694f17793560f3d1f29c7cdab60b3f19be34b04e96cb1097d5deee6bcfa83762e49371c2d3e6f41c38671461cc2c017f76e19a63a43005e797bb
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [1.0.5] - 2026-05-24
2
+
3
+ - [Feature] Add mocks to help apps test Jobber integration
4
+
1
5
  ## [1.0.3] - 2026-05-22
2
6
 
3
7
  - [Fix] Jobber returns invoice.issuedDate as a time, not as a date
data/README.md CHANGED
@@ -2,10 +2,24 @@
2
2
 
3
3
  ## Available methods
4
4
 
5
- Initialize the authentication with a code:
5
+ ### Credentials
6
+
7
+ Generate the URL for Jobber users to authorize the app:
8
+
9
+ ```ruby
10
+ url = Jbr.oauth_url_for redirect_uri:, state:
11
+ url # => 'https://api.getjobber.com/api/oauth/authorize?state=...&redirect_uri=...'
12
+
13
+ Create credentials with a code and a redirect URI:
14
+
15
+ ```ruby
16
+ oauth = Jbr.create_oauth code:, redirect_uri:
17
+ ```
18
+
19
+ Initialize with existing credentials:
6
20
 
7
21
  ```ruby
8
- oauth = Jbr::OAuth.create code: code, redirect_uri: jobber_callback_url
22
+ oauth = Jbr.oauth_for access_token:, refresh_token, expires_at:, account_id:
9
23
  ```
10
24
 
11
25
  Access OAuth attributes:
@@ -17,6 +31,14 @@ oauth.expires_at # => 2026-05-22 14:32:53
17
31
  oauth.account_id # => 'Z2lkOi8vSm9iYmV'
18
32
  ````
19
33
 
34
+ Revoke credentials:
35
+
36
+ ```ruby
37
+ oauth.delete
38
+ ```
39
+
40
+ ### Requests
41
+
20
42
  Create a Jobber request, finding or creating a Client with a matching phone number:
21
43
 
22
44
  ```ruby
@@ -26,7 +48,9 @@ request.id # => 'Z2lkOi8vSm9iYmVyL'
26
48
  request.client_id # => 'MwMTU0Mg'
27
49
  ```
28
50
 
29
- Fetches a quote from Jobber:
51
+ ### Quotes
52
+
53
+ Fetch a quote from Jobber:
30
54
 
31
55
  ```ruby
32
56
  quote = oauth.quotes.find 'Z2lkOi8vS'
@@ -34,7 +58,9 @@ quote.id # => 'Z2lkOi8vS'
34
58
  quote.request_id # => 'Z2lkOi8vSm9iYmVyL'
35
59
  ```
36
60
 
37
- Fetches a job from Jobber:
61
+ ### Jobs
62
+
63
+ Fetch a job from Jobber:
38
64
 
39
65
  ```ruby
40
66
  job = oauth.jobs.find 'Njc5MTk5'
@@ -44,7 +70,9 @@ job.scheduled_at # => 2026-05-14 23:02:52
44
70
  job.completed_at # => 2026-05-18 11:36:13
45
71
  ```
46
72
 
47
- Fetches a non-draft invoice from Jobber:
73
+ ### Invoices
74
+
75
+ Fetch a non-draft invoice from Jobber:
48
76
 
49
77
  ```ruby
50
78
  invoice = oauth.invoices.find 'MjU3ODA0'
@@ -55,10 +83,58 @@ invoice.issued_at # => 2026-05-22 12:12:53
55
83
  invoice.completed_at # => 2026-05-22 14:32:53
56
84
  ```
57
85
 
86
+ ## Available mocks
87
+
88
+ Use these methods to mock request to Jobber when testing an app:
58
89
 
59
- Revoke authentication:
90
+ ### Credentials
91
+
92
+ Mock successfully creating and revoking credentials:
60
93
 
61
94
  ```ruby
62
- oauth.delete
95
+ Jbr.mock
96
+ ```
97
+
98
+ Mock an error when creating credentials:
99
+
100
+ ```ruby
101
+ Jbr.mock.oauth_error = 'Flow rejected'
63
102
  ```
64
103
 
104
+ Mock a custom redirect URL:
105
+
106
+ ```ruby
107
+ Jbr.mock.oauth_url_for = 'https://example.com'
108
+ ```
109
+
110
+ ### Requests
111
+
112
+ Mock successfully creating a request:
113
+
114
+ ```ruby
115
+ Jbr.mock.request = { id: 'request-01', client_id: 'client-01' }
116
+ ```
117
+
118
+ ### Quotes
119
+
120
+ Mock successfully fetching a quote:
121
+
122
+ ```ruby
123
+ Jbr.mock.quote = { id: 'quote-01', request_id: 'request-01' }
124
+ ```
125
+
126
+ ### Jobs
127
+
128
+ Mock successfully fetching a job:
129
+
130
+ ```ruby
131
+ Jbr.mock.job = { id: 'job-01', quote_id: 'quote-01', scheduled_at: Date.tomorrow.noon }
132
+ ```
133
+
134
+ ### Invoices
135
+
136
+ Mock successfully fetching an invoice:
137
+
138
+ ```ruby
139
+ Jbr.mock.invoice = { id: 'invoice-01', job_id: 'job-01', total: 19.99, issued_at: Date.yesterday.noon }
140
+ ```
@@ -0,0 +1,34 @@
1
+ require 'json'
2
+ require 'net/http'
3
+
4
+ # A minimal GraphQL client used to talk to third-party APIs.
5
+ module GraphQL
6
+ # An HTTP wrapper that posts queries to a GraphQL endpoint and returns the `data` payload.
7
+ class Client
8
+ # @param endpoint [String] the GraphQL endpoint URL.
9
+ # @param token [String] the bearer access token used to authorize the request.
10
+ # @param headers [Hash] any extra headers required by the API (e.g. a version pin).
11
+ def initialize(endpoint:, token:, headers: {})
12
+ @endpoint = URI endpoint
13
+ @token = token
14
+ @headers = headers
15
+ end
16
+
17
+ # @param query [String] the GraphQL query string.
18
+ # @param variables [Hash] the variables to interpolate into the query.
19
+ # @return [Hash] the `data` portion of the GraphQL response.
20
+ def query(query, variables: {})
21
+ response = Net::HTTP.post @endpoint, { query:, variables: }.to_json, request_headers
22
+ raise Unauthorized, response.body if response.code == '401'
23
+ raise Error, response.body unless response.is_a? Net::HTTPSuccess
24
+ body = JSON.parse(response.body)
25
+ raise Error, body['errors'].pluck('message').join('; ') if body['errors'].present?
26
+ body.fetch('data')
27
+ end
28
+
29
+ private
30
+ def request_headers
31
+ { 'Authorization' => "Bearer #{@token}", 'Content-Type' => 'application/json' }.merge @headers
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module GraphQL
2
+ # An error raised when a GraphQL request fails.
3
+ class Error < StandardError
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module GraphQL
2
+ # An error raised when the API rejects the access token (HTTP 401).
3
+ class Unauthorized < Error
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Jbr
2
+ class Configuration
3
+ attr_accessor :mock_account, :mock_request, :mock_auth, :mock_auth_error, :mock_disconnect, :mock_invoice, :mock_job, :mock_quote, :mock_redirect_url
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Jbr
2
+ class Mock::Account < Account
3
+ def id = 'account-01'
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ module Jbr
2
+ class Mock::Invoice < Invoice
3
+ def find(_)
4
+ @id = Jbr.mock.invoice[:id]
5
+ @job_id = Jbr.mock.invoice[:job_id]
6
+ @total = Jbr.mock.invoice[:total]
7
+
8
+ self
9
+ end
10
+
11
+ def issued_at = Jbr.mock.invoice[:issued_at]
12
+
13
+ def completed_at = Jbr.mock.invoice[:completed_at]
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Jbr
2
+ class Mock::Job < Job
3
+ def find(_)
4
+ @id = Jbr.mock.job[:id]
5
+ @quote_id = Jbr.mock.job[:quote_id]
6
+
7
+ self
8
+ end
9
+
10
+ def scheduled_at = Jbr.mock.job[:scheduled_at]
11
+
12
+ def completed_at = Jbr.mock.job[:completed_at]
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module Jbr
2
+ class Mock::OAuth < OAuth
3
+ def invoices = Mock::Invoice.new(oauth: self)
4
+ def jobs = Mock::Job.new(oauth: self)
5
+ def quotes = Mock::Quote.new(oauth: self)
6
+ def requests = Mock::Request.new(oauth: self)
7
+ def account = Mock::Account.new oauth: self
8
+
9
+ def delete; end
10
+
11
+ private
12
+
13
+ def self.post(_)
14
+ raise Error, error: Jbr.mock.oauth_error if Jbr.mock.oauth_error
15
+
16
+ { access_token: 'mock-token', refresh_token: 'mock-token', expires_at: (Time.current + 3600) }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module Jbr
2
+ class Mock::Quote < Quote
3
+ def find(_)
4
+ @id = Jbr.mock.quote[:id]
5
+ @request_id = Jbr.mock.quote[:request_id]
6
+
7
+ self
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Jbr
2
+ class Mock::Request < Request
3
+ def create(_)
4
+ @id = Jbr.mock.request[:id]
5
+ @client_id = Jbr.mock.request[:client_id]
6
+
7
+ self
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Jbr
2
+ class Mock::URL < URL
3
+ def self.for(_)
4
+ Jbr.mock.oauth_url
5
+ end
6
+ end
7
+ end
data/lib/jbr/mock.rb ADDED
@@ -0,0 +1,5 @@
1
+ module Jbr
2
+ class Mock
3
+ attr_accessor :quote, :job, :invoice, :request, :oauth_url, :oauth_error
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ module Jbr
2
+ module Mocking
3
+ def mock
4
+ @mock ||= Jbr::Mock.new
5
+ end
6
+
7
+ def create_oauth(params = {})
8
+ (@mock ? Mock::OAuth : OAuth).create **params
9
+ end
10
+
11
+ def oauth_url_for(params = {})
12
+ (@mock ? Mock::URL : URL).for **params
13
+ end
14
+
15
+ def oauth_for(params = {})
16
+ (@mock ? Mock::OAuth : OAuth).new params
17
+ end
18
+ end
19
+
20
+ extend Mocking
21
+ end
data/lib/jbr/oauth.rb CHANGED
@@ -74,11 +74,5 @@ module Jbr
74
74
  end
75
75
 
76
76
  def headers = { 'X-JOBBER-GRAPHQL-VERSION' => '2026-04-22' }
77
-
78
- def self.url_for(params = {})
79
- uri = URI 'https://api.getjobber.com/api/oauth/authorize'
80
- uri.query ||= params.merge(response_type: 'code', client_id: client_id).to_query
81
- uri.to_s
82
- end
83
77
  end
84
78
  end
data/lib/jbr/url.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Jbr
2
+ class URL
3
+ def self.for(params = {})
4
+ uri = URI 'https://api.getjobber.com/api/oauth/authorize'
5
+ uri.query ||= params.merge(response_type: 'code', client_id: client_id).to_query
6
+ uri.to_s
7
+ end
8
+
9
+ private
10
+
11
+ def self.client_id = ENV['JOBBER_CLIENT_ID']
12
+ end
13
+ end
data/lib/jbr/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Jbr
4
- VERSION = '1.0.3'
4
+ VERSION = '1.0.5'
5
5
  end
data/lib/jbr.rb CHANGED
@@ -3,6 +3,13 @@
3
3
  require 'json'
4
4
  require 'net/http'
5
5
 
6
+ require 'graphql/error'
7
+ require 'graphql/unauthorized'
8
+ require 'graphql/client'
9
+
10
+ require 'jbr/mock'
11
+
12
+ require 'jbr/url'
6
13
  require 'jbr/error'
7
14
  require 'jbr/resource'
8
15
  require 'jbr/request'
@@ -13,3 +20,13 @@ require 'jbr/client'
13
20
  require 'jbr/invoice'
14
21
  require 'jbr/job'
15
22
  require 'jbr/quote'
23
+
24
+ require 'jbr/mock/oauth'
25
+ require 'jbr/mock/quote'
26
+ require 'jbr/mock/job'
27
+ require 'jbr/mock/invoice'
28
+ require 'jbr/mock/request'
29
+ require 'jbr/mock/account'
30
+ require 'jbr/mock/url'
31
+
32
+ require 'jbr/mocking'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jbr
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claudio Baccigalupo
@@ -19,16 +19,30 @@ files:
19
19
  - CHANGELOG.md
20
20
  - LICENSE.txt
21
21
  - README.md
22
+ - lib/graphql/client.rb
23
+ - lib/graphql/error.rb
24
+ - lib/graphql/unauthorized.rb
22
25
  - lib/jbr.rb
23
26
  - lib/jbr/account.rb
24
27
  - lib/jbr/client.rb
28
+ - lib/jbr/configuration.rb
25
29
  - lib/jbr/error.rb
26
30
  - lib/jbr/invoice.rb
27
31
  - lib/jbr/job.rb
32
+ - lib/jbr/mock.rb
33
+ - lib/jbr/mock/account.rb
34
+ - lib/jbr/mock/invoice.rb
35
+ - lib/jbr/mock/job.rb
36
+ - lib/jbr/mock/oauth.rb
37
+ - lib/jbr/mock/quote.rb
38
+ - lib/jbr/mock/request.rb
39
+ - lib/jbr/mock/url.rb
40
+ - lib/jbr/mocking.rb
28
41
  - lib/jbr/oauth.rb
29
42
  - lib/jbr/quote.rb
30
43
  - lib/jbr/request.rb
31
44
  - lib/jbr/resource.rb
45
+ - lib/jbr/url.rb
32
46
  - lib/jbr/version.rb
33
47
  homepage: https://github.com/HouseAccountEng/jbr
34
48
  licenses: