From a1012b4054a27acb55d5cf57d476ee1a43dc0d93 Mon Sep 17 00:00:00 2001 From: Brandon Shipley Date: Sun, 10 Aug 2025 02:35:25 -0700 Subject: [PATCH] product photos first commit - only upload base photo --- .gitignore | 3 +- .../20250810031402_CreateProductPhotos.php | 87 +++++ config/app.example.php | 3 + src/Controller/ProductPhotosController.php | 149 ++++++++ src/Model/Entity/ProductPhoto.php | 49 +++ src/Model/Table/ProductPhotosTable.php | 119 ++++++ templates/ProductPhotos/add.php | 26 ++ templates/ProductPhotos/edit.php | 31 ++ templates/ProductPhotos/index.php | 60 ++++ templates/ProductPhotos/view.php | 70 ++++ templates/element/ProductPhotos/form.php | 24 ++ tests/Fixture/ProductPhotosFixture.php | 37 ++ .../ProductPhotosControllerTest.php | 338 ++++++++++++++++++ tests/bootstrap.php | 26 ++ webroot/images/cake_icon.png | Bin 0 -> 943 bytes 15 files changed, 1021 insertions(+), 1 deletion(-) create mode 100644 config/Migrations/20250810031402_CreateProductPhotos.php create mode 100644 src/Controller/ProductPhotosController.php create mode 100644 src/Model/Entity/ProductPhoto.php create mode 100644 src/Model/Table/ProductPhotosTable.php create mode 100644 templates/ProductPhotos/add.php create mode 100644 templates/ProductPhotos/edit.php create mode 100644 templates/ProductPhotos/index.php create mode 100644 templates/ProductPhotos/view.php create mode 100644 templates/element/ProductPhotos/form.php create mode 100644 tests/Fixture/ProductPhotosFixture.php create mode 100644 tests/TestCase/Controller/ProductPhotosControllerTest.php create mode 100644 webroot/images/cake_icon.png diff --git a/.gitignore b/.gitignore index 946a34e..ad5b07f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ /config/Migrations/schema-dump-default.lock /vendor/ /.idea/ -tmp \ No newline at end of file +tmp +tests/test_app/webroot/images/products \ No newline at end of file diff --git a/config/Migrations/20250810031402_CreateProductPhotos.php b/config/Migrations/20250810031402_CreateProductPhotos.php new file mode 100644 index 0000000..77fe785 --- /dev/null +++ b/config/Migrations/20250810031402_CreateProductPhotos.php @@ -0,0 +1,87 @@ +table('product_photos', ['id' => false, 'primary_key' => ['id']]); + $table->addColumn('id', 'uuid', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('product_id', 'uuid', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('product_sku_id', 'uuid', [ + 'default' => null, + 'null' => true, + ]); + + $table->addColumn('photo_dir', 'text', [ + 'default' => null, + 'length' => 255, + 'null' => false, + ]); + $table->addColumn('photo_filename', 'string', [ + 'default' => null, + 'length' => 255, + 'null' => false, + ]); + + $table->addColumn('primary_photo', 'boolean', [ + 'default' => false, + 'null' => false, + ]); + + $table->addColumn('photo_position', 'integer', [ + 'default' => 100, + 'limit' => 11, + 'null' => false, + ]); + + $table->addColumn('enabled', 'boolean', [ + 'default' => false, + 'null' => false, + ]); + + $table->addColumn('created', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('modified', 'datetime', [ + 'default' => null, + 'null' => true, + ]); + $table->addColumn('deleted', 'datetime', [ + 'default' => null, + 'null' => true, + ]); + + $table->addIndex([ + 'product_id', + ], [ + 'name' => 'PRODUCT_PHOTOS_BY_PRODUCT_ID', + 'unique' => false, + ]); + $table->addIndex([ + 'product_sku_id', + ], [ + 'name' => 'PRODUCT_PHOTOS_BY_PRODUCT_SKU_ID', + 'unique' => true, + ]); + + $table->create(); + } +} diff --git a/config/app.example.php b/config/app.example.php index 488d47d..6f05787 100644 --- a/config/app.example.php +++ b/config/app.example.php @@ -4,6 +4,9 @@ return [ 'CakeProducts' => [ + 'photos' => [ + 'directory' => WWW_ROOT . 'images' . DS . 'products' . DS, + ], /** * internal CakeProducts settings - used in the source of truth/internal only system. * Can optionally manage external catalogs diff --git a/src/Controller/ProductPhotosController.php b/src/Controller/ProductPhotosController.php new file mode 100644 index 0000000..e2f43fc --- /dev/null +++ b/src/Controller/ProductPhotosController.php @@ -0,0 +1,149 @@ +getTable()->find() + ->contain(['Products']); + $productPhotos = $this->paginate($query); + + $this->set(compact('productPhotos')); + } + + /** + * View method + * + * @param string|null $id Product Photo id. + * @return \Cake\Http\Response|null|void Renders view + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function view($id = null) + { + $productPhoto = $this->getTable()->get($id, contain: ['Products']); + $this->set(compact('productPhoto')); + } + + /** + * Add method + * + * @return \Cake\Http\Response|null|void Redirects on successful add, renders view otherwise. + */ + public function add() + { + $productPhotosTable = $this->getTable(); + $productPhoto = $productPhotosTable->newEmptyEntity(); + if ($this->request->is('post')) { + $uuid = Text::uuid(); + $postData = $this->request->getData(); + $postData['id'] = $uuid; + $baseDir = Configure::readOrFail('CakeProducts.photos.directory'); + $product = $productPhotosTable->Products->get($this->request->getData('product_id')); + $path = $baseDir . $product->id; + if ($this->request->getData('product_sku_id')) { + $productSku = $productPhotosTable->ProductSkus->get($this->request->getData('product_sku_id')); + $path .= DS . 'skus' . DS . $productSku->id; + } + /** + * @var UploadedFileInterface $photoObject + */ + $photoObject = $this->request->getData('photo'); + + if (!file_exists($path)) { + if (!mkdir($path, 0777, true)) { + dd('Failed to create the required folders. Please check the folder permissions and try again. PATH: ' . $path); + throw new ForbiddenException('Failed to create the required folders. Please check the folder permissions and try again.'); + } + } + $destination = $path . DS . $uuid; + + // Existing files with the same name will be replaced. + $photoObject->moveTo($destination); + if (!file_exists($destination)) { + dd('Failed to move the uploaded image to the appropriate folder. Please try again.'); + throw new ForbiddenException('Failed to move the uploaded image to the appropriate folder. Please try again.'); + } + $postData['photo_dir'] = $path; + $postData['photo_filename'] = $uuid; + $productPhoto = $productPhotosTable->patchEntity($productPhoto, $postData); + if ($productPhotosTable->save($productPhoto)) { + $this->Flash->success(__('The product photo has been saved.')); + + return $this->redirect(['action' => 'index']); + } + dd(print_r($productPhoto->getErrors(), true)); + $this->Flash->error(__('The product photo could not be saved. Please, try again.')); + } + $products = $productPhotosTable->Products->find('list', limit: 200)->all(); + $this->set(compact('productPhoto', 'products')); + } + + /** + * Edit method + * + * @param string|null $id Product Photo id. + * @return \Cake\Http\Response|null|void Redirects on successful edit, renders view otherwise. + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function edit($id = null) + { + $productPhotosTable = $this->getTable(); + $productPhoto = $productPhotosTable->get($id, contain: []); + if ($this->request->is(['patch', 'post', 'put'])) { + $productPhoto = $productPhotosTable->patchEntity($productPhoto, $this->request->getData()); + if ($productPhotosTable->save($productPhoto)) { + $this->Flash->success(__('The product photo has been saved.')); + + return $this->redirect(['action' => 'index']); + } + dd(print_r($productPhoto->getErrors(), true)); + $this->Flash->error(__('The product photo could not be saved. Please, try again.')); + } + $products = $productPhotosTable->Products->find('list', limit: 200)->all(); + $this->set(compact('productPhoto', 'products')); + } + + /** + * Delete method + * + * @param string|null $id Product Photo id. + * @return \Cake\Http\Response|null Redirects to index. + * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found. + */ + public function delete($id = null) + { + $this->request->allowMethod(['post', 'delete']); + $productPhotosTable = $this->getTable(); + + $productPhoto = $productPhotosTable->get($id); + if ($productPhotosTable->delete($productPhoto)) { + $this->Flash->success(__('The product photo has been deleted.')); + } else { + $this->Flash->error(__('The product photo could not be deleted. Please, try again.')); + } + + return $this->redirect(['action' => 'index']); + } +} diff --git a/src/Model/Entity/ProductPhoto.php b/src/Model/Entity/ProductPhoto.php new file mode 100644 index 0000000..177793a --- /dev/null +++ b/src/Model/Entity/ProductPhoto.php @@ -0,0 +1,49 @@ + + */ + protected array $_accessible = [ + 'product_id' => true, + 'product_sku_id' => true, + 'photo_dir' => true, + 'photo_filename' => true, + 'primary_photo' => true, + 'photo_position' => true, + 'enabled' => true, + 'created' => true, + 'modified' => true, + 'deleted' => true, + 'product' => true, + ]; +} diff --git a/src/Model/Table/ProductPhotosTable.php b/src/Model/Table/ProductPhotosTable.php new file mode 100644 index 0000000..1cd436c --- /dev/null +++ b/src/Model/Table/ProductPhotosTable.php @@ -0,0 +1,119 @@ + newEntities(array $data, array $options = []) + * @method \CakeProducts\Model\Entity\ProductPhoto get(mixed $primaryKey, array|string $finder = 'all', \Psr\SimpleCache\CacheInterface|string|null $cache = null, \Closure|string|null $cacheKey = null, mixed ...$args) + * @method \CakeProducts\Model\Entity\ProductPhoto findOrCreate($search, ?callable $callback = null, array $options = []) + * @method \CakeProducts\Model\Entity\ProductPhoto patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = []) + * @method array<\CakeProducts\Model\Entity\ProductPhoto> patchEntities(iterable $entities, array $data, array $options = []) + * @method \CakeProducts\Model\Entity\ProductPhoto|false save(\Cake\Datasource\EntityInterface $entity, array $options = []) + * @method \CakeProducts\Model\Entity\ProductPhoto saveOrFail(\Cake\Datasource\EntityInterface $entity, array $options = []) + * @method iterable<\CakeProducts\Model\Entity\ProductPhoto>|\Cake\Datasource\ResultSetInterface<\CakeProducts\Model\Entity\ProductPhoto>|false saveMany(iterable $entities, array $options = []) + * @method iterable<\CakeProducts\Model\Entity\ProductPhoto>|\Cake\Datasource\ResultSetInterface<\CakeProducts\Model\Entity\ProductPhoto> saveManyOrFail(iterable $entities, array $options = []) + * @method iterable<\CakeProducts\Model\Entity\ProductPhoto>|\Cake\Datasource\ResultSetInterface<\CakeProducts\Model\Entity\ProductPhoto>|false deleteMany(iterable $entities, array $options = []) + * @method iterable<\CakeProducts\Model\Entity\ProductPhoto>|\Cake\Datasource\ResultSetInterface<\CakeProducts\Model\Entity\ProductPhoto> deleteManyOrFail(iterable $entities, array $options = []) + * + * @mixin \Cake\ORM\Behavior\TimestampBehavior + */ +class ProductPhotosTable extends Table +{ + /** + * Initialize method + * + * @param array $config The configuration for the Table. + * @return void + */ + public function initialize(array $config): void + { + parent::initialize($config); + + $this->setTable('product_photos'); + $this->setDisplayField('photo_filename'); + $this->setPrimaryKey('id'); + + $this->addBehavior('Timestamp'); + + $this->belongsTo('Products', [ + 'foreignKey' => 'product_id', + 'joinType' => 'INNER', + 'className' => 'CakeProducts.Products', + ]); + } + + /** + * Default validation rules. + * + * @param \Cake\Validation\Validator $validator Validator instance. + * @return \Cake\Validation\Validator + */ + public function validationDefault(Validator $validator): Validator + { + $validator + ->uuid('product_id') + ->notEmptyString('product_id'); + + $validator + ->uuid('product_sku_id') + ->allowEmptyString('product_sku_id') + ->add('product_sku_id', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']); + + $validator + ->scalar('photo_dir') + ->maxLength('photo_dir', 255) + ->requirePresence('photo_dir', 'create') + ->notEmptyString('photo_dir'); + + $validator + ->scalar('photo_filename') + ->maxLength('photo_filename', 255) + ->requirePresence('photo_filename', 'create') + ->notEmptyString('photo_filename'); + + $validator + ->boolean('primary_photo') + ->notEmptyString('primary_photo'); + + $validator + ->integer('photo_position') + ->notEmptyString('photo_position'); + + $validator + ->boolean('enabled') + ->notEmptyString('enabled'); + + $validator + ->dateTime('deleted') + ->allowEmptyDateTime('deleted'); + + return $validator; + } + + /** + * Returns a rules checker object that will be used for validating + * application integrity. + * + * @param \Cake\ORM\RulesChecker $rules The rules object to be modified. + * @return \Cake\ORM\RulesChecker + */ + public function buildRules(RulesChecker $rules): RulesChecker + { + $rules->add($rules->isUnique(['product_sku_id'], ['allowMultipleNulls' => true]), ['errorField' => '0']); + $rules->add($rules->existsIn(['product_id'], 'Products'), ['errorField' => '1']); + + return $rules; + } +} diff --git a/templates/ProductPhotos/add.php b/templates/ProductPhotos/add.php new file mode 100644 index 0000000..1898f09 --- /dev/null +++ b/templates/ProductPhotos/add.php @@ -0,0 +1,26 @@ + +
+ +
+
+ Form->create($productPhoto, ['type' => 'file']) ?> +
+ + element('ProductPhotos/form', ['includeFile' => true]); ?> +
+ Form->button(__('Submit')) ?> + Form->end() ?> +
+
+
diff --git a/templates/ProductPhotos/edit.php b/templates/ProductPhotos/edit.php new file mode 100644 index 0000000..6f2c777 --- /dev/null +++ b/templates/ProductPhotos/edit.php @@ -0,0 +1,31 @@ + +
+ +
+
+ Form->create($productPhoto) ?> +
+ + element('ProductPhotos/form', ['includeFile' => false]); ?> +
+ Form->button(__('Submit')) ?> + Form->end() ?> +
+
+
diff --git a/templates/ProductPhotos/index.php b/templates/ProductPhotos/index.php new file mode 100644 index 0000000..64dd76d --- /dev/null +++ b/templates/ProductPhotos/index.php @@ -0,0 +1,60 @@ + $productPhotos + */ +?> +
+ Html->link(__('New Product Photo'), ['action' => 'add'], ['class' => 'button float-right']) ?> +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Paginator->sort('id') ?>Paginator->sort('product_id') ?>Paginator->sort('product_sku_id') ?>Paginator->sort('photo_filename') ?>Paginator->sort('primary_photo') ?>Paginator->sort('photo_position') ?>Paginator->sort('enabled') ?>Paginator->sort('created') ?>Paginator->sort('modified') ?>Paginator->sort('deleted') ?>
id) ?>hasValue('product') ? $this->Html->link($productPhoto->product->name, ['controller' => 'Products', 'action' => 'view', $productPhoto->product->id]) : '' ?>product_sku_id) ?>photo_filename) ?>primary_photo) ?>Number->format($productPhoto->photo_position) ?>enabled) ?>created) ?>modified) ?>deleted) ?> + Html->link(__('View'), ['action' => 'view', $productPhoto->id]) ?> + Html->link(__('Edit'), ['action' => 'edit', $productPhoto->id]) ?> + Form->postLink(__('Delete'), ['action' => 'delete', $productPhoto->id], ['confirm' => __('Are you sure you want to delete # {0}?', $productPhoto->id)]) ?> +
+
+
+
    + Paginator->first('<< ' . __('first')) ?> + Paginator->prev('< ' . __('previous')) ?> + Paginator->numbers() ?> + Paginator->next(__('next') . ' >') ?> + Paginator->last(__('last') . ' >>') ?> +
+

Paginator->counter(__('Page {{page}} of {{pages}}, showing {{current}} record(s) out of {{count}} total')) ?>

+
+
diff --git a/templates/ProductPhotos/view.php b/templates/ProductPhotos/view.php new file mode 100644 index 0000000..d059ae9 --- /dev/null +++ b/templates/ProductPhotos/view.php @@ -0,0 +1,70 @@ + +
+ +
+
+

photo_filename) ?>

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
id) ?>
hasValue('product') ? $this->Html->link($productPhoto->product->name, ['controller' => 'Products', 'action' => 'view', $productPhoto->product->id]) : '' ?>
product_sku_id) ?>
photo_filename) ?>
Number->format($productPhoto->photo_position) ?>
created) ?>
modified) ?>
deleted) ?>
primary_photo ? __('Yes') : __('No'); ?>
enabled ? __('Yes') : __('No'); ?>
+
+ +
+ Text->autoParagraph(h($productPhoto->photo_dir)); ?> +
+
+
+
+
diff --git a/templates/element/ProductPhotos/form.php b/templates/element/ProductPhotos/form.php new file mode 100644 index 0000000..1f40879 --- /dev/null +++ b/templates/element/ProductPhotos/form.php @@ -0,0 +1,24 @@ +Form->control('product_id', [ + 'options' => $products ?? [], +]); +echo $this->Form->control('product_sku_id', [ + 'options' => $productSkus ?? [] +]); + +echo $includeFile ? $this->Form->control('photo', [ + 'type' => 'file', +]) : ''; +echo $this->Form->control('primary_photo'); +echo $this->Form->control('photo_position'); +echo $this->Form->control('enabled'); +?> diff --git a/tests/Fixture/ProductPhotosFixture.php b/tests/Fixture/ProductPhotosFixture.php new file mode 100644 index 0000000..7c8de7f --- /dev/null +++ b/tests/Fixture/ProductPhotosFixture.php @@ -0,0 +1,37 @@ +records = [ + [ + 'id' => '2c386086-f4c5-4093-bea5-ee9c29479f58', + 'product_id' => 'cfc98a9a-29b2-44c8-b587-8156adc05317', + 'product_sku_id' => null, + 'photo_dir' => '/products/cfc98a9a-29b2-44c8-b587-8156adc05317/', + 'photo_filename' => '2c386086-f4c5-4093-bea5-ee9c29479f58.jpg', + 'primary_photo' => 1, + 'photo_position' => 100, + 'enabled' => 1, + 'created' => '2025-08-10 04:32:10', + 'modified' => '2025-08-10 04:32:10', + 'deleted' => null, + ], + ]; + parent::init(); + } +} diff --git a/tests/TestCase/Controller/ProductPhotosControllerTest.php b/tests/TestCase/Controller/ProductPhotosControllerTest.php new file mode 100644 index 0000000..d8f4b3a --- /dev/null +++ b/tests/TestCase/Controller/ProductPhotosControllerTest.php @@ -0,0 +1,338 @@ + + */ + protected array $fixtures = [ + 'plugin.CakeProducts.Products', + 'plugin.CakeProducts.ProductSkus', + 'plugin.CakeProducts.ProductPhotos', + ]; + + /** + * setUp method + * + * @return void + */ + protected function setUp(): void + { + parent::setUp(); + $this->ProductPhotos = $this->getTableLocator()->get('ProductPhotos'); + } + + /** + * tearDown method + * + * @return void + */ + protected function tearDown(): void + { + unset($this->ProductPhotos); + $path = Configure::readOrFail('CakeProducts.photos.directory'); + if (file_exists($path)) { + $di = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS); + $ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST); + foreach ( $ri as $file ) { + $file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath()); + } + } + parent::tearDown(); + } + + /** + * Test index method + * + * Tests the index action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::index + */ + public function testIndexGetLoggedIn(): void + { + // $this->loginUserByRole('admin'); + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'index', + ]; + $this->get($url); + $this->assertResponseCode(200); + } + + /** + * Test view method + * + * Tests the view action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::view + */ + public function testViewGetLoggedIn(): void + { + $id = '2c386086-f4c5-4093-bea5-ee9c29479f58'; + // $this->loginUserByRole('admin'); + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'view', + $id, + ]; + $this->get($url); + $this->assertResponseCode(200); + } + + /** + * Test add method + * + * Tests the add action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::add + */ + public function testAddGetLoggedIn(): void + { + $cntBefore = $this->ProductPhotos->find()->count(); + + // $this->loginUserByRole('admin'); + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'add', + ]; + $this->get($url); + $this->assertResponseCode(200); + + $cntAfter = $this->ProductPhotos->find()->count(); + $this->assertEquals($cntBefore, $cntAfter); + } + + /** + * Test add method + * + * Tests a POST request to the add action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::add + */ + public function testAddPostLoggedInSuccess(): void + { + $cntBefore = $this->ProductPhotos->find()->count(); + + // $this->loginUserByRole('admin'); + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'add', + ]; + $image = new UploadedFile( + Configure::readOrFail('App.paths.webroot') . 'images' . DS . 'cake_icon.png', // stream or path to file representing the temp file + 12345, // the filesize in bytes + UPLOAD_ERR_OK, // the upload/error status + 'cake_icon.png', // the filename as sent by the client + 'image/png' // the mimetype as sent by the client + ); + $this->configRequest([ + 'files' => [ + 'photo' => $image, + ], + ]); + $data = [ + 'product_id' => 'cfc98a9a-29b2-44c8-b587-8156adc05317', + 'product_sku_id' => '', + 'primary_photo' => 0, + 'photo' => $image, + 'enabled' => 1, + ]; + $this->post($url, $data); + $this->assertResponseCode(302); + $this->assertRedirectContains('product-photos'); + + $cntAfter = $this->ProductPhotos->find()->count(); + $this->assertEquals($cntBefore + 1, $cntAfter); + } + + /** + * Test add method + * + * Tests a POST request to the add action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::add + */ + public function testAddPostLoggedInFailure(): void + { + $cntBefore = $this->ProductPhotos->find()->count(); + + // $this->loginUserByRole('admin'); + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'add', + ]; + $data = []; + $this->post($url, $data); + $this->assertResponseCode(200); + + $cntAfter = $this->ProductPhotos->find()->count(); + $this->assertEquals($cntBefore, $cntAfter); + } + + /** + * Test edit method + * + * Tests the edit action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::edit + */ + public function testEditGetLoggedIn(): void + { + // $this->loginUserByRole('admin'); + $id = '2c386086-f4c5-4093-bea5-ee9c29479f58'; + + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'edit', + $id, + ]; + $this->get($url); + $this->assertResponseCode(200); + } + + /** + * Test edit method + * + * Tests a PUT request to the edit action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::edit + */ + public function testEditPutLoggedInSuccess(): void + { + // $this->loginUserByRole('admin'); + $id = '2c386086-f4c5-4093-bea5-ee9c29479f58'; + $before = $this->ProductPhotos->get($id); + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'edit', + $id, + ]; + $data = [ + // test new data here + 'enabled' => 1, + 'primary_photo' => 1, + 'photo_position' => 999, + ]; +// $this->put($url, $data); + + $this->assertResponseCode(302); + $this->assertRedirectContains('product-photos'); + + $after = $this->ProductPhotos->get($id); + // assert saved properly below + } + + /** + * Test edit method + * + * Tests a PUT request to the edit action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::edit + */ + public function testEditPutLoggedInFailure(): void + { + // $this->loginUserByRole('admin'); + $id = '2c386086-f4c5-4093-bea5-ee9c29479f58'; + $before = $this->ProductPhotos->get($id); + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'edit', + $id, + ]; + $data = []; + $this->put($url, $data); + $this->assertResponseCode(200); + $after = $this->ProductPhotos->get($id); + + // assert save failed below + } + + /** + * Test delete method + * + * Tests the delete action with a logged in user + * + * @return void + * @throws Exception + * + * @uses ProductPhotosController::delete + */ + public function testDeleteLoggedIn(): void + { + $cntBefore = $this->ProductPhotos->find()->count(); + $id = '2c386086-f4c5-4093-bea5-ee9c29479f58'; + // $this->loginUserByRole('admin'); + $url = [ + 'plugin' => 'CakeProducts', + 'controller' => 'ProductPhotos', + 'action' => 'delete', + $id, + ]; + $this->delete($url); + $this->assertResponseCode(302); + $this->assertRedirectContains('product-photos'); + + $cntAfter = $this->ProductPhotos->find()->count(); + $this->assertEquals($cntBefore - 1, $cntAfter); + } +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index af2ea74..662ce6b 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -48,6 +48,7 @@ Configure::write('App', [ 'namespace' => 'TestApp', 'encoding' => 'UTF-8', 'paths' => [ + 'webroot' => PLUGIN_ROOT . DS . 'webroot' . DS, 'templates' => [ PLUGIN_ROOT . DS . 'tests' . DS . 'test_app' . DS . 'templates' . DS, ], @@ -55,6 +56,31 @@ Configure::write('App', [ ]); Configure::write('debug', true); +Configure::write('CakeProducts', [ + 'photos' => [ + 'directory' => PLUGIN_ROOT . DS . 'tests' . DS . 'test_app' . DS . 'webroot' . DS . 'images' . DS . 'products' . DS, + ], + /** + * internal CakeProducts settings - used in the source of truth/internal only system. + * Can optionally manage external catalogs + * + * - syncExternally - defaults to false - product catalogs can have 1 or more external catalogs linked to them + * which will receive changes to the catalogs and optionally allow for external API access. + * Will have no effect if true but no external catalogs have been added or none are enabled + */ + 'internal' => [ + 'enabled' => true, + /** + * syncExternally defaults to false - product catalogs can have 1 or more external catalogs linked to them + * which will receive changes to the catalogs and optionally allow for external API access. + * Will have no effect if true but no external catalogs have been added or none are enabled + */ + 'syncExternally' => false, + ], + 'external' => [ // product catalog settings for external use (as an API server to power an ecommerce site for example) + 'enabled' => false, + ], +]); $cache = [ 'default' => [ diff --git a/webroot/images/cake_icon.png b/webroot/images/cake_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..394fa42d5131cdc7b0b1246af93b92179f3c887e GIT binary patch literal 943 zcmV;g15o^lP)9q)cr7> zIGsQFGn3| zCzs2iP$-yfVPOGVTU&6sT(-5fwHb2tVsLP9#{Vr9Ct?R7q(rf?v2A5#W$OI=e1YUJ zQ1YRnA&iWSQ1XYAm__>aYb6XIhMiYVD+-z8_pYi6+CsH{*^m;vOjqvbr=H&DFkeqxHQBh$Scsoy0Glw(T zsaSG*ok62V;~yXYNgP*DUw;o98^+0@vGFb{HC+As}XJ=;xg=B7N_;-mKbHH{|lXs_o+aPcs5~J?s%^P2Odb)Uz z$GvY6^!N9(C2-h?28B$qx7%_yHnt2eU%nQ0qThbl6a_+b)EirjBgQ`g1_07Fr&6R? RzIgxu002ovPDHLkV1mdlwUYn< literal 0 HcmV?d00001