sms_aero 0.0.1

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +7 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +12 -0
  6. data/.travis.yml +17 -0
  7. data/Gemfile +12 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +126 -0
  10. data/Rakefile +6 -0
  11. data/lib/sms_aero.rb +55 -0
  12. data/lib/sms_aero/models/answer.rb +8 -0
  13. data/lib/sms_aero/models/sms.rb +15 -0
  14. data/lib/sms_aero/models/tariff.rb +8 -0
  15. data/lib/sms_aero/operations/add_blacklist.rb +11 -0
  16. data/lib/sms_aero/operations/add_group.rb +11 -0
  17. data/lib/sms_aero/operations/add_phone.rb +17 -0
  18. data/lib/sms_aero/operations/check_balance.rb +11 -0
  19. data/lib/sms_aero/operations/check_groups.rb +12 -0
  20. data/lib/sms_aero/operations/check_senders.rb +15 -0
  21. data/lib/sms_aero/operations/check_sending.rb +11 -0
  22. data/lib/sms_aero/operations/check_sign.rb +15 -0
  23. data/lib/sms_aero/operations/check_status.rb +11 -0
  24. data/lib/sms_aero/operations/check_tariff.rb +12 -0
  25. data/lib/sms_aero/operations/delete_group.rb +11 -0
  26. data/lib/sms_aero/operations/delete_phone.rb +12 -0
  27. data/lib/sms_aero/operations/send_sms.rb +17 -0
  28. data/lib/sms_aero/operations/send_to_group.rb +15 -0
  29. data/lib/sms_aero/types/birthday.rb +20 -0
  30. data/lib/sms_aero/types/channel.rb +4 -0
  31. data/lib/sms_aero/types/digital.rb +8 -0
  32. data/lib/sms_aero/types/filled_string.rb +3 -0
  33. data/lib/sms_aero/types/future.rb +16 -0
  34. data/lib/sms_aero/types/phone.rb +12 -0
  35. data/lib/sms_aero/types/sign_status.rb +9 -0
  36. data/sms_aero.gemspec +23 -0
  37. data/spec/sms_aero/operations/add_blacklist_spec.rb +88 -0
  38. data/spec/sms_aero/operations/add_group_spec.rb +88 -0
  39. data/spec/sms_aero/operations/add_phone_spec.rb +221 -0
  40. data/spec/sms_aero/operations/check_balance_spec.rb +85 -0
  41. data/spec/sms_aero/operations/check_groups_spec.rb +72 -0
  42. data/spec/sms_aero/operations/check_senders_spec.rb +98 -0
  43. data/spec/sms_aero/operations/check_sending_spec.rb +88 -0
  44. data/spec/sms_aero/operations/check_sign_spec.rb +87 -0
  45. data/spec/sms_aero/operations/check_status_spec.rb +88 -0
  46. data/spec/sms_aero/operations/check_tariff_spec.rb +72 -0
  47. data/spec/sms_aero/operations/delete_group_spec.rb +88 -0
  48. data/spec/sms_aero/operations/delete_phone_spec.rb +118 -0
  49. data/spec/sms_aero/operations/send_sms_spec.rb +179 -0
  50. data/spec/sms_aero/operations/send_to_group_spec.rb +171 -0
  51. data/spec/sms_aero/types/birthday_spec.rb +33 -0
  52. data/spec/sms_aero/types/channel_spec.rb +19 -0
  53. data/spec/sms_aero/types/digital_spec.rb +15 -0
  54. data/spec/sms_aero/types/future_spec.rb +38 -0
  55. data/spec/sms_aero/types/phone_spec.rb +21 -0
  56. data/spec/sms_aero/types/sign_status_spec.rb +19 -0
  57. data/spec/spec_helper.rb +20 -0
  58. metadata +205 -0
@@ -0,0 +1,11 @@
1
+ class SmsAero
2
+ operation :check_status do
3
+ documentation "https://smsaero.ru/api/description/#check-status"
4
+
5
+ path { "status" }
6
+
7
+ query do
8
+ attribute :id, Types::Coercible::String.constrained(filled: true)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class SmsAero
2
+ operation :check_tariff do
3
+ documentation "https://smsaero.ru/api/description/#get-balance"
4
+
5
+ path { "checktarif" }
6
+
7
+ response :success, 200, format: :json do
8
+ attribute :result, Types::FilledString
9
+ attribute :reason, Tariff, as: :tariff
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ class SmsAero
2
+ operation :delete_group do
3
+ documentation "https://smsaero.ru/api/description/#groups"
4
+
5
+ path { "delgroup" }
6
+
7
+ query do
8
+ attribute :group, Types::FilledString
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class SmsAero
2
+ operation :delete_phone do
3
+ documentation "https://smsaero.ru/api/description/#contacts"
4
+
5
+ path { "delphone" }
6
+
7
+ query do
8
+ attribute :phone, Types::Phone
9
+ attribute :group, Types::FilledString, optional: true
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ class SmsAero
2
+ operation :send_sms do
3
+ documentation "https://smsaero.ru/api/description/#send-sms"
4
+
5
+ path do |test: false, **|
6
+ test ? "testsend" : "send"
7
+ end
8
+
9
+ query model: Sms do
10
+ attribute :to, Types::Phone
11
+ end
12
+
13
+ response :success, 200, format: :json, model: Answer do
14
+ attribute :id, Types::Coercible::String
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ class SmsAero
2
+ operation :send_to_group do
3
+ documentation "https://smsaero.ru/api/description/#send-sms"
4
+
5
+ path { "sendtogroup" }
6
+
7
+ query model: Sms do
8
+ attribute :group, Types::FilledString, default: proc { "all" }
9
+ end
10
+
11
+ response :success, 200, format: :json, model: Answer do
12
+ attribute :id, Types::Coercible::String, optional: true
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,20 @@
1
+ module SmsAero::Types
2
+ # Describes a user's birthday in year-month-date format
3
+ # Accepts dates, times, datetimes, and strings parceable to dates
4
+ #
5
+ # @example
6
+ # SmsAero::Types::Birthday["1901-12-9"] # => "1901-12-09"
7
+ # SmsAero::Types::Birthday[Time.new(1901, 12, 9, 10, 12)] # => "1901-12-09"
8
+ #
9
+ Birthday = Strict::String
10
+ .constrained(format: /\A\d{4}-\d{2}-\d{2}\z/)
11
+ .constructor do |value|
12
+ begin
13
+ date = value.to_date if value.respond_to? :to_date
14
+ date ||= ::Date.parse(value.to_s)
15
+ date.strftime "%Y-%m-%d"
16
+ rescue
17
+ raise TypeError, "#{value.inspect} cannot be coerced to date"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ module SmsAero::Types
2
+ # Describes acceptable channel codes
3
+ Channel = Coercible::Int.constrained included_in: [1, 2, 3, 4, 6]
4
+ end
@@ -0,0 +1,8 @@
1
+ module SmsAero::Types
2
+ # Converts any value to either 1 or 0 flag for digital sending channel
3
+ #
4
+ # @example
5
+ # SmsAero::Types::Digital[true] # => 1
6
+ #
7
+ Digital = Strict::Int.constructor { |value| value ? 1 : 0 }
8
+ end
@@ -0,0 +1,3 @@
1
+ module SmsAero::Types
2
+ FilledString = Coercible::String.constrained(filled: true)
3
+ end
@@ -0,0 +1,16 @@
1
+ module SmsAero::Types
2
+ # Describes coercible Unix time in future
3
+ Future = Strict::Int.constructor do |value|
4
+ begin
5
+ error = TypeError.new "#{value.inspect} is not a valid time in future"
6
+
7
+ time = value.to_time if value.respond_to? :to_time
8
+ time ||= ::Time.parse(value.to_s) unless value.is_a? Numeric
9
+ number = time.to_i
10
+
11
+ (number > ::Time.now.to_i) ? number : raise(error)
12
+ rescue
13
+ raise error
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,12 @@
1
+ module SmsAero::Types
2
+ # Describes a valid phone containing digits without lead zeros
3
+ #
4
+ # @example
5
+ # SmsAero::Types::Phone["07 (123) 134-12-08"] # => "71231341208"
6
+ # SmsAero::Types::Phone["008"] # raises #<Dry::Types::ConstraintError ...>
7
+ #
8
+ Phone = Strict::String.constrained(format: /\A\d{11,13}\z/)
9
+ .constructor do |value|
10
+ value.to_s.scan(/\d/).join[/[^0].*/].to_s
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ module SmsAero::Types
2
+ # Describes statuses of the sign
3
+ SignStatus = Strict::String.constrained included_in: %w(
4
+ accepted
5
+ approved
6
+ rejected
7
+ pending
8
+ )
9
+ end
data/sms_aero.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = "sms_aero"
3
+ gem.version = "0.0.1"
4
+ gem.author = "Andrew Kozin (nepalez)"
5
+ gem.email = "andrew.kozin@gmail.com"
6
+ gem.homepage = "https://github.com/nepalez/sms_aero"
7
+ gem.summary = "HTTP(s) client to SMS Aero API"
8
+ gem.license = "MIT"
9
+
10
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
11
+ gem.test_files = gem.files.grep(/^spec/)
12
+ gem.extra_rdoc_files = Dir["README.md", "LICENSE", "CHANGELOG.md"]
13
+
14
+ gem.required_ruby_version = ">= 2.2"
15
+
16
+ gem.add_runtime_dependency "dry-types", "~> 0.9.1"
17
+ gem.add_runtime_dependency "evil-client", "~> 0.3.0"
18
+
19
+ gem.add_development_dependency "rake", "~> 11.0"
20
+ gem.add_development_dependency "rspec", "~> 3.0"
21
+ gem.add_development_dependency "rubocop", "~> 0.42"
22
+ gem.add_development_dependency "webmock", "~> 2.1"
23
+ end
@@ -0,0 +1,88 @@
1
+ RSpec.describe SmsAero, "#add_blacklist" do
2
+ let(:settings) { { user: "LOGIN", password: "PASSWORD" } }
3
+ let(:client) { described_class.new(settings) }
4
+ let(:params) { { phone: "+7 (018) 132-4388" } }
5
+ let(:answer) { { result: "accepted" } }
6
+
7
+ before { stub_request(:any, //).to_return(body: answer.to_json) }
8
+ subject { client.add_blacklist(params) }
9
+
10
+ context "using ssl via POST:" do
11
+ let(:host) { "https://gate.smsaero.ru/addblacklist" }
12
+ let(:query) { "answer=json&password=PASSWORD&phone=70181324388&user=LOGIN" }
13
+
14
+ it "sends a request" do
15
+ subject
16
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
17
+ end
18
+
19
+ it "returns success" do
20
+ expect(subject).to be_kind_of SmsAero::Answer
21
+ expect(subject.result).to eq "accepted"
22
+ end
23
+ end
24
+
25
+ context "via GET:" do
26
+ let(:host) { "https://gate.smsaero.ru/addblacklist" }
27
+ let(:query) { "answer=json&password=PASSWORD&phone=70181324388&user=LOGIN" }
28
+
29
+ before { settings[:use_post] = false }
30
+
31
+ it "sends a request" do
32
+ subject
33
+ expect(a_request(:get, "#{host}?#{query}")).to have_been_made
34
+ end
35
+ end
36
+
37
+ context "not using ssl:" do
38
+ let(:host) { "http://gate.smsaero.ru/addblacklist" }
39
+ let(:query) { "answer=json&password=PASSWORD&phone=70181324388&user=LOGIN" }
40
+
41
+ before { settings[:use_ssl] = false }
42
+
43
+ it "sends a request via http" do
44
+ subject
45
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
46
+ end
47
+ end
48
+
49
+ context "with custom user:" do
50
+ let(:host) { "https://gate.smsaero.ru/addblacklist" }
51
+ let(:query) { "answer=json&password=PASSWORD&phone=70181324388&user=USER" }
52
+
53
+ before { params[:user] = "USER" }
54
+
55
+ it "sends a request" do
56
+ subject
57
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
58
+ end
59
+ end
60
+
61
+ context "with custom password:" do
62
+ let(:host) { "https://gate.smsaero.ru/addblacklist" }
63
+ let(:query) { "answer=json&password=PSWD&phone=70181324388&user=LOGIN" }
64
+
65
+ before { params[:password] = "PSWD" }
66
+
67
+ it "sends a request" do
68
+ subject
69
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
70
+ end
71
+ end
72
+
73
+ context "with invalid phone:" do
74
+ before { params[:phone] = "foobar23" }
75
+
76
+ it "raises an exception" do
77
+ expect { subject }.to raise_error(TypeError, /23/)
78
+ end
79
+ end
80
+
81
+ context "without a phone:" do
82
+ before { params.delete :phone }
83
+
84
+ it "raises an exception" do
85
+ expect { subject }.to raise_error(KeyError)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,88 @@
1
+ RSpec.describe SmsAero, "#add_group" do
2
+ let(:settings) { { user: "LOGIN", password: "PASSWORD" } }
3
+ let(:client) { described_class.new(settings) }
4
+ let(:params) { { group: "foobar" } }
5
+ let(:answer) { { result: "accepted" } }
6
+
7
+ before { stub_request(:any, //).to_return(body: answer.to_json) }
8
+ subject { client.add_group(params) }
9
+
10
+ context "using ssl via POST:" do
11
+ let(:host) { "https://gate.smsaero.ru/addgroup" }
12
+ let(:query) { "answer=json&group=foobar&password=PASSWORD&user=LOGIN" }
13
+
14
+ it "sends a request" do
15
+ subject
16
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
17
+ end
18
+
19
+ it "returns success" do
20
+ expect(subject).to be_kind_of SmsAero::Answer
21
+ expect(subject.result).to eq "accepted"
22
+ end
23
+ end
24
+
25
+ context "via GET:" do
26
+ let(:host) { "https://gate.smsaero.ru/addgroup" }
27
+ let(:query) { "answer=json&group=foobar&password=PASSWORD&user=LOGIN" }
28
+
29
+ before { settings[:use_post] = false }
30
+
31
+ it "sends a request" do
32
+ subject
33
+ expect(a_request(:get, "#{host}?#{query}")).to have_been_made
34
+ end
35
+ end
36
+
37
+ context "not using ssl:" do
38
+ let(:host) { "http://gate.smsaero.ru/addgroup" }
39
+ let(:query) { "answer=json&group=foobar&password=PASSWORD&user=LOGIN" }
40
+
41
+ before { settings[:use_ssl] = false }
42
+
43
+ it "sends a request" do
44
+ subject
45
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
46
+ end
47
+ end
48
+
49
+ context "with custom user:" do
50
+ let(:host) { "https://gate.smsaero.ru/addgroup" }
51
+ let(:query) { "answer=json&group=foobar&password=PASSWORD&user=USER" }
52
+
53
+ before { params[:user] = "USER" }
54
+
55
+ it "sends a request" do
56
+ subject
57
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
58
+ end
59
+ end
60
+
61
+ context "with custom password:" do
62
+ let(:host) { "https://gate.smsaero.ru/addgroup" }
63
+ let(:query) { "answer=json&group=foobar&password=PSWD&user=LOGIN" }
64
+
65
+ before { params[:password] = "PSWD" }
66
+
67
+ it "sends a request" do
68
+ subject
69
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
70
+ end
71
+ end
72
+
73
+ context "with invalid group:" do
74
+ before { params[:group] = "" }
75
+
76
+ it "raises an exception" do
77
+ expect { subject }.to raise_error(TypeError)
78
+ end
79
+ end
80
+
81
+ context "without a group:" do
82
+ before { params.delete :group }
83
+
84
+ it "raises an exception" do
85
+ expect { subject }.to raise_error(KeyError)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,221 @@
1
+ RSpec.describe SmsAero, "#add_phone" do
2
+ let(:settings) { { user: "LOGIN", password: "PASSWORD" } }
3
+ let(:client) { described_class.new(settings) }
4
+ let(:params) { { phone: "+7 (909) 382-84-45" } }
5
+ let(:answer) { { result: "accepted" } }
6
+
7
+ before { stub_request(:any, //).to_return(body: answer.to_json) }
8
+ subject { client.add_phone(params) }
9
+
10
+ context "using ssl via POST:" do
11
+ let(:host) { "https://gate.smsaero.ru/addphone" }
12
+ let(:query) { "answer=json&password=PASSWORD&phone=79093828445&user=LOGIN" }
13
+
14
+ it "sends a request" do
15
+ subject
16
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
17
+ end
18
+
19
+ it "returns success" do
20
+ expect(subject).to be_kind_of SmsAero::Answer
21
+ expect(subject.result).to eq "accepted"
22
+ end
23
+ end
24
+
25
+ context "via GET:" do
26
+ let(:host) { "https://gate.smsaero.ru/addphone" }
27
+ let(:query) { "answer=json&password=PASSWORD&phone=79093828445&user=LOGIN" }
28
+
29
+ before { settings[:use_post] = false }
30
+
31
+ it "sends a request" do
32
+ subject
33
+ expect(a_request(:get, "#{host}?#{query}")).to have_been_made
34
+ end
35
+ end
36
+
37
+ context "not using ssl:" do
38
+ let(:host) { "http://gate.smsaero.ru/addphone" }
39
+ let(:query) { "answer=json&password=PASSWORD&phone=79093828445&user=LOGIN" }
40
+
41
+ before { settings[:use_ssl] = false }
42
+
43
+ it "sends a request" do
44
+ subject
45
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
46
+ end
47
+ end
48
+
49
+ context "with custom user:" do
50
+ let(:host) { "https://gate.smsaero.ru/addphone" }
51
+ let(:query) { "answer=json&password=PASSWORD&phone=79093828445&user=USER" }
52
+
53
+ before { params[:user] = "USER" }
54
+
55
+ it "sends a request" do
56
+ subject
57
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
58
+ end
59
+ end
60
+
61
+ context "with custom password:" do
62
+ let(:host) { "https://gate.smsaero.ru/addphone" }
63
+ let(:query) { "answer=json&password=PSWD&phone=79093828445&user=LOGIN" }
64
+
65
+ before { params[:password] = "PSWD" }
66
+
67
+ it "sends a request" do
68
+ subject
69
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
70
+ end
71
+ end
72
+
73
+ context "with invalid phone:" do
74
+ before { params[:phone] = "1324" }
75
+
76
+ it "raises an exception" do
77
+ expect { subject }.to raise_error(TypeError, /1324/)
78
+ end
79
+ end
80
+
81
+ context "without a phone:" do
82
+ before { params.delete :phone }
83
+
84
+ it "raises an exception" do
85
+ expect { subject }.to raise_error(KeyError)
86
+ end
87
+ end
88
+
89
+ context "with valid fname:" do
90
+ let(:host) { "https://gate.smsaero.ru/addphone" }
91
+ let(:query) do
92
+ "answer=json&fname=joe&password=PASSWORD&phone=79093828445&user=LOGIN"
93
+ end
94
+
95
+ before { params[:fname] = "joe" }
96
+
97
+ it "sends a request" do
98
+ subject
99
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
100
+ end
101
+ end
102
+
103
+ context "with invalid fname:" do
104
+ before { params[:fname] = "" }
105
+
106
+ it "raises an exception" do
107
+ expect { subject }.to raise_error(TypeError)
108
+ end
109
+ end
110
+
111
+ context "with valid sname:" do
112
+ let(:host) { "https://gate.smsaero.ru/addphone" }
113
+ let(:query) do
114
+ "answer=json&password=PASSWORD&phone=79093828445&sname=joe&user=LOGIN"
115
+ end
116
+
117
+ before { params[:sname] = "joe" }
118
+
119
+ it "sends a request" do
120
+ subject
121
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
122
+ end
123
+ end
124
+
125
+ context "with invalid sname:" do
126
+ before { params[:sname] = "" }
127
+
128
+ it "raises an exception" do
129
+ expect { subject }.to raise_error(TypeError)
130
+ end
131
+ end
132
+
133
+ context "with valid lname:" do
134
+ let(:host) { "https://gate.smsaero.ru/addphone" }
135
+ let(:query) do
136
+ "answer=json&lname=smith&password=PASSWORD&phone=79093828445&user=LOGIN"
137
+ end
138
+
139
+ before { params[:lname] = "smith" }
140
+
141
+ it "sends a request" do
142
+ subject
143
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
144
+ end
145
+ end
146
+
147
+ context "with invalid lname:" do
148
+ before { params[:lname] = "" }
149
+
150
+ it "raises an exception" do
151
+ expect { subject }.to raise_error(TypeError)
152
+ end
153
+ end
154
+
155
+ context "with valid group:" do
156
+ let(:host) { "https://gate.smsaero.ru/addphone" }
157
+ let(:query) do
158
+ "answer=json&group=qux&password=PASSWORD&phone=79093828445&user=LOGIN"
159
+ end
160
+
161
+ before { params[:group] = "qux" }
162
+
163
+ it "sends a request" do
164
+ subject
165
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
166
+ end
167
+ end
168
+
169
+ context "with invalid group:" do
170
+ before { params[:group] = "" }
171
+
172
+ it "raises an exception" do
173
+ expect { subject }.to raise_error(TypeError)
174
+ end
175
+ end
176
+
177
+ context "with valid param:" do
178
+ let(:host) { "https://gate.smsaero.ru/addphone" }
179
+ let(:query) do
180
+ "answer=json&param=qux&password=PASSWORD&phone=79093828445&user=LOGIN"
181
+ end
182
+
183
+ before { params[:param] = "qux" }
184
+
185
+ it "sends a request" do
186
+ subject
187
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
188
+ end
189
+ end
190
+
191
+ context "with invalid param:" do
192
+ before { params[:param] = "" }
193
+
194
+ it "raises an exception" do
195
+ expect { subject }.to raise_error(TypeError)
196
+ end
197
+ end
198
+
199
+ context "with valid bday:" do
200
+ let(:host) { "https://gate.smsaero.ru/addphone" }
201
+ let(:query) do
202
+ "answer=json&bday=1901-08-17&password=PASSWORD&" \
203
+ "phone=79093828445&user=LOGIN"
204
+ end
205
+
206
+ before { params[:bday] = Date.parse("1901-08-17") }
207
+
208
+ it "sends a request" do
209
+ subject
210
+ expect(a_request(:post, "#{host}?#{query}")).to have_been_made
211
+ end
212
+ end
213
+
214
+ context "with invalid bday:" do
215
+ before { params[:bday] = "foo" }
216
+
217
+ it "raises an exception" do
218
+ expect { subject }.to raise_error(TypeError, /foo/)
219
+ end
220
+ end
221
+ end