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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +2 -0
  3. data/.github/workflows/main.yml +4 -4
  4. data/.github/workflows/publish_gem.yml +5 -5
  5. data/.gitignore +2 -1
  6. data/.rubocop.yml +1 -1
  7. data/CHANGELOG.md +21 -0
  8. data/README.md +583 -1
  9. data/flake.lock +27 -0
  10. data/flake.nix +25 -0
  11. data/lefthook.yml +14 -0
  12. data/lib/mailerlite/api_response.rb +24 -0
  13. data/lib/mailerlite/automations/automation_emails.rb +57 -0
  14. data/lib/mailerlite/automations/automation_reports.rb +114 -0
  15. data/lib/mailerlite/automations/automation_steps.rb +105 -0
  16. data/lib/mailerlite/automations/automation_templates.rb +34 -0
  17. data/lib/mailerlite/automations/automation_triggers.rb +62 -0
  18. data/lib/mailerlite/automations/automations.rb +177 -8
  19. data/lib/mailerlite/brand_styles/brand_styles.rb +24 -0
  20. data/lib/mailerlite/campaigns/campaigns.rb +33 -1
  21. data/lib/mailerlite/client.rb +15 -5
  22. data/lib/mailerlite/ecommerce/ecommerce.rb +316 -0
  23. data/lib/mailerlite/emails/emails.rb +306 -0
  24. data/lib/mailerlite/errors.rb +88 -0
  25. data/lib/mailerlite/files/files.rb +45 -0
  26. data/lib/mailerlite/groups/groups.rb +16 -8
  27. data/lib/mailerlite/params.rb +58 -0
  28. data/lib/mailerlite/query_params.rb +33 -0
  29. data/lib/mailerlite/request.rb +24 -0
  30. data/lib/mailerlite/segments/segments.rb +10 -10
  31. data/lib/mailerlite/subscribers/subscribers.rb +63 -8
  32. data/lib/mailerlite/version.rb +1 -1
  33. data/lib/mailerlite.rb +14 -0
  34. data/mailerlite-ruby.gemspec +8 -8
  35. data/spec/api_response_rspec.rb +140 -0
  36. data/spec/automation_emails_rspec.rb +40 -0
  37. data/spec/automation_reports_rspec.rb +92 -0
  38. data/spec/automation_steps_rspec.rb +76 -0
  39. data/spec/automation_templates_rspec.rb +24 -0
  40. data/spec/automation_triggers_rspec.rb +48 -0
  41. data/spec/automations_rspec.rb +143 -0
  42. data/spec/brand_styles_rspec.rb +49 -0
  43. data/spec/campaigns_rspec.rb +42 -0
  44. data/spec/ecommerce_rspec.rb +141 -0
  45. data/spec/emails_rspec.rb +692 -0
  46. data/spec/files_rspec.rb +90 -0
  47. data/spec/fixtures/brand_styles.json +50 -0
  48. data/spec/fixtures/campaign_simple_editor.json +24 -0
  49. data/spec/fixtures/email.json +81 -0
  50. data/spec/fixtures/email_block.json +12 -0
  51. data/spec/fixtures/email_block_created.json +12 -0
  52. data/spec/fixtures/email_block_created_rejected.json +15 -0
  53. data/spec/fixtures/email_block_moved.json +12 -0
  54. data/spec/fixtures/email_block_updated.json +9 -0
  55. data/spec/fixtures/email_block_updated_rejected.json +13 -0
  56. data/spec/fixtures/email_block_validation_invalid.json +5 -0
  57. data/spec/fixtures/email_etag.json +1 -0
  58. data/spec/fixtures/email_html.json +1 -0
  59. data/spec/fixtures/email_settings.json +111 -0
  60. data/spec/fixtures/email_settings_validation.json +9 -0
  61. data/spec/fixtures/email_template.json +1 -0
  62. data/spec/fixtures/email_version.json +1 -0
  63. data/spec/fixtures/email_versions.json +26 -0
  64. data/spec/fixtures/files.json +44 -0
  65. data/spec/groups_rspec.rb +21 -0
  66. data/spec/params_rspec.rb +126 -0
  67. data/spec/request_rspec.rb +139 -0
  68. data/spec/segments_rspec.rb +39 -0
  69. data/spec/spec_helper.rb +1 -0
  70. data/spec/subscribers_rspec.rb +57 -0
  71. data/spec/support/json_fixture.rb +27 -0
  72. data/spec/support/request_stub.rb +25 -0
  73. metadata +69 -22
  74. /data/{renovate.json → .github/renovate.json} +0 -0
@@ -14,18 +14,33 @@ module MailerLite
14
14
 
15
15
  # Returns a list of subscribers that match the specified filter criteria.
16
16
  #
17
- # @param filter[:status] [Array] the status of the subscribers to include in the results
17
+ # @param filter[:status] [String] the status of the subscribers to include in the results
18
+ # @param filter[:group] [String] the ID of a group, or several IDs separated with commas
19
+ # @param filter[:automation] [String] the ID of an automation, or several IDs separated with commas
20
+ # @param filter[:form] [String] the ID of a form, or several IDs separated with commas
21
+ # @param filter[:site] [String] the ID of a site, or several IDs separated with commas
22
+ # @param filter[:page] [String] the ID of a landing page, or several IDs separated with commas
23
+ # @param filter[:import] [String] the ID of an import, or several IDs separated with commas
24
+ # @param filter[:post] [String] the ID of a blog post, or several IDs separated with commas
18
25
  # @param limit [Integer] the maximum number of subscribers to return
19
- # @param page [Integer] the page number of the results to return
26
+ # @param cursor [String] the cursor for the page of results to return
27
+ # @param sort [String] the field to sort by, prefix with - for descending order
28
+ # @param include [String] the relations to embed, only groups is supported
29
+ # @param start_date [String] only subscribers who signed up on or after this date
30
+ # @param end_date [String] only subscribers who signed up on or before this date
31
+ # @param with_fields [Boolean] return each custom field with its name, value and type
20
32
  # @return [HTTP::Response] the response from the API
21
- def fetch(filter:, limit: nil, cursor: nil)
22
- params = { 'filter[status]' => filter[:status] }
33
+ def fetch(filter:, limit: nil, cursor: nil, sort: nil, include: nil, start_date: nil, end_date: nil, with_fields: nil)
34
+ params = QueryParams.filter(filter, %i[status group automation form site page import post])
23
35
 
24
36
  params['limit'] = limit if limit
25
37
  params['cursor'] = cursor if cursor
26
- uri = URI("#{MAILERLITE_API_URL}/subscribers")
27
- uri.query = URI.encode_www_form(params.compact)
28
- client.http.get(uri)
38
+ params['sort'] = sort if sort
39
+ params['include'] = include if include
40
+ params['start_date'] = start_date if start_date
41
+ params['end_date'] = end_date if end_date
42
+ params['with_fields'] = QueryParams.boolean(with_fields)
43
+ client.http.get(QueryParams.uri("#{MAILERLITE_API_URL}/subscribers", params))
29
44
  end
30
45
 
31
46
  # Creates a new subscriber with the specified details.
@@ -39,8 +54,11 @@ module MailerLite
39
54
  # @param opted_in_at [DateTime] the date and time when the subscriber confirmed their subscription
40
55
  # @param optin_ip [String] the IP address of the subscriber when they confirmed their subscription
41
56
  # @param unsubscribed_at [DateTime] the date and time when the subscriber was unsubscribed
57
+ # @param resubscribe [Boolean] reactivate the subscriber if they are unsubscribed, bounced or junk
58
+ # @param double_optin [Boolean] send a confirmation email to the new subscriber
59
+ # @param notes [Array] notes to add to the subscriber
42
60
  # @return [HTTP::Response] the response from the API
43
- def create(email:, fields: nil, groups: nil, status: nil, subscribed_at: nil, ip_address: nil, opted_in_at: nil, optin_ip: nil, unsubscribed_at: nil)
61
+ def create(email:, fields: nil, groups: nil, status: nil, subscribed_at: nil, ip_address: nil, opted_in_at: nil, optin_ip: nil, unsubscribed_at: nil, resubscribe: nil, double_optin: nil, notes: nil)
44
62
  params = { 'email' => email }
45
63
 
46
64
  params['fields'] = fields if fields
@@ -51,6 +69,9 @@ module MailerLite
51
69
  params['opted_in_at'] = opted_in_at if opted_in_at
52
70
  params['optin_ip'] = optin_ip if optin_ip
53
71
  params['unsubscribed_at'] = unsubscribed_at if unsubscribed_at
72
+ params['resubscribe'] = resubscribe
73
+ params['double_optin'] = double_optin
74
+ params['notes'] = notes if notes
54
75
 
55
76
  client.http.post("#{MAILERLITE_API_URL}/subscribers", json: params.compact)
56
77
  end
@@ -92,6 +113,18 @@ module MailerLite
92
113
  client.http.get("#{MAILERLITE_API_URL}/subscribers/#{subscriber_id}")
93
114
  end
94
115
 
116
+ # Returns the activity log of the specified subscriber
117
+ #
118
+ # @param subscriber_id [String] the ID of the subscriber
119
+ # @param filter[:log_name] [String] the type of activity to include in the results
120
+ # @param limit [Integer] the maximum number of entries to return
121
+ # @param page [Integer] the page number of the results to return
122
+ # @return [HTTP::Response] the response from the API
123
+ def activity_log(subscriber_id:, filter: {}, limit: nil, page: nil)
124
+ params = QueryParams.filter(filter, %i[log_name]).merge('limit' => limit, 'page' => page)
125
+ client.http.get(QueryParams.uri("#{MAILERLITE_API_URL}/subscribers/#{subscriber_id}/activity-log", params))
126
+ end
127
+
95
128
  # Returns the details of the specified subscribers
96
129
  #
97
130
  # @param import_id [String] the ID of the import to fetch report
@@ -122,5 +155,27 @@ module MailerLite
122
155
  def forget(subscriber_id)
123
156
  client.http.post("#{MAILERLITE_API_URL}/subscribers/#{subscriber_id}/forget")
124
157
  end
158
+
159
+ # Returns a subscriber's lifetime orders and revenue in one shop
160
+ #
161
+ # @param subscriber_id [String] the ID of the subscriber
162
+ # @param shop_id [String] the ID of the shop
163
+ # @return [HTTP::Response] the response from the API
164
+ def ecommerce_stats(subscriber_id:, shop_id:)
165
+ uri = URI("#{MAILERLITE_API_URL}/subscribers/#{subscriber_id}/reports/ecommerce")
166
+ uri.query = URI.encode_www_form('shop_id' => shop_id)
167
+ client.http.get(uri)
168
+ end
169
+
170
+ # Returns a subscriber's orders in one shop
171
+ #
172
+ # @param subscriber_id [String] the ID of the subscriber
173
+ # @param shop_id [String] the ID of the shop
174
+ # @return [HTTP::Response] the response from the API
175
+ def ecommerce_orders(subscriber_id:, shop_id:, page: nil, limit: nil)
176
+ uri = URI("#{MAILERLITE_API_URL}/subscribers/#{subscriber_id}/reports/ecommerce/orders")
177
+ uri.query = URI.encode_www_form({ 'shop_id' => shop_id, 'page' => page, 'limit' => limit }.compact)
178
+ client.http.get(uri)
179
+ end
125
180
  end
126
181
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MailerLite
4
- VERSION = '1.0.6'
4
+ VERSION = '2.1.0'
5
5
  end
data/lib/mailerlite.rb CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  require_relative 'mailerlite/client'
4
4
  require_relative 'mailerlite/version'
5
+ require_relative 'mailerlite/query_params'
6
+ require_relative 'mailerlite/errors'
7
+ require_relative 'mailerlite/params'
8
+ require_relative 'mailerlite/api_response'
9
+ require_relative 'mailerlite/request'
5
10
  require_relative 'mailerlite/subscribers/subscribers'
6
11
  require_relative 'mailerlite/campaigns/campaigns'
7
12
  require_relative 'mailerlite/forms/forms'
@@ -9,6 +14,15 @@ require_relative 'mailerlite/groups/groups'
9
14
  require_relative 'mailerlite/segments/segments'
10
15
  require_relative 'mailerlite/fields/fields'
11
16
  require_relative 'mailerlite/automations/automations'
17
+ require_relative 'mailerlite/automations/automation_triggers'
18
+ require_relative 'mailerlite/automations/automation_steps'
19
+ require_relative 'mailerlite/automations/automation_emails'
20
+ require_relative 'mailerlite/automations/automation_templates'
21
+ require_relative 'mailerlite/automations/automation_reports'
12
22
  require_relative 'mailerlite/webhooks/webhooks'
13
23
  require_relative 'mailerlite/batch/batch'
14
24
  require_relative 'mailerlite/timezones/timezones'
25
+ require_relative 'mailerlite/ecommerce/ecommerce'
26
+ require_relative 'mailerlite/emails/emails'
27
+ require_relative 'mailerlite/brand_styles/brand_styles'
28
+ require_relative 'mailerlite/files/files'
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.description = "MailerLite's official Ruby SDK. Interacts with all endpoints at MailerLite API."
15
15
  spec.homepage = 'https://www.MailerLite.com'
16
16
  spec.license = 'MIT'
17
- spec.required_ruby_version = '>= 2.5.0'
17
+ spec.required_ruby_version = '>= 3.1'
18
18
 
19
19
  spec.metadata['allowed_push_host'] = 'https://rubygems.org'
20
20
 
@@ -27,13 +27,13 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ['lib']
29
29
 
30
- spec.add_development_dependency 'bundler', '~> 2.4.1'
31
- spec.add_development_dependency 'rake', '~> 13.0'
32
- spec.add_development_dependency 'rubocop', '~> 1.7'
33
- spec.add_dependency 'dotenv', '~> 2.7'
34
- spec.add_dependency 'http', '~> 5.0'
35
- spec.add_dependency 'json', '~> 2.5'
36
- spec.add_dependency 'uri', '~> 0.13.0'
30
+ spec.add_development_dependency 'bundler', '~> 2.5'
31
+ spec.add_development_dependency 'rake', '~> 13.2'
32
+ spec.add_development_dependency 'rubocop', '~> 1.69'
33
+ spec.add_dependency 'dotenv', '~> 3.1'
34
+ spec.add_dependency 'http', '~> 5.2'
35
+ spec.add_dependency 'json', '~> 2.9'
36
+ spec.add_dependency 'uri', '~> 1.0'
37
37
  spec.add_development_dependency 'rspec'
38
38
  spec.add_development_dependency 'simplecov'
39
39
  spec.add_development_dependency 'vcr'
@@ -0,0 +1,140 @@
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::ApiResponse do
9
+ include_context 'with the dummy API token'
10
+
11
+ let(:url) { "#{MAILERLITE_API_URL}/emails/123" }
12
+
13
+ def response_for(status:, body: '', headers: {})
14
+ stub_request(:get, url).to_return(status: status, body: body, headers: headers)
15
+ MailerLite::Client.new.http.get(url).extend(described_class)
16
+ end
17
+
18
+ describe '#idempotent_replay?' do
19
+ it 'is true for the value true' do
20
+ expect(response_for(status: 201, headers: { 'Idempotent-Replayed' => 'true' }).idempotent_replay?).to be(true)
21
+ end
22
+
23
+ it 'reads the value without case and without spaces' do
24
+ expect(response_for(status: 201, headers: { 'Idempotent-Replayed' => 'True' }).idempotent_replay?).to be(true)
25
+ expect(response_for(status: 201, headers: { 'Idempotent-Replayed' => ' TRUE ' }).idempotent_replay?).to be(true)
26
+ end
27
+
28
+ it 'is false when the header is not there' do
29
+ expect(response_for(status: 201).idempotent_replay?).to be(false)
30
+ end
31
+
32
+ it 'is false for other values' do
33
+ expect(response_for(status: 201, headers: { 'Idempotent-Replayed' => 'false' }).idempotent_replay?).to be(false)
34
+ end
35
+
36
+ it 'reads a header that comes two times' do
37
+ expect(response_for(status: 201, headers: { 'Idempotent-Replayed' => %w[true true] }).idempotent_replay?).to be(true)
38
+ expect(response_for(status: 201, headers: { 'Idempotent-Replayed' => %w[false false] }).idempotent_replay?).to be(false)
39
+ end
40
+ end
41
+
42
+ describe '#api_error' do
43
+ it 'is nil for a 2xx status' do
44
+ response = response_for(status: 200, body: load_fixture('email').to_json)
45
+ expect(response.api_error).to be_nil
46
+ expect(response).to be_a(HTTP::Response)
47
+ end
48
+
49
+ it 'has the code and the etag of a 409 version conflict' do
50
+ body = { message: 'The email was changed.', code: 'version_conflict', etag: '45' }.to_json
51
+ error = response_for(status: 409, body: body).api_error
52
+
53
+ expect(error).to be_a(MailerLite::ApiError)
54
+ expect(error.status).to eq 409
55
+ expect(error.code).to eq 'version_conflict'
56
+ expect(error.etag).to eq '45'
57
+ expect(error.api_message).to eq 'The email was changed.'
58
+ expect(error.errors).to be_nil
59
+ expect(error.message).to start_with('409')
60
+ expect(error.message).to include('(version_conflict): The email was changed.')
61
+ end
62
+
63
+ it 'has no code for a 409 without a code' do
64
+ error = response_for(status: 409, body: { message: 'The campaign is not a draft.' }.to_json).api_error
65
+
66
+ expect(error.status).to eq 409
67
+ expect(error.code).to be_nil
68
+ expect(error.etag).to be_nil
69
+ expect(error.message).to include(': The campaign is not a draft.')
70
+ end
71
+
72
+ it 'has the errors of a 422' do
73
+ body = { message: 'The etag field is required.', errors: { etag: ['The etag field is required.'] } }.to_json
74
+ error = response_for(status: 422, body: body).api_error
75
+
76
+ expect(error.status).to eq 422
77
+ expect(error.errors).to eq('etag' => ['The etag field is required.'])
78
+ expect(error.api_message).to eq 'The etag field is required.'
79
+ end
80
+
81
+ it 'has no errors for a 422 with only a message' do
82
+ error = response_for(status: 422, body: { message: 'This email was not built with the simple editor.' }.to_json).api_error
83
+
84
+ expect(error.errors).to be_nil
85
+ expect(error.api_message).to eq 'This email was not built with the simple editor.'
86
+ end
87
+
88
+ it 'does not raise for a 503 with an HTML body' do
89
+ html = '<html><body>Service Unavailable</body></html>'
90
+ error = response_for(status: 503, body: html, headers: { 'Content-Type' => 'text/html' }).api_error
91
+
92
+ expect(error.status).to eq 503
93
+ expect(error.api_message).to be_nil
94
+ expect(error.code).to be_nil
95
+ expect(error.body).to eq html
96
+ expect(error.headers['Content-Type']).to eq 'text/html'
97
+ expect(error.message).to start_with('503')
98
+ end
99
+
100
+ it 'accepts a JSON body that is not an object' do
101
+ error = response_for(status: 500, body: '[1, 2]').api_error
102
+
103
+ expect(error.status).to eq 500
104
+ expect(error.api_message).to be_nil
105
+ expect(error.body).to eq '[1, 2]'
106
+ end
107
+
108
+ it 'survives a Marshal round trip' do
109
+ body = { message: 'The email was changed.', code: 'version_conflict', etag: '45' }.to_json
110
+ error = response_for(status: 409, body: body).api_error
111
+ copy = Marshal.load(Marshal.dump(error))
112
+
113
+ expect(copy).to be_a(MailerLite::ApiError)
114
+ expect(copy.message).to eq error.message
115
+ expect([copy.status, copy.code, copy.etag, copy.api_message, copy.body]).to eq [409, 'version_conflict', '45', 'The email was changed.', body]
116
+ end
117
+ end
118
+
119
+ describe MailerLite::ApiError do
120
+ it 'is a MailerLite::Error and not an HTTP::Error' do
121
+ expect(described_class.ancestors).to include(MailerLite::Error)
122
+ expect(described_class.ancestors).not_to include(HTTP::Error)
123
+ end
124
+
125
+ it 'builds an error from any HTTP::Response' do
126
+ stub_request(:get, url).to_return(status: 404, body: { message: 'Not found.' }.to_json)
127
+ error = described_class.from_response(MailerLite::Client.new.http.get(url))
128
+
129
+ expect(error.status).to eq 404
130
+ expect(error.api_message).to eq 'Not found.'
131
+ end
132
+
133
+ it 'can be raised' do
134
+ stub_request(:get, url).to_return(status: 404, body: { message: 'Not found.' }.to_json)
135
+ error = described_class.from_response(MailerLite::Client.new.http.get(url))
136
+
137
+ expect { raise error }.to raise_error(described_class, /Not found\./)
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe MailerLite::AutomationEmails do
6
+ let(:client) { MailerLite::Client.new }
7
+ let(:emails) { described_class.new(client: client) }
8
+
9
+ describe '#content' do
10
+ it 'sends GET /automations/{automation}/emails/{email}/content' do
11
+ stub = stub_api(:get, '/automations/1/emails/2/content')
12
+ emails.content(automation_id: 1, email_id: 2)
13
+ expect(stub).to have_been_requested
14
+ end
15
+ end
16
+
17
+ describe '#update_content' do
18
+ it 'sends PUT /automations/{automation}/emails/{email}/content' do
19
+ stub = stub_api(:put, '/automations/1/emails/2/content', body: { 'html' => '<p>Hi</p>' })
20
+ emails.update_content(automation_id: 1, email_id: 2, html: '<p>Hi</p>')
21
+ expect(stub).to have_been_requested
22
+ end
23
+ end
24
+
25
+ describe '#report_stats' do
26
+ it 'sends GET /automations/{automation}/emails/{email}/report-stats with the query' do
27
+ stub = stub_api(:get, '/automations/1/emails/2/report-stats', query: { 'start_date' => '2026-01-01', 'end_date' => '2026-01-31', 'shop_id' => '3', 'excel' => '0' })
28
+ emails.report_stats(automation_id: 1, email_id: 2, start_date: '2026-01-01', end_date: '2026-01-31', shop_id: '3', excel: false)
29
+ expect(stub).to have_been_requested
30
+ end
31
+ end
32
+
33
+ describe '#unsubscribe_reasons' do
34
+ it 'sends GET /automations/{automation}/emails/{email}/unsubscribe-reasons' do
35
+ stub = stub_api(:get, '/automations/1/emails/2/unsubscribe-reasons')
36
+ emails.unsubscribe_reasons(automation_id: 1, email_id: 2)
37
+ expect(stub).to have_been_requested
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe MailerLite::AutomationReports do
6
+ let(:client) { MailerLite::Client.new }
7
+ let(:reports) { described_class.new(client: client) }
8
+
9
+ describe '#step_run_counts' do
10
+ it 'sends GET /automations/{automation}/steps/run-counts' do
11
+ stub = stub_api(:get, '/automations/1/steps/run-counts')
12
+ reports.step_run_counts(1)
13
+ expect(stub).to have_been_requested
14
+ end
15
+ end
16
+
17
+ describe '#step_run_count' do
18
+ it 'sends GET /automations/{automation}/steps/run-counts/{step}' do
19
+ stub = stub_api(:get, '/automations/1/steps/run-counts/2')
20
+ reports.step_run_count(automation_id: 1, step_id: 2)
21
+ expect(stub).to have_been_requested
22
+ end
23
+ end
24
+
25
+ describe '#trigger_run_counts' do
26
+ it 'sends GET /automations/{automation}/triggers/run-counts' do
27
+ stub = stub_api(:get, '/automations/1/triggers/run-counts')
28
+ reports.trigger_run_counts(1)
29
+ expect(stub).to have_been_requested
30
+ end
31
+ end
32
+
33
+ describe '#trigger_run_count' do
34
+ it 'sends GET /automations/{automation}/triggers/run-counts/{trigger}' do
35
+ stub = stub_api(:get, '/automations/1/triggers/run-counts/2')
36
+ reports.trigger_run_count(automation_id: 1, trigger_id: 2)
37
+ expect(stub).to have_been_requested
38
+ end
39
+ end
40
+
41
+ describe '#ab_steps_stats' do
42
+ it 'sends GET /automations/{automation}/ab-steps-stats' do
43
+ stub = stub_api(:get, '/automations/1/ab-steps-stats')
44
+ reports.ab_steps_stats(1)
45
+ expect(stub).to have_been_requested
46
+ end
47
+ end
48
+
49
+ describe '#ab_step_stats' do
50
+ it 'sends GET /automations/{automation}/ab-steps-stats/{step}' do
51
+ stub = stub_api(:get, '/automations/1/ab-steps-stats/2')
52
+ reports.ab_step_stats(automation_id: 1, step_id: 2)
53
+ expect(stub).to have_been_requested
54
+ end
55
+ end
56
+
57
+ describe '#step_subscribers' do
58
+ it 'sends GET /automations/{automation}/activity/steps/{step}/subscribers with the query' do
59
+ stub = stub_api(:get, '/automations/1/activity/steps/2/subscribers', query: {
60
+ 'status' => 'queued', 'limit' => '10', 'page' => '2', 'sort' => '-id',
61
+ 'filter[date_from]' => '2026-01-01', 'filter[date_to]' => '2026-01-31', 'filter[search]' => 'a@b.com', 'filter[id]' => '5'
62
+ })
63
+ reports.step_subscribers(automation_id: 1, step_id: 2, status: 'queued', limit: 10, page: 2, sort: '-id',
64
+ filter: { date_from: '2026-01-01', date_to: '2026-01-31', search: 'a@b.com', id: '5' })
65
+ expect(stub).to have_been_requested
66
+ end
67
+ end
68
+
69
+ describe '#step_subscribers with an Array filter[:id]' do
70
+ it 'sends the ids as one comma-separated value' do
71
+ stub = stub_api(:get, '/automations/1/activity/steps/2/subscribers', query: { 'status' => 'queued', 'filter[id]' => '5,6' })
72
+ reports.step_subscribers(automation_id: 1, step_id: 2, status: 'queued', filter: { id: %w[5 6] })
73
+ expect(stub).to have_been_requested
74
+ end
75
+ end
76
+
77
+ describe '#trigger_subscribers' do
78
+ it 'sends GET /automations/{automation}/activity/triggers/{trigger}/subscribers with the query' do
79
+ stub = stub_api(:get, '/automations/1/activity/triggers/2/subscribers', query: { 'status' => 'completed', 'filter[search]' => 'a@b.com' })
80
+ reports.trigger_subscribers(automation_id: 1, trigger_id: 2, status: 'completed', filter: { search: 'a@b.com' })
81
+ expect(stub).to have_been_requested
82
+ end
83
+ end
84
+
85
+ describe '#email_monthly_totals' do
86
+ it 'sends GET /automations/{automation}/monthly-totals/email with the query' do
87
+ stub = stub_api(:get, '/automations/1/monthly-totals/email', query: { 'start_date' => '2026-01-01', 'end_date' => '2026-06-30', 'shop_id' => '3', 'excel' => '1' })
88
+ reports.email_monthly_totals(automation_id: 1, start_date: '2026-01-01', end_date: '2026-06-30', shop_id: '3', excel: true)
89
+ expect(stub).to have_been_requested
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe MailerLite::AutomationSteps do
6
+ let(:client) { MailerLite::Client.new }
7
+ let(:steps) { described_class.new(client: client) }
8
+
9
+ describe '#list' do
10
+ it 'sends GET /automations/{automation}/steps/index' do
11
+ stub = stub_api(:get, '/automations/1/steps/index')
12
+ steps.list(1)
13
+ expect(stub).to have_been_requested
14
+ end
15
+ end
16
+
17
+ describe '#create' do
18
+ it 'sends POST /automations/{automation}/steps' do
19
+ stub = stub_api(:post, '/automations/1/steps', body: {
20
+ 'type' => 'action', 'parent_id' => 2, 'branch' => 'yes', 'move_to_branch' => 'no',
21
+ 'data' => { 'action_type' => 'copy_to_group', 'group_id' => '3' }
22
+ })
23
+ steps.create(automation_id: 1, type: 'action', parent_id: 2, branch: 'yes', move_to_branch: 'no',
24
+ data: { 'action_type' => 'copy_to_group', 'group_id' => '3' })
25
+ expect(stub).to have_been_requested
26
+ end
27
+ end
28
+
29
+ describe '#fetch' do
30
+ it 'sends GET /automations/{automation}/steps/{step}' do
31
+ stub = stub_api(:get, '/automations/1/steps/2')
32
+ steps.fetch(automation_id: 1, step_id: 2)
33
+ expect(stub).to have_been_requested
34
+ end
35
+ end
36
+
37
+ describe '#update' do
38
+ it 'sends PUT /automations/{automation}/steps/{step}' do
39
+ stub = stub_api(:put, '/automations/1/steps/2', body: { 'parent_id' => 3, 'data' => { 'value' => 2 }, 'reschedule_waiting_subscribers' => true })
40
+ steps.update(automation_id: 1, step_id: 2, parent_id: 3, data: { 'value' => 2 }, reschedule_waiting_subscribers: true)
41
+ expect(stub).to have_been_requested
42
+ end
43
+ end
44
+
45
+ describe '#delete' do
46
+ it 'sends DELETE /automations/{automation}/steps/{step} with branch_to_delete' do
47
+ stub = stub_api(:delete, '/automations/1/steps/2', query: { 'branch_to_delete' => 'yes' })
48
+ steps.delete(automation_id: 1, step_id: 2, branch_to_delete: 'yes')
49
+ expect(stub).to have_been_requested
50
+ end
51
+ end
52
+
53
+ describe '#copy' do
54
+ it 'sends POST /automations/{automation}/steps/{step}/copy' do
55
+ stub = stub_api(:post, '/automations/1/steps/2/copy', body: { 'parent_id' => 3, 'branch' => 'a_path', 'move_to_branch' => 'yes' })
56
+ steps.copy(automation_id: 1, step_id: 2, parent_id: 3, branch: 'a_path', move_to_branch: 'yes')
57
+ expect(stub).to have_been_requested
58
+ end
59
+ end
60
+
61
+ describe '#reorder' do
62
+ it 'sends POST /automations/{automation}/steps/reorder' do
63
+ stub = stub_api(:post, '/automations/1/steps/reorder', body: { 'dragged_step_id' => 2, 'new_parent_step_id' => 3, 'branch' => 'no', 'branch_for_child' => 'yes' })
64
+ steps.reorder(automation_id: 1, dragged_step_id: 2, new_parent_step_id: 3, branch: 'no', branch_for_child: 'yes')
65
+ expect(stub).to have_been_requested
66
+ end
67
+ end
68
+
69
+ describe '#select_ab_test_winner' do
70
+ it 'sends PUT /automations/{automation}/steps/{step}/select-ab-test-winner' do
71
+ stub = stub_api(:put, '/automations/1/steps/2/select-ab-test-winner', body: { 'ab_test_winner' => 'b_path' })
72
+ steps.select_ab_test_winner(automation_id: 1, step_id: 2, ab_test_winner: 'b_path')
73
+ expect(stub).to have_been_requested
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe MailerLite::AutomationTemplates do
6
+ let(:client) { MailerLite::Client.new }
7
+ let(:templates) { described_class.new(client: client) }
8
+
9
+ describe '#list' do
10
+ it 'sends GET /automation-templates with the query' do
11
+ stub = stub_api(:get, '/automation-templates', query: { 'filter[type]' => 'ecommerce', 'filter[category]' => '2', 'limit' => '10', 'page' => '3' })
12
+ templates.list(filter: { type: 'ecommerce', category: 2 }, limit: 10, page: 3)
13
+ expect(stub).to have_been_requested
14
+ end
15
+ end
16
+
17
+ describe '#select_template' do
18
+ it 'sends POST /automations/select-template/{automationTemplate}' do
19
+ stub = stub_api(:post, '/automations/select-template/welcome')
20
+ templates.select_template('welcome')
21
+ expect(stub).to have_been_requested
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe MailerLite::AutomationTriggers do
6
+ let(:client) { MailerLite::Client.new }
7
+ let(:triggers) { described_class.new(client: client) }
8
+
9
+ describe '#list' do
10
+ it 'sends GET /automations/{automation}/triggers' do
11
+ stub = stub_api(:get, '/automations/1/triggers')
12
+ triggers.list(1)
13
+ expect(stub).to have_been_requested
14
+ end
15
+ end
16
+
17
+ describe '#create' do
18
+ it 'sends POST /automations/{automation}/triggers' do
19
+ stub = stub_api(:post, '/automations/1/triggers', body: { 'type' => 'subscriber_joins_group', 'data' => { 'group_ids' => ['2'] } })
20
+ triggers.create(automation_id: 1, type: 'subscriber_joins_group', data: { 'group_ids' => ['2'] })
21
+ expect(stub).to have_been_requested
22
+ end
23
+ end
24
+
25
+ describe '#fetch' do
26
+ it 'sends GET /automations/{automation}/triggers/{trigger}' do
27
+ stub = stub_api(:get, '/automations/1/triggers/2')
28
+ triggers.fetch(automation_id: 1, trigger_id: 2)
29
+ expect(stub).to have_been_requested
30
+ end
31
+ end
32
+
33
+ describe '#update' do
34
+ it 'sends PUT /automations/{automation}/triggers/{trigger}' do
35
+ stub = stub_api(:put, '/automations/1/triggers/2', body: { 'data' => { 'group_ids' => ['3'] } })
36
+ triggers.update(automation_id: 1, trigger_id: 2, data: { 'group_ids' => ['3'] })
37
+ expect(stub).to have_been_requested
38
+ end
39
+ end
40
+
41
+ describe '#delete' do
42
+ it 'sends DELETE /automations/{automation}/triggers/{trigger}' do
43
+ stub = stub_api(:delete, '/automations/1/triggers/2')
44
+ triggers.delete(automation_id: 1, trigger_id: 2)
45
+ expect(stub).to have_been_requested
46
+ end
47
+ end
48
+ end