tracking_number 1.3.2 → 1.4.0

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.
@@ -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