mail-x_smtpapi-ksr 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2dc958a654b3a3c27b0c08ddbce12d514691889367ee499b3c11e5fe1764f4ba
4
+ data.tar.gz: 3b8ad87c7deeb1e3c357ec42c9d1d2b003cf7e7aa86870f9695c6c8fe97f0a4d
5
+ SHA512:
6
+ metadata.gz: 13191e816e6f83a45426d86d0716771d73befa5347c252981e399aad5a667808965c4b90a5fcfb815f644cd58a6dc947ef13532b7747db67c32bfd0b1c761cfd
7
+ data.tar.gz: 3b6369a8743f6dd7466a06d8110c3e095b74c8f7aedec6ef6d95a4d000b94bbabe7629a0b6fc2bbbe03f21fa0a18b79cf6b18bb96bbb6aa8f9eefe3c11d00d80
@@ -0,0 +1,28 @@
1
+ name: Ruby
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ pull_request:
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ name: Ruby ${{ matrix.ruby }}
14
+ strategy:
15
+ matrix:
16
+ ruby:
17
+ - '3.2'
18
+ - '3.3'
19
+
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - name: Set up Ruby
23
+ uses: ruby/setup-ruby@v1
24
+ with:
25
+ ruby-version: ${{ matrix.ruby }}
26
+ bundler-cache: true
27
+ - name: Run the default task
28
+ run: bundle exec rake
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # CHANGELOG
2
+
3
+ ## Version 1.2.0
4
+
5
+ ### Non-breaking changes
6
+
7
+ * fixes for compatibility with mail 2.7.0 (@cainlevy)
8
+
9
+ ## Version 1.1.0
10
+
11
+ ### Non-breaking changes
12
+
13
+ * add support for asm_group_id, PR #2 (@stevewilhelm)
14
+
15
+ ## Version 1.0.2
16
+
17
+ ### Non-breaking changes
18
+
19
+ * add minitest dependency
20
+ * declare dependency on ruby 2.3.1
21
+
22
+ ## Version 1.0.1
23
+
24
+ ### Non-breaking changes
25
+
26
+ * changed underlying key for sections() from 'sections' to 'section'
27
+
28
+ ## Version 1.0.0
29
+
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development do
4
+ gem 'irb'
5
+ gem 'debug'
6
+ end
7
+
8
+ # Specify your gem's dependencies in mail-x_smtpapi.gemspec
9
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Kickstarter, Inc
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # Mail::X::Smtpapi
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/mail-x_smtpapi.svg)](http://badge.fury.io/rb/mail-x_smtpapi) [![Circle CI](https://img.shields.io/circleci/project/kickstarter/mail-x_smtpapi.svg?style=shield)](https://circleci.com/gh/kickstarter/mail-x_smtpapi/tree/master) [![Code Climate](https://codeclimate.com/github/kickstarter/mail-x_smtpapi/badges/gpa.svg)](https://codeclimate.com/github/kickstarter/mail-x_smtpapi)
4
+
5
+ Integrates support for SendGrid's X-SMTPAPI field into the [Mail](https://github.com/mikel/mail) gem.
6
+
7
+ Please refer to [SendGrid docs](https://sendgrid.com/docs/API_Reference/SMTP_API/index.html) for ideas of what you can do with the X-SMTPAPI header!
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'mail-x_smtpapi'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install mail-x_smtpapi
24
+
25
+ ## Usage
26
+
27
+ Write into the X-SMTPAPI header from anywhere with access to the `mail` object.
28
+
29
+ ### Example: Rails Mailer
30
+
31
+ Adapting the [Rails Guide example](http://guides.rubyonrails.org/v4.0.8/action_mailer_basics.html#edit-the-mailer):
32
+
33
+ ```ruby
34
+ class UserMailer < ActionMailer::Base
35
+
36
+ def welcome(user)
37
+ @user = user
38
+
39
+ mail.smtpapi.category = 'welcome'
40
+ mail.smtpapi.unique_args['user_id'] = @user.id
41
+
42
+ mail(to: @user.email, subject: 'Welcome to My Awesome Site')
43
+ end
44
+
45
+ end
46
+ ```
47
+
48
+ ### Example: Mail Interceptor
49
+
50
+ Adapting the [Rails Guide example](http://guides.rubyonrails.org/v4.0.8/action_mailer_basics.html#intercepting-emails):
51
+
52
+ ```ruby
53
+ class SandboxEmailInterceptor
54
+ def self.delivering_email(message)
55
+ message.smtpapi.unique_args['original'] = message.to
56
+ message.to = ['sandbox@example.com']
57
+ end
58
+ end
59
+ ```
60
+
61
+ ### Example: Template Helper
62
+
63
+ ```ruby
64
+ module MailerABHelper
65
+ def show_variant?(name)
66
+ variant = rand(2) == 0 ? 'main' : 'alternate'
67
+ mail.smtpapi.unique_args["#{name}_variant"] = variant
68
+ return variant == 'alternate'
69
+ end
70
+ end
71
+ ```
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it ( https://github.com/kickstarter/mail-x_smtpapi/fork )
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/*test.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,23 @@
1
+ require_relative '../mail_x_smtpapi/field'
2
+ require 'mail'
3
+
4
+ module Mail
5
+ class Message
6
+ def smtpapi
7
+ header.smtpapi
8
+ end
9
+ end
10
+
11
+ class Header
12
+ def smtpapi
13
+ name = MailXSMTPAPI::Field::FIELD_NAME
14
+ self.fields << Field.new(name) unless self[name]
15
+ self[name]
16
+ end
17
+ end
18
+ end
19
+
20
+ MailXSMTPAPI::Field::FIELD_NAME.tap do |name|
21
+ Mail::Field::FIELDS_MAP[name] = MailXSMTPAPI::Field
22
+ Mail::Field::FIELD_NAME_MAP[name] = MailXSMTPAPI::Field::CAPITALIZED_FIELD
23
+ end
@@ -0,0 +1,2 @@
1
+ require 'mail_x_smtpapi/version.rb'
2
+ require 'mail/x_smtpapi.rb'
@@ -0,0 +1,74 @@
1
+ module MailXSMTPAPI
2
+
3
+ module Recipients
4
+ def to
5
+ data['to'] ||= []
6
+ end
7
+
8
+ def to=(val)
9
+ val = [val] unless val.is_a? Array
10
+ to.replace val
11
+ end
12
+ end
13
+
14
+ module Substitutions
15
+ def substitutions
16
+ data['sub'] ||= {}
17
+ end
18
+
19
+ def substitutions=(val)
20
+ raise ArgumentError unless val.is_a? Hash
21
+ substitutions.replace(val)
22
+ end
23
+
24
+ def merge_substitutions(name, val)
25
+ substitutions[name] ||= []
26
+ val = [val] unless val.is_a? Array
27
+ substitutions[name].concat val.map(&:to_s)
28
+ end
29
+ end
30
+
31
+ module UniqueArguments
32
+ def unique_args
33
+ data['unique_args'] ||= {}
34
+ end
35
+
36
+ def unique_args=(val)
37
+ raise ArgumentError unless val.is_a? Hash
38
+ unique_args.replace(val)
39
+ end
40
+ end
41
+
42
+ module Category
43
+ def category
44
+ data['category']
45
+ end
46
+
47
+ def category=(val)
48
+ data['category'] = val
49
+ end
50
+ end
51
+
52
+ module AsmGroupId
53
+ def asm_group_id
54
+ data['asm_group_id']
55
+ end
56
+
57
+ def asm_group_id=(val)
58
+ data['asm_group_id'] = val
59
+ end
60
+ end
61
+
62
+ module Filters
63
+ def filters
64
+ data['filters'] ||= {}
65
+ end
66
+ end
67
+
68
+ module Sections
69
+ def sections
70
+ data['section'] ||= {}
71
+ end
72
+ end
73
+
74
+ end
@@ -0,0 +1,51 @@
1
+ require 'mail'
2
+ require 'json'
3
+ require_relative 'accessors'
4
+
5
+ module MailXSMTPAPI
6
+ class Field < ::Mail::UnstructuredField
7
+ FIELD_NAME = 'x-smtpapi'
8
+ CAPITALIZED_FIELD = 'X-SMTPAPI'
9
+
10
+ def self.singular? = true
11
+
12
+ attr_reader :data
13
+
14
+ # Accessors
15
+ include Recipients
16
+ include Substitutions
17
+ include UniqueArguments
18
+ include Category
19
+ include AsmGroupId
20
+ include Filters
21
+ include Sections
22
+
23
+ def initialize(value = nil, charset = 'utf-8')
24
+ self.charset = charset
25
+ self.name = CAPITALIZED_FIELD
26
+ @data = value || {}
27
+ end
28
+
29
+ def value
30
+ JSON.generate(data)
31
+ end
32
+
33
+ def encoded
34
+ if empty?
35
+ ''
36
+ else
37
+ "#{wrapped_value}\r\n"
38
+ end
39
+ end
40
+
41
+ # to take advantage of folding, decoded must return a string of
42
+ # JSON with extra spaces inserted for line wrapping.
43
+ def decoded
44
+ value.gsub(/(["\]}])([,:])(["\[{])/, '\\1\\2 \\3')
45
+ end
46
+
47
+ def empty?
48
+ data.values.all?{|v| !v || v.empty? }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module MailXSMTPAPI
2
+ VERSION = '1.3.0'
3
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'mail_x_smtpapi/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "mail-x_smtpapi-ksr"
8
+ spec.version = MailXSMTPAPI::VERSION
9
+ spec.authors = ["Kickstarter"]
10
+ spec.email = ["devops@kickstarter.com"]
11
+ spec.summary = %q{Adds SendGrid X-SMTPAPI header support to Mail}
12
+ spec.homepage = "https://github.com/kickstarter/mail-x_smtpapi"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", ">= 2.2.3"
21
+ spec.add_development_dependency "rake", "~> 13.1"
22
+ spec.add_development_dependency "minitest", "~> 5.21"
23
+
24
+ spec.add_dependency "mail", "~> 2.8.1"
25
+ spec.add_dependency "net-smtp"
26
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MailXSMTPAPI::AccessorsTest < Minitest::Test
4
+
5
+ def test_to
6
+ subject.to = 'a@example.com'
7
+ assert_equal ['a@example.com'], subject.to
8
+ subject.to << 'b@example.com'
9
+ assert_equal ['a@example.com', 'b@example.com'], subject.to
10
+ end
11
+
12
+ def test_substitutions
13
+ subject.substitutions['foo'] = ['bar']
14
+ assert_equal ['bar'], subject.substitutions['foo']
15
+
16
+ subject.merge_substitutions('foo', ['baz', 'qux'])
17
+ assert_equal ['bar', 'baz', 'qux'], subject.substitutions['foo']
18
+ end
19
+
20
+ def test_unique_args
21
+ subject.unique_args['foo'] = 'bar'
22
+ assert_equal 'bar', subject.unique_args['foo']
23
+
24
+ subject.unique_args = {'hello' => 'world'}
25
+ refute subject.unique_args['foo']
26
+ assert_equal 'world', subject.unique_args['hello']
27
+ end
28
+
29
+ def test_category
30
+ subject.category = 'password_reset'
31
+ assert_equal 'password_reset', subject.category
32
+ end
33
+
34
+ def test_asm_group_id
35
+ subject.asm_group_id = 123
36
+ assert_equal 123, subject.asm_group_id
37
+ end
38
+
39
+ private
40
+
41
+ def subject
42
+ @subject ||= MailXSMTPAPI::Field.new
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MailIntegrationTest < Minitest::Test
4
+
5
+ def test_smtpapi_accessor
6
+ assert subject.smtpapi
7
+ assert subject.smtpapi.empty?
8
+
9
+ subject.smtpapi.data['to'] = 'c@example.com'
10
+ refute subject.smtpapi.empty?
11
+
12
+ assert_equal 'c@example.com', subject.smtpapi.data['to']
13
+ assert_equal '{"to":"c@example.com"}', subject.smtpapi.value
14
+ end
15
+
16
+ def test_hash_reader
17
+ assert_equal subject.smtpapi, subject['x-smtpapi']
18
+ assert_equal subject.smtpapi, subject['X_SMTPAPI']
19
+ end
20
+
21
+ def test_hash_writer
22
+ subject['x-smtpapi'] = {'to' => 'c@example.com'}
23
+ assert_equal 'c@example.com', subject.smtpapi.data['to']
24
+ assert_equal '{"to":"c@example.com"}', subject.smtpapi.value
25
+ end
26
+
27
+ def test_limited_field
28
+ subject['x-smtpapi'] = {'to' => 'c@example.com'}
29
+ subject['x-smtpapi'] = {'to' => 'd@example.com'}
30
+
31
+ refute subject['x-smtpapi'].is_a?(Array)
32
+ assert_equal 'd@example.com', subject['x-smtpapi'].data['to']
33
+ assert_equal '{"to":"d@example.com"}', subject.smtpapi.value
34
+ end
35
+
36
+ def test_field_name
37
+ assert_equal 'X-SMTPAPI', subject.smtpapi.name
38
+ end
39
+
40
+ private
41
+
42
+ def subject
43
+ @subject ||= ::Mail.new do
44
+ to 'a@example.com'
45
+ reply_to 'b@example.com'
46
+ date Time.now
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'test_helper'
2
+
3
+ class MailXSMTPAPI::FieldTest < Minitest::Test
4
+ def test_encoded_with_blank_values
5
+ assert_equal '', subject.new.encoded
6
+ end
7
+
8
+ def test_encoded_has_field_name
9
+ f = subject.new({'to' => ['a@example.com', 'b@example.com']})
10
+ assert f.encoded.match(/^X-SMTPAPI: /), f.encoded
11
+ end
12
+
13
+ def test_encoded_has_spaces_in_json
14
+ f = subject.new({'to' => ['a@example.com', 'b@example.com']})
15
+ assert f.encoded.include?('{"to": ["a@example.com", "b@example.com"]}')
16
+ end
17
+
18
+ def test_encoded_ends_with_crlf
19
+ f = subject.new({'to' => ['a@example.com', 'b@example.com']})
20
+ assert f.encoded.match(/\r\n$/)
21
+ end
22
+
23
+ def test_encoded_is_folded
24
+ f = subject.new('to' => 100.times.map{ 'test@example.com'} )
25
+ max_line_length = f.encoded.split(/[\r\n]+/).map(&:length).max
26
+ assert max_line_length < 998, "max line length: #{max_line_length}"
27
+ end
28
+
29
+ def test_decoded
30
+ h = {'to' => ['a@example.com', 'b@example.com']}
31
+ assert_equal '{"to": ["a@example.com", "b@example.com"]}', subject.new(h).decoded
32
+ end
33
+
34
+ def test_empty?
35
+ refute subject.new({'to' => ['a@example.com']}).empty?
36
+ assert subject.new({'to' => []}).empty?
37
+ end
38
+
39
+ private
40
+
41
+ def subject
42
+ MailXSMTPAPI::Field
43
+ end
44
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler.require(:default, :test)
4
+
5
+ require 'minitest/autorun'
6
+
7
+ require 'mail-x_smtpapi'
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mail-x_smtpapi-ksr
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Kickstarter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.2.3
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 2.2.3
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
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.21'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.21'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mail
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.8.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.8.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: net-smtp
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - devops@kickstarter.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".github/workflows/main.yml"
91
+ - ".gitignore"
92
+ - CHANGELOG.md
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - lib/mail-x_smtpapi.rb
98
+ - lib/mail/x_smtpapi.rb
99
+ - lib/mail_x_smtpapi/accessors.rb
100
+ - lib/mail_x_smtpapi/field.rb
101
+ - lib/mail_x_smtpapi/version.rb
102
+ - mail-x_smtpapi.gemspec
103
+ - test/mail_accessors_test.rb
104
+ - test/mail_integration_test.rb
105
+ - test/mail_x_smtpapi_field_test.rb
106
+ - test/test_helper.rb
107
+ homepage: https://github.com/kickstarter/mail-x_smtpapi
108
+ licenses:
109
+ - MIT
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubygems_version: 3.5.23
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Adds SendGrid X-SMTPAPI header support to Mail
130
+ test_files:
131
+ - test/mail_accessors_test.rb
132
+ - test/mail_integration_test.rb
133
+ - test/mail_x_smtpapi_field_test.rb
134
+ - test/test_helper.rb