tracking_number 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,15 +1,16 @@
1
1
  module TrackingNumber
2
2
  class Base
3
- attr_accessor :tracking_number
4
- attr_accessor :original_number
3
+ attr_accessor :tracking_number, :original_number, :partner, :partner_data
4
+
5
+ PartnerStruct = Struct.new(:shipper, :carrier)
5
6
 
6
7
  def initialize(tracking_number)
7
8
  @original_number = tracking_number
8
- @tracking_number = tracking_number.strip.gsub(" ", "").upcase
9
+ @tracking_number = tracking_number.strip.gsub(' ', '').upcase
9
10
  end
10
11
 
11
12
  def self.search(body)
12
- valids = self.scan(body).uniq.collect { |possible| new(possible) }.select { |t| t.valid? }
13
+ valids = scan(body).uniq.collect { |possible| new(possible) }.select { |t| t.valid? }
13
14
 
14
15
  uniques = {}
15
16
  valids.each do |t|
@@ -23,10 +24,10 @@ module TrackingNumber
23
24
  # matches with match groups within the match data
24
25
  matches = []
25
26
 
26
- body.scan(self.const_get(:SEARCH_PATTERN)){
27
- #get the match data instead, which is needed with these types of regexes
27
+ body.scan(const_get(:SEARCH_PATTERN)) do
28
+ # get the match data instead, which is needed with these types of regexes
28
29
  matches << $~
29
- }
30
+ end
30
31
 
31
32
  if matches
32
33
  matches.collect { |m| m[0] }
@@ -36,31 +37,29 @@ module TrackingNumber
36
37
  end
37
38
 
38
39
  def serial_number
39
- return match_group("SerialNumber") unless self.class.const_get("VALIDATION")
40
+ return match_group('SerialNumber') unless self.class.const_get('VALIDATION')
40
41
 
41
42
  format_info = self.class.const_get(:VALIDATION)[:serial_number_format]
42
- raw_serial = match_group("SerialNumber")
43
+ raw_serial = match_group('SerialNumber')
43
44
 
44
- if format_info
45
- if format_info[:prepend_if] && raw_serial.match(Regexp.new(format_info[:prepend_if][:matches_regex]))
46
- return "#{format_info[:prepend_if][:content]}#{raw_serial}"
47
- elsif format_info[:prepend_if_missing]
45
+ if format_info && format_info[:prepend_if] && raw_serial.match(Regexp.new(format_info[:prepend_if][:matches_regex]))
46
+ return "#{format_info[:prepend_if][:content]}#{raw_serial}"
47
+ # elsif format_info && format_info[:prepend_if_missing]
48
48
 
49
- end
50
49
  end
51
50
 
52
- return raw_serial
51
+ raw_serial
53
52
  end
54
53
 
55
54
  def check_digit
56
- match_group("CheckDigit")
55
+ match_group('CheckDigit')
57
56
  end
58
57
 
59
58
  def decode
60
59
  decoded = {}
61
- (self.matches.try(:names) || []).each do |name|
60
+ (matches.try(:names) || []).each do |name|
62
61
  sym = name.underscore.to_sym
63
- decoded[sym] = self.matches[name]
62
+ decoded[sym] = matches[name]
64
63
  end
65
64
 
66
65
  decoded
@@ -70,7 +69,8 @@ module TrackingNumber
70
69
  return false unless valid_format?
71
70
  return false unless valid_checksum?
72
71
  return false unless valid_optional_checks?
73
- return true
72
+
73
+ true
74
74
  end
75
75
 
76
76
  def valid_format?
@@ -78,7 +78,7 @@ module TrackingNumber
78
78
  end
79
79
 
80
80
  def valid_optional_checks?
81
- additional_check = self.class.const_get("VALIDATION")[:additional]
81
+ additional_check = self.class.const_get('VALIDATION')[:additional]
82
82
  return true unless additional_check
83
83
 
84
84
  exist_checks = (additional_check[:exists] ||= [])
@@ -86,8 +86,9 @@ module TrackingNumber
86
86
  end
87
87
 
88
88
  def valid_checksum?
89
- return false unless self.valid_format?
90
- checksum_info = self.class.const_get(:VALIDATION)[:checksum]
89
+ return false unless valid_format?
90
+
91
+ checksum_info = self.class.const_get(:VALIDATION)[:checksum]
91
92
  return true unless checksum_info
92
93
 
93
94
  name = checksum_info[:name]
@@ -97,22 +98,25 @@ module TrackingNumber
97
98
  end
98
99
 
99
100
  def to_s
100
- self.tracking_number
101
+ tracking_number
101
102
  end
102
103
 
103
104
  def inspect
104
- "#<%s:%#0x %s>" % [self.class.to_s, self.object_id, tracking_number]
105
+ format('#<%s:%#0x %s>', self.class.to_s, object_id, tracking_number)
105
106
  end
106
107
 
107
108
  def info
108
109
  Info.new({
109
- :courier => courier_info,
110
- :service_type => service_type,
111
- :service_description => service_description,
112
- :destination_zip => destination_zip,
113
- :shipper_id => shipper_id,
114
- :package_type => package_type
115
- })
110
+ courier: courier_info,
111
+ service_type: service_type,
112
+ service_description: service_description,
113
+ destination_zip: destination_zip,
114
+ shipper_id: shipper_id,
115
+ package_type: package_type,
116
+ tracking_url: tracking_url,
117
+ partners: partners,
118
+ decode: decode
119
+ })
116
120
  end
117
121
 
118
122
  def courier_code
@@ -120,68 +124,88 @@ module TrackingNumber
120
124
  end
121
125
 
122
126
  def courier_name
123
- if matching_additional["Courier"]
124
- matching_additional["Courier"][:courier]
125
- else
126
- if self.class.constants.include?(:COURIER_INFO)
127
- self.class.const_get(:COURIER_INFO)[:name]
128
- end
127
+ if matching_additional['Courier']
128
+ matching_additional['Courier'][:courier]
129
+ elsif self.class.constants.include?(:COURIER_INFO)
130
+ self.class.const_get(:COURIER_INFO)[:name]
129
131
  end
130
132
  end
131
133
 
132
- alias_method :carrier, :courier_code #OG tracking_number gem used :carrier.
133
- alias_method :carrier_code, :courier_code
134
- alias_method :carrier_name, :courier_name
134
+ alias carrier courier_code # OG tracking_number gem used :carrier.
135
+ alias carrier_code courier_code
136
+ alias carrier_name courier_name
135
137
 
136
138
  def courier_info
137
- basics = {:name => courier_name, :code => courier_code}
139
+ basics = { name: courier_name, code: courier_code }
138
140
 
139
- if info = matching_additional["Courier"]
140
- basics.merge!(:name => info[:courier], :url => info[:courier_url], :country => info[:country])
141
+ if info = matching_additional['Courier']
142
+ basics.merge!(name: info[:courier], url: info[:courier_url], country: info[:country])
141
143
  end
142
144
 
143
145
  @courier ||= Info.new(basics)
144
146
  end
145
147
 
146
- def service_type
147
- if matching_additional["Service Type"]
148
- @service_type ||= Info.new(matching_additional["Service Type"]).name
148
+ def partnership?
149
+ partners.present?
150
+ end
151
+
152
+ def shipper?
153
+ return true unless partnership?
154
+
155
+ partners.shipper == self
156
+ end
157
+
158
+ def carrier?
159
+ return true unless partnership?
160
+
161
+ partners.carrier == self
162
+ end
163
+
164
+ def partners
165
+ return unless self.class.const_defined?(:PARTNERS)
166
+
167
+ partner_hash = {}
168
+
169
+ return unless (partner_tn = find_matching_partner)
170
+
171
+ possible_twin = partner_tn.send(:find_matching_partner)
172
+ if possible_twin.instance_of?(self.class) && possible_twin.tracking_number == tracking_number
173
+ partner_hash[partner_data[:partner_type].to_sym] = partner_tn
174
+ partner_hash[partner_tn.partner_data[:partner_type].to_sym] = self
149
175
  end
176
+
177
+ PartnerStruct.new(partner_hash[:shipper], partner_hash[:carrier]) if partner_hash.keys.any?
178
+ end
179
+
180
+ def service_type
181
+ @service_type ||= Info.new(matching_additional['Service Type']).name if matching_additional['Service Type']
150
182
  end
151
183
 
152
184
  def service_description
153
- if matching_additional["Service Type"]
154
- @service_description ||= Info.new(matching_additional["Service Type"]).description
155
- end
185
+ @service_description ||= Info.new(matching_additional['Service Type']).description if matching_additional['Service Type']
156
186
  end
157
187
 
158
188
  def package_type
159
- if matching_additional["Container Type"]
160
- @package_type ||= Info.new(matching_additional["Container Type"]).name
161
- end
189
+ @package_type ||= Info.new(matching_additional['Container Type']).name if matching_additional['Container Type']
162
190
  end
163
191
 
164
192
  def destination_zip
165
- match_group("DestinationZip")
193
+ match_group('DestinationZip')
166
194
  end
167
195
 
168
196
  def shipper_id
169
- match_group("ShipperId")
197
+ match_group('ShipperId')
170
198
  end
171
199
 
172
200
  def tracking_url
173
201
  url = nil
174
- if matching_additional["Courier"]
175
- url = matching_additional["Courier"][:tracking_url]
176
- else
177
- if self.class.const_defined?(:TRACKING_URL)
178
- url = self.class.const_get(:TRACKING_URL)
179
- end
202
+ if matching_additional['Courier']
203
+ url = matching_additional['Courier'][:tracking_url]
204
+ elsif self.class.const_defined?(:TRACKING_URL)
205
+ url = self.class.const_get(:TRACKING_URL)
180
206
  end
181
207
 
182
- if url
183
- url.sub('%s', self.tracking_number)
184
- end
208
+ url.sub('%s', tracking_number) if url
185
209
  end
186
210
 
187
211
  def matching_additional
@@ -189,21 +213,21 @@ module TrackingNumber
189
213
 
190
214
  relevant_sections = {}
191
215
 
192
- additional.each do |info|
193
- if self.matches && self.matches.length > 0
194
- if value = self.matches[info[:regex_group_name]].gsub(/\s/, "")
195
- # has matching value
196
- matches = info[:lookup].find do |i|
197
- if i[:matches]
198
- value == i[:matches]
199
- elsif i[:matches_regex]
200
- value =~ Regexp.new(i[:matches_regex])
201
- end
202
- end
203
-
204
- relevant_sections[info[:name]] = matches
216
+ additional.each do |additional_info|
217
+ next unless matches && matches.length > 0 # skip if no match groups
218
+ value = matches[additional_info[:regex_group_name]].gsub(/\s/, '') # match is empty
219
+ next unless value
220
+
221
+ matches = additional_info[:lookup].find do |i|
222
+ if i[:matches]
223
+ value == i[:matches]
224
+ elsif i[:matches_regex]
225
+ value =~ Regexp.new(i[:matches_regex])
205
226
  end
206
227
  end
228
+
229
+ # has matching value
230
+ relevant_sections[additional_info[:name]] = matches
207
231
  end
208
232
 
209
233
  relevant_sections
@@ -211,21 +235,78 @@ module TrackingNumber
211
235
 
212
236
  protected
213
237
 
214
- def matches
215
- if self.class.constants.include?(:VERIFY_PATTERN)
216
- self.tracking_number.match(self.class.const_get(:VERIFY_PATTERN))
217
- else
218
- []
238
+ def match_all(items)
239
+ items.all? do |i|
240
+ if i[:matches] && matches[i[:regex_group_name]]
241
+ matches[i[:regex_group_name]] == i[:matches]
242
+ elsif i[:matches_regex] && matches[i[:regex_group_name]]
243
+ matches[i[:regex_group_name]] =~ Regexp.new(i[:matches_regex])
244
+ else
245
+ false
246
+ end
219
247
  end
220
248
  end
221
249
 
250
+ def match_any(items)
251
+ items.find do |i|
252
+ if i[:matches] && matches[i[:regex_group_name]]
253
+ matches[i[:regex_group_name]] == i[:matches]
254
+ elsif i[:matches_regex] && matches[i[:regex_group_name]]
255
+ matches[i[:regex_group_name]] =~ Regexp.new(i[:matches_regex])
256
+ else
257
+ false
258
+ end
259
+ end
260
+ end
261
+
262
+ def matches
263
+ @matches ||= if self.class.constants.include?(:VERIFY_PATTERN)
264
+ tracking_number.match(self.class.const_get(:VERIFY_PATTERN))
265
+ else
266
+ []
267
+ end
268
+ end
269
+
222
270
  def match_group(name)
223
- begin
224
- self.matches[name].gsub(/\s/, '')
225
- rescue
226
- nil
271
+ matches[name].gsub(/\s/, '')
272
+ rescue StandardError
273
+ nil
274
+ end
275
+
276
+ def find_matching_partner
277
+ partner_info = self.class.const_get(:PARTNERS) || []
278
+
279
+ partner_info.each do |partner_data|
280
+ klass = find_tracking_class_by_id(partner_data[:partner_id])
281
+
282
+ return false unless klass
283
+
284
+ tn = klass.new(tracking_number)
285
+
286
+ valid = if partner_data.dig(:validation, :matches_all)
287
+ tn.valid? && match_all(partner_data.dig(:validation, :matches_all))
288
+ elsif partner_data.dig(:validation, :matches_any)
289
+ tn.valid? && match_any(partner_data.dig(:validation, :matches_any))
290
+ else
291
+ tn.valid?
292
+ end
293
+
294
+ next unless valid
295
+
296
+ @partner = tn
297
+ @partner_data = partner_data
298
+ break
227
299
  end
300
+
301
+ return @partner
228
302
  end
229
303
 
304
+ def find_tracking_class_by_id(id)
305
+ return unless id
306
+
307
+ TrackingNumber::TYPES.detect do |type|
308
+ type.const_get('ID') == id
309
+ end
310
+ end
230
311
  end
231
312
  end
@@ -1,22 +1,21 @@
1
-
2
1
  module TrackingNumber
3
2
  module Loader
4
3
  class << self
5
- def load_tracking_number_data(couriers_path = File.join(File.dirname(__FILE__), "../data/couriers/"))
4
+ def load_tracking_number_data(couriers_path = File.join(File.dirname(__FILE__), '../data/couriers/'))
6
5
  mapping_data = []
7
6
  tracking_number_types = []
8
7
  Dir.glob(File.join(couriers_path, '/*.json')).each do |file|
9
- courier_info = read_courier_info(file)
8
+ courier_info = read_courier_info(file)
10
9
 
11
10
  courier_info[:tracking_numbers].each do |tracking_info|
12
- tracking_name = tracking_info[:name]
13
- klass = create_class(klass, courier_info, tracking_info)
11
+ klass = create_class(courier_info, tracking_info)
14
12
 
15
13
  # Do some basic checks on the data file
16
14
  throw 'missing test numbers' unless has_test_numbers?(tracking_info)
17
- throw 'missing regex match groups' unless test_numbers_return_required_groups?(tracking_info, Regexp.new(klass::VERIFY_PATTERN))
15
+ throw 'missing regex match groups' unless test_numbers_return_required_groups?(tracking_info,
16
+ Regexp.new(klass::VERIFY_PATTERN))
17
+ const = register_class(klass, tracking_info[:name])
18
18
 
19
- const = register_class(klass, tracking_name)
20
19
  tracking_number_types.push(const)
21
20
  mapping_data << {
22
21
  class: const,
@@ -28,33 +27,33 @@ module TrackingNumber
28
27
  end
29
28
 
30
29
  TrackingNumber.const_set('TYPES', tracking_number_types)
31
-
32
- return mapping_data
30
+
31
+ mapping_data
33
32
  end
34
33
 
35
34
  private
36
35
 
37
36
  def has_test_numbers?(tracking)
38
- return tracking[:test_numbers] && tracking[:test_numbers][:valid]
37
+ tracking[:test_numbers] && tracking[:test_numbers][:valid]
39
38
  end
40
39
 
41
40
  def test_numbers_return_required_groups?(tracking, regex)
42
41
  test_number = tracking[:test_numbers][:valid][0]
43
42
  matches = test_number.match(regex)
44
43
 
45
- return matches["SerialNumber"]
44
+ matches['SerialNumber']
46
45
  end
47
46
 
48
47
  def read_courier_info(file)
49
- return JSON.parse(File.read(file)).deep_symbolize_keys!
48
+ JSON.parse(File.read(file)).deep_symbolize_keys!
50
49
  end
51
50
 
52
- def create_class(klass, courier_info, tracking_info)
51
+ def create_class(courier_info, tracking_info)
53
52
  klass = Class.new(TrackingNumber::Base)
54
- klass.const_set("COURIER_CODE", courier_info[:courier_code])
53
+ klass.const_set('COURIER_CODE', courier_info[:courier_code])
55
54
  info = courier_info.dup
56
55
  info.delete(:tracking_numbers)
57
- klass.const_set("COURIER_INFO", info)
56
+ klass.const_set('COURIER_INFO', info)
58
57
 
59
58
  pattern = tracking_info[:regex]
60
59
  pattern = tracking_info[:regex].join if tracking_info[:regex].is_a?(Array)
@@ -62,19 +61,21 @@ module TrackingNumber
62
61
  verify_pattern = "^#{pattern}$"
63
62
  search_pattern = "\\b#{pattern}\\b"
64
63
 
65
- klass.const_set("SEARCH_PATTERN", Regexp.new(search_pattern))
66
- klass.const_set("VERIFY_PATTERN", Regexp.new(verify_pattern))
64
+ klass.const_set('SEARCH_PATTERN', Regexp.new(search_pattern))
65
+ klass.const_set('VERIFY_PATTERN', Regexp.new(verify_pattern))
67
66
 
68
- klass.const_set("VALIDATION", tracking_info[:validation])
69
- klass.const_set("ADDITIONAL", tracking_info[:additional])
70
- klass.const_set("TRACKING_URL", tracking_info[:tracking_url])
67
+ klass.const_set('VALIDATION', tracking_info[:validation])
68
+ klass.const_set('ADDITIONAL', tracking_info[:additional])
69
+ klass.const_set('TRACKING_URL', tracking_info[:tracking_url])
70
+ klass.const_set('PARTNERS', tracking_info[:partners])
71
+ klass.const_set('ID', tracking_info[:id])
71
72
 
72
- return klass
73
+ klass
73
74
  end
74
75
 
75
76
  def register_class(klass, tracking_name)
76
77
  klass_name = tracking_name.gsub(/[^0-9A-Za-z]/, '')
77
- return TrackingNumber.const_set(klass_name, klass)
78
+ TrackingNumber.const_set(klass_name, klass)
78
79
  end
79
80
  end
80
81
  end
@@ -0,0 +1,7 @@
1
+ module TrackingNumber
2
+ class Partnership
3
+ def initialize(tracking_numbers)
4
+
5
+ end
6
+ end
7
+ end
@@ -1,3 +1,3 @@
1
1
  module TrackingNumber
2
- VERSION = "1.3.2"
2
+ VERSION = "1.4.0"
3
3
  end
@@ -5,6 +5,7 @@ require 'tracking_number/checksum_validations'
5
5
  require 'tracking_number/loader'
6
6
  require 'tracking_number/base'
7
7
  require 'tracking_number/info'
8
+ require 'tracking_number/partnership'
8
9
  require 'tracking_number/unknown'
9
10
  require 'active_support/core_ext/string'
10
11
  require 'active_support/core_ext/hash'
@@ -16,29 +17,51 @@ end
16
17
  TrackingNumber::Loader.load_tracking_number_data
17
18
 
18
19
  module TrackingNumber
19
- def self.search(body)
20
- TYPES.collect { |type| type.search(body) }.flatten
20
+ def self.search(body, match: :carrier)
21
+ matches = TYPES.collect { |type| type.search(body) }.flatten
22
+
23
+ # Some tracking numbers (e.g. Fedex Smartpost) are partnerships between two parties, where one party is the shipper (e.g. Fedex)
24
+ # and the other party is the [last mile] carrier (e.g. USPS). We're probably interested in the last mile aspect of
25
+ # the partnership, so by default we'll show those
26
+
27
+ # Tracking numbers without a partnership are both the shipper and carrier.
28
+
29
+ case match
30
+ when :carrier
31
+ matches.filter(&:carrier?)
32
+ when :shipper
33
+ matches.filter(&:shipper?)
34
+ when :all
35
+ matches
36
+ else
37
+ matches
38
+ end
21
39
  end
22
40
 
23
- def self.detect(tracking_number)
41
+ def self.detect(tracking_number, match: :carrier)
24
42
  tn = nil
25
- for test_klass in (TYPES+[Unknown])
43
+ (TYPES + [Unknown]).each do |test_klass|
26
44
  tn = test_klass.new(tracking_number)
27
- break if tn.valid?
45
+ if tn.valid? && (!match || match == :all)
46
+ break
47
+ elsif tn.valid? && tn.send("#{match}?")
48
+ break
49
+ end
28
50
  end
29
- return tn
51
+ tn
30
52
  end
31
53
 
32
54
  def self.detect_all(tracking_number)
33
55
  matches = []
34
- for test_klass in (TYPES+[Unknown])
56
+ (TYPES + [Unknown]).each do |test_klass|
35
57
  tn = test_klass.new(tracking_number)
36
58
  matches << tn if tn.valid?
37
59
  end
38
- return matches
60
+
61
+ matches
39
62
  end
40
63
 
41
64
  def self.new(tracking_number)
42
- self.detect(tracking_number)
65
+ detect(tracking_number)
43
66
  end
44
67
  end
data/test/test_helper.rb CHANGED
@@ -18,8 +18,6 @@ class Minitest::Test
18
18
  possible_numbers = []
19
19
  possible_numbers << tracking
20
20
  possible_numbers << tracking.to_s.gsub(" ", "")
21
- possible_numbers << tracking.chars.to_a.join(" ")
22
- possible_numbers << tracking.chars.to_a.join(" ")
23
21
  possible_numbers << tracking.slice(0, (tracking.length / 2)) + " " + tracking.slice((tracking.length / 2), tracking.length)
24
22
 
25
23
  possible_numbers.flatten.uniq
@@ -23,7 +23,7 @@ class TrackingNumberMetaTest < Minitest::Test
23
23
  tracking_info[:test_numbers][:valid].each do |valid_number|
24
24
  should "detect #{valid_number} as #{klass_name}" do
25
25
  #TODO fix this multiple matching thing
26
- matches = TrackingNumber.search(valid_number)
26
+ matches = TrackingNumber.search(valid_number, match: :all)
27
27
  assert matches.collect(&:class).include?(klass)
28
28
  end
29
29
 
@@ -147,9 +147,77 @@ class TrackingNumberTest < Minitest::Test
147
147
  assert_nil tracking_number.package_type
148
148
  end
149
149
 
150
+ should "report no partnership" do
151
+ assert_equal false, tracking_number.partnership?
152
+ end
153
+
154
+ should "report no partners" do
155
+ assert_equal nil, tracking_number.partners
156
+ end
157
+
158
+ should "report as shipper and carrier" do
159
+ assert_equal true, tracking_number.shipper?
160
+ assert_equal true, tracking_number.carrier?
161
+ end
162
+
150
163
  should "have valid tracking url" do
151
164
  assert tracking_number.tracking_url, "Tracking url should not be blank"
152
165
  assert tracking_number.tracking_url.include?(tracking_number.tracking_number), "Should include tracking number in the url"
153
166
  end
154
167
  end
168
+
169
+ context "tracking number partnership data for FedExSmartPost/USPS91" do
170
+ tracking_number = TrackingNumber.new("420 11213 92 6129098349792366623 8")
171
+
172
+ should "report correct courier name" do
173
+ assert_equal "United States Postal Service", tracking_number.courier_name
174
+ end
175
+
176
+ should "report correct courier code" do
177
+ assert_equal :usps, tracking_number.courier_code
178
+ end
179
+
180
+ should "report correct service type" do
181
+ assert_equal "Fedex Smart Post", tracking_number.service_type
182
+ end
183
+
184
+ should "report partnership" do
185
+ assert_equal true, tracking_number.partnership?
186
+ end
187
+
188
+ should "report not shipper side of the partnership" do
189
+ assert_equal false, tracking_number.shipper?
190
+ end
191
+
192
+ should "report carrier side of the partnership" do
193
+ assert_equal true, tracking_number.carrier?
194
+ end
195
+
196
+ should "report partner pairing" do
197
+ assert_equal :fedex, tracking_number.partners.shipper.courier_code
198
+ end
199
+ end
200
+
201
+ context "searching numbers that have partners" do
202
+ partnership_number = "420 11213 92 6129098349792366623 8"
203
+ single_number = "0307 1790 0005 2348 3741"
204
+
205
+ search_string = ["number that matches two services", partnership_number, " number that matches only one: ", single_number, "let's see if that does it"].join(' ')
206
+
207
+ should "match only carriers by default" do
208
+ matches = TrackingNumber.search(search_string)
209
+ assert_equal 2, matches.size
210
+ assert_equal [true, true], matches.collect { |t| t.carrier? }
211
+ end
212
+
213
+ should "match all if specified" do
214
+ matches = TrackingNumber.search(search_string, match: :all)
215
+ assert_equal 3, matches.size
216
+ end
217
+
218
+ should "match only shippers if specified" do
219
+ matches = TrackingNumber.search(search_string, match: :shipper)
220
+ assert_equal 2, matches.size
221
+ end
222
+ end
155
223
  end