mailerlite-ruby 1.0.6 → 2.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.
- checksums.yaml +4 -4
- data/.envrc +2 -0
- data/.github/workflows/main.yml +4 -4
- data/.github/workflows/publish_gem.yml +5 -5
- data/.gitignore +2 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +21 -0
- data/README.md +583 -1
- data/flake.lock +27 -0
- data/flake.nix +25 -0
- data/lefthook.yml +14 -0
- data/lib/mailerlite/api_response.rb +24 -0
- data/lib/mailerlite/automations/automation_emails.rb +57 -0
- data/lib/mailerlite/automations/automation_reports.rb +114 -0
- data/lib/mailerlite/automations/automation_steps.rb +105 -0
- data/lib/mailerlite/automations/automation_templates.rb +34 -0
- data/lib/mailerlite/automations/automation_triggers.rb +62 -0
- data/lib/mailerlite/automations/automations.rb +177 -8
- data/lib/mailerlite/brand_styles/brand_styles.rb +24 -0
- data/lib/mailerlite/campaigns/campaigns.rb +33 -1
- data/lib/mailerlite/client.rb +15 -5
- data/lib/mailerlite/ecommerce/ecommerce.rb +316 -0
- data/lib/mailerlite/emails/emails.rb +306 -0
- data/lib/mailerlite/errors.rb +88 -0
- data/lib/mailerlite/files/files.rb +45 -0
- data/lib/mailerlite/groups/groups.rb +16 -8
- data/lib/mailerlite/params.rb +58 -0
- data/lib/mailerlite/query_params.rb +33 -0
- data/lib/mailerlite/request.rb +24 -0
- data/lib/mailerlite/segments/segments.rb +10 -10
- data/lib/mailerlite/subscribers/subscribers.rb +63 -8
- data/lib/mailerlite/version.rb +1 -1
- data/lib/mailerlite.rb +14 -0
- data/mailerlite-ruby.gemspec +8 -8
- data/spec/api_response_rspec.rb +140 -0
- data/spec/automation_emails_rspec.rb +40 -0
- data/spec/automation_reports_rspec.rb +92 -0
- data/spec/automation_steps_rspec.rb +76 -0
- data/spec/automation_templates_rspec.rb +24 -0
- data/spec/automation_triggers_rspec.rb +48 -0
- data/spec/automations_rspec.rb +143 -0
- data/spec/brand_styles_rspec.rb +49 -0
- data/spec/campaigns_rspec.rb +42 -0
- data/spec/ecommerce_rspec.rb +141 -0
- data/spec/emails_rspec.rb +692 -0
- data/spec/files_rspec.rb +90 -0
- data/spec/fixtures/brand_styles.json +50 -0
- data/spec/fixtures/campaign_simple_editor.json +24 -0
- data/spec/fixtures/email.json +81 -0
- data/spec/fixtures/email_block.json +12 -0
- data/spec/fixtures/email_block_created.json +12 -0
- data/spec/fixtures/email_block_created_rejected.json +15 -0
- data/spec/fixtures/email_block_moved.json +12 -0
- data/spec/fixtures/email_block_updated.json +9 -0
- data/spec/fixtures/email_block_updated_rejected.json +13 -0
- data/spec/fixtures/email_block_validation_invalid.json +5 -0
- data/spec/fixtures/email_etag.json +1 -0
- data/spec/fixtures/email_html.json +1 -0
- data/spec/fixtures/email_settings.json +111 -0
- data/spec/fixtures/email_settings_validation.json +9 -0
- data/spec/fixtures/email_template.json +1 -0
- data/spec/fixtures/email_version.json +1 -0
- data/spec/fixtures/email_versions.json +26 -0
- data/spec/fixtures/files.json +44 -0
- data/spec/groups_rspec.rb +21 -0
- data/spec/params_rspec.rb +126 -0
- data/spec/request_rspec.rb +139 -0
- data/spec/segments_rspec.rb +39 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/subscribers_rspec.rb +57 -0
- data/spec/support/json_fixture.rb +27 -0
- data/spec/support/request_stub.rb +25 -0
- metadata +69 -22
- /data/{renovate.json → .github/renovate.json} +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe MailerLite::Params do
|
|
6
|
+
describe 'error classes' do
|
|
7
|
+
it 'makes the client-side errors subclasses of ArgumentError' do
|
|
8
|
+
expect(MailerLite::EmptyIdError.ancestors).to include(ArgumentError)
|
|
9
|
+
expect(MailerLite::InvalidIdError.ancestors).to include(ArgumentError)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'has a base error class for the SDK' do
|
|
13
|
+
expect(MailerLite::Error.ancestors).to include(StandardError)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '.path_id' do
|
|
18
|
+
it 'returns a String of digits as it is' do
|
|
19
|
+
expect(described_class.path_id('123', 'email_id')).to eq '123'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'returns an Integer as a String' do
|
|
23
|
+
expect(described_class.path_id(123, 'email_id')).to eq '123'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'raises EmptyIdError for an empty String and for nil' do
|
|
27
|
+
expect { described_class.path_id('', 'email_id') }.to raise_error(MailerLite::EmptyIdError, /`email_id`/)
|
|
28
|
+
expect { described_class.path_id(nil, 'email_id') }.to raise_error(MailerLite::EmptyIdError, /`email_id`/)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
['123/html', '123%2Fhtml', "123\n", ' 123', '²', '١٢', '1.5', '-5'].each do |value|
|
|
32
|
+
it "raises InvalidIdError for #{value.inspect}" do
|
|
33
|
+
expect { described_class.path_id(value, 'block_id') }.to raise_error(MailerLite::InvalidIdError, /`block_id`/)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'raises InvalidIdError for a negative Integer' do
|
|
38
|
+
expect { described_class.path_id(-5, 'email_id') }.to raise_error(MailerLite::InvalidIdError)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'raises InvalidIdError for a String with invalid bytes' do
|
|
42
|
+
value = (+"12\xFF").force_encoding('UTF-8')
|
|
43
|
+
expect { described_class.path_id(value, 'email_id') }.to raise_error(MailerLite::InvalidIdError)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'raises InvalidIdError for a String in an encoding that is not ASCII compatible' do
|
|
47
|
+
expect { described_class.path_id('12'.encode('UTF-16LE'), 'email_id') }.to raise_error(MailerLite::InvalidIdError)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
[true, false, 1.5, :'123', ['1']].each do |value|
|
|
51
|
+
it "raises TypeError for #{value.inspect}" do
|
|
52
|
+
expect { described_class.path_id(value, 'email_id') }.to raise_error(TypeError, /`email_id`/)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe '.require_hash' do
|
|
58
|
+
it 'returns the Hash' do
|
|
59
|
+
value = { 'a' => 1 }
|
|
60
|
+
expect(described_class.require_hash(value, 'settings')).to be value
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
[nil, 'image', [], 1].each do |value|
|
|
64
|
+
it "raises TypeError for #{value.inspect}" do
|
|
65
|
+
expect { described_class.require_hash(value, 'filter') }.to raise_error(TypeError, /`filter`/)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
describe '.settings' do
|
|
71
|
+
it 'returns the settings' do
|
|
72
|
+
settings = { brand: { mode: 'account' } }
|
|
73
|
+
expect(described_class.settings(settings)).to eq settings
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it 'raises ArgumentError for an etag key' do
|
|
77
|
+
expect { described_class.settings({ etag: '1' }) }.to raise_error(ArgumentError, /etag/)
|
|
78
|
+
expect { described_class.settings({ 'etag' => '1' }) }.to raise_error(ArgumentError, /etag/)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'raises TypeError for settings that are not a Hash' do
|
|
82
|
+
expect { described_class.settings(nil) }.to raise_error(TypeError, /`settings`/)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe '.query' do
|
|
87
|
+
it 'returns an empty Hash with no values' do
|
|
88
|
+
expect(described_class.query).to eq({})
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
it 'adds a filter[key] value for each filter key' do
|
|
92
|
+
expect(described_class.query(filter: { name: 'summer sale', type: 'image' }, sort: '-size'))
|
|
93
|
+
.to eq('filter[name]' => 'summer sale', 'filter[type]' => 'image', 'sort' => '-size')
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'drops nil values, also in the filter' do
|
|
97
|
+
expect(described_class.query(filter: { name: nil, type: 'pdf' }, page: nil, limit: 10))
|
|
98
|
+
.to eq('filter[type]' => 'pdf', 'limit' => 10)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'keeps empty String values' do
|
|
102
|
+
expect(described_class.query(filter: { type: '' }, sort: ''))
|
|
103
|
+
.to eq('filter[type]' => '', 'sort' => '')
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'raises TypeError for a filter that is not a Hash' do
|
|
107
|
+
expect { described_class.query(filter: 'image') }.to raise_error(TypeError, /`filter`/)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
describe '.idempotency_headers' do
|
|
112
|
+
it 'returns no headers without a key' do
|
|
113
|
+
expect(described_class.idempotency_headers(nil)).to eq({})
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'returns the header for a valid key' do
|
|
117
|
+
expect(described_class.idempotency_headers('key-1')).to eq('Idempotency-Key' => 'key-1')
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
['', 'a' * 256, 'é', ' k', 'k ', 1].each do |key|
|
|
121
|
+
it "returns the header with the key as given for #{key.inspect[0, 20]}" do
|
|
122
|
+
expect(described_class.idempotency_headers(key)).to eq('Idempotency-Key' => key)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'webmock/rspec'
|
|
5
|
+
require 'json'
|
|
6
|
+
require_relative 'support/json_fixture'
|
|
7
|
+
|
|
8
|
+
RSpec.describe MailerLite::Request do
|
|
9
|
+
include_context 'with the dummy API token'
|
|
10
|
+
|
|
11
|
+
let(:client) { MailerLite::Client.new }
|
|
12
|
+
let(:api) { MAILERLITE_API_URL }
|
|
13
|
+
|
|
14
|
+
# WebMock normalizes the URI (for example `+` becomes `%20`), so keep the raw URI that http.rb sends.
|
|
15
|
+
def capture_raw_uris
|
|
16
|
+
uris = []
|
|
17
|
+
allow_any_instance_of(HTTP::Client).to receive(:perform).and_wrap_original do |method, request, options|
|
|
18
|
+
uris << request.uri.to_s
|
|
19
|
+
method.call(request, options)
|
|
20
|
+
end
|
|
21
|
+
uris
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'sends a PATCH request with the JSON body' do
|
|
25
|
+
stub = stub_request(:patch, "#{api}/emails/1/settings")
|
|
26
|
+
.with { |req| req.body == '{"layout":{"alignment":"left"},"etag":"42"}' }
|
|
27
|
+
.to_return(status: 200, body: '{}')
|
|
28
|
+
|
|
29
|
+
described_class.call(client, :patch, '/emails/1/settings', json: { 'layout' => { 'alignment' => 'left' }, 'etag' => '42' })
|
|
30
|
+
|
|
31
|
+
expect(stub).to have_been_requested
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'sends a DELETE request with the JSON body' do
|
|
35
|
+
stub = stub_request(:delete, "#{api}/emails/1/blocks/2")
|
|
36
|
+
.with { |req| req.body == '{"etag":"42"}' }
|
|
37
|
+
.to_return(status: 200, body: '{"etag":"43"}')
|
|
38
|
+
|
|
39
|
+
described_class.call(client, :delete, '/emails/1/blocks/2', json: { 'etag' => '42' })
|
|
40
|
+
|
|
41
|
+
expect(stub).to have_been_requested
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'sends no body, not null, when there is no body' do
|
|
45
|
+
stub = stub_request(:post, "#{api}/emails/1/versions")
|
|
46
|
+
.with { |req| req.body == '' }
|
|
47
|
+
.to_return(status: 201, body: '{}')
|
|
48
|
+
|
|
49
|
+
described_class.call(client, :post, '/emails/1/versions')
|
|
50
|
+
|
|
51
|
+
expect(stub).to have_been_requested
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'sends the query and encodes a space as +' do
|
|
55
|
+
uris = capture_raw_uris
|
|
56
|
+
stub = stub_request(:get, "#{api}/files")
|
|
57
|
+
.with(query: { 'filter[name]' => 'summer sale', 'filter[type]' => '', 'page' => '2' })
|
|
58
|
+
.to_return(status: 200, body: '{}')
|
|
59
|
+
|
|
60
|
+
described_class.call(client, :get, '/files', query: { 'filter[name]' => 'summer sale', 'filter[type]' => '', 'page' => 2 })
|
|
61
|
+
|
|
62
|
+
expect(stub).to have_been_requested
|
|
63
|
+
expect(uris).to eq ["#{api}/files?filter%5Bname%5D=summer+sale&filter%5Btype%5D=&page=2"]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it 'sends no query for an empty query' do
|
|
67
|
+
uris = capture_raw_uris
|
|
68
|
+
stub_request(:get, "#{api}/emails/1/versions").to_return(status: 200, body: '{}')
|
|
69
|
+
|
|
70
|
+
described_class.call(client, :get, '/emails/1/versions', query: {})
|
|
71
|
+
|
|
72
|
+
expect(uris).to eq ["#{api}/emails/1/versions"]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'sends the Authorization header and no Idempotency-Key header without a key' do
|
|
76
|
+
stub = stub_request(:post, "#{api}/emails/1/versions")
|
|
77
|
+
.with(headers: { 'Authorization' => 'Bearer dummy_token' })
|
|
78
|
+
.with { |req| !req.headers.key?('Idempotency-Key') }
|
|
79
|
+
.to_return(status: 201, body: '{}')
|
|
80
|
+
|
|
81
|
+
described_class.call(client, :post, '/emails/1/versions')
|
|
82
|
+
|
|
83
|
+
expect(stub).to have_been_requested
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'sends the Idempotency-Key header with a key' do
|
|
87
|
+
stub = stub_request(:post, "#{api}/emails/1/versions")
|
|
88
|
+
.with(headers: { 'Idempotency-Key' => 'key-1' })
|
|
89
|
+
.to_return(status: 201, body: '{}')
|
|
90
|
+
|
|
91
|
+
described_class.call(client, :post, '/emails/1/versions', idempotency_key: 'key-1')
|
|
92
|
+
|
|
93
|
+
expect(stub).to have_been_requested
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it 'sends a key with a space at the end as given' do
|
|
97
|
+
stub = stub_request(:post, "#{api}/emails/1/versions")
|
|
98
|
+
.with { |req| req.headers['Idempotency-Key'] == 'a b ' }
|
|
99
|
+
.to_return(status: 400, body: '{"message":"The idempotency key is not valid."}')
|
|
100
|
+
|
|
101
|
+
response = described_class.call(client, :post, '/emails/1/versions', idempotency_key: 'a b ')
|
|
102
|
+
|
|
103
|
+
expect(stub).to have_been_requested
|
|
104
|
+
expect(response.api_error.status).to eq 400
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'returns the HTTP::Response with the ApiResponse methods' do
|
|
108
|
+
stub_request(:get, "#{api}/emails/1").to_return(status: 409, body: '{"message":"Conflict.","code":"version_conflict","etag":"9"}')
|
|
109
|
+
|
|
110
|
+
response = described_class.call(client, :get, '/emails/1')
|
|
111
|
+
|
|
112
|
+
expect(response).to be_a(HTTP::Response)
|
|
113
|
+
expect(response.code).to eq 409
|
|
114
|
+
expect(response.api_error.etag).to eq '9'
|
|
115
|
+
expect(response.idempotent_replay?).to be(false)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'reads the base URL when the request runs' do
|
|
119
|
+
original = MAILERLITE_API_URL
|
|
120
|
+
Object.send(:remove_const, :MAILERLITE_API_URL)
|
|
121
|
+
Object.const_set(:MAILERLITE_API_URL, 'http://api.example.com/api')
|
|
122
|
+
stub = stub_request(:get, 'http://api.example.com/api/emails/1').to_return(status: 200, body: '{}')
|
|
123
|
+
|
|
124
|
+
described_class.call(client, :get, '/emails/1')
|
|
125
|
+
|
|
126
|
+
expect(stub).to have_been_requested
|
|
127
|
+
ensure
|
|
128
|
+
Object.send(:remove_const, :MAILERLITE_API_URL)
|
|
129
|
+
Object.const_set(:MAILERLITE_API_URL, original)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'does not change the Hashes of the caller' do
|
|
133
|
+
body = { 'etag' => '42' }.freeze
|
|
134
|
+
query = { 'page' => 1 }.freeze
|
|
135
|
+
stub_request(:delete, "#{api}/emails/1/blocks/2?page=1").to_return(status: 200, body: '{}')
|
|
136
|
+
|
|
137
|
+
expect { described_class.call(client, :delete, '/emails/1/blocks/2', json: body, query: query) }.not_to raise_error
|
|
138
|
+
end
|
|
139
|
+
end
|
data/spec/segments_rspec.rb
CHANGED
|
@@ -67,4 +67,43 @@ RSpec.describe MailerLite::Segments do
|
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
|
+
|
|
71
|
+
describe '#list query string' do
|
|
72
|
+
it 'sends the params in the query string with no body' do
|
|
73
|
+
stub = stub_api(:get, '/segments', query: { 'limit' => '10', 'page' => '2', 'sort' => '-created_at' })
|
|
74
|
+
|
|
75
|
+
segments.list(limit: 10, page: 2, sort: '-created_at')
|
|
76
|
+
|
|
77
|
+
expect(stub).to have_been_requested
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe '#update with filter' do
|
|
82
|
+
it 'sends name and filter' do
|
|
83
|
+
rules = [[{ 'operator' => 'in_any', 'args' => %w[groups 1] }]]
|
|
84
|
+
stub = stub_api(:put, '/segments/1', body: { 'name' => 'VIP', 'filter' => rules })
|
|
85
|
+
|
|
86
|
+
segments.update(segment_id: 1, name: 'VIP', filter: rules)
|
|
87
|
+
|
|
88
|
+
expect(stub).to have_been_requested
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe '#get_subscribers cursor' do
|
|
93
|
+
it 'sends cursor' do
|
|
94
|
+
stub = stub_api(:get, '/segments/1/subscribers', query: { 'filter[status]' => 'active', 'limit' => '10', 'cursor' => 'abc' })
|
|
95
|
+
|
|
96
|
+
segments.get_subscribers(segment_id: 1, filter: { status: 'active' }, limit: 10, cursor: 'abc')
|
|
97
|
+
|
|
98
|
+
expect(stub).to have_been_requested
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'still sends after' do
|
|
102
|
+
stub = stub_api(:get, '/segments/1/subscribers', query: { 'after' => '123' })
|
|
103
|
+
|
|
104
|
+
segments.get_subscribers(segment_id: 1, after: 123)
|
|
105
|
+
|
|
106
|
+
expect(stub).to have_been_requested
|
|
107
|
+
end
|
|
108
|
+
end
|
|
70
109
|
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'simplecov'
|
|
4
4
|
SimpleCov.start
|
|
5
5
|
require 'mailerlite'
|
|
6
|
+
require_relative 'support/request_stub'
|
|
6
7
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
|
7
8
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
|
8
9
|
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
data/spec/subscribers_rspec.rb
CHANGED
|
@@ -105,4 +105,61 @@ RSpec.describe MailerLite::Subscribers do
|
|
|
105
105
|
end
|
|
106
106
|
end
|
|
107
107
|
end
|
|
108
|
+
|
|
109
|
+
describe '#fetch with every filter' do
|
|
110
|
+
it 'sends the documented query params' do
|
|
111
|
+
stub = stub_api(:get, '/subscribers', query: {
|
|
112
|
+
'filter[status]' => 'active', 'filter[group]' => '1,2', 'filter[automation]' => '3', 'filter[form]' => '4',
|
|
113
|
+
'filter[site]' => '5', 'filter[page]' => '6', 'filter[import]' => '7', 'filter[post]' => '8',
|
|
114
|
+
'limit' => '10', 'cursor' => 'abc', 'sort' => '-id', 'include' => 'groups',
|
|
115
|
+
'start_date' => '2026-01-01', 'end_date' => '2026-01-31', 'with_fields' => '1'
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
subscribers.fetch(
|
|
119
|
+
filter: { status: 'active', group: '1,2', automation: '3', form: '4', site: '5', page: '6', import: '7', post: '8' },
|
|
120
|
+
limit: 10, cursor: 'abc', sort: '-id', include: 'groups', start_date: '2026-01-01', end_date: '2026-01-31', with_fields: true
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
expect(stub).to have_been_requested
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'sends Array filter values comma-separated' do
|
|
127
|
+
stub = stub_api(:get, '/subscribers', query: { 'filter[group]' => '1,2' })
|
|
128
|
+
|
|
129
|
+
subscribers.fetch(filter: { group: %w[1 2] })
|
|
130
|
+
|
|
131
|
+
expect(stub).to have_been_requested
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
describe '#fetch filter order' do
|
|
136
|
+
it 'sends the filter keys in the order the caller gave them' do
|
|
137
|
+
uris = capture_raw_uris
|
|
138
|
+
stub_api(:get, '/subscribers', query: { 'filter[form]' => '4', 'filter[status]' => 'active', 'filter[group]' => '1', 'sort' => 'signup_date' })
|
|
139
|
+
|
|
140
|
+
subscribers.fetch(filter: { form: '4', status: 'active', group: '1' }, sort: 'signup_date')
|
|
141
|
+
|
|
142
|
+
expect(uris).to eq ["#{MAILERLITE_API_URL}/subscribers?filter%5Bform%5D=4&filter%5Bstatus%5D=active&filter%5Bgroup%5D=1&sort=signup_date"]
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
describe '#create with the new body fields' do
|
|
147
|
+
it 'sends resubscribe, double_optin and notes' do
|
|
148
|
+
stub = stub_api(:post, '/subscribers', body: { 'email' => 'a@b.com', 'resubscribe' => false, 'double_optin' => true, 'notes' => ['VIP'] })
|
|
149
|
+
|
|
150
|
+
subscribers.create(email: 'a@b.com', resubscribe: false, double_optin: true, notes: ['VIP'])
|
|
151
|
+
|
|
152
|
+
expect(stub).to have_been_requested
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
describe '#activity_log' do
|
|
157
|
+
it 'sends GET /subscribers/{subscriber}/activity-log with the query' do
|
|
158
|
+
stub = stub_api(:get, '/subscribers/1/activity-log', query: { 'filter[log_name]' => 'email_opened', 'limit' => '10', 'page' => '2' })
|
|
159
|
+
|
|
160
|
+
subscribers.activity_log(subscriber_id: 1, filter: { log_name: 'email_opened' }, limit: 10, page: 2)
|
|
161
|
+
|
|
162
|
+
expect(stub).to have_been_requested
|
|
163
|
+
end
|
|
164
|
+
end
|
|
108
165
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
# Reads the JSON fixtures in `spec/fixtures` for the specs that use WebMock.
|
|
6
|
+
module JsonFixture
|
|
7
|
+
FIXTURE_DIR = File.expand_path('../fixtures', __dir__)
|
|
8
|
+
|
|
9
|
+
def load_fixture(name)
|
|
10
|
+
JSON.parse(File.read(File.join(FIXTURE_DIR, "#{name}.json")))
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if defined?(RSpec)
|
|
15
|
+
RSpec.configure { |config| config.include JsonFixture }
|
|
16
|
+
|
|
17
|
+
# The specs do not depend on the token in the shell.
|
|
18
|
+
RSpec.shared_context 'with the dummy API token' do
|
|
19
|
+
around do |example|
|
|
20
|
+
old_token = ENV.fetch('MAILERLITE_API_TOKEN', nil)
|
|
21
|
+
ENV['MAILERLITE_API_TOKEN'] = 'dummy_token'
|
|
22
|
+
example.run
|
|
23
|
+
ensure
|
|
24
|
+
ENV['MAILERLITE_API_TOKEN'] = old_token
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'webmock/rspec'
|
|
4
|
+
|
|
5
|
+
module RequestStub
|
|
6
|
+
def stub_api(method, path, query: nil, body: nil)
|
|
7
|
+
pattern = {}
|
|
8
|
+
pattern[:query] = query if query
|
|
9
|
+
pattern[:body] = body if body
|
|
10
|
+
stub_request(method, "#{MAILERLITE_API_URL}#{path}")
|
|
11
|
+
.with(**pattern) { |request| body || request.body.to_s.empty? }
|
|
12
|
+
.to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def capture_raw_uris
|
|
16
|
+
uris = []
|
|
17
|
+
allow_any_instance_of(HTTP::Client).to receive(:perform).and_wrap_original do |method, request, options|
|
|
18
|
+
uris << request.uri.to_s
|
|
19
|
+
method.call(request, options)
|
|
20
|
+
end
|
|
21
|
+
uris
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
RSpec.configure { |config| config.include RequestStub }
|
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mailerlite-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nikola Milojević
|
|
8
8
|
- Ahsan Gondal
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: bundler
|
|
@@ -17,98 +16,98 @@ dependencies:
|
|
|
17
16
|
requirements:
|
|
18
17
|
- - "~>"
|
|
19
18
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: 2.
|
|
19
|
+
version: '2.5'
|
|
21
20
|
type: :development
|
|
22
21
|
prerelease: false
|
|
23
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
23
|
requirements:
|
|
25
24
|
- - "~>"
|
|
26
25
|
- !ruby/object:Gem::Version
|
|
27
|
-
version: 2.
|
|
26
|
+
version: '2.5'
|
|
28
27
|
- !ruby/object:Gem::Dependency
|
|
29
28
|
name: rake
|
|
30
29
|
requirement: !ruby/object:Gem::Requirement
|
|
31
30
|
requirements:
|
|
32
31
|
- - "~>"
|
|
33
32
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: '13.
|
|
33
|
+
version: '13.2'
|
|
35
34
|
type: :development
|
|
36
35
|
prerelease: false
|
|
37
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
37
|
requirements:
|
|
39
38
|
- - "~>"
|
|
40
39
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: '13.
|
|
40
|
+
version: '13.2'
|
|
42
41
|
- !ruby/object:Gem::Dependency
|
|
43
42
|
name: rubocop
|
|
44
43
|
requirement: !ruby/object:Gem::Requirement
|
|
45
44
|
requirements:
|
|
46
45
|
- - "~>"
|
|
47
46
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: '1.
|
|
47
|
+
version: '1.69'
|
|
49
48
|
type: :development
|
|
50
49
|
prerelease: false
|
|
51
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
52
51
|
requirements:
|
|
53
52
|
- - "~>"
|
|
54
53
|
- !ruby/object:Gem::Version
|
|
55
|
-
version: '1.
|
|
54
|
+
version: '1.69'
|
|
56
55
|
- !ruby/object:Gem::Dependency
|
|
57
56
|
name: dotenv
|
|
58
57
|
requirement: !ruby/object:Gem::Requirement
|
|
59
58
|
requirements:
|
|
60
59
|
- - "~>"
|
|
61
60
|
- !ruby/object:Gem::Version
|
|
62
|
-
version: '
|
|
61
|
+
version: '3.1'
|
|
63
62
|
type: :runtime
|
|
64
63
|
prerelease: false
|
|
65
64
|
version_requirements: !ruby/object:Gem::Requirement
|
|
66
65
|
requirements:
|
|
67
66
|
- - "~>"
|
|
68
67
|
- !ruby/object:Gem::Version
|
|
69
|
-
version: '
|
|
68
|
+
version: '3.1'
|
|
70
69
|
- !ruby/object:Gem::Dependency
|
|
71
70
|
name: http
|
|
72
71
|
requirement: !ruby/object:Gem::Requirement
|
|
73
72
|
requirements:
|
|
74
73
|
- - "~>"
|
|
75
74
|
- !ruby/object:Gem::Version
|
|
76
|
-
version: '5.
|
|
75
|
+
version: '5.2'
|
|
77
76
|
type: :runtime
|
|
78
77
|
prerelease: false
|
|
79
78
|
version_requirements: !ruby/object:Gem::Requirement
|
|
80
79
|
requirements:
|
|
81
80
|
- - "~>"
|
|
82
81
|
- !ruby/object:Gem::Version
|
|
83
|
-
version: '5.
|
|
82
|
+
version: '5.2'
|
|
84
83
|
- !ruby/object:Gem::Dependency
|
|
85
84
|
name: json
|
|
86
85
|
requirement: !ruby/object:Gem::Requirement
|
|
87
86
|
requirements:
|
|
88
87
|
- - "~>"
|
|
89
88
|
- !ruby/object:Gem::Version
|
|
90
|
-
version: '2.
|
|
89
|
+
version: '2.9'
|
|
91
90
|
type: :runtime
|
|
92
91
|
prerelease: false
|
|
93
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
94
93
|
requirements:
|
|
95
94
|
- - "~>"
|
|
96
95
|
- !ruby/object:Gem::Version
|
|
97
|
-
version: '2.
|
|
96
|
+
version: '2.9'
|
|
98
97
|
- !ruby/object:Gem::Dependency
|
|
99
98
|
name: uri
|
|
100
99
|
requirement: !ruby/object:Gem::Requirement
|
|
101
100
|
requirements:
|
|
102
101
|
- - "~>"
|
|
103
102
|
- !ruby/object:Gem::Version
|
|
104
|
-
version:
|
|
103
|
+
version: '1.0'
|
|
105
104
|
type: :runtime
|
|
106
105
|
prerelease: false
|
|
107
106
|
version_requirements: !ruby/object:Gem::Requirement
|
|
108
107
|
requirements:
|
|
109
108
|
- - "~>"
|
|
110
109
|
- !ruby/object:Gem::Version
|
|
111
|
-
version:
|
|
110
|
+
version: '1.0'
|
|
112
111
|
- !ruby/object:Gem::Dependency
|
|
113
112
|
name: rspec
|
|
114
113
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -190,6 +189,8 @@ extensions: []
|
|
|
190
189
|
extra_rdoc_files: []
|
|
191
190
|
files:
|
|
192
191
|
- ".env.example"
|
|
192
|
+
- ".envrc"
|
|
193
|
+
- ".github/renovate.json"
|
|
193
194
|
- ".github/workflows/main.yml"
|
|
194
195
|
- ".github/workflows/publish_gem.yml"
|
|
195
196
|
- ".gitignore"
|
|
@@ -247,31 +248,79 @@ files:
|
|
|
247
248
|
- fixtures/webhooks/get.yml
|
|
248
249
|
- fixtures/webhooks/list.yml
|
|
249
250
|
- fixtures/webhooks/update.yml
|
|
251
|
+
- flake.lock
|
|
252
|
+
- flake.nix
|
|
253
|
+
- lefthook.yml
|
|
250
254
|
- lib/mailerlite-ruby.rb
|
|
251
255
|
- lib/mailerlite.rb
|
|
256
|
+
- lib/mailerlite/api_response.rb
|
|
257
|
+
- lib/mailerlite/automations/automation_emails.rb
|
|
258
|
+
- lib/mailerlite/automations/automation_reports.rb
|
|
259
|
+
- lib/mailerlite/automations/automation_steps.rb
|
|
260
|
+
- lib/mailerlite/automations/automation_templates.rb
|
|
261
|
+
- lib/mailerlite/automations/automation_triggers.rb
|
|
252
262
|
- lib/mailerlite/automations/automations.rb
|
|
253
263
|
- lib/mailerlite/batch/batch.rb
|
|
264
|
+
- lib/mailerlite/brand_styles/brand_styles.rb
|
|
254
265
|
- lib/mailerlite/campaigns/campaigns.rb
|
|
255
266
|
- lib/mailerlite/client.rb
|
|
267
|
+
- lib/mailerlite/ecommerce/ecommerce.rb
|
|
268
|
+
- lib/mailerlite/emails/emails.rb
|
|
269
|
+
- lib/mailerlite/errors.rb
|
|
256
270
|
- lib/mailerlite/fields/fields.rb
|
|
271
|
+
- lib/mailerlite/files/files.rb
|
|
257
272
|
- lib/mailerlite/forms/forms.rb
|
|
258
273
|
- lib/mailerlite/groups/groups.rb
|
|
274
|
+
- lib/mailerlite/params.rb
|
|
275
|
+
- lib/mailerlite/query_params.rb
|
|
276
|
+
- lib/mailerlite/request.rb
|
|
259
277
|
- lib/mailerlite/segments/segments.rb
|
|
260
278
|
- lib/mailerlite/subscribers/subscribers.rb
|
|
261
279
|
- lib/mailerlite/timezones/timezones.rb
|
|
262
280
|
- lib/mailerlite/version.rb
|
|
263
281
|
- lib/mailerlite/webhooks/webhooks.rb
|
|
264
282
|
- mailerlite-ruby.gemspec
|
|
265
|
-
-
|
|
283
|
+
- spec/api_response_rspec.rb
|
|
284
|
+
- spec/automation_emails_rspec.rb
|
|
285
|
+
- spec/automation_reports_rspec.rb
|
|
286
|
+
- spec/automation_steps_rspec.rb
|
|
287
|
+
- spec/automation_templates_rspec.rb
|
|
288
|
+
- spec/automation_triggers_rspec.rb
|
|
266
289
|
- spec/automations_rspec.rb
|
|
267
290
|
- spec/batches_rspec.rb
|
|
291
|
+
- spec/brand_styles_rspec.rb
|
|
268
292
|
- spec/campaigns_rspec.rb
|
|
293
|
+
- spec/ecommerce_rspec.rb
|
|
294
|
+
- spec/emails_rspec.rb
|
|
269
295
|
- spec/fields_rspec.rb
|
|
296
|
+
- spec/files_rspec.rb
|
|
297
|
+
- spec/fixtures/brand_styles.json
|
|
298
|
+
- spec/fixtures/campaign_simple_editor.json
|
|
299
|
+
- spec/fixtures/email.json
|
|
300
|
+
- spec/fixtures/email_block.json
|
|
301
|
+
- spec/fixtures/email_block_created.json
|
|
302
|
+
- spec/fixtures/email_block_created_rejected.json
|
|
303
|
+
- spec/fixtures/email_block_moved.json
|
|
304
|
+
- spec/fixtures/email_block_updated.json
|
|
305
|
+
- spec/fixtures/email_block_updated_rejected.json
|
|
306
|
+
- spec/fixtures/email_block_validation_invalid.json
|
|
307
|
+
- spec/fixtures/email_etag.json
|
|
308
|
+
- spec/fixtures/email_html.json
|
|
309
|
+
- spec/fixtures/email_settings.json
|
|
310
|
+
- spec/fixtures/email_settings_validation.json
|
|
311
|
+
- spec/fixtures/email_template.json
|
|
312
|
+
- spec/fixtures/email_version.json
|
|
313
|
+
- spec/fixtures/email_versions.json
|
|
314
|
+
- spec/fixtures/files.json
|
|
270
315
|
- spec/forms_rspec.rb
|
|
271
316
|
- spec/groups_rspec.rb
|
|
317
|
+
- spec/params_rspec.rb
|
|
318
|
+
- spec/request_rspec.rb
|
|
272
319
|
- spec/segments_rspec.rb
|
|
273
320
|
- spec/spec_helper.rb
|
|
274
321
|
- spec/subscribers_rspec.rb
|
|
322
|
+
- spec/support/json_fixture.rb
|
|
323
|
+
- spec/support/request_stub.rb
|
|
275
324
|
- spec/timezones_rspec.rb
|
|
276
325
|
- spec/webhooks_rspec.rb
|
|
277
326
|
homepage: https://www.MailerLite.com
|
|
@@ -283,7 +332,6 @@ metadata:
|
|
|
283
332
|
source_code_uri: https://github.com/mailerlite/mailerlite-ruby
|
|
284
333
|
changelog_uri: https://github.com/mailerlite/mailerlite-ruby/blob/main/CHANGELOG.md
|
|
285
334
|
rubygems_mfa_required: 'true'
|
|
286
|
-
post_install_message:
|
|
287
335
|
rdoc_options: []
|
|
288
336
|
require_paths:
|
|
289
337
|
- lib
|
|
@@ -291,15 +339,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
291
339
|
requirements:
|
|
292
340
|
- - ">="
|
|
293
341
|
- !ruby/object:Gem::Version
|
|
294
|
-
version:
|
|
342
|
+
version: '3.1'
|
|
295
343
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
296
344
|
requirements:
|
|
297
345
|
- - ">="
|
|
298
346
|
- !ruby/object:Gem::Version
|
|
299
347
|
version: '0'
|
|
300
348
|
requirements: []
|
|
301
|
-
rubygems_version: 3.
|
|
302
|
-
signing_key:
|
|
349
|
+
rubygems_version: 3.6.9
|
|
303
350
|
specification_version: 4
|
|
304
351
|
summary: MailerLite's official Ruby SDK
|
|
305
352
|
test_files: []
|
|
File without changes
|