Skip to content

Commit

Permalink
Refactor Resource Action
Browse files Browse the repository at this point in the history
  • Loading branch information
nasrulhazim committed Oct 31, 2024
1 parent 5522e61 commit de5b305
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 165 deletions.
2 changes: 1 addition & 1 deletion .phpunit.cache/test-results
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"pest_2.34.4","defects":[],"times":{"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_a_menu_action":0.001,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_API_action":0.001,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_without_model_option":0.003,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_has_make_action_command":0.004,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_with_model_option":0.002,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_creates_a_user_with_valid_data":0.136,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_applies_hashing_to_password_field":0.141,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.099,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.003,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_validates_required_fields":0.001,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_throws_exception_if_model_is_not_set":0.066,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.069,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_creates_a_user_with_valid_data":0.129,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.065,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.094,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_validates_required_fields":0.001,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_throws_exception_if_model_is_not_set":0.062,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_hashing_to_password_field":0.124,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.001}}
{"version":"pest_2.34.4","defects":[],"times":{"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_a_menu_action":0.002,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_API_action":0.001,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_without_model_option":0.002,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_has_make_action_command":0.004,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_with_model_option":0.001,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_creates_a_user_with_valid_data":0.136,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_applies_hashing_to_password_field":0.141,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.099,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.003,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_validates_required_fields":0.001,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_throws_exception_if_model_is_not_set":0.066,"P\\Tests\\AbstractActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.069,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_creates_a_user_with_valid_data":0.124,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.063,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.093,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_validates_required_fields":0.001,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_throws_exception_if_model_is_not_set":0.062,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_hashing_to_password_field":0.122,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.001}}
193 changes: 42 additions & 151 deletions src/ResourceAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Validator;

abstract class ResourceAction implements Execute
Expand Down Expand Up @@ -71,113 +72,23 @@ public function __construct(array $inputs = [])
abstract public function rules(): array;

/**
* Set input data.
* Generic setter for properties.
*
* @param array $inputs
* @return $this
*/
public function setInputs(array $inputs): self
{
$this->inputs = $inputs;
return $this;
}

/**
* Set fields for constraint-based operations.
*
* @param array $constrainedBy
* @param string $property
* @param array $value
* @return $this
* @throws ActionException
*/
public function setConstrainedBy(array $constrainedBy): self
{
$this->constrainedBy = $constrainedBy;
return $this;
}

/**
* Get constrained fields.
*
* @return array
*/
public function getConstrainedBy(): array
{
return $this->constrainedBy;
}

/**
* Check if constraints are applied.
*
* @return bool
*/
public function hasConstrained(): bool
{
return count($this->constrainedBy) > 0;
}

/**
* Set fields to hash before saving.
*
* @param array $hashFields
* @return $this
*/
public function setHashFields(array $hashFields): self
{
$this->hashFields = $hashFields;
return $this;
}

/**
* Get fields to hash.
*
* @return array
*/
public function getHashFields(): array
{
return $this->hashFields;
}

/**
* Check if any fields are set to be hashed.
*
* @return bool
*/
public function hasHashFields(): bool
public function setProperty(string $property, array $value): self
{
return count($this->getHashFields()) > 0;
}
if (!property_exists($this, $property)) {
throw new ActionException("Property {$property} does not exist.");
}

/**
* Set fields to encrypt before saving.
*
* @param array $encryptFields
* @return $this
*/
public function setEncryptFields(array $encryptFields): self
{
$this->encryptFields = $encryptFields;
$this->{$property} = $value;
return $this;
}

/**
* Get fields to encrypt.
*
* @return array
*/
public function getEncryptFields(): array
{
return $this->encryptFields;
}

/**
* Check if any fields are set to be encrypted.
*
* @return bool
*/
public function hasEncryptFields(): bool
{
return count($this->getEncryptFields()) > 0;
}

/**
* Retrieve the current record.
*
Expand All @@ -203,23 +114,10 @@ public function inputs(): array
*
* @return void
*/
public function hashFields(): void
protected function transformFields(): void
{
if ($this->hasHashFields()) {
$this->applyTransformationOnFields($this->getHashFields(), fn ($value) => Hash::make($value));
}
}

/**
* Encrypt specified fields in the inputs or constraints.
*
* @return void
*/
public function encryptFields(): void
{
if ($this->hasEncryptFields()) {
$this->applyTransformationOnFields($this->getEncryptFields(), fn ($value) => encrypt($value));
}
$this->applyTransformationOnFields($this->hashFields, fn($value) => Hash::make($value));
$this->applyTransformationOnFields($this->encryptFields, fn($value) => encrypt($value));
}

/**
Expand All @@ -229,21 +127,7 @@ public function encryptFields(): void
*/
public function removeConfirmationFields(): void
{
$this->inputs = array_filter($this->inputs, fn ($value, $key) => ! str($key)->contains('_confirmation'), ARRAY_FILTER_USE_BOTH);
}

/**
* Validate inputs against defined rules.
*
* @throws \Illuminate\Validation\ValidationException
* @return void
*/
protected function validateInputs(): void
{
Validator::make(
array_merge($this->constrainedBy, $this->inputs),
$this->rules()
)->validate();
$this->inputs = array_filter($this->inputs, fn($value, $key) => !Str::contains($key, '_confirmation'), ARRAY_FILTER_USE_BOTH);
}

/**
Expand All @@ -255,23 +139,14 @@ protected function validateInputs(): void
*/
protected function applyTransformationOnFields(array $fields, callable $transformation): void
{
if ($this->hasConstrained()) {
$constrainedBy = $this->constrainedBy;
foreach ($fields as $field) {
if (isset($constrainedBy[$field])) {
$constrainedBy[$field] = $transformation($constrainedBy[$field]);
}
$transformFields = function (&$value, $key) use ($fields, $transformation) {
if (in_array($key, $fields, true)) {
$value = $transformation($value);
}
$this->constrainedBy = $constrainedBy;
}
};

$inputs = $this->inputs;
foreach ($fields as $field) {
if (isset($inputs[$field])) {
$inputs[$field] = $transformation($inputs[$field]);
}
}
$this->inputs = $inputs;
array_walk_recursive($this->inputs, $transformFields);
array_walk_recursive($this->constrainedBy, $transformFields);
}

/**
Expand Down Expand Up @@ -303,22 +178,38 @@ public function prepare(): void
// Placeholder for child classes to implement custom preparation.
}

/**
* Validates the inputs against the defined rules.
*
* @throws \Illuminate\Validation\ValidationException
* @return void
*/
protected function validateInputs(): void
{
Validator::make(
array_merge($this->constrainedBy, $this->inputs),
$this->rules()
)->validate();
}


/**
* Execute the action with preparation, validation, and data processing.
*
* @return Model
* @throws \Illuminate\Validation\ValidationException
*/
public function execute()
public function execute(): Model
{
$this->prepare();
$this->validateInputs();
$this->hashFields();
$this->encryptFields();
$this->transformFields();
$this->removeConfirmationFields();

return $this->record = DB::transaction(fn () => $this->hasConstrained()
? $this->model()::updateOrCreate($this->constrainedBy, $this->inputs)
: $this->model()::create($this->inputs));
return $this->record = DB::transaction(function () {
return !empty($this->constrainedBy)
? $this->model()::updateOrCreate($this->constrainedBy, $this->inputs)
: $this->model()::create($this->inputs);
});
}
}
23 changes: 10 additions & 13 deletions tests/ResourceActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
expect($record)->toBeInstanceOf(User::class)
->and($record->name)->toBe('John Doe')
->and($record->email)->toBe('[email protected]')
->and(Hash::check('secretpassword', $record->password))->toBeTrue(); // Check if the password is hashed
->and(Hash::check('secretpassword', $record->password))->toBeTrue();
});

// it validates required fields
Expand All @@ -54,9 +54,8 @@
];

// Stub a class without a model definition
$stubAction = new class($inputs) extends CreateUserAction
{
protected string $model = ''; // Not setting the model on purpose
$stubAction = new class($inputs) extends CreateUserAction {
protected string $model = ''; // Intentionally leave model empty
};

// Act & Assert
Expand All @@ -73,13 +72,13 @@
];

$action = new CreateUserAction($inputs);
$action->setHashFields(['password']);
$action->setProperty('hashFields', ['password']); // Use setProperty to define hash fields

// Act
$record = $action->execute();

// Assert
expect(Hash::check('secretpassword', $record->password))->toBeTrue(); // Verify password hash
expect(Hash::check('secretpassword', $record->password))->toBeTrue();
});

// it removes confirmation fields from inputs
Expand Down Expand Up @@ -113,16 +112,16 @@

$action = new CreateUserAction($inputs);

// // Mock the database connection and query builder
// Mock the database connection and query builder
$mockConnection = Mockery::mock();
$mockQueryBuilder = Mockery::mock();

// Mock the chain of methods on the query builder
$mockConnection->shouldReceive('table')->andReturn($mockQueryBuilder);
$mockQueryBuilder->shouldReceive('useWritePdo')->andReturn($mockQueryBuilder);
$mockQueryBuilder->shouldReceive('where')->andReturn($mockQueryBuilder);
$mockQueryBuilder->shouldReceive('count')->andReturn(0); // Return a count of 0 for the test
$mockQueryBuilder->shouldReceive('updateOrCreate')->andReturn(Mockery::mock(User::class)); // Mock the final result
$mockQueryBuilder->shouldReceive('count')->andReturn(0);
$mockQueryBuilder->shouldReceive('updateOrCreate')->andReturn(Mockery::mock(User::class));

// Mock the transaction flow
DB::shouldReceive('connection')->andReturn($mockConnection);
Expand All @@ -139,6 +138,7 @@

// it applies encryption to specified fields
it('applies encryption to specified fields', function () {
// Arrange
$inputs = [
'name' => 'John Doe',
'email' => '[email protected]',
Expand All @@ -147,9 +147,7 @@
];

$action = new CreateUserAction($inputs);

// Assuming we want to encrypt the 'ssn' field
$action->setEncryptFields(['ssn']);
$action->setProperty('encryptFields', ['ssn']); // Use setProperty to define encryption fields

// Act
$record = $action->execute();
Expand All @@ -159,6 +157,5 @@

// Ensure 'ssn' field was encrypted
$decryptedSSN = decrypt($record->ssn);

expect($decryptedSSN)->toBe('123-45-6789');
});

0 comments on commit de5b305

Please sign in to comment.