paradocs 1.0.22

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.
@@ -0,0 +1,416 @@
1
+ require "spec_helper"
2
+
3
+ describe Paradocs::Field do
4
+ let(:context) { Paradocs::Context.new }
5
+
6
+ subject { described_class.new(:a_key) }
7
+
8
+ def register_coercion(name, block)
9
+ Paradocs.registry.policy name do
10
+ coerce &block
11
+ end
12
+ end
13
+
14
+ def resolve(subject, payload)
15
+ subject.resolve(payload, context)
16
+ end
17
+
18
+ def has_errors
19
+ expect(context.errors.keys).not_to be_empty
20
+ end
21
+
22
+ def no_errors
23
+ expect(context.errors.keys).to be_empty
24
+ end
25
+
26
+ def has_error(key, message)
27
+ expect(context.errors[key]).to include(message)
28
+ end
29
+
30
+ let(:payload) { {a_key: "Joe"} }
31
+
32
+ describe "#resolve" do
33
+ it "returns value" do
34
+ resolve(subject, payload).tap do |r|
35
+ expect(r.eligible?).to be true
36
+ no_errors
37
+ expect(r.value).to eq "Joe"
38
+ end
39
+ end
40
+ end
41
+
42
+ BUILT_IN_COERCIONS = [:string, :integer, :number, :array, :object, :boolean]
43
+
44
+ describe "#meta_data" do
45
+ BUILT_IN_COERCIONS.each do |t|
46
+ it "policy #{t} adds #{t} to meta data" do
47
+ subject.policy(t)
48
+ expect(subject.meta_data[:type]).to eq t
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#type" do
54
+ it "is an alias for #policy" do
55
+ subject.type(:integer)
56
+ resolve(subject, a_key: "10.0").tap do |r|
57
+ expect(r.value).to eq 10
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "#policy" do
63
+ it "coerces integer" do
64
+ subject.policy(:integer)
65
+ resolve(subject, a_key: "10.0").tap do |r|
66
+ expect(r.eligible?).to be true
67
+ no_errors
68
+ expect(r.value).to eq 10
69
+ end
70
+ end
71
+
72
+ it "coerces number" do
73
+ subject.policy(:number)
74
+ resolve(subject, a_key: "10.0").tap do |r|
75
+ expect(r.eligible?).to be true
76
+ no_errors
77
+ expect(r.value).to eq 10.0
78
+ end
79
+ end
80
+
81
+ it "coerces string" do
82
+ subject.policy(:string)
83
+ resolve(subject, a_key: 10.0).tap do |r|
84
+ expect(r.eligible?).to be true
85
+ no_errors
86
+ expect(r.value).to eq "10.0"
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "#default" do
92
+ it "is default if missing key" do
93
+ resolve(subject.default("AA"), foobar: 1).tap do |r|
94
+ expect(r.eligible?).to be true
95
+ no_errors
96
+ expect(r.value).to eq "AA"
97
+ end
98
+ end
99
+
100
+ it "returns value if key is present" do
101
+ resolve(subject.default("AA"), a_key: nil).tap do |r|
102
+ expect(r.eligible?).to be true
103
+ no_errors
104
+ expect(r.value).to eq nil
105
+ end
106
+
107
+ resolve(subject.default("AA"), a_key: "abc").tap do |r|
108
+ expect(r.eligible?).to be true
109
+ no_errors
110
+ expect(r.value).to eq "abc"
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "#present" do
116
+ it "is valid if value is present" do
117
+ resolve(subject.present, a_key: "abc").tap do |r|
118
+ expect(r.eligible?).to be true
119
+ no_errors
120
+ expect(r.value).to eq "abc"
121
+ end
122
+ end
123
+
124
+ it "is invalid if value is empty" do
125
+ resolve(subject.present, a_key: "").tap do |r|
126
+ expect(r.eligible?).to be true
127
+ has_errors
128
+ expect(r.value).to eq ""
129
+ end
130
+
131
+ resolve(subject.present, a_key: nil).tap do |r|
132
+ expect(r.eligible?).to be true
133
+ has_errors
134
+ expect(r.value).to eq nil
135
+ end
136
+ end
137
+
138
+ it "is invalid if key is missing" do
139
+ resolve(subject.present, foo: "abc").tap do |r|
140
+ expect(r.eligible?).to be true
141
+ has_errors
142
+ expect(r.value).to eq nil
143
+ end
144
+ end
145
+ end
146
+
147
+ describe "#required" do
148
+ it "is valid if key is present" do
149
+ resolve(subject.required, a_key: "abc").tap do |r|
150
+ expect(r.eligible?).to be true
151
+ no_errors
152
+ expect(r.value).to eq "abc"
153
+ end
154
+ end
155
+
156
+ it "is valid if key is present and value empty" do
157
+ resolve(subject.required, a_key: "").tap do |r|
158
+ expect(r.eligible?).to be true
159
+ no_errors
160
+ expect(r.value).to eq ""
161
+ end
162
+ end
163
+
164
+ it "is invalid if key is missing" do
165
+ resolve(subject.required, foobar: "lala").tap do |r|
166
+ expect(r.eligible?).to be true
167
+ has_errors
168
+ expect(r.value).to eq nil
169
+ end
170
+ end
171
+ end
172
+
173
+ describe "#options" do
174
+ before do
175
+ subject.options(['a', 'b', 'c'])
176
+ end
177
+
178
+ it "resolves if value within options" do
179
+ resolve(subject, a_key: "b").tap do |r|
180
+ expect(r.eligible?).to be true
181
+ no_errors
182
+ expect(r.value).to eq "b"
183
+ end
184
+ end
185
+
186
+ it "resolves if value is array within options" do
187
+ resolve(subject, a_key: ["b", "c"]).tap do |r|
188
+ expect(r.eligible?).to be true
189
+ no_errors
190
+ expect(r.value).to eq ["b", "c"]
191
+ end
192
+ end
193
+
194
+ it "does not resolve if missing key" do
195
+ resolve(subject, foobar: ["b", "c"]).tap do |r|
196
+ expect(r.eligible?).to be false
197
+ no_errors
198
+ expect(r.value).to be_nil
199
+ end
200
+ end
201
+
202
+ it "does resolve if missing key and default set" do
203
+ subject.default("Foobar")
204
+ resolve(subject, foobar: ["b", "c"]).tap do |r|
205
+ expect(r.eligible?).to be true
206
+ has_errors
207
+ expect(r.value).to eq "Foobar"
208
+ end
209
+ end
210
+
211
+ it "is invalid if missing key and required" do
212
+ subject = described_class.new(:a_key).required.options(%w(a b c))
213
+ resolve(subject, foobar: ["b", "c"]).tap do |r|
214
+ expect(r.eligible?).to be true
215
+ has_errors
216
+ expect(r.value).to be_nil
217
+ end
218
+ end
219
+
220
+ it "is invalid if value outside options" do
221
+ resolve(subject, a_key: "x").tap do |r|
222
+ expect(r.eligible?).to be true
223
+ has_errors
224
+ expect(r.value).to eq "x"
225
+ end
226
+
227
+ resolve(subject, a_key: ["x", "b"]).tap do |r|
228
+ expect(r.eligible?).to be true
229
+ has_errors
230
+ expect(r.value).to eq ["x", "b"]
231
+ end
232
+ end
233
+ end
234
+
235
+ describe ":split policy" do
236
+ it "splits by comma" do
237
+ resolve(subject.policy(:split), a_key: "tag1,tag2").tap do |r|
238
+ expect(r.eligible?).to be true
239
+ no_errors
240
+ expect(r.value).to eq ["tag1", "tag2"]
241
+ end
242
+ end
243
+ end
244
+
245
+ describe ":declared policy" do
246
+ it "is eligible if key exists" do
247
+ resolve(subject.policy(:declared).present, a_key: "").tap do |r|
248
+ expect(r.eligible?).to be true
249
+ has_errors
250
+ expect(r.value).to eq ""
251
+ end
252
+ end
253
+
254
+ it "is available as method" do
255
+ resolve(subject.declared.present, a_key: "").tap do |r|
256
+ expect(r.eligible?).to be true
257
+ has_errors
258
+ expect(r.value).to eq ""
259
+ end
260
+ end
261
+
262
+ it "is not eligible if key does not exist" do
263
+ resolve(subject.policy(:declared).present, foo: "").tap do |r|
264
+ expect(r.eligible?).to be false
265
+ no_errors
266
+ expect(r.value).to eq nil
267
+ end
268
+ end
269
+ end
270
+
271
+ describe ":noop policy" do
272
+ it "does not do anything" do
273
+ resolve(subject.policy(:noop).present, a_key: "").tap do |r|
274
+ expect(r.eligible?).to be true
275
+ has_errors
276
+ expect(r.value).to eq ""
277
+ end
278
+
279
+ resolve(subject.policy(:noop).present, foo: "").tap do |r|
280
+ expect(r.eligible?).to be true
281
+ has_errors
282
+ expect(r.value).to eq nil
283
+ end
284
+ end
285
+ end
286
+
287
+ describe "#schema" do
288
+ it "runs sub-schema" do
289
+ subject.schema do
290
+ field(:name).policy(:string)
291
+ field(:tags).policy(:split).policy(:array)
292
+ end
293
+
294
+ payload = {a_key: [{name: "n1", tags: "t1,t2"}, {name: "n2", tags: ["t3"]}]}
295
+
296
+ resolve(subject, payload).tap do |r|
297
+ expect(r.eligible?).to be true
298
+ no_errors
299
+ expect(r.value).to eq([
300
+ {name: "n1", tags: ["t1", "t2"]},
301
+ {name: "n2", tags: ["t3"]},
302
+ ])
303
+ end
304
+ end
305
+ end
306
+
307
+ describe '#policy' do
308
+ let(:custom_klass) do
309
+ Class.new do
310
+ def initialize(title = 'Sr.')
311
+ @title = title
312
+ end
313
+
314
+ def eligible?(*_)
315
+ true
316
+ end
317
+
318
+ def valid?(*_)
319
+ true
320
+ end
321
+
322
+ def coerce(value, key, context)
323
+ "#{@title} #{value}"
324
+ end
325
+
326
+ def meta_data
327
+ {foo: "bar"}
328
+ end
329
+
330
+ def policy_name
331
+ :custom_policy
332
+ end
333
+ end
334
+ end
335
+
336
+ it 'works with policy in registry' do
337
+ register_coercion :foo, ->(v, k, c){ "Hello #{v}" }
338
+ subject.policy(:foo)
339
+ resolve(subject, a_key: "Ismael").tap do |r|
340
+ expect(r.eligible?).to be true
341
+ no_errors
342
+ expect(r.value).to eq "Hello Ismael"
343
+ end
344
+ end
345
+
346
+ it 'raises if policy not found' do
347
+ expect{
348
+ subject.policy(:foobar)
349
+ }.to raise_exception Paradocs::ConfigurationError
350
+ end
351
+
352
+ it 'chains policies' do
353
+ Paradocs.registry.policy :general, custom_klass.new("General")
354
+ Paradocs.registry.policy :commander, custom_klass.new("Commander")
355
+
356
+ subject
357
+ .policy(:general)
358
+ .policy(:commander)
359
+
360
+ resolve(subject, a_key: "Ismael").tap do |r|
361
+ expect(r.eligible?).to be true
362
+ no_errors
363
+ expect(r.value).to eq "Commander General Ismael"
364
+ end
365
+ end
366
+
367
+ it "can instantiate policy class and pass arguments" do
368
+ Paradocs.registry.policy :job_title, custom_klass
369
+
370
+ subject.policy(:job_title, "Developer")
371
+
372
+ resolve(subject, a_key: "Ismael").tap do |r|
373
+ expect(r.eligible?).to be true
374
+ no_errors
375
+ expect(r.value).to eq "Developer Ismael"
376
+ end
377
+ end
378
+
379
+ it "can take a class not in the registry" do
380
+ subject.policy(custom_klass, "Developer")
381
+
382
+ resolve(subject, a_key: "Ismael").tap do |r|
383
+ expect(r.eligible?).to be true
384
+ no_errors
385
+ expect(r.value).to eq "Developer Ismael"
386
+ end
387
+ end
388
+
389
+ it "adds policy meta data" do
390
+ subject.policy(custom_klass, "Developer")
391
+ expect(subject.meta_data[:foo]).to eq "bar"
392
+ end
393
+
394
+ it "can take an instance not in the registry" do
395
+ subject.policy(custom_klass.new("Developer"), "ignore this")
396
+
397
+ resolve(subject, a_key: "Ismael").tap do |r|
398
+ expect(r.eligible?).to be true
399
+ no_errors
400
+ expect(r.value).to eq "Developer Ismael"
401
+ end
402
+ end
403
+
404
+ it 'add policy message to #errors if validation fails' do
405
+ register_coercion :error, ->(v, k, c) { raise "This is an error" }
406
+
407
+ subject.policy(:error)
408
+
409
+ resolve(subject, a_key: "b").tap do |r|
410
+ expect(r.eligible?).to be true
411
+ has_error("$", "is invalid")
412
+ expect(r.value).to eq "b"
413
+ end
414
+ end
415
+ end
416
+ end
data/spec/helpers.rb ADDED
@@ -0,0 +1,18 @@
1
+ def expected_policy_behavior(policy:, policy_args: [], input:, output: nil, errors: {}, environment: {}, ignore_for: [])
2
+ output ||= input
3
+ schema = Paradocs::Schema.new do
4
+ input.map do |key, value|
5
+ instruction = ignore_for.include?(key) ? -> { field(key) } : -> { field(key).policy(*([policy] + policy_args)) }
6
+
7
+ instance_exec &instruction
8
+ end
9
+ end
10
+
11
+ if block_given? || output.nil?
12
+ expect { schema.resolve(input, environment) }.to yield
13
+ else
14
+ result = schema.resolve(input, environment)
15
+ expect(result.output).to eq(output)
16
+ expect(result.errors).to eq(errors)
17
+ end
18
+ end
@@ -0,0 +1,159 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'default coercions' do
4
+ def test_coercion(key, value, expected)
5
+ coercion = Paradocs.registry.coercions[key]
6
+ expect(coercion.new.coerce(value, nil, nil)).to eq expected
7
+ end
8
+
9
+ describe ':datetime' do
10
+ it {
11
+ coercion = Paradocs.registry.coercions[:datetime]
12
+ coercion.new.coerce("2016-11-05T14:23:34Z", nil, nil).tap do |d|
13
+ expect(d).to be_a Date
14
+ expect(d.year).to eq 2016
15
+ expect(d.month).to eq 11
16
+ expect(d.day).to eq 5
17
+ expect(d.hour).to eq 14
18
+ expect(d.minute).to eq 23
19
+ expect(d.second).to eq 34
20
+ expect(d.zone).to eq "+00:00"
21
+ end
22
+ }
23
+ end
24
+
25
+ describe ':integer' do
26
+ it {
27
+ test_coercion(:integer, '10', 10)
28
+ test_coercion(:integer, '10.20', 10)
29
+ test_coercion(:integer, 10.20, 10)
30
+ test_coercion(:integer, 10, 10)
31
+ }
32
+ end
33
+
34
+ describe ':number' do
35
+ it {
36
+ test_coercion(:number, '10', 10.0)
37
+ test_coercion(:number, '10.20', 10.20)
38
+ test_coercion(:number, 10.20, 10.20)
39
+ test_coercion(:number, 10, 10.0)
40
+ }
41
+ end
42
+
43
+ describe ':string' do
44
+ it {
45
+ test_coercion(:string, '10', '10')
46
+ test_coercion(:string, '10.20', '10.20')
47
+ test_coercion(:string, 10.20, '10.2')
48
+ test_coercion(:string, 10, '10')
49
+ test_coercion(:string, true, 'true')
50
+ test_coercion(:string, 'hello', 'hello')
51
+ }
52
+ end
53
+
54
+ describe ':boolean' do
55
+ it {
56
+ test_coercion(:boolean, true, true)
57
+ test_coercion(:boolean, '10', true)
58
+ test_coercion(:boolean, '', true)
59
+ test_coercion(:boolean, nil, false)
60
+ test_coercion(:boolean, false, false)
61
+ }
62
+ end
63
+
64
+ describe ':split' do
65
+ it {
66
+ test_coercion(:split, 'aaa,bb,cc', ['aaa', 'bb', 'cc'])
67
+ test_coercion(:split, 'aaa ,bb, cc', ['aaa', 'bb', 'cc'])
68
+ test_coercion(:split, 'aaa', ['aaa'])
69
+ test_coercion(:split, ['aaa', 'bb', 'cc'], ['aaa', 'bb', 'cc'])
70
+ }
71
+ end
72
+
73
+ describe ':gt' do
74
+ it "passes the value if it's greater than policy param" do
75
+ expected_policy_behavior(policy: :gt, policy_args: [3], input: {a: 4}, output: {a: 4})
76
+ end
77
+
78
+ it "raises error if value is greater to policy param" do
79
+ errors = {"$.a"=>["value must be strictly greater than 10"]}
80
+ errors2 = {"$.a"=>["value must be strictly greater than 10"]}
81
+ expected_policy_behavior(policy: :gt, policy_args: [10], input: {a: 4}, errors: errors)
82
+ expected_policy_behavior(policy: :gt, policy_args: [10], input: {a: 10}, errors: errors2)
83
+ end
84
+ end
85
+
86
+ describe ':gte' do
87
+ it "passes the value if it's greater or equal to policy param" do
88
+ expected_policy_behavior(policy: :gte, policy_args: [3], input: {a: 4}, output: {a: 4})
89
+ expected_policy_behavior(policy: :gte, policy_args: [3], input: {a: 3}, output: {a: 3})
90
+ end
91
+
92
+ it "raises error if value is greater than policy param" do
93
+ errors = {"$.a"=>["value must be greater than or equal to 10"]}
94
+ expected_policy_behavior(policy: :gte, policy_args: [10], input: {a: 4}, errors: errors)
95
+ end
96
+ end
97
+
98
+ describe ':lt' do
99
+ it "passes the value if it's less to policy param" do
100
+ expected_policy_behavior(policy: :lt, policy_args: [100], input: {a: 4}, output: {a: 4})
101
+ end
102
+
103
+ it "raises error if value is strictly less to policy param" do
104
+ errors = {"$.a"=>["value must be strictly less than 10"]}
105
+ errors2 = {"$.a"=>["value must be strictly less than 10"]}
106
+ expected_policy_behavior(policy: :lt, policy_args: [10], input: {a: 40}, errors: errors)
107
+ expected_policy_behavior(policy: :lt, policy_args: [10], input: {a: 10}, errors: errors2)
108
+ end
109
+ end
110
+
111
+ describe ':lte' do
112
+ it "passes the value if it's less or equal to policy param" do
113
+ expected_policy_behavior(policy: :lte, policy_args: [100], input: {a: 4}, output: {a: 4})
114
+ expected_policy_behavior(policy: :lte, policy_args: [100], input: {a: 100}, output: {a: 100})
115
+ end
116
+
117
+ it "raises error if value is less or equal than policy param" do
118
+ errors = {"$.a"=>["value must be less than or equal to 1"]}
119
+ expected_policy_behavior(policy: :lte, policy_args: [1], input: {a: 40}, output: {a: 40}, errors: errors)
120
+ end
121
+ end
122
+
123
+ describe ':length' do
124
+ it "passes the value if it's between min and max limits" do
125
+ expected_policy_behavior(policy: :length, policy_args: [{min: 5, max: 20}], input: {a: "string"}, output: {a: "string"})
126
+ expected_policy_behavior(policy: :length, policy_args: [{min: 5}], input: {a: "string"}, output: {a: "string"})
127
+ expected_policy_behavior(policy: :length, policy_args: [{max: 20}], input: {a: "string"}, output: {a: "string"})
128
+ end
129
+
130
+ it "passes the value if it's exactly :eq limit" do
131
+ expected_policy_behavior(policy: :length, policy_args: [{eq: 5}], input: {a: "12345"}, output: {a: "12345"})
132
+ end
133
+
134
+ it "raises error if value is not exactly than defined :eq limit" do
135
+ errors = {"$.a"=>["value must be exactly 10 characters"]}
136
+ expected_policy_behavior(policy: :length, policy_args: [{eq: 10}], input: {a: "test"}, output: {a: "test"}, errors: errors)
137
+ end
138
+
139
+ it "raises error if value is less than min limit" do
140
+ errors = {"$.a"=>["value must be minimum 10 characters"]}
141
+ expected_policy_behavior(policy: :length, policy_args: [{min: 10}], input: {a: "test"}, output: {a: "test"}, errors: errors)
142
+ end
143
+
144
+ it "raises error if value is greater than max limit" do
145
+ errors = {"$.a"=>["value must be maximum 3 characters"]}
146
+ expected_policy_behavior(policy: :length, policy_args: [{max: 3}], input: {a: "test"}, output: {a: "test"}, errors: errors)
147
+ end
148
+
149
+ it "raises error with full description if value is less than min limit" do
150
+ errors = {"$.a"=>["value must be minimum 10 characters, maximum 20 characters"]}
151
+ expected_policy_behavior(policy: :length, policy_args: [{min: 10, max: 20}], input: {a: "test"}, output: {a: "test"}, errors: errors)
152
+ end
153
+
154
+ it "raises error with full description if value is greater than max limit" do
155
+ errors = {"$.a"=>["value must be minimum 3 characters, maximum 5 characters"]}
156
+ expected_policy_behavior(policy: :length, policy_args: [{min: 3, max: 5}], input: {a: "test_test"}, output: {a: "test_test"}, errors: errors)
157
+ end
158
+ end
159
+ end