diff --git a/app/Bus/Handlers/Traits/StoresMeta.php b/app/Bus/Handlers/Traits/StoresMeta.php index 0c4a4c2f..b308c657 100644 --- a/app/Bus/Handlers/Traits/StoresMeta.php +++ b/app/Bus/Handlers/Traits/StoresMeta.php @@ -18,38 +18,64 @@ trait StoresMeta /** * Stores all Meta values of a model. * - * @param $meta - * @param $type - * @param $id - * - * @throws \Exception + * @param array $metaData + * @param string $metaType + * @param string|int $metaId + * @param string $metaModel * * @return void */ - public function storeMeta($meta, $type, $id) + public function storeMeta($metaData, $metaType, $metaId, $metaModel = Meta::class) { // Validation required instead of type hinting because it could be passed as false or NULL - if (!is_array($meta)) { + if (!is_array($metaData)) { return; } - foreach ($meta as $key => $value) { - $meta = Meta::firstOrNew([ - 'key' => $key, - 'meta_type' => $type, - 'meta_id' => $id, - ]); + foreach ($metaData as $key => $value) { + $modelInstance = call_user_func( + [$metaModel, 'firstOrNew'], + [ + 'key' => $key, + 'meta_type' => $metaType, + 'meta_id' => $metaId, + ] + ); + $value = $this->removeEmptyValues($value); if (!empty($value)) { - $meta->value = $value; - $meta->save(); + $modelInstance->setAttribute('value', $value); + $modelInstance->save(); continue; } // The value is empty, remove the row - if ($meta->exists) { - $meta->delete(); + if ($modelInstance->exists) { + $modelInstance->delete(); } } } + + /** + * Determine if a Value is empty. + * + * @param $values + * + * @return array|mixed + */ + protected function removeEmptyValues($values) + { + if (!is_array($values)) { + return empty($values) ? null : $values; + } + + foreach ($values as $key => $value) { + if (!empty($value)) { + continue; + } + unset($values[$key]); + } + + return $values; + } } diff --git a/tests/Bus/Traits/StoresMetaTest.php b/tests/Bus/Traits/StoresMetaTest.php new file mode 100644 index 00000000..641878f8 --- /dev/null +++ b/tests/Bus/Traits/StoresMetaTest.php @@ -0,0 +1,133 @@ +markTestSkipped('This test requires Mockery'); + } + + $this->metaModel = Mockery::mock(Meta::class)->makePartial(); + $this->app->instance(Meta::class, $this->metaModel); + } + + /** + * Our Mockery expectations should count as assertions to prevent warnings from PHPUnit. + */ + public function tearDown() + { + $this->addToAssertionCount(Mockery::getContainer()->mockery_getExpectationCount()); + parent::tearDown(); + } + + /** + * Each array value passed to the MetaValues should result in a new model instance. + */ + public function testStoresMetaWithSimpleMultipleArrays() + { + $mock = $this->getMockForTrait(StoresMeta::class); + $metaData = [ + 'somekey1' => 'somevalue', + 'somekey2' => 'somevalue', + 'somekey3' => 'somevalue', + ]; + + $this->metaModel->shouldReceive('firstOrNew')->times(3)->andReturn($this->metaModel); + $this->metaModel->shouldReceive('save')->times(3); + $this->metaModel->shouldReceive('setAttribute')->times(3)->andReturnUsing(function ($key, $value) { + $this->assertEquals('value', $key); + $this->assertEquals('somevalue', $value); + + return $this->metaModel; + }); + + $mock->storeMeta($metaData, 'some_class', 1, $this->metaModel); + } + + /** + * It will pass nested arrays to the value property of the Meta model. + */ + public function testStoresNestedArraysAsSingleValue() + { + $mock = $this->getMockForTrait(StoresMeta::class); + $metaData = ['somekey1' => ['subkey' => ['second' => 'key']]]; + + $this->metaModel->shouldReceive('firstOrNew')->once()->andReturn($this->metaModel); + $this->metaModel->shouldReceive('save')->once(); + $this->metaModel->shouldReceive('setAttribute')->once()->andReturnUsing(function ($key, $value) { + $this->assertEquals('value', $key); + $this->assertEquals(['subkey' => ['second' => 'key']], $value); + + return $this->metaModel; + }); + + $mock->storeMeta($metaData, 'some_class', 1, $this->metaModel); + } + + /** + * If a value is empty or null it will be removed. + */ + public function testEmptyValuesWillBeDeleted() + { + $mock = $this->getMockForTrait(StoresMeta::class); + $metaData = ['somekey1' => '']; + + $this->metaModel->exists = true; + $this->metaModel->shouldReceive('firstOrNew')->once()->andReturn($this->metaModel); + $this->metaModel->shouldReceive('delete')->once(); + $this->metaModel->shouldReceive('setAttribute')->never(); + $this->metaModel->shouldReceive('save')->never(); + + $mock->storeMeta($metaData, 'some_class', 1, $this->metaModel); + } + + /** + * If a value is empty or null in a nested array it will be removed. + */ + public function testEmptyNestedArrayKeysAreRemoved() + { + $mock = $this->getMockForTrait(StoresMeta::class); + $metaData = ['somekey1' => ['keyWithValue' => 'value123', 'keyWithoutValue' => null]]; + + $this->metaModel->exists = true; + $this->metaModel->shouldReceive('firstOrNew')->once()->andReturn($this->metaModel); + $this->metaModel->shouldReceive('setAttribute')->once()->andReturnUsing(function ($key, $value) { + $this->assertEquals('value', $key); + $this->assertEquals(['keyWithValue' => 'value123'], $value); + + return $this->metaModel; + }); + $this->metaModel->shouldReceive('save')->once(); + $this->metaModel->shouldReceive('delete')->never(); + + $mock->storeMeta($metaData, 'some_class', 1, $this->metaModel); + } +} diff --git a/tests/Functional/Incident/MetaSeoTest.php b/tests/Functional/Incident/MetaSeoTest.php index 10d375c6..c6f6cd38 100644 --- a/tests/Functional/Incident/MetaSeoTest.php +++ b/tests/Functional/Incident/MetaSeoTest.php @@ -13,12 +13,10 @@ namespace CachetHQ\Tests\Cachet\Functional\Bus\Commands\Incident; use CachetHQ\Cachet\Bus\Commands\Incident\CreateIncidentCommand; use CachetHQ\Cachet\Models\Incident; -use CachetHQ\Cachet\Models\User; use CachetHQ\Cachet\Presenters\IncidentPresenter; use CachetHQ\Cachet\Settings\Repository as SettingsRepository; use CachetHQ\Tests\Cachet\AbstractTestCase; use Illuminate\Foundation\Testing\DatabaseMigrations; -use Illuminate\Support\Facades\Auth; /** * This is the create incident command test class. @@ -43,8 +41,9 @@ class MetaSeoTest extends AbstractTestCase /** * CreateIncidentCommandTest constructor. * - * @param null $name - * @param array $data + * @param null $name + * @param array $data + * @param string $dataName */ public function __construct($name = null, array $data = [], $dataName = '') { @@ -60,6 +59,7 @@ class MetaSeoTest extends AbstractTestCase { parent::setUp(); $this->app->make(SettingsRepository::class)->set('app_name', $this->appName); + $this->app->config->set('setting.app_name', $this->appName); } /** @@ -153,14 +153,11 @@ class MetaSeoTest extends AbstractTestCase /** * @param array $meta * - * @return \Illuminate\Database\Eloquent\Model|static + * @return Incident */ protected function createIncidentWithMeta(array $meta) { - $user = factory(User::class)->create(); - $user->save(); - - Auth::login($user); + $this->signIn(); $name = $this->fakerFactory->name; $message = $this->fakerFactory->sentence;