Permalink
Browse files

Add tests and cleanup.

Added tests for usps formats and checksums.
Consolidated validation into shared examples.
Fixed a couple of assumptions based on tests.
  • Loading branch information...
1 parent 57b1daa commit 2ce387750890b37ab00736eec77509ed4e38f7a9 @z00b z00b committed Jul 12, 2011
Showing with 108 additions and 32 deletions.
  1. +14 −2 lib/active_model/validations/tracking_number_validator.rb
  2. +94 −30 spec/validations/tracking_number_spec.rb
@@ -9,17 +9,29 @@ def validate_each(record, attribute, value)
record.errors.add(attribute) unless self.send(method, value)
end
+ # UPS:
+ # ups tracking codes are validated solely on their format
+ # see https://www.ups.com/content/us/en/tracking/help/tracking/tnh.html
UPS_REGEXES = [ /^1Z[a-zA-Z0-9]{16}$/, /^[a-zA-Z0-9]{12}$/, /^[a-zA-Z0-9]{9}$/, /^T[a-zA-Z0-9]{10}$/ ]
def valid_ups?(value)
!!UPS_REGEXES.detect { |fmt| value.match(fmt) }
end
+ # USPS:
+ # usps tracking codes are validated based on format (one of USS228 or USS39)
+ # and a check digit (using either of the USPS's MOD10 or MOD11 algorithms)
+ # see USPS Publications:
+ # - #91 (05/2008) pp. 38
+ # - #97 (05/2002) pp. 62-63
+ # - #109 (09/2007) pp. 19-21
def valid_usps?(value)
uss228?(value) || uss39?(value)
end
+ USS128_REGEX = /^(\d{19})(\d)$/
def uss228?(value)
- value[-1].to_i == usps_mod10(value[0..2].reverse)
+ m = value.match(USS128_REGEX)
+ m.present? && (m[2].to_i == usps_mod10(m[1]))
end
USS39_REGEX = /^[a-zA-Z0-9]{2}(\d{8})(\d)US$/
@@ -32,7 +44,7 @@ def uss39?(value)
MOD10_WEIGHTS = [3,1]
def usps_mod10(chars)
- 10 - weighted_sum(chars, MOD10_WEIGHTS) % 10
+ 10 - weighted_sum(chars.reverse, MOD10_WEIGHTS) % 10
end
MOD11_WEIGHTS = [8,6,4,2,3,5,9,7]
@@ -1,17 +1,39 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
+shared_examples "valid tracking number" do |tracking_number|
+ it "validates" do
+ subject.tracking_number = tracking_number
+ subject.should be_valid
+ subject.should have(0).errors
+ end
+end
+
+shared_examples "invalid tracking number" do |tracking_number|
+ before { subject.tracking_number = tracking_number }
+
+ it "fails validation" do
+ subject.should_not be_valid
+ subject.should have(1).errors
+ end
+
+ it "has error of type 'invalid'" do
+ subject.should_not be_valid
+ subject.errors[:tracking_number].should include subject.errors.generate_message(:tracking_number, :invalid)
+ end
+end
+
describe "Tracking Number Validation" do
subject { TestRecord.new }
context "when no carrier parameter is given" do
before(:each) do
TestRecord.reset_callbacks(:validate)
- TestRecord.validates :tracking_number, :tracking_number => true
+ TestRecord.validates :tracking_number, :tracking_number => true
end
it "raises an exception" do
- lambda { subject.valid? }.should raise_error
+ expect { subject.valid? }.to raise_error
end
end
@@ -21,46 +43,88 @@
TestRecord.validates :tracking_number, :tracking_number => {:carrier => :ups}
end
- it 'should validate format of tracking number with 1Z................' do
- subject.tracking_number = '1Z12345E0205271688'
- subject.should be_valid
- subject.should have(0).errors
- end
+ context 'with valid formats' do
+ context 'with tracking number in 1Z................ format' do
+ it_should_behave_like "valid tracking number", '1Z12345E0205271688'
+ end
- it 'should validate format of tracking number with ............' do
- subject.tracking_number = '9999V999J999'
- subject.should be_valid
- subject.should have(0).errors
- end
+ context 'with tracking number in ............ format' do
+ it_should_behave_like "valid tracking number", '9999V999J999'
+ end
+
+ context 'with tracking number in T.......... format' do
+ it_should_behave_like "valid tracking number", 'T99F99E9999'
+ end
- it 'should validate format of tracking number with T..........' do
- subject.tracking_number = 'T99F99E9999'
- subject.should be_valid
- subject.should have(0).errors
+ context 'with tracking number in ......... format' do
+ it_should_behave_like "valid tracking number", '990728071'
+ end
end
- it 'should validate format of tracking number with .........' do
- subject.tracking_number = '990728071'
- subject.should be_valid
- subject.should have(0).errors
+ context 'with invalid formats' do
+ before :each do
+ TestRecord.reset_callbacks(:validate)
+ TestRecord.validates :tracking_number, :tracking_number => {:carrier => :ups}
+ end
+
+ context "tracking number with invalid character" do
+ it_should_behave_like "invalid tracking number", '1Z12345E020_271688'
+ end
end
end
- describe "for invalid formats" do
- before :each do
+ context "when given a usps carrier parameter" do
+ before(:each) do
TestRecord.reset_callbacks(:validate)
- TestRecord.validates :tracking_number, :tracking_number => {:carrier => :ups}
- subject.tracking_number = '1Z12345E020_271688'
+ TestRecord.validates :tracking_number, :tracking_number => {:carrier => :usps}
end
- it "rejects invalid formats" do
- subject.should_not be_valid
- subject.should have(1).error
+ context "with valid formats" do
+ context 'USS39 tracking number with valid MOD10 check digit' do
+ it_should_behave_like "valid tracking number", 'EA123456784US'
+ end
+
+ context 'USS39 tracking number with valid MOD11 check digit' do
+ it_should_behave_like "valid tracking number", 'RB123456785US'
+ end
+
+ context 'USS128 tracking number with valid MOD10 check digit' do
+ it_should_behave_like "valid tracking number", '71123456789123456787'
+ end
end
- it "generates an error message of type invalid" do
- subject.should_not be_valid
- subject.errors[:tracking_number].should include subject.errors.generate_message(:tracking_number, :invalid)
+ context "with invalid formats" do
+ context 'USS39 tracking number with invalid check digit' do
+ it_should_behave_like "invalid tracking number", 'EA123456782US'
+ end
+
+ context 'USS39 tracking number that is too short' do
+ it_should_behave_like "invalid tracking number", '123456784US'
+ end
+
+ context 'USS39 tracking number that is too long' do
+ it_should_behave_like "invalid tracking number", 'EAB123456784US'
+ end
+
+ context 'USS39 tracking number with non-"US" product id' do
+ it_should_behave_like "invalid tracking number", 'EA123456784UT'
+ end
+
+ context 'USS128 tracking number with invalid check-digit' do
+ it_should_behave_like "invalid tracking number", '71123456789123456788'
+ end
+
+ context 'USS128 tracking number that is too short' do
+ it_should_behave_like "invalid tracking number", '7112345678912345678'
+ end
+
+ context 'USS128 tracking number that is too long' do
+ it_should_behave_like "invalid tracking number", '711234567891234567879'
+ end
+
+ context 'USS128 tracking number with invalid chars' do
+ it_should_behave_like "invalid tracking number", 'U11234567891234567879'
+ end
end
end
end

0 comments on commit 2ce3877

Please sign in to comment.