genius_referrals 0.1.0

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.
@@ -0,0 +1,130 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ClientTest < Minitest::Test
4
+ def test_sends_x_auth_token_header_and_json_body
5
+ transport = FakeTransport.new([response(200, '{"code":200,"data":{"ok":true}}')])
6
+ client = GeniusReferrals::Client.new(
7
+ auth_token: 'secret-token',
8
+ transport: transport,
9
+ base_url: 'https://api.example.test'
10
+ )
11
+
12
+ result = client.authentications.test
13
+
14
+ assert_equal({ 'ok' => true }, result.data)
15
+ request = transport.requests.first
16
+ assert_equal('GET', request[:method])
17
+ assert_equal('secret-token', request[:headers]['X-Auth-Token'])
18
+ assert_equal('https://api.example.test/test-authentication', request[:url])
19
+ end
20
+
21
+ def test_redacts_token_from_api_error_request_snapshot
22
+ transport = FakeTransport.new([response(401, '{"code":401,"message":"bad token"}')])
23
+ client = GeniusReferrals::Client.new(auth_token: 'secret-token', transport: transport)
24
+
25
+ error = assert_raises(GeniusReferrals::AuthenticationError) do
26
+ client.authentications.test
27
+ end
28
+
29
+ assert_equal('[REDACTED]', error.request[:headers]['X-Auth-Token'])
30
+ refute_includes(error.message, 'secret-token')
31
+ end
32
+
33
+ def test_redacts_token_from_api_error_message_body_and_response
34
+ token = 'synthetic-secret-token'
35
+ body = JSON.generate(
36
+ 'code' => 401,
37
+ 'message' => "server echoed #{token}",
38
+ 'data' => {
39
+ 'auth_token' => token,
40
+ 'nested' => {
41
+ 'note' => "also echoed #{token}",
42
+ 'api_key' => token
43
+ }
44
+ }
45
+ )
46
+ transport = FakeTransport.new([response(401, body)])
47
+ client = GeniusReferrals::Client.new(auth_token: token, transport: transport)
48
+
49
+ error = assert_raises(GeniusReferrals::AuthenticationError) do
50
+ client.authentications.test
51
+ end
52
+
53
+ refute_includes(error.message, token)
54
+ refute_includes(error.body, token)
55
+ refute_includes(error.response.inspect, token)
56
+ assert_includes(error.message, '[REDACTED]')
57
+ assert_equal('[REDACTED]', error.response['data']['auth_token'])
58
+ assert_equal('[REDACTED]', error.response['data']['nested']['api_key'])
59
+ assert_equal('also echoed [REDACTED]', error.response['data']['nested']['note'])
60
+ assert_equal('[REDACTED]', error.request[:headers]['X-Auth-Token'])
61
+ end
62
+
63
+ def test_retries_transient_status_before_success
64
+ transport = FakeTransport.new([
65
+ response(500, '{"message":"temporary"}'),
66
+ response(200, '{"code":200,"data":{"ok":true}}')
67
+ ])
68
+ client = GeniusReferrals::Client.new(
69
+ auth_token: 'token',
70
+ transport: transport,
71
+ max_retries: 1,
72
+ backoff_factor: 0
73
+ )
74
+
75
+ result = client.authentications.test
76
+
77
+ assert_equal(true, result.data['ok'])
78
+ assert_equal(2, transport.requests.length)
79
+ end
80
+
81
+ def test_raises_after_retry_budget
82
+ transport = FakeTransport.new([
83
+ response(429, '{"message":"slow down"}'),
84
+ response(429, '{"message":"still slow"}')
85
+ ])
86
+ client = GeniusReferrals::Client.new(
87
+ auth_token: 'token',
88
+ transport: transport,
89
+ max_retries: 1,
90
+ backoff_factor: 0
91
+ )
92
+
93
+ assert_raises(GeniusReferrals::RateLimitError) do
94
+ client.authentications.test
95
+ end
96
+ end
97
+
98
+ def test_backoff_uses_exponential_delay
99
+ transport = FakeTransport.new([
100
+ response(503, '{"message":"temporary"}'),
101
+ response(503, '{"message":"temporary"}'),
102
+ response(200, '{"code":200,"data":{}}')
103
+ ])
104
+ client = GeniusReferrals::Client.new(
105
+ auth_token: 'token',
106
+ transport: transport,
107
+ max_retries: 2,
108
+ backoff_factor: 0.5
109
+ )
110
+ sleeps = []
111
+ client.define_singleton_method(:sleep) { |delay| sleeps << delay }
112
+
113
+ client.authentications.test
114
+
115
+ assert_equal([0.5, 1.0], sleeps)
116
+ end
117
+
118
+ def test_legacy_client_class_accepts_old_token_keyword
119
+ transport = FakeTransport.new([response(200, '{"code":200,"data":{"ok":true}}')])
120
+ client = GeniusReferrals::GeniusReferralsClient.new(
121
+ x_auth_token: 'token',
122
+ transport: transport,
123
+ base_url: 'https://api.example.test'
124
+ )
125
+
126
+ client.authentications.test
127
+
128
+ assert_equal('token', transport.requests.first[:headers]['X-Auth-Token'])
129
+ end
130
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../test_helper'
2
+
3
+ class AuthenticationIntegrationTest < Minitest::Test
4
+ def test_live_authentication_when_enabled
5
+ skip 'Set GR_SDK_RUN_INTEGRATION=1 and GR_API_TOKEN to run live authentication smoke test' unless ENV['GR_SDK_RUN_INTEGRATION'] == '1'
6
+
7
+ client = GeniusReferrals::Client.new(auth_token: ENV.fetch('GR_API_TOKEN'))
8
+ result = client.authentications.test
9
+
10
+ assert_kind_of(Hash, result.raw)
11
+ assert(result.raw.key?('code'))
12
+ end
13
+ end
@@ -0,0 +1,101 @@
1
+ require_relative 'test_helper'
2
+
3
+ class ResourcesTest < Minitest::Test
4
+ def test_advocate_create_uses_wrapper
5
+ transport = FakeTransport.new([response(201, '{"code":201,"data":{"token":"a1"}}')])
6
+ client = GeniusReferrals::Client.new(
7
+ auth_token: 'token',
8
+ transport: transport,
9
+ base_url: 'https://api.example.test'
10
+ )
11
+
12
+ client.advocates.create('acct', { email: 'a@example.com' })
13
+
14
+ request = transport.requests.first
15
+ assert_equal('POST', request[:method])
16
+ assert_equal('https://api.example.test/accounts/acct/advocates', request[:url])
17
+ assert_equal({ 'advocate' => { 'email' => 'a@example.com' } }, JSON.parse(request[:body]))
18
+ end
19
+
20
+ def test_advocate_delete_all_sends_collection_delete_with_optional_tokens
21
+ transport = FakeTransport.new([response(204)])
22
+ client = GeniusReferrals::Client.new(
23
+ auth_token: 'token',
24
+ transport: transport,
25
+ base_url: 'https://api.example.test'
26
+ )
27
+
28
+ result = client.advocates.delete_all('acct', advocates: %w[a1 a2])
29
+
30
+ request = transport.requests.first
31
+ assert_nil(result.data)
32
+ assert_equal('DELETE', request[:method])
33
+ assert_equal('https://api.example.test/accounts/acct/advocates?advocates=a1%2Ca2', request[:url])
34
+ assert_equal('token', request[:headers]['X-Auth-Token'])
35
+ assert_nil(request[:body])
36
+ end
37
+
38
+ def test_bonus_patch_is_unwrapped
39
+ transport = FakeTransport.new([response(204)])
40
+ client = GeniusReferrals::Client.new(auth_token: 'token', transport: transport)
41
+
42
+ result = client.bonuses.patch('acct', 123, status: 'approved')
43
+
44
+ assert_nil(result.data)
45
+ assert_equal('PATCH', transport.requests.first[:method])
46
+ assert_equal({ 'status' => 'approved' }, JSON.parse(transport.requests.first[:body]))
47
+ end
48
+
49
+ def test_list_query_preserves_page_and_limit
50
+ transport = FakeTransport.new([
51
+ response(200, '{"code":200,"data":{"page":2,"limit":25,"total":0,"results":[]}}')
52
+ ])
53
+ client = GeniusReferrals::Client.new(
54
+ auth_token: 'token',
55
+ transport: transport,
56
+ base_url: 'https://api.example.test'
57
+ )
58
+
59
+ page = client.accounts.list(page: 2, limit: 25, filter: 'status=active')
60
+
61
+ assert_equal(2, page.page)
62
+ assert_includes(transport.requests.first[:url], 'page=2')
63
+ assert_includes(transport.requests.first[:url], 'limit=25')
64
+ assert_includes(transport.requests.first[:url], 'filter=status%3Dactive')
65
+ end
66
+
67
+ def test_report_accepts_live_and_sdk_parameter_names
68
+ transport = FakeTransport.new([response(200, '{"code":200,"data":[]}')])
69
+ client = GeniusReferrals::Client.new(
70
+ auth_token: 'token',
71
+ transport: transport,
72
+ base_url: 'https://api.example.test'
73
+ )
74
+
75
+ client.reports.revenue(
76
+ client_account_slug: 'acct',
77
+ start_date: '2026-01-01',
78
+ end_date: '2026-01-31'
79
+ )
80
+
81
+ assert_includes(transport.requests.first[:url], 'client_account_slug=acct')
82
+ end
83
+
84
+ def test_product_variant_price_patch_uses_unwrapped_amount
85
+ transport = FakeTransport.new([response(204)])
86
+ client = GeniusReferrals::Client.new(auth_token: 'token', transport: transport)
87
+
88
+ client.products.patch_variant_price('acct', 'sku', 123, 'USD', amount: 19.95)
89
+
90
+ assert_equal({ 'amount' => 19.95 }, JSON.parse(transport.requests.first[:body]))
91
+ end
92
+
93
+ def test_bonus_tag_uses_nested_bonus_tag_shape
94
+ transport = FakeTransport.new([response(201, '{"code":201,"data":{}}')])
95
+ client = GeniusReferrals::Client.new(auth_token: 'token', transport: transport)
96
+
97
+ client.bonuses.add_tag('acct', 123, 'vip')
98
+
99
+ assert_equal({ 'bonus' => { 'tag' => { 'slug' => 'vip' } } }, JSON.parse(transport.requests.first[:body]))
100
+ end
101
+ end
@@ -0,0 +1,32 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __dir__)
2
+
3
+ require 'json'
4
+ require 'minitest/autorun'
5
+ require 'genius_referrals'
6
+
7
+ class FakeTransport
8
+ attr_reader :requests
9
+
10
+ def initialize(responses)
11
+ @responses = responses.dup
12
+ @requests = []
13
+ end
14
+
15
+ def request(method, url, headers:, body:, timeout:)
16
+ @requests << {
17
+ method: method,
18
+ url: url,
19
+ headers: headers,
20
+ body: body,
21
+ timeout: timeout
22
+ }
23
+ response = @responses.shift
24
+ raise response if response.is_a?(StandardError)
25
+
26
+ response
27
+ end
28
+ end
29
+
30
+ def response(status_code, body = '')
31
+ GeniusReferrals::TransportResponse.new(status_code: status_code, headers: {}, body: body)
32
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: genius_referrals
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Genius Referrals
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-08-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.20'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.20'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.1'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.1'
41
+ description: A modern Ruby SDK for the Genius Referrals public API.
42
+ email: support@geniusreferrals.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - LICENSE
48
+ - README.md
49
+ - lib/genius_referrals.rb
50
+ - lib/genius_referrals/client.rb
51
+ - lib/genius_referrals/errors.rb
52
+ - lib/genius_referrals/resource.rb
53
+ - lib/genius_referrals/resources.rb
54
+ - lib/genius_referrals/resources/accounts.rb
55
+ - lib/genius_referrals/resources/advocates.rb
56
+ - lib/genius_referrals/resources/authentications.rb
57
+ - lib/genius_referrals/resources/bonuses.rb
58
+ - lib/genius_referrals/resources/campaigns.rb
59
+ - lib/genius_referrals/resources/products.rb
60
+ - lib/genius_referrals/resources/redemption_requests.rb
61
+ - lib/genius_referrals/resources/referrals.rb
62
+ - lib/genius_referrals/resources/reports.rb
63
+ - lib/genius_referrals/resources/root.rb
64
+ - lib/genius_referrals/resources/tags.rb
65
+ - lib/genius_referrals/resources/utilities.rb
66
+ - lib/genius_referrals/resources/vouchers.rb
67
+ - lib/genius_referrals/resources/widgets_packages.rb
68
+ - lib/genius_referrals/response.rb
69
+ - lib/genius_referrals/transport.rb
70
+ - lib/genius_referrals/version.rb
71
+ - test/client_test.rb
72
+ - test/integration/authentication_test.rb
73
+ - test/resources_test.rb
74
+ - test/test_helper.rb
75
+ homepage: https://www.geniusreferrals.com
76
+ licenses:
77
+ - MIT
78
+ metadata: {}
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '3.0'
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ requirements: []
94
+ rubygems_version: 3.5.22
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: Official Ruby SDK for the Genius Referrals public API
98
+ test_files: []