# vim:syntax=perl #------------------------------------------------------------------- # WebGUI is Copyright 2001-2009 Plain Black Corporation. #------------------------------------------------------------------- # Please read the legal notices (docs/legal.txt) and the license # (docs/license.txt) that came with this distribution before using # this software. #------------------------------------------------------------------ # http://www.plainblack.com info@plainblack.com #------------------------------------------------------------------ # Write a little about what this script tests. # # use FindBin; use strict; use lib "$FindBin::Bin/../../lib"; use Test::More; use Test::Deep; use Test::MockObject::Extends; use Exception::Class; use Data::Dumper; use WebGUI::Test; # Must use this before any other WebGUI modules use WebGUI::Session; use WebGUI::Text; use WebGUI::Shop::Cart; use WebGUI::Shop::AddressBook; use WebGUI::Shop::TaxDriver::EU; #---------------------------------------------------------------------------- # Init my $session = WebGUI::Test->session; # Test user my $taxUser = WebGUI::User->new( $session, 'new' ); $taxUser->username( 'Tex Evasion' ); $session->user({userId => $taxUser->getId}); WebGUI::Test->addToCleanup($taxUser); # Test VAT numbers my $testVAT_NL = 'NL123456789B12'; my $testVAT_BE = 'BE0123456789'; my $noServiceVAT= 'NotGonnaWork'; my $invalidVAT = 'ByNoMeansAllowed'; my $visitorUser = WebGUI::User->new( $session, 1 ); my @EU_COUNTRIES = ( 'Austria', 'Belgium', 'Bulgaria', 'Cyprus', 'Czech Republic', 'Germany', 'Denmark', 'Estonia', 'Greece', 'Spain', 'Finland', 'France', 'United Kingdom', 'Hungary', 'Ireland', 'Italy', 'Lithuania', 'Luxembourg', 'Latvia', 'Malta', 'Netherlands', 'Poland', 'Portugal', 'Romania', 'Sweden', 'Slovenia', 'Slovakia', ); # Test SKU my $sku = WebGUI::Asset->getRoot($session)->addChild( { className => 'WebGUI::Asset::Sku::Donation', title => 'Taxable donation', defaultPrice => 100.00, } ); WebGUI::Test->addToCleanup($sku); my $book = WebGUI::Shop::AddressBook->create($session); WebGUI::Test->addToCleanup($book); # setup address in EU but not in residential country of merchant my $beAddress = $book->addAddress({ label => 'BE', city => 'Antwerpen', country => 'Belgium', }); # setup address in residential country of merchant my $nlAddress = $book->addAddress({ label => 'NL', city => 'Delft', country => 'Netherlands', }); # setup address outside EU my $usAddress = $book->addAddress({ label => 'outside eu', city => 'New Amsterdam', country => 'US', }); #---------------------------------------------------------------------------- # Tests my $tests = 342; plan tests => $tests; #---------------------------------------------------------------------------- # put your tests here ####################################################################### # # new # ####################################################################### { my $taxer = WebGUI::Shop::TaxDriver::EU->new($session); isa_ok($taxer, 'WebGUI::Shop::TaxDriver::EU'); isa_ok($taxer->session, 'WebGUI::Session', 'session method returns a session object'); is($session->getId, $taxer->session->getId, 'session method returns OUR session object'); } ####################################################################### # # className # ####################################################################### { my $taxer = WebGUI::Shop::TaxDriver::EU->new($session); is( $taxer->className, 'WebGUI::Shop::TaxDriver::EU', 'className returns correct class name' ); } ####################################################################### # # getConfigurationScreen # ####################################################################### #### TODO: Figure out how to test this. ####################################################################### # # getCountryCode / getCOuntryName # ####################################################################### { my $taxer = WebGUI::Shop::TaxDriver::EU->new($session); is( $taxer->getCountryCode( 'Netherlands' ), 'NL', 'getCountryCode returns correct code for country inside EU.' ); is( $taxer->getCountryCode( 'United States' ), undef, 'getCountryCode returns undef for countries outside EU.' ); is( $taxer->getCountryName( 'NL' ), 'Netherlands', 'getCountryName returns correct name for country code within EU.' ); is( $taxer->getCountryName( 'US' ), undef, 'getCountryName returns undef for county codes outside EU.' ); } ####################################################################### # # updateVATNumber # ####################################################################### { my $taxer = WebGUI::Shop::TaxDriver::EU->new($session); $session->user( {userId=>$taxUser->userId} ); # Mock the Validation module my $validator = Test::MockObject::Extends->new( Business::Tax::VAT::Validation->new ); local *Business::Tax::VAT::Validation::new; $validator->fake_new( 'Business::Tax::VAT::Validation' ); eval { $taxer->updateVATNumber }; my $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'A VAT number is required' ); is( $e, 'A VAT number is required', 'updateVATNumber returns correct message for missing VAT number' ); eval { $taxer->updateVATNumber( $testVAT_NL, 'NotAUserObject' ) }; $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'Second argument must be a user object' ); is( $e, 'The second argument must be an instanciated WebGUI::User object', 'updateVATNumber returns correct message when user object is of wrong type' ); eval { $taxer->updateVATNumber( $testVAT_NL, $visitorUser ) }; $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'User may not be visitor' ); is( $e, 'Visitor cannot add VAT numbers', 'updateVATNumber returns correct message when user is visitor' ); for my $errorCode ( 0 .. 16 ) { $validator->set_always( 'check', 0 ); $validator->set_always( 'get_last_error_code', $errorCode ); is( $taxer->updateVATNumber( $invalidVAT, $taxUser ), 'INVALID', "updateVATNumber returns INVALID for error $errorCode", ); } for my $errorCode ( 17 .. 255 ) { $validator->set_always( 'check', 0 ); $validator->set_always( 'get_last_error_code', $errorCode ); is( $taxer->updateVATNumber( $invalidVAT, $taxUser ), 'UNKNOWN', "updateVATNumber returns UNKNOWN for error $errorCode", ); } $validator->set_always( 'check', 1 ); $validator->set_always( 'get_last_error_code', undef ); is( $taxer->updateVATNumber( $testVAT_NL, $taxUser ), 'VALID', "updateVATNumber returns VALID for valid numbers", ); } ####################################################################### # # addVATNumber # ####################################################################### { my $taxer = WebGUI::Shop::TaxDriver::EU->new($session); my $response; local *WebGUI::Shop::TaxDriver::EU::updateVATNumber = sub { return $response }; #----- invalid vat number $response = 'INVALID'; is( $taxer->addVATNumber( $invalidVAT, $taxUser ), 'The entered VAT number is invalid.', 'addVATNumber returns the correct error message for invalid numbers', ); #----- service unavailable $response = 'UNKNOWN'; like( $taxer->addVATNumber( $noServiceVAT, $taxUser ), qr{^Number validation is currently not available.}, 'addVATNumber returns the correct message when VIES is unavailable', ); my $workflows = WebGUI::Workflow::Instance->getAllInstances( $session ); my ($workflow) = grep { $_->get('parameters')->{ vatNumber } eq $noServiceVAT } @{ $workflows }; ok( defined $workflow , 'addVATNumber fires a recheck workflow when VIES is down' ); #----- valid number $response = 'VALID'; ok( !defined $taxer->addVATNumber( $testVAT_NL, $taxUser ), 'Valid VAT numbers return undef.', ); } ####################################################################### # # recheckVATNumber # ####################################################################### { my $taxer = WebGUI::Shop::TaxDriver::EU->new($session); for my $response ( qw{ INVALID VALID UNKNOWN } ) { local *WebGUI::Shop::TaxDriver::EU::updateVATNumber = sub { return $response }; is( $taxer->recheckVATNumber( $invalidVAT, $taxUser ), $response, "recheckVATNumber returns correct value when updateVATNumber returns $response", ); } } ####################################################################### # # getVATNumbers / deleteVATNumber # ####################################################################### { my $taxer = setupTestNumbers(); my $expectNL = { userId => $taxUser->userId, countryCode => 'NL', vatNumber => $testVAT_NL, viesValidated => 1, viesErrorCode => undef, approved => 0, }; my $expectBE = { userId => $taxUser->userId, countryCode => 'BE', vatNumber => $testVAT_BE, approved => 0, viesErrorCode => undef, viesValidated => 1, }; my $vatNumbers = $taxer->getVATNumbers( undef, $taxUser ); cmp_bag( $vatNumbers, [ $expectNL, $expectBE ], 'VAT Numbers are correctly returned by getVATNumbers' ); $vatNumbers = $taxer->getVATNumbers( 'BE', $taxUser ); cmp_bag( $vatNumbers, [ $expectBE ], 'getVATNumbers filters on country code when one is passed' ); $taxer->deleteVATNumber( $testVAT_BE, $taxUser ); $vatNumbers = $taxer->getVATNumbers( undef, $taxUser ); cmp_bag( $vatNumbers, [ $expectNL ], 'deleteVATNumber deletes number' ); $taxer->deleteVATNumber( $testVAT_NL, $taxUser ); } ####################################################################### # # addGroup / getGroupRate / deleteGroup # ####################################################################### { my $taxer = setupTestNumbers(); eval { $taxer->addGroup }; my $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'addGroup requires a group name' ); is( $e, 'A group name is required', 'addGroup returns correct message for omitted group name' ); eval { $taxer->addGroup( 'Dummy' ) }; $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'addGroup requires a tax rate' ); is( $e, 'Group rate must be within 0 and 100', 'addGroup returns correct message on omitted tax rate' ); eval { $taxer->addGroup( 'Dummy', -1 ) }; $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'addGroup: tax rate cannot be < 0' ); is( $e, 'Group rate must be within 0 and 100', 'addGroup returns correct message on tax rate < 0' ); eval { $taxer->addGroup( 'Dummy', 101 ) }; $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'addGroup: tax rate cannot be > 100' ); is( $e, 'Group rate must be within 0 and 100', 'addGroup returns correct message on tax rate > 100' ); my $id0 = eval { $taxer->addGroup( 'Group0', 0 ) }; $e = Exception::Class->caught(); ok( !$e, 'addGroup: 0% is a valid group rate' ); my $id100 = eval { $taxer->addGroup( 'Group100', 100 ) }; $e = Exception::Class->caught(); ok( !$e, 'addGroup: 100% is a valid group rate' ); my $id50_5 = eval { $taxer->addGroup( 'Group50.5', 50.5 ) }; $e = Exception::Class->caught(); ok( !$e, 'addGroup: floats are a valid group rate' ); my $taxGroups = $taxer->get( 'taxGroups' ); my $expectGroups = [ { name => 'Group0', rate => 0, id => $id0, }, { name => 'Group100', rate => 100, id => $id100, }, { name => 'Group50.5', rate => 50.5, id => $id50_5, }, ]; cmp_bag( $taxGroups, $expectGroups, 'addGroup saves correctly' ); # getGroupRate ok( $taxer->getGroupRate( $id0 ) == 0 && $taxer->getGroupRate( $id100 ) == 100 && $taxer->getGroupRate( $id50_5 ) == 50.5, 'getGroup rate gets correct rates' ); # deleteGroup eval { $taxer->deleteGroup }; my $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'addGroup requires a group id' ); is( $e, 'A group id is required', 'addGroup returns correct message for missing group id' ); $taxer->deleteGroup( $id50_5 ); $taxGroups = $taxer->get( 'taxGroups' ); cmp_bag( $taxGroups, [ { name => 'Group0', rate => 0, id => $id0, }, { name => 'Group100', rate => 100, id => $id100, }, ], 'deleteGroup deletes correctly' ); # Clean up a bit. $taxer->deleteGroup( $_ ) for ( $id0, $id100 ); } ####################################################################### # # getTaxRate # ####################################################################### { my $taxer = setupTestNumbers(); my $id100 = $taxer->addGroup( 'Group100', 100 ); my $id50_5 = $taxer->addGroup( 'Group50.5', 50.5 ); $taxer->update( { 'automaticViesApproval' => 1 } ); eval { $taxer->getTaxRate(); }; my $e = Exception::Class->caught(); isa_ok($e, 'WebGUI::Error::InvalidParam', 'getTaxRate: error handling for not sending a sku'); is($e->error, 'Must pass in a WebGUI::Asset::Sku object', 'getTaxRate: error handling for not sending a sku'); # Set defaultTaxGroup and residential country $taxer->update( { defaultGroup => $id50_5, shopCountry => 'NL' } ); # Check default tax group is( $taxer->getTaxRate( $sku ), 50.5, 'getTaxRate returns default tax group when no address is given and sku has no tax group set'); # Check case when no address is given $sku->setTaxConfiguration( 'WebGUI::Shop::TaxDriver::EU', { taxGroup => $id100 } ); is( $taxer->getTaxRate( $sku ), 100, 'getTaxRate returns tax group set by sku when no address is given'); # Addresses inside EU with VAT number is( $taxer->getTaxRate( $sku, $beAddress ), 0, 'getTaxRate: shipping addresses inside EU but other country than merchant w/ VAT number are tax exempt.' ); is( $taxer->getTaxRate( $sku, $nlAddress ), 100, 'getTaxRate: shipping addresses in country of merchant w/ VAT number pay tax' ); $taxer->deleteVATNumber( $testVAT_NL, $taxUser ); $taxer->deleteVATNumber( $testVAT_BE, $taxUser ); # Addresses inside EU without VAT number foreach my $country ( @EU_COUNTRIES ) { next if $country eq $nlAddress->get('country'); # Residents of merchant country should be checked separately. $beAddress->update( { country => $country } ); is( $taxer->getTaxRate( $sku, $beAddress ), 100, "getTaxRate: shipping addresses in $country w/o VAT number pay tax" ); } $beAddress->update( { country => 'Belgium' } ); is( $taxer->getTaxRate( $sku, $nlAddress ), 100, 'getTaxRate: shipping addresses in country of merchant w/o VAT number pay tax' ); # Address outside EU is( $taxer->getTaxRate( $sku, $usAddress ), 0, 'getTaxRate: shipping addresses outside EU are tax exempt' ); } ####################################################################### # # appendCartItemVars # ####################################################################### { my $taxer = setupTestNumbers(); eval { $taxer->appendCartItemVars }; my $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidParam', 'appendCartItemVars requires a hash ref.' ); is( $e, 'Must supply a hash ref', 'appendCartItemVars returns correct message for missing hash ref' ); eval { $taxer->appendCartItemVars( {}, 'NotAUserObject' ) }; $e = Exception::Class->caught(); isa_ok( $e, 'WebGUI::Error::InvalidObject', 'appendCartItemVars: Second argument must be a cart item object' ); cmp_deeply( $e, methods( error => 'Must pass a cart item', expected => 'WebGUI::Shop::CartItem', got => '', ), 'appendCartItemVars returns correct error for missing CartItem' ); my $cart = WebGUI::Shop::Cart->newBySession( $session ); WebGUI::Test->addToCleanup($cart); my $item = $cart->addItem( $sku ); $item->setQuantity( 2 ); $item->update( { shippingAddressId => $nlAddress->getId } ); my $cartItemVars = { must => 'be kept' }; $taxer->appendCartItemVars( $cartItemVars, $item ); cmp_deeply( $cartItemVars, { pricePlusTax => '200.00', extendedPricePlusTax => '400.00', taxRate => '100', taxAmount => '100.00', VATNumber => $testVAT_NL, must => 'be kept', }, 'appendCartItemVars returns correct data for address in shopy country.' ); $item->update( { shippingAddressId => $beAddress->getId } ); $cartItemVars = { must => 'be kept' }; $taxer->appendCartItemVars( $cartItemVars, $item ); cmp_deeply( $cartItemVars, { pricePlusTax => '100.00', extendedPricePlusTax => '200.00', taxRate => '0', taxAmount => '0.00', VATNumber => $testVAT_BE, must => 'be kept', }, 'appendCartItemVars returns correct data for address in otrher country in EU.' ); $item->update( { shippingAddressId => $usAddress->getId } ); $cartItemVars = { must => 'be kept' }; $taxer->appendCartItemVars( $cartItemVars, $item ); cmp_deeply( $cartItemVars, { pricePlusTax => '100.00', extendedPricePlusTax => '200.00', taxRate => '0', taxAmount => '0.00', must => 'be kept', }, 'appendCartItemVars returns correct data for address outside EU.' ); } ####################################################################### # # getTransactionTaxData # ####################################################################### { my $taxer = setupTestNumbers(); $taxer->update( { 'automaticViesApproval' => 1 } ); my $details = $taxer->getTransactionTaxData( $sku, $usAddress ); cmp_deeply( $details, { className => 'WebGUI::Shop::TaxDriver::EU', outsideEU => 1, }, 'getTransactionTaxData returns correct hashref for addresses outside EU' ); $details = $taxer->getTransactionTaxData( $sku, $beAddress ); cmp_deeply( $details, { className => 'WebGUI::Shop::TaxDriver::EU', useVATNumber => 1, VATNumber => $testVAT_BE, }, 'getTransactionTaxData returns correct hashref for addresses inside EU but not shop country w/ VAT number' ); $details = $taxer->getTransactionTaxData( $sku, $nlAddress ); cmp_deeply( $details, { className => 'WebGUI::Shop::TaxDriver::EU', useVATNumber => 1, VATNumber => $testVAT_NL, }, 'getTransactionTaxData returns correct hashref for addresses in shop country w/ VAT number' ); $taxer->deleteVATNumber( $testVAT_NL ); $details = $taxer->getTransactionTaxData( $sku, $nlAddress ); cmp_deeply( $details, { className => 'WebGUI::Shop::TaxDriver::EU', useVATNumber => 0, }, 'getTransactionTaxData returns correct hashref for addresses in EU w/o VAT number' ); } #---------------------------------------------------------------------------- sub setupTestNumbers { my $taxer = WebGUI::Shop::TaxDriver::EU->new($session); $session->db->write('delete from taxDriver where className=?', [ 'WebGUI::Shop::TaxDriver::EU' ]); $session->db->write('delete from tax_eu_vatNumbers'); $taxer->addVATNumber( $testVAT_NL, $taxUser, 1); $taxer->addVATNumber( $testVAT_BE, $taxUser, 1); return $taxer; } #---------------------------------------------------------------------------- # Cleanup END { $session->db->write('delete from tax_eu_vatNumbers'); $session->db->write('delete from addressBook'); $session->db->write('delete from address'); $session->db->write('delete from taxDriver where className=?', [ 'WebGUI::Shop::TaxDriver::EU' ]); }