spf-query 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.
- checksums.yaml +7 -0
- data/.gitignore +57 -0
- data/.rspec +1 -0
- data/.travis.yml +18 -0
- data/ChangeLog.md +6 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +20 -0
- data/README.md +75 -0
- data/Rakefile +23 -0
- data/bin/spf-query +28 -0
- data/lib/resolv/dns/resource/in/spf.rb +9 -0
- data/lib/spf/query.rb +3 -0
- data/lib/spf/query/exceptions.rb +8 -0
- data/lib/spf/query/ip.rb +22 -0
- data/lib/spf/query/macro.rb +28 -0
- data/lib/spf/query/macro_string.rb +25 -0
- data/lib/spf/query/mechanism.rb +52 -0
- data/lib/spf/query/modifier.rb +24 -0
- data/lib/spf/query/parser.rb +281 -0
- data/lib/spf/query/query.rb +48 -0
- data/lib/spf/query/record.rb +204 -0
- data/lib/spf/query/version.rb +5 -0
- data/spec/mechanism_spec.rb +65 -0
- data/spec/parser_spec.rb +608 -0
- data/spec/query_spec.rb +38 -0
- data/spec/record_spec.rb +212 -0
- data/spec/spec_helper.rb +9 -0
- data/spf-query.gemspec +26 -0
- metadata +120 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spf/query/mechanism'
|
3
|
+
|
4
|
+
describe SPF::Query::Mechanism do
|
5
|
+
let(:name) { :all }
|
6
|
+
let(:qualifier) { :soft_fail }
|
7
|
+
|
8
|
+
subject { described_class.new(name, qualifier: qualifier) }
|
9
|
+
|
10
|
+
describe "#initialize" do
|
11
|
+
it "should set the name" do
|
12
|
+
expect(subject.name).to be :all
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should set qualifier" do
|
16
|
+
expect(subject.qualifier).to be :soft_fail
|
17
|
+
end
|
18
|
+
|
19
|
+
context "when qualifier is omitted" do
|
20
|
+
subject { described_class.new(:all) }
|
21
|
+
|
22
|
+
it "should default qualifier to :pass" do
|
23
|
+
expect(subject.qualifier).to be :pass
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#pass?" do
|
29
|
+
subject { described_class.new(name, qualifier: :pass) }
|
30
|
+
|
31
|
+
it "should check if qualifier is :pass" do
|
32
|
+
expect(subject.pass?).to be true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#fail?" do
|
37
|
+
subject { described_class.new(name, qualifier: :fail) }
|
38
|
+
|
39
|
+
it "should check if qualifier is :fail" do
|
40
|
+
expect(subject.fail?).to be true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#soft_fail?" do
|
45
|
+
subject { described_class.new(name, qualifier: :soft_fail) }
|
46
|
+
|
47
|
+
it "should check if qualifier is :soft_fail" do
|
48
|
+
expect(subject.soft_fail?).to be true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#neutral?" do
|
53
|
+
subject { described_class.new(name, qualifier: :neutral) }
|
54
|
+
|
55
|
+
it "should check if qualifier is :neutral" do
|
56
|
+
expect(subject.neutral?).to be true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#to_s" do
|
61
|
+
it "should map the qualifier back to a Symbol" do
|
62
|
+
expect(subject.to_s).to be == "~#{name}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,608 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'spf/query/parser'
|
3
|
+
|
4
|
+
describe Parser do
|
5
|
+
describe "rules" do
|
6
|
+
describe "record" do
|
7
|
+
subject { super().record }
|
8
|
+
|
9
|
+
it "should parse a version then multiple terms" do
|
10
|
+
expect(subject.parse("v=spf1 -all redirect=_spf.example.com")).to be == {
|
11
|
+
version: 'spf1',
|
12
|
+
|
13
|
+
rules: [
|
14
|
+
{
|
15
|
+
directive: {
|
16
|
+
qualifier: '-',
|
17
|
+
name: "all"
|
18
|
+
}
|
19
|
+
},
|
20
|
+
|
21
|
+
{
|
22
|
+
modifier: {
|
23
|
+
name: 'redirect',
|
24
|
+
value: {macro_string: [{literal: '_spf.example.com'}]}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
]
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "version" do
|
33
|
+
subject { super().version }
|
34
|
+
|
35
|
+
it "should match v=spf1" do
|
36
|
+
expect(subject.parse('v=spf1')).to be == {version: 'spf1'}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "terms" do
|
41
|
+
subject { super().terms }
|
42
|
+
|
43
|
+
it "should parse a single term" do
|
44
|
+
expect(subject.parse("-all")).to be == {
|
45
|
+
directive: {
|
46
|
+
qualifier: '-',
|
47
|
+
name: "all"
|
48
|
+
}
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should parse multiple terms separated by one or more spaces" do
|
53
|
+
expect(subject.parse("-all redirect=_spf.example.com")).to be == [
|
54
|
+
{
|
55
|
+
directive: {
|
56
|
+
qualifier: '-',
|
57
|
+
name: "all"
|
58
|
+
}
|
59
|
+
},
|
60
|
+
|
61
|
+
{
|
62
|
+
modifier: {
|
63
|
+
name: 'redirect',
|
64
|
+
value: {macro_string: [{literal: '_spf.example.com'}]}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "term" do
|
72
|
+
subject { super().term }
|
73
|
+
|
74
|
+
it "should parse a directive" do
|
75
|
+
expect(subject.parse("-all")).to be == {
|
76
|
+
directive: {
|
77
|
+
qualifier: '-',
|
78
|
+
name: "all"
|
79
|
+
}
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should also parse a modifier" do
|
84
|
+
expect(subject.parse('redirect=_spf.example.com')).to be == {
|
85
|
+
modifier: {
|
86
|
+
name: 'redirect',
|
87
|
+
value: {macro_string: [{literal: '_spf.example.com'}]}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "directive" do
|
94
|
+
subject { super().directive }
|
95
|
+
|
96
|
+
it "should parse a mechanism" do
|
97
|
+
expect(subject.parse("all")).to be == {directive: {name: "all"}}
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should parse a mechanism with a qualifier" do
|
101
|
+
expect(subject.parse("-all")).to be == {
|
102
|
+
directive: {
|
103
|
+
qualifier: '-',
|
104
|
+
name: "all"
|
105
|
+
}
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "qualifier" do
|
111
|
+
subject { super().qualifier }
|
112
|
+
|
113
|
+
%w[+ - ~ ?].each do |char|
|
114
|
+
it "should match '#{char}'" do
|
115
|
+
expect(subject.parse(char)).to be == {qualifier: char}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should match other characters" do
|
120
|
+
expect { subject.parse('x') }.to raise_error(Parslet::ParseFailed)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "mechanism" do
|
125
|
+
subject { super().mechanism }
|
126
|
+
|
127
|
+
it "should parse a mechanism" do
|
128
|
+
expect(subject.parse('all')).to be == {name: 'all'}
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "all" do
|
133
|
+
subject { super().all }
|
134
|
+
|
135
|
+
it "should parse \"all\"" do
|
136
|
+
expect(subject.parse('all')).to be == {name: 'all'}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "include" do
|
141
|
+
subject { super().include }
|
142
|
+
|
143
|
+
let(:domain) { 'example.com' }
|
144
|
+
|
145
|
+
it "should parse \"include:domain\"" do
|
146
|
+
expect(subject.parse("include:#{domain}")).to be == {
|
147
|
+
name: 'include',
|
148
|
+
value: {macro_string: [{literal: domain}]}
|
149
|
+
}
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "a" do
|
154
|
+
subject { super().a }
|
155
|
+
|
156
|
+
let(:domain) { 'example.com' }
|
157
|
+
|
158
|
+
it "should parse \"a:domain\"" do
|
159
|
+
expect(subject.parse("a:#{domain}")).to be == {
|
160
|
+
name: 'a',
|
161
|
+
value: {
|
162
|
+
macro_string: [{literal: domain}],
|
163
|
+
}
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
let(:cidr_length) { '30' }
|
168
|
+
|
169
|
+
it "should parse \"a:domain/cidr-length\"" do
|
170
|
+
expect(subject.parse("a:#{domain}/#{cidr_length}")).to be == {
|
171
|
+
name: 'a',
|
172
|
+
value: {
|
173
|
+
macro_string: [{literal: "#{domain}/#{cidr_length}"}]
|
174
|
+
}
|
175
|
+
}
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should parse \"a:/cidr-length\"" do
|
179
|
+
expect(subject.parse("a:/#{cidr_length}")).to be == {
|
180
|
+
name: 'a',
|
181
|
+
value: {
|
182
|
+
macro_string: [{literal: "/#{cidr_length}"}]
|
183
|
+
}
|
184
|
+
}
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
describe "mx" do
|
189
|
+
subject { super().mx }
|
190
|
+
|
191
|
+
let(:domain) { 'example.com' }
|
192
|
+
|
193
|
+
it "should parse \"mx:domain\"" do
|
194
|
+
expect(subject.parse("mx:#{domain}")).to be == {
|
195
|
+
name: 'mx',
|
196
|
+
value: {
|
197
|
+
macro_string: [{literal: domain}]
|
198
|
+
}
|
199
|
+
}
|
200
|
+
end
|
201
|
+
|
202
|
+
let(:cidr_length) { '30' }
|
203
|
+
|
204
|
+
it "should parse \"mx:domain/cidr-length\"" do
|
205
|
+
expect(subject.parse("mx:#{domain}/#{cidr_length}")).to be == {
|
206
|
+
name: 'mx',
|
207
|
+
value: {
|
208
|
+
macro_string: [{literal: "#{domain}/#{cidr_length}"}]
|
209
|
+
}
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should parse \"mx:/cidr-length\"" do
|
214
|
+
expect(subject.parse("mx:/#{cidr_length}")).to be == {
|
215
|
+
name: 'mx',
|
216
|
+
value: {
|
217
|
+
macro_string: [{literal: "/#{cidr_length}"}]
|
218
|
+
}
|
219
|
+
}
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe "ip4" do
|
224
|
+
subject { super().ip4 }
|
225
|
+
|
226
|
+
let(:ip) { '1.2.3.4' }
|
227
|
+
|
228
|
+
it "should parse \"ip4:ip\"" do
|
229
|
+
expect(subject.parse("ip4:#{ip}")).to be == {
|
230
|
+
name: 'ip4',
|
231
|
+
value: {ip: ip}
|
232
|
+
}
|
233
|
+
end
|
234
|
+
|
235
|
+
let(:cidr_length) { '24' }
|
236
|
+
|
237
|
+
it "should parse \"ip4:ip/cidr-length\"" do
|
238
|
+
expect(subject.parse("ip4:#{ip}/#{cidr_length}")).to be == {
|
239
|
+
name: 'ip4',
|
240
|
+
value: {ip: ip, cidr_length: cidr_length}
|
241
|
+
}
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
describe "ip6" do
|
246
|
+
subject { super().ip6 }
|
247
|
+
|
248
|
+
let(:ip) { '2001:0db8:85a3:0000:0000:8a2e:0370:7334' }
|
249
|
+
|
250
|
+
it "should parse \"ip6:ip\"" do
|
251
|
+
expect(subject.parse("ip6:#{ip}")).to be == {
|
252
|
+
name: 'ip6',
|
253
|
+
value: {ip: ip}
|
254
|
+
}
|
255
|
+
end
|
256
|
+
|
257
|
+
let(:cidr_length) { '32' }
|
258
|
+
|
259
|
+
it "should parse \"ip6:ip/cidr-length\"" do
|
260
|
+
expect(subject.parse("ip6:#{ip}/#{cidr_length}")).to be == {
|
261
|
+
name: 'ip6',
|
262
|
+
value: {ip: ip, cidr_length: cidr_length}
|
263
|
+
}
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe "ipv4_cidr_length" do
|
268
|
+
subject { super().ipv4_cidr_length }
|
269
|
+
|
270
|
+
it "should not match \"/\"" do
|
271
|
+
expect { subject.parse("/") }.to raise_error(Parslet::ParseFailed)
|
272
|
+
end
|
273
|
+
|
274
|
+
it "should match \"/1\"" do
|
275
|
+
expect(subject.parse("/1")).to be == {cidr_length: '1'}
|
276
|
+
end
|
277
|
+
|
278
|
+
it "should match \"/123\"" do
|
279
|
+
expect(subject.parse("/123")).to be == {cidr_length: '123'}
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
describe "ipv6_cidr_length" do
|
284
|
+
subject { super().ipv6_cidr_length }
|
285
|
+
|
286
|
+
it "should not match \"/\"" do
|
287
|
+
expect { subject.parse("/") }.to raise_error(Parslet::ParseFailed)
|
288
|
+
end
|
289
|
+
|
290
|
+
it "should match \"/1\"" do
|
291
|
+
expect(subject.parse("/1")).to be == {cidr_length: '1'}
|
292
|
+
end
|
293
|
+
|
294
|
+
it "should match \"/123\"" do
|
295
|
+
expect(subject.parse("/123")).to be == {cidr_length: '123'}
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
describe "exists" do
|
300
|
+
subject { super().exists }
|
301
|
+
|
302
|
+
it "should parse \"exists:domain\"" do
|
303
|
+
expect(subject.parse('exists:%{ir}.sbl.spamhaus.example.org')).to be == {
|
304
|
+
name: 'exists',
|
305
|
+
value: {macro_string: [
|
306
|
+
{macro: {letter: 'i', reverse: 'r'}},
|
307
|
+
{literal: '.sbl.spamhaus.example.org'}
|
308
|
+
]}
|
309
|
+
}
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
describe "modifier" do
|
314
|
+
subject { super().modifier }
|
315
|
+
|
316
|
+
it "should parse a modifier" do
|
317
|
+
expect(subject.parse('redirect=_spf.example.com')).to be == {
|
318
|
+
modifier: {
|
319
|
+
name: 'redirect',
|
320
|
+
value: {macro_string: [{literal: '_spf.example.com'}]}
|
321
|
+
}
|
322
|
+
}
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe "redirect" do
|
327
|
+
subject { super().redirect }
|
328
|
+
|
329
|
+
it "should parse \"redirect=domain\"" do
|
330
|
+
expect(subject.parse('redirect=_spf.example.com')).to be == {
|
331
|
+
name: 'redirect',
|
332
|
+
value: {macro_string: [{literal: '_spf.example.com'}]}
|
333
|
+
}
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
describe "explanation" do
|
338
|
+
subject { super().explanation }
|
339
|
+
|
340
|
+
it "should parse \"exp=domain\"" do
|
341
|
+
expect(subject.parse("exp=explain._spf.%{d}")).to be == {
|
342
|
+
name: 'exp',
|
343
|
+
value: {macro_string: [
|
344
|
+
{literal: 'explain._spf.'},
|
345
|
+
{macro: {letter: 'd'}}
|
346
|
+
]}
|
347
|
+
}
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
describe "unknown_modifier" do
|
352
|
+
subject { super().unknown_modifier }
|
353
|
+
|
354
|
+
it "should parse \"name=\"" do
|
355
|
+
expect(subject.parse("foo=")).to be == {name: 'foo', value: nil}
|
356
|
+
end
|
357
|
+
|
358
|
+
it "should parse \"name=value\"" do
|
359
|
+
expect(subject.parse("foo=bar")).to be == {
|
360
|
+
name: 'foo',
|
361
|
+
value: {macro_string: [{literal: 'bar'}]}
|
362
|
+
}
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
describe "domain_spec" do
|
367
|
+
subject { super().domain_spec }
|
368
|
+
|
369
|
+
it "should not parse \"\"" do
|
370
|
+
expect { subject.parse('') }.to raise_error(Parslet::ParseFailed)
|
371
|
+
end
|
372
|
+
|
373
|
+
it "should parse macro_literals" do
|
374
|
+
expect(subject.parse('AAA')).to be == {macro_string: [{literal: 'AAA'}]}
|
375
|
+
end
|
376
|
+
|
377
|
+
it "should parse macro_expands" do
|
378
|
+
expect(subject.parse('%{s}%{d}')).to be == {macro_string: [
|
379
|
+
{macro: {letter: 's'}},
|
380
|
+
{macro: {letter: 'd'}}
|
381
|
+
]}
|
382
|
+
end
|
383
|
+
|
384
|
+
it "should parse a mixture of macro_literals and macro_expands" do
|
385
|
+
expect(subject.parse('foo.%{s}.bar.%{d}')).to be == {macro_string: [
|
386
|
+
{literal: 'foo.'},
|
387
|
+
{macro: {letter: 's'}},
|
388
|
+
{literal: '.bar.'},
|
389
|
+
{macro: {letter: 'd'}}
|
390
|
+
]}
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
describe "name" do
|
395
|
+
subject { super().name }
|
396
|
+
|
397
|
+
%w[A AAA A123 A_123 A-123 A.123 A123_ A123- A123.].each do |str|
|
398
|
+
it "should parse #{str.inspect}" do
|
399
|
+
expect(subject.parse(str)).to be == str
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
describe "macro_string" do
|
405
|
+
subject { super().macro_string }
|
406
|
+
|
407
|
+
it "should not parse ''" do
|
408
|
+
expect { subject.parse('') }.to raise_error(Parslet::ParseFailed)
|
409
|
+
end
|
410
|
+
|
411
|
+
it "should parse macro_literals" do
|
412
|
+
expect(subject.parse('AAA')).to be == {macro_string: [{literal: 'AAA'}]}
|
413
|
+
end
|
414
|
+
|
415
|
+
it "should parse macro_expands" do
|
416
|
+
expect(subject.parse('%{s}%{d}')).to be == {macro_string: [
|
417
|
+
{macro: {letter: 's'}},
|
418
|
+
{macro: {letter: 'd'}}
|
419
|
+
]}
|
420
|
+
end
|
421
|
+
|
422
|
+
it "should parse a mixture of macro_literals and macro_expands" do
|
423
|
+
expect(subject.parse('foo.%{s}.bar.%{d}')).to be == {macro_string: [
|
424
|
+
{literal: 'foo.'},
|
425
|
+
{macro: {letter: 's'}},
|
426
|
+
{literal: '.bar.'},
|
427
|
+
{macro: {letter: 'd'}}
|
428
|
+
]}
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
describe "macro_string?" do
|
433
|
+
subject { super().macro_string? }
|
434
|
+
|
435
|
+
it "should parse ''" do
|
436
|
+
expect(subject.parse('')).to be == ''
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
describe "macro_expand" do
|
441
|
+
subject { super().macro_expand }
|
442
|
+
|
443
|
+
%w[%% %_ %-].each do |str|
|
444
|
+
it "should parse #{str.inspect}" do
|
445
|
+
expect(subject.parse(str)).to be == {macro: str}
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
it "should parse \"%{s}\"" do
|
450
|
+
expect(subject.parse("%{s}")).to be == {
|
451
|
+
macro: {letter: 's'}
|
452
|
+
}
|
453
|
+
end
|
454
|
+
|
455
|
+
it "should parse \"%{d4}\"" do
|
456
|
+
expect(subject.parse("%{d4}")).to be == {
|
457
|
+
macro: {
|
458
|
+
letter: 'd',
|
459
|
+
digits: '4'
|
460
|
+
}
|
461
|
+
}
|
462
|
+
end
|
463
|
+
|
464
|
+
it "should parse \"%{dr}\"" do
|
465
|
+
expect(subject.parse("%{dr}")).to be == {
|
466
|
+
macro: {
|
467
|
+
letter: 'd',
|
468
|
+
reverse: 'r'
|
469
|
+
}
|
470
|
+
}
|
471
|
+
end
|
472
|
+
|
473
|
+
it "should parse \"%{d2r}\"" do
|
474
|
+
expect(subject.parse("%{d2r}")).to be == {
|
475
|
+
macro: {
|
476
|
+
letter: 'd',
|
477
|
+
digits: '2',
|
478
|
+
reverse: 'r'
|
479
|
+
}
|
480
|
+
}
|
481
|
+
end
|
482
|
+
|
483
|
+
it "should parse \"%{l-}\"" do
|
484
|
+
expect(subject.parse("%{l-}")).to be == {
|
485
|
+
macro: {
|
486
|
+
letter: 'l',
|
487
|
+
delimiters: [{char: '-'}]
|
488
|
+
}
|
489
|
+
}
|
490
|
+
end
|
491
|
+
|
492
|
+
it "should parse \"%{lr-}\"" do
|
493
|
+
expect(subject.parse("%{lr-}")).to be == {
|
494
|
+
macro: {
|
495
|
+
letter: 'l',
|
496
|
+
reverse: 'r',
|
497
|
+
delimiters: [{char: '-'}]
|
498
|
+
}
|
499
|
+
}
|
500
|
+
end
|
501
|
+
|
502
|
+
it "should parse \"%{l1r-}\"" do
|
503
|
+
expect(subject.parse("%{l1r-}")).to be == {
|
504
|
+
macro: {
|
505
|
+
letter: 'l',
|
506
|
+
digits: '1',
|
507
|
+
reverse: 'r',
|
508
|
+
delimiters: [{char: '-'}]
|
509
|
+
}
|
510
|
+
}
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
describe "macro_literal" do
|
515
|
+
subject { super().macro_literal }
|
516
|
+
|
517
|
+
[*"\x21".."\x24", *"\x26".."\x7e"].each do |char|
|
518
|
+
it "should recognize the #{char.inspect} char" do
|
519
|
+
expect(subject.parse(char)).to be == char
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
describe "macro_letter" do
|
525
|
+
subject { super().macro_letter }
|
526
|
+
|
527
|
+
%w[s l o d i p h c r t].each do |char|
|
528
|
+
it "should recognize the '#{char}' char" do
|
529
|
+
expect(subject.parse(char)).to be == char
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
534
|
+
describe "transformers" do
|
535
|
+
subject { super().transformers }
|
536
|
+
|
537
|
+
it "should parse \"\"" do
|
538
|
+
expect(subject.parse("")).to be == ""
|
539
|
+
end
|
540
|
+
|
541
|
+
it "should parse a single digit" do
|
542
|
+
expect(subject.parse("1")).to be == {digits: "1"}
|
543
|
+
end
|
544
|
+
|
545
|
+
it "should parse a multiple digits" do
|
546
|
+
expect(subject.parse("123")).to be == {digits: "123"}
|
547
|
+
end
|
548
|
+
|
549
|
+
it "should parse 'r'" do
|
550
|
+
expect(subject.parse("r")).to be == {reverse: "r"}
|
551
|
+
end
|
552
|
+
|
553
|
+
it "should parse a single digit then 'r'" do
|
554
|
+
expect(subject.parse("1r")).to be == {digits: "1", reverse: "r"}
|
555
|
+
end
|
556
|
+
|
557
|
+
it "should parse a multiple digits then 'r'" do
|
558
|
+
expect(subject.parse("123r")).to be == {digits: "123", reverse: "r"}
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
describe "delimiter" do
|
563
|
+
subject { super().delimiter }
|
564
|
+
|
565
|
+
%w[- . + , / _ =].each do |char|
|
566
|
+
it "should match '#{char}'" do
|
567
|
+
expect(subject.parse(char)).to be == {char: char}
|
568
|
+
end
|
569
|
+
end
|
570
|
+
|
571
|
+
it "should not match other characters" do
|
572
|
+
expect { subject.parse('x') }.to raise_error(Parslet::ParseFailed)
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
describe Parser::Transform do
|
578
|
+
describe "directive" do
|
579
|
+
subject do
|
580
|
+
super().apply(directive: {qualifier: '~', name: 'all'})
|
581
|
+
end
|
582
|
+
|
583
|
+
it "should map directives to Mechanism objects" do
|
584
|
+
expect(subject).to be_kind_of(Mechanism)
|
585
|
+
end
|
586
|
+
|
587
|
+
it "should set the name" do
|
588
|
+
expect(subject.name).to be == :all
|
589
|
+
end
|
590
|
+
|
591
|
+
it "should map qualifier to a Symbol" do
|
592
|
+
expect(subject.qualifier).to be :soft_fail
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
describe ".parse" do
|
598
|
+
let(:spf) do
|
599
|
+
%{v=spf1 ip4:199.16.156.0/22 ip4:199.59.148.0/22 ip4:8.25.194.0/23 ip4:8.25.196.0/23 ip4:204.92.114.203 ip4:204.92.114.204/31 ip4:107.20.52.15 ip4:23.21.83.90 include:_spf.google.com include:_thirdparty.twitter.com all}
|
600
|
+
end
|
601
|
+
|
602
|
+
subject { described_class.parse(spf) }
|
603
|
+
|
604
|
+
it "should return a Record" do
|
605
|
+
expect(subject).to be_kind_of(Record)
|
606
|
+
end
|
607
|
+
end
|
608
|
+
end
|