diff --git a/lib/WebGUI/AssetHelper/Product/ImportCSV.pm b/lib/WebGUI/AssetHelper/Product/ImportCSV.pm index 12d4dbd71..b8e84b330 100644 --- a/lib/WebGUI/AssetHelper/Product/ImportCSV.pm +++ b/lib/WebGUI/AssetHelper/Product/ImportCSV.pm @@ -85,8 +85,9 @@ if old data has been deleted and new has been inserted. sub importProducts { my ( $process, $args ) = @_; + $args ||= {}; my $session = $process->session; - my $asset = WebGUI::Asset->newById( $session, $args->{assetId} ); + my $parent = WebGUI::Asset->newById( $session, $args->{assetId} ); my $filePath = $args->{filePath}; WebGUI::Error::InvalidParam->throw(error => q{Must provide the path to a file}) unless $filePath; @@ -191,7 +192,7 @@ sub importProducts { else { ##Insert a new product; $session->log->warn("Making a new product: $productRow{sku}\n"); - my $newProduct = $asset->addChild({className => 'WebGUI::Asset::Sku::Product'}); + my $newProduct = $parent->addChild({className => 'WebGUI::Asset::Sku::Product'}); $newProduct->update({ title => $productRow{title}, menuTitle => $productRow{title}, @@ -245,6 +246,7 @@ Import the products from the CSV file in a forked process sub www_importProductsSave { my ( $self ) = @_; my $session = $self->session; + return $session->privilege->insufficient unless $self->asset->canEdit; my $storage = WebGUI::Storage->create($session); my $productFile = $storage->addFileFromFormPost( 'importFile_file', 1 ); diff --git a/t/AssetHelper/Product/ImportCSV.t b/t/AssetHelper/Product/ImportCSV.t new file mode 100644 index 000000000..2ff481205 --- /dev/null +++ b/t/AssetHelper/Product/ImportCSV.t @@ -0,0 +1,431 @@ +# 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 WebGUI::Test; # Must use this before any other WebGUI modules +use WebGUI::Session; +use WebGUI::Asset::Wobject::Shelf; +use WebGUI::AssetHelper::Product::ImportCSV; +use Test::MockObject::Extends; +use WebGUI::Fork; + +#---------------------------------------------------------------------------- +# Init +my $session = WebGUI::Test->session; + + +#---------------------------------------------------------------------------- +# Tests + +my $root = WebGUI::Test->asset; +my $class = 'WebGUI::Asset::Wobject::Shelf'; +my $shelf = $root->addChild({className => $class}); + +####################################################################### +# +# import +# +####################################################################### + +my $helper = WebGUI::AssetHelper::Product::ImportCSV->new( + id => 'importProducts', + session => $session, + asset => $shelf, +); +my $importProducts = \&WebGUI::AssetHelper::Product::ImportCSV::importProducts; +my $process = Test::MockObject::Extends->new( WebGUI::Fork->create( $session ) ); +addToCleanup( sub { $process->delete } ); + +eval { $importProducts->( $process, { assetId => $helper->asset->getId } ); }; +my $e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::InvalidParam', 'importProducts: error handling for an undefined path to file'); +is($e->error, 'Must provide the path to a file', 'importProducts: error handling for an undefined path to file'); + +eval { $importProducts->( $process, { assetId => $helper->asset->getId, filePath => '/path/to/nowhere' } ); }; +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::InvalidFile', 'importProducts: error handling for file that does not exist in the filesystem'); +is($e->error, 'File could not be found', 'importProducts: error handling for file that does not exist in the filesystem'); +cmp_deeply( + $e, + methods( + brokenFile => '/path/to/nowhere', + ), + 'importTaxData: error handling for file that does not exist in the filesystem', +); + +my $productsFile = WebGUI::Test->getTestCollateralPath('productTables/goodProductTable.csv'); + +SKIP: { + skip 'Root will cause this test to fail since it does not obey file permissions', 3 + if $< == 0; + + my $originalChmod = (stat $productsFile)[2]; + chmod oct(0000), $productsFile; + + eval { $shelf->importProducts($productsFile); }; + $e = Exception::Class->caught(); + isa_ok($e, 'WebGUI::Error::InvalidFile', 'importProducts: error handling for file that cannot be read'); + is($e->error, 'File is not readable', 'importProducts: error handling for file that that cannot be read'); + cmp_deeply( + $e, + methods( + brokenFile => $productsFile, + ), + 'importProducts: error handling for file that that cannot be read', + ); + + chmod $originalChmod, $productsFile; + +} + +my $failure=0; +eval { + $failure = $importProducts->( $process, { + assetId => $helper->asset->getId, + filePath => WebGUI::Test->getTestCollateralPath('productTables/missingHeaders.csv'), + } ); +}; +ok (!$failure, 'Product data is not imported when headers are missing'); +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::InvalidFile', 'importProducts: a file with a missing header column'); +cmp_deeply( + $e, + methods( + error => 'Bad header found in the CSV file', + brokenFile => WebGUI::Test->getTestCollateralPath('productTables/missingHeaders.csv'), + ), + 'importProducts: error handling for a file with a missing header', +); + +$failure=0; +eval { + $failure = $importProducts->( $process, { + assetId => $helper->asset->getId, + filePath => WebGUI::Test->getTestCollateralPath('productTables/badHeaders.csv'), + } + ); +}; +ok (!$failure, 'Product data is not imported when the headers are wrong'); +$e = Exception::Class->caught(); +isa_ok($e, 'WebGUI::Error::InvalidFile', 'importProducts: a file with bad headers'); +cmp_deeply( + $e, + methods( + error => 'Bad header found in the CSV file', + brokenFile => WebGUI::Test->getTestCollateralPath('productTables/badHeaders.csv'), + ), + 'importProducts: error handling for a file with a missing header', +); + +my $pass=0; +$pass = $importProducts->( $process, { + assetId => $helper->asset->getId, + filePath => WebGUI::Test->getTestCollateralPath('productTables/goodProductTable.csv'), +} ); +ok($pass, 'Products imported'); + +my $count = $session->db->quickScalar('select count(*) from Product'); +is($count, 2, 'two products were imported'); + +my $soda = WebGUI::Asset::Sku->newBySku($session, 'soda'); +isa_ok($soda, 'WebGUI::Asset::Sku::Product'); +is($soda->getTitle(), 'Sweet Soda-bottled in Oregon', 'Title set correctly for soda'); +is($soda->get('url'), 'sweet-soda-bottled-in-oregon', 'URL for new product from the title'); +is($soda->get('menuTitle'), $soda->getTitle, 'menuTitle is the same as title'); +my $sodaCollateral = $soda->getAllCollateral('variantsJSON'); +cmp_deeply( + $sodaCollateral, + [ + { + varSku => 'soda-sweet', + shortdesc => 'Sweet Soda', + price => 0.95, + weight => 0.95, + quantity => 500, + variantId => ignore(), + }, + ], + 'collateral set correctly for soda' +); + +my $shirt = WebGUI::Asset::Sku->newBySku($session, 't-shirt'); +isa_ok($shirt, 'WebGUI::Asset::Sku::Product'); +is($shirt->getTitle(), 'Colored T-Shirts', 'Title set correctly for t-shirt'); +my $shirtCollateral = $shirt->getAllCollateral('variantsJSON'); +cmp_deeply( + $shirtCollateral, + [ + { + varSku => 'red-t-shirt', + shortdesc => 'Red T-Shirt', + price => '5.00', + weight => '1.33', + quantity => '1000', + variantId => ignore(), + }, + { + varSku => 'blue-t-shirt', + shortdesc => 'Blue T-Shirt', + price => '5.25', + weight => '1.33', + quantity => '2000', + variantId => ignore(), + }, + ], + 'collateral set correctly for shirt' +); + +####################################################################### +# +# import, part 2 +# +####################################################################### + +$pass=0; +$pass = $importProducts->( $process, { + assetId => $helper->asset->getId, + filePath => WebGUI::Test->getTestCollateralPath('productTables/secondProductTable.csv'), +}); +ok($pass, 'Products imported for the second time'); + +$count = $session->db->quickScalar('select count(*) from Product'); +is($count, 3, 'three products were imported'); + +$soda = WebGUI::Asset::Sku->newBySku($session, 'soda'); +$sodaCollateral = $soda->getAllCollateral('variantsJSON'); +cmp_deeply( + $sodaCollateral, + [ + { + varSku => 'soda-sweet', + shortdesc => 'Sweet Soda', + price => '1.00', + weight => 0.85, + quantity => 500, + variantId => ignore(), + }, + ], + 'collateral updated correctly for soda' +); + +$shirt = WebGUI::Asset::Sku->newBySku($session, 't-shirt'); +$shirtCollateral = $shirt->getAllCollateral('variantsJSON'); +cmp_deeply( + $shirtCollateral, + [ + { + varSku => 'red-t-shirt', + shortdesc => 'Red T-Shirt', + price => '5.00', + weight => '1.33', + quantity => '500', + variantId => ignore(), + }, + { + varSku => 'blue-t-shirt', + shortdesc => 'Blue T-Shirt', + price => '5.25', + weight => '1.33', + quantity => '2000', + variantId => ignore(), + }, + ], + 'collateral updated correctly for shirt' +); + +my $record = WebGUI::Asset::Sku->newBySku($session, 'classical-records-1'); +isa_ok($record, 'WebGUI::Asset::Sku::Product'); +my $recordCollateral = $record->getAllCollateral('variantsJSON'); +cmp_deeply( + $recordCollateral, + [ + { + varSku => 'track-16', + shortdesc => 'Track 16', + price => '3.25', + weight => '0.00', + quantity => 50, + variantId => ignore(), + }, + ], + 'collateral set correctly for classical record' +); + +####################################################################### +# +# import, part 3 +# +####################################################################### + +$pass=0; +$pass = $importProducts->( $process, { + assetId => $helper->asset->getId, + filePath => WebGUI::Test->getTestCollateralPath('productTables/thirdProductTable.csv'), +} ); +ok($pass, 'Products imported for the third time'); + +$count = $session->db->quickScalar('select count(*) from Product'); +is($count, 3, 'still have 3 products, nothing new added'); + +$soda = WebGUI::Asset::Sku->newBySku($session, 'soda'); +is($soda->getTitle(), 'Sweet Soda-totally organic', 'Title updated correctly for soda'); +is($soda->get('menuTitle'), 'Sweet Soda-totally organic', 'menuTitle updated correctly for soda'); +is($soda->get('url'), 'sweet-soda-bottled-in-oregon', 'URL for updated product from the original title, not the updated title'); +$shirt = WebGUI::Asset::Sku->newBySku($session, 't-shirt'); +$shirtCollateral = $shirt->getAllCollateral('variantsJSON'); +cmp_deeply( + $shirtCollateral, + [ + { + varSku => 'red-t-shirt', + shortdesc => 'Red T-Shirt', + price => '5.00', + weight => '1.33', + quantity => '500', + variantId => ignore(), + }, + { + varSku => 'blue-t-shirt', + shortdesc => 'Blue T-Shirt', + price => '5.25', + weight => '1.33', + quantity => '2000', + variantId => ignore(), + }, + ], + 'collateral updated correctly for shirt' +); + +$record = WebGUI::Asset::Sku->newBySku($session, 'classical-records-1'); +$recordCollateral = $record->getAllCollateral('variantsJSON'); +cmp_deeply( + $recordCollateral, + [ + { + varSku => 'track-16', + shortdesc => 'Track 16', + price => '3.25', + weight => '0.00', + quantity => 50, + variantId => ignore(), + }, + { + varSku => 'track-9', + shortdesc => 'Track 9', + price => '3.25', + weight => '0.00', + quantity => 55, + variantId => ignore(), + }, + ], + 'collateral added correctly for classical record' +); + +$shelf->purge; +undef $shelf; + +$record = eval { WebGUI::Asset::Sku->newBySku($session, 'classical-records-1'); }; +ok(Exception::Class->caught(), 'deleting a shelf deletes all products beneath it'); + +####################################################################### +# +# import, quoted headers and fields +# +####################################################################### + +my $shelf2 = $root->addChild({className => $class}); +$helper = WebGUI::AssetHelper::Product::ImportCSV->new( + session => $session, + asset => $shelf2, + id => 'importProducts', +); + +$pass = 0; +eval { + $pass = $importProducts->( $process, { + assetId => $helper->asset->getId, + filePath => WebGUI::Test->getTestCollateralPath('productTables/quotedTable.csv'), + } ); +}; +ok($pass, 'Able to load a table with quoted fields'); +$e = Exception::Class->caught(); +is($e, '', 'No exception thrown on a file with quoted fields'); +is($shelf2->getChildCount, 3, 'imported 3 children skus for shelf2 with quoted fields'); + +$shelf2->purge; +undef $shelf2; + +####################################################################### +# +# import, windows line endings +# +####################################################################### + +$shelf2 = WebGUI::Asset->getRoot($session)->addChild({className => $class}); +$helper = WebGUI::AssetHelper::Product::ImportCSV->new( + session => $session, + asset => $shelf2, + id => 'importProducts', +); + +$pass = 0; +eval { + $pass = $importProducts->( $process, { + assetId => $helper->asset->getId, + filePath => WebGUI::Test->getTestCollateralPath('productTables/windowsTable.csv'), + } ); +}; +ok($pass, 'Able to load a table with windows style newlines'); +$e = Exception::Class->caught(); +is($e, '', 'No exception thrown on a file with quoted fields'); +is($shelf2->getChildCount, 2, 'imported 2 children skus for shelf2 with windows line endings fields'); + +$shelf2->purge; +undef $shelf2; + +####################################################################### +# +# import, old sku column header +# +####################################################################### + +$shelf2 = WebGUI::Test->asset->addChild({className => $class}); +$helper = WebGUI::AssetHelper::Product::ImportCSV->new( + session => $session, + asset => $shelf2, + id => 'importProducts', +); + +$pass = 0; +eval { + $pass = $importProducts->( $process, { + assetId => $helper->asset->getId, + filePath => WebGUI::Test->getTestCollateralPath('productTables/windowsTable.csv'), + }); +}; +ok($pass, 'Able to load a table with old style, sku instead of varSku'); +$e = Exception::Class->caught(); +is($e, '', 'No exception thrown on a file old headers'); +is($shelf2->getChildCount, 2, 'imported 2 children skus for shelf2 with old headers'); + +$shelf2->purge; +undef $shelf2; + +done_testing(); +#vim:ft=perl