anadea-mailsocio-qa 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 358f0bf77ff8f08d406dd4b7d46e7141c09b94c2
4
+ data.tar.gz: f482a30a4827d7f173eb02dc6b131184feb77738
5
+ SHA512:
6
+ metadata.gz: c63adb9ba1a8bca9fe7bd5740c0ac145311c882fdeb03ebcbb190aa21bbfd14a1c70395158fa696cbdded59588c30288ac38198b50a9c8e954bee6a07e97d855
7
+ data.tar.gz: 044fd1dfeecddc8959b12192ca3cbd4900608e6d6b19bc4b54b05e4e2dcd6a17411155809064e0b6973c07e9def4d900e88a7e8aa55a09c66470fae934d6e77b
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle
2
+ /log
3
+ /tmp
4
+ *.swp
5
+ .rbenv-version
6
+ .vagrant/*
7
+ provision/cache/*
8
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ anadea-mailsocio-qa (0.0.3)
5
+ activesupport (>= 3.0, < 5.0)
6
+ httpi (>= 2.0)
7
+ json (>= 1.0)
8
+ mail (>= 2.0)
9
+ minitest (>= 4.0)
10
+ nokogiri (>= 1.0)
11
+ settingslogic (>= 2.0)
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ activesupport (4.2.1)
17
+ i18n (~> 0.7)
18
+ json (~> 1.7, >= 1.7.7)
19
+ minitest (~> 5.1)
20
+ thread_safe (~> 0.3, >= 0.3.4)
21
+ tzinfo (~> 1.1)
22
+ httpi (2.4.0)
23
+ rack
24
+ i18n (0.7.0)
25
+ json (1.8.2)
26
+ mail (2.6.3)
27
+ mime-types (>= 1.16, < 3)
28
+ mime-types (2.5)
29
+ mini_portile (0.6.2)
30
+ minitest (5.6.1)
31
+ nokogiri (1.6.6.2)
32
+ mini_portile (~> 0.6.0)
33
+ rack (1.6.1)
34
+ settingslogic (2.0.9)
35
+ thread_safe (0.3.5)
36
+ tzinfo (1.2.2)
37
+ thread_safe (~> 0.1)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ anadea-mailsocio-qa!
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/bin/mailsocio-qa ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ Bundler.setup(:default)
6
+
7
+ require 'mailsocio_qa'
8
+ HTTPI.log = false
9
+
10
+ require 'minitest/autorun'
@@ -0,0 +1,269 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'mailsocio_qa/settings'
4
+ require 'mailsocio_qa/test_client'
5
+ require 'mailsocio_qa/test_html_page'
6
+
7
+ module MailsocioQA
8
+ class MailsocioTest < ::Minitest::Test
9
+ def setup
10
+ @base_domain = Regexp.escape(Settings.mailsocio.base_domain)
11
+ @track_domain = "track.#{Settings.mailsocio.base_domain}"
12
+ @client = TestClient.new
13
+
14
+ addresses = Settings.trap.addresses
15
+ @john = addresses.john
16
+ @jane = addresses.jane
17
+ @invalid = addresses.invalid
18
+ end
19
+
20
+ def test_create_account
21
+ @client.create_account
22
+ refute_nil @client.account_id
23
+ end
24
+
25
+ def test_send_mail
26
+ @client.create_account
27
+ @client.clean_messages
28
+
29
+ @client.deliver(
30
+ 'X-To' => "{\"#{@john}\":{\"name\":\"John Doe\"}}",
31
+ 'Subject' => 'Hello',
32
+ 'Body' => 'Hello, __NAME__'
33
+ )
34
+
35
+ message = @client.read_message
36
+ assert_equal 'Hello', message.subject
37
+ assert_equal 'Hello, John Doe', message.body.raw_source.strip
38
+ end
39
+
40
+ def test_nonascii_mail_css_and_tracking
41
+ @client.create_account
42
+ @client.clean_messages
43
+
44
+ @client.deliver(
45
+ 'X-To' => "[\"#{@john}\"]",
46
+ 'X-Inline-Css' => 1,
47
+ 'X-Track' => 1,
48
+ 'Content-Type' => 'text/html; charset=ISO-8859-1',
49
+ 'Content-Transfer-Encoding' => 'quoted-printable',
50
+ 'Subject' => 'Hello',
51
+ 'Body' => <<NONASCIIHTML
52
+ <html>
53
+ <head>
54
+ <style type=3D"text/css">
55
+ body, .wrapper {
56
+ height: 20px;
57
+ }
58
+ </style>
59
+ </head>
60
+ <body>
61
+ <p>English Caf=E9</p>
62
+ </body>
63
+ </html>
64
+ NONASCIIHTML
65
+ )
66
+
67
+ message = @client.read_message
68
+ assert_equal 'Hello', message.subject
69
+ assert_match /\s*<body style=3D"height:20px">\r\n/, message.body.raw_source.strip, 'No inlined css styles'
70
+ assert_match /\s*<p>English Caf=E9<\/p>\r\n/, message.body.raw_source.strip, 'Not original quoted-printable'
71
+ assert_match /<img src=3D".*\/assets\/dot\.png\?token=3D/, message.body.raw_source.strip, 'No tracking image injected'
72
+ end
73
+
74
+ def test_send_newsletter
75
+ @client.create_account
76
+ @client.clean_messages('john')
77
+ @client.clean_messages('jane')
78
+
79
+ @client.deliver(
80
+ 'X-Newsletter-Id' => '15',
81
+ 'X-To' => "[\"#{@john}\", \"#{@jane}\"]"
82
+ )
83
+
84
+ @client.read_message
85
+ stat = @client.newsletter_statistics('15')
86
+ assert_equal 2, stat['queued']
87
+ end
88
+
89
+ def test_newsletter_tracking
90
+ @client.create_account
91
+ @client.clean_messages('john')
92
+ @client.clean_messages('jane')
93
+
94
+ @client.deliver(
95
+ 'X-Newsletter-Id' => '15',
96
+ 'X-To' => "[\"#{@john}\",\"#{@jane}\"]",
97
+ 'X-Track' => 'yes',
98
+ 'Content-Type' => 'text/html',
99
+ 'Body' => '<a href="http://google.com/search">Search</a>'
100
+ )
101
+
102
+ message = @client.read_message('john')
103
+ page = TestHtmlPage.new(message.body.raw_source)
104
+
105
+ stat = @client.newsletter_statistics('15')
106
+ assert_equal 2, stat['queued']
107
+ assert_equal 2, stat['sent']
108
+ assert_equal 0, stat['viewed']
109
+ assert_equal 0, stat['clicked']
110
+
111
+ page.load
112
+ stat = @client.newsletter_statistics('15')
113
+ assert_equal 2, stat['queued']
114
+ assert_equal 2, stat['sent']
115
+ assert_equal 1, stat['viewed']
116
+ assert_equal 0, stat['clicked']
117
+
118
+ assert_equal 'http://google.com/search', page.click
119
+ stat = @client.newsletter_statistics('15')
120
+ assert_equal 2, stat['queued']
121
+ assert_equal 2, stat['sent']
122
+ assert_equal 1, stat['viewed']
123
+ assert_equal 1, stat['clicked']
124
+
125
+ message = @client.read_message('jane')
126
+ page = TestHtmlPage.new(message.body.raw_source)
127
+
128
+ page.load
129
+ stat = @client.newsletter_statistics('15')
130
+ assert_equal 2, stat['queued']
131
+ assert_equal 2, stat['sent']
132
+ assert_equal 2, stat['viewed']
133
+ assert_equal 1, stat['clicked']
134
+
135
+ assert_equal 'http://google.com/search', page.click
136
+ stat = @client.newsletter_statistics('15')
137
+ assert_equal 2, stat['queued']
138
+ assert_equal 2, stat['sent']
139
+ assert_equal 2, stat['viewed']
140
+ assert_equal 2, stat['clicked']
141
+ end
142
+
143
+ def test_hard_bounce
144
+ @client.create_account
145
+ @client.deliver(
146
+ 'X-Newsletter-Id' => '15',
147
+ 'X-To' => "[\"#{@invalid}\"]"
148
+ )
149
+ sleep 90
150
+
151
+ stat = @client.newsletter_statistics('15')
152
+ assert_equal 1, stat['queued']
153
+ assert_equal 1, stat['hard_bounced']
154
+ end
155
+
156
+ def test_newsletter_tracking_subsequent_failures
157
+ skip("still not in production")
158
+
159
+ @client.create_account
160
+
161
+ @client.deliver(
162
+ 'X-Newsletter-Id' => '15',
163
+ 'X-To' => "[\"#{@invalid}\"]",
164
+ )
165
+
166
+ sleep 90
167
+
168
+ stat = @client.newsletter_statistics('15')
169
+ assert_equal 0, stat['new']
170
+ assert_equal 1, stat['queued']
171
+ assert_equal 1, stat['hard_bounced']
172
+ assert_equal 0, stat['skipped']
173
+ assert_equal 1, stat['sent']
174
+ assert_equal 0, stat['delivered']
175
+
176
+ @client.deliver(
177
+ 'X-Newsletter-Id' => '16',
178
+ 'X-To' => "[\"#{@invalid}\"]",
179
+ )
180
+
181
+ sleep 30
182
+
183
+ stat = @client.newsletter_statistics('16')
184
+ assert_equal 0, stat['new']
185
+ assert_equal 1, stat['queued']
186
+ assert_equal 1, stat['hard_bounced']
187
+ assert_equal 1, stat['skipped']
188
+ assert_equal 1, stat['sent']
189
+ assert_equal 0, stat['delivered']
190
+ end
191
+
192
+ def test_authentication_results
193
+ @client.create_account
194
+ @client.clean_messages
195
+ @client.deliver('X-To' => "[\"#{@john}\"]")
196
+
197
+ message = @client.read_message
198
+ auth_results = message['Authentication-Results'].value
199
+ assert_match /spf=pass/, auth_results
200
+ assert_match /dkim=pass/, auth_results
201
+
202
+ assert_match "@#{@track_domain}", message.return_path
203
+ assert_match "d=#{@track_domain}", message['DKIM-Signature'].try(:value)
204
+ end
205
+
206
+ def test_custom_tracking_domain
207
+ domain = Settings.trap.custom_tracking_domain
208
+ attrs = @client.create_account(custom_tracking_domain: domain)
209
+ @client.custom_tracking_domain = domain
210
+ assert_wont_raise('Could not read account via custom tracking domain URL') do
211
+ @client.read_account
212
+ end
213
+ assert attrs['custom_tracking_enabled'], 'Custom tracking is disabled'
214
+ assert attrs['custom_signing_enabled'], 'Custom signing is disabled'
215
+
216
+ @client.clean_messages
217
+ @client.deliver('X-To' => "[\"#{@john}\"]")
218
+
219
+ message = @client.read_message
220
+ auth_results = message['Authentication-Results'].value
221
+ assert_match /spf=pass/, auth_results
222
+ assert_match /dkim=pass/, auth_results
223
+
224
+ assert_match "@#{domain}", message.return_path
225
+ assert_match "d=#{domain}", message['DKIM-Signature'].try(:value)
226
+ end
227
+
228
+ def test_custom_tracking_hard_bounce
229
+ domain = Settings.trap.custom_tracking_domain
230
+ @client.create_account(custom_tracking_domain: domain)
231
+
232
+ @client.clean_messages
233
+ @client.deliver(
234
+ 'X-Newsletter-Id' => '15',
235
+ 'X-To' => "[\"#{@invalid}\"]"
236
+ )
237
+ sleep 90
238
+
239
+ stat = @client.newsletter_statistics('15')
240
+ assert_equal 1, stat['queued']
241
+ assert_equal 1, stat['hard_bounced']
242
+ end
243
+
244
+ def test_transactional_routing
245
+ @client.create_account
246
+ @client.clean_messages
247
+ @client.deliver('X-To' => "[\"#{@john}\"]")
248
+
249
+ received = @client.read_message.received
250
+ assert_match /from (smtp0\.#{@base_domain}) \(\1\. /, received[-3].value
251
+ assert_match 'from localhost.localdomain (localhost ', received[-2].value
252
+ assert_match 'from localhost.localdomain (localhost ', received[-1].value
253
+ end
254
+
255
+ def test_bulk_routing
256
+ @client.create_account
257
+ @client.clean_messages
258
+ @client.deliver(
259
+ 'Precedence' => 'bulk',
260
+ 'X-To' => "[\"#{@john}\"]"
261
+ )
262
+
263
+ received = @client.read_message.received
264
+ assert_match /^from (smtp[1-9]\d*\.#{@base_domain}) \(\1\. /, received[-3].value
265
+ assert_match 'from localhost.localdomain (localhost ', received[-2].value
266
+ assert_match 'from localhost.localdomain (localhost ', received[-1].value
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,5 @@
1
+ require 'settingslogic'
2
+ module MailsocioQA
3
+ class Settings < Settingslogic
4
+ end
5
+ end
@@ -0,0 +1,91 @@
1
+ require 'mailsocio_qa/settings'
2
+
3
+ module MailsocioQA
4
+ class TestClient
5
+ attr_accessor :account_id, :custom_tracking_domain
6
+
7
+ def create_account(attrs = {})
8
+ attrs.reverse_merge!(api_key: Settings.mailsocio.api_key)
9
+ response = HTTPI.post(accounts_url, { account: attrs}.to_query)
10
+ raise "Could not create account:\n#{response.body}" if response.error?
11
+
12
+ JSON.parse(response.body).tap do |json|
13
+ self.account_id = json['id']
14
+ end
15
+ end
16
+
17
+ def read_account
18
+ response = HTTPI.get(accounts_url(self.account_id))
19
+ raise "Could not read account:\n#{response.body}" if response.error?
20
+
21
+ JSON.parse(response.body)
22
+ end
23
+
24
+ def newsletter_statistics(newsletter_id)
25
+ response = HTTPI.get(newsletter_statistics_url(newsletter_id))
26
+ raise "Could not get newsletter statistics:\n#{response.body}" if response.error?
27
+
28
+ JSON.parse(response.body)
29
+ end
30
+
31
+ def deliver(fields = {}, &block)
32
+ config = Settings.mailsocio.mail
33
+ fields.reverse_merge!(
34
+ 'X-Account-Id' => account_id,
35
+ 'X-Api-Key' => Settings.mailsocio.api_key
36
+ )
37
+ fields.reverse_merge!(config.message)
38
+
39
+ Mail::Message.new(fields, &block).tap do |mail|
40
+ mail.subject ||= 'Good news, everyone!'
41
+ delivery = config.delivery
42
+ mail.delivery_method(delivery.method.to_sym, delivery.settings.symbolize_keys)
43
+ mail.deliver
44
+ end
45
+ end
46
+
47
+ def read_message(user = 'john')
48
+ 10.times do
49
+ message = retriever(user).first(:delete_after_find => true)
50
+ return message if message.present?
51
+
52
+ sleep 10
53
+ end
54
+
55
+ raise 'Could not read message'
56
+ end
57
+
58
+ def clean_messages(user = 'john')
59
+ retriever(user).delete_all
60
+ end
61
+
62
+ def retriever(user)
63
+ address = Settings.trap.addresses[user]
64
+ server = Settings.trap.server
65
+ settings = server.settings.symbolize_keys
66
+ settings.merge!(user_name: address)
67
+
68
+ Mail::Configuration.instance.retriever_method(server.method.to_sym, settings)
69
+ end
70
+
71
+ def accounts_url(account_id = nil)
72
+ if account_id.blank?
73
+ app_url('/api/accounts')
74
+ else
75
+ app_url("/api/accounts/#{account_id}")
76
+ end
77
+ end
78
+
79
+ def newsletter_statistics_url(newsletter_id)
80
+ app_url("/api/accounts/#{account_id}/newsletters/#{newsletter_id}/statistics")
81
+ end
82
+
83
+ def app_url(path)
84
+ if custom_tracking_domain.blank?
85
+ "http://app.#{Settings.mailsocio.base_domain}#{path}"
86
+ else
87
+ "http://#{self.custom_tracking_domain}#{path}"
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,21 @@
1
+ module MailsocioQA
2
+ class TestHtmlPage
3
+ def initialize(html)
4
+ @doc = Nokogiri::HTML(html)
5
+ end
6
+
7
+ def load
8
+ @doc.css('img').each do |img|
9
+ open(img['src'])
10
+ end
11
+ end
12
+
13
+ def click
14
+ @doc.css('a').each do |a|
15
+ response = HTTPI.get(a['href'])
16
+ raise "Expected redirect but got #{response.code}" unless response.code == 302
17
+ return response.headers['location']
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_support/all'
2
+ require 'json'
3
+ require 'nokogiri'
4
+ require 'mail'
5
+ require 'httpi'
6
+
7
+
8
+ require 'mailsocio_qa/settings'
9
+ require 'mailsocio_qa/test_client'
10
+ require 'mailsocio_qa/test_html_page'
11
+
12
+ require 'minitest'
13
+ require 'mailsocio_qa/mailsocio_test'
@@ -0,0 +1,28 @@
1
+ require 'mailsocio_qa/settings'
2
+
3
+ module Minitest
4
+ def self.plugin_mailsocio_qa_options(opts, options)
5
+ opts.on "-c", "--config PATH", String, "Mailsocio QA config file." do |source|
6
+ MailsocioQA::Settings.source source
7
+ end
8
+ end
9
+
10
+ def self.plugin_mailsocio_qa_init(options)
11
+ MailsocioQA::Settings.source File.expand_path('../../../settings.yml', __FILE__)
12
+ MailsocioQA::Settings.load!
13
+ end
14
+
15
+ class Test
16
+ def self.runnable_methods
17
+ methods_matching(/^test_/)
18
+ end
19
+ end
20
+
21
+ module Assertions
22
+ def assert_wont_raise error_message
23
+ yield
24
+ rescue
25
+ assert false, error_message
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'anadea-mailsocio-qa'
3
+ s.version = '0.1.0'
4
+ s.platform = Gem::Platform::RUBY
5
+ s.summary = "Mailsocio Quality Assurance"
6
+ s.description = s.summary
7
+ s.authors = 'Anadea'
8
+
9
+ s.files = `git ls-files`.split("\n")
10
+ s.executables = 'mailsocio-qa'
11
+ s.require_paths = ['lib']
12
+
13
+ s.add_dependency 'minitest', ['>= 4.0']
14
+ s.add_dependency 'httpi', ['>= 2.0']
15
+ s.add_dependency 'mail', ['>= 2.0']
16
+ s.add_dependency 'json', ['>= 1.0']
17
+ s.add_dependency 'activesupport', ['>= 3.0', '< 5.0']
18
+ s.add_dependency 'settingslogic', ['>= 2.0']
19
+ s.add_dependency 'nokogiri', ['>= 1.0']
20
+ end
data/settings.yml ADDED
@@ -0,0 +1,27 @@
1
+ mailsocio:
2
+ base_domain: sendbrain.dev
3
+ api_key: "91d10da569d04883f0345e21c7b16e6caa7cea3eb84f3538a799cf96f3acb9c4"
4
+ mail:
5
+ delivery:
6
+ method: smtp
7
+ settings:
8
+ address: app.sendbrain.dev
9
+ port: 25
10
+ message:
11
+ from: tester@mailsocio.com
12
+ to: submit@app.sendbrain.dev
13
+ trap:
14
+ server:
15
+ method: pop3
16
+ settings:
17
+ password: epyfnm123
18
+ address: pop.gmail.com
19
+ port: 995
20
+ enable_ssl: true
21
+
22
+ addresses:
23
+ john: john.doe@raven.org.ua
24
+ jane: jane.doe@raven.org.ua
25
+ invalid: invalid@raven.org.ua
26
+
27
+ custom_tracking_domain: emailz.raven.org.ua
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: anadea-mailsocio-qa
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Anadea
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-11 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: '4.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '4.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: httpi
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mail
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: json
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ - - "<"
77
+ - !ruby/object:Gem::Version
78
+ version: '5.0'
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '3.0'
86
+ - - "<"
87
+ - !ruby/object:Gem::Version
88
+ version: '5.0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: settingslogic
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '2.0'
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '2.0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: nokogiri
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '1.0'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '1.0'
117
+ description: Mailsocio Quality Assurance
118
+ email:
119
+ executables:
120
+ - mailsocio-qa
121
+ extensions: []
122
+ extra_rdoc_files: []
123
+ files:
124
+ - ".gitignore"
125
+ - Gemfile
126
+ - Gemfile.lock
127
+ - Rakefile
128
+ - bin/mailsocio-qa
129
+ - lib/mailsocio_qa.rb
130
+ - lib/mailsocio_qa/mailsocio_test.rb
131
+ - lib/mailsocio_qa/settings.rb
132
+ - lib/mailsocio_qa/test_client.rb
133
+ - lib/mailsocio_qa/test_html_page.rb
134
+ - lib/minitest/mailsocio_qa_plugin.rb
135
+ - mailsocio-qa.gemspec
136
+ - settings.yml
137
+ homepage:
138
+ licenses: []
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.4.5
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Mailsocio Quality Assurance
160
+ test_files: []
161
+ has_rdoc: