Skip to content

Commit e47487d

Browse files
committed
Update Resource Action Test
1 parent de5b305 commit e47487d

File tree

5 files changed

+106
-62
lines changed

5 files changed

+106
-62
lines changed

.phpunit.cache/test-results

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"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}}
1+
{"version":"pest_2.34.4","defects":{"P\\Tests\\ResourceActionTest::__pest_evaluable_it_fails_to_apply_transformation_if_field_is_missing":8},"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.046,"P\\Tests\\LaravelActionTest::__pest_evaluable_it_can_make_an_action_without_model_option":0.001,"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.121,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_encryption_to_specified_fields":0.063,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_uses_transactions_during_execution":0.069,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_validates_required_fields":0.002,"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.123,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_removes_confirmation_fields_from_inputs":0.001,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_both_hashing_and_encryption_to_specified_fields":0.145,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_handles_multiple_fields_for_hashing_and_encryption":0.241,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_fails_to_apply_transformation_if_field_is_missing":0.001,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_applies_constraints_for_update_or_create":0.182,"P\\Tests\\ResourceActionTest::__pest_evaluable_it_does_not_apply_transformation_if_optional_field_is_missing":0.001}}

src/ResourceAction.php

Lines changed: 8 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,57 +7,43 @@
77
use Illuminate\Database\Eloquent\Model;
88
use Illuminate\Support\Facades\DB;
99
use Illuminate\Support\Facades\Hash;
10-
use Illuminate\Support\Str;
1110
use Illuminate\Support\Facades\Validator;
11+
use Illuminate\Support\Str;
1212

1313
abstract class ResourceAction implements Execute
1414
{
1515
/**
1616
* The model class the action operates on.
17-
*
18-
* @var string
1917
*/
2018
protected string $model;
2119

2220
/**
2321
* Input data for the action.
24-
*
25-
* @var array
2622
*/
2723
protected array $inputs;
2824

2925
/**
3026
* Fields to use for constraint-based operations.
31-
*
32-
* @var array
3327
*/
3428
protected array $constrainedBy = [];
3529

3630
/**
3731
* Fields to hash before saving.
38-
*
39-
* @var array
4032
*/
4133
protected array $hashFields = [];
4234

4335
/**
4436
* Fields to encrypt before saving.
45-
*
46-
* @var array
4737
*/
4838
protected array $encryptFields = [];
4939

5040
/**
5141
* The Eloquent model record.
52-
*
53-
* @var Model
5442
*/
5543
protected Model $record;
5644

5745
/**
5846
* Constructor to initialize input data.
59-
*
60-
* @param array $inputs
6147
*/
6248
public function __construct(array $inputs = [])
6349
{
@@ -66,33 +52,29 @@ public function __construct(array $inputs = [])
6652

6753
/**
6854
* Abstract method to define validation rules for the action.
69-
*
70-
* @return array
7155
*/
7256
abstract public function rules(): array;
7357

7458
/**
7559
* Generic setter for properties.
7660
*
77-
* @param string $property
78-
* @param array $value
7961
* @return $this
62+
*
8063
* @throws ActionException
8164
*/
8265
public function setProperty(string $property, array $value): self
8366
{
84-
if (!property_exists($this, $property)) {
67+
if (! property_exists($this, $property)) {
8568
throw new ActionException("Property {$property} does not exist.");
8669
}
8770

8871
$this->{$property} = $value;
72+
8973
return $this;
9074
}
9175

9276
/**
9377
* Retrieve the current record.
94-
*
95-
* @return Model
9678
*/
9779
public function getRecord(): Model
9880
{
@@ -101,8 +83,6 @@ public function getRecord(): Model
10183

10284
/**
10385
* Retrieve the inputs.
104-
*
105-
* @return array
10686
*/
10787
public function inputs(): array
10888
{
@@ -111,31 +91,23 @@ public function inputs(): array
11191

11292
/**
11393
* Hash specified fields in the inputs or constraints.
114-
*
115-
* @return void
11694
*/
11795
protected function transformFields(): void
11896
{
119-
$this->applyTransformationOnFields($this->hashFields, fn($value) => Hash::make($value));
120-
$this->applyTransformationOnFields($this->encryptFields, fn($value) => encrypt($value));
97+
$this->applyTransformationOnFields($this->hashFields, fn ($value) => Hash::make($value));
98+
$this->applyTransformationOnFields($this->encryptFields, fn ($value) => encrypt($value));
12199
}
122100

123101
/**
124102
* Remove confirmation fields from inputs.
125-
*
126-
* @return void
127103
*/
128104
public function removeConfirmationFields(): void
129105
{
130-
$this->inputs = array_filter($this->inputs, fn($value, $key) => !Str::contains($key, '_confirmation'), ARRAY_FILTER_USE_BOTH);
106+
$this->inputs = array_filter($this->inputs, fn ($value, $key) => ! Str::contains($key, '_confirmation'), ARRAY_FILTER_USE_BOTH);
131107
}
132108

133109
/**
134110
* Apply transformation to specified fields in inputs and constraints.
135-
*
136-
* @param array $fields
137-
* @param callable $transformation
138-
* @return void
139111
*/
140112
protected function applyTransformationOnFields(array $fields, callable $transformation): void
141113
{
@@ -153,7 +125,6 @@ protected function applyTransformationOnFields(array $fields, callable $transfor
153125
* Retrieve the model class for the action.
154126
*
155127
* @throws ActionException
156-
* @return string
157128
*/
158129
public function model(): string
159130
{
@@ -170,8 +141,6 @@ public function model(): string
170141

171142
/**
172143
* Preparation method for the action.
173-
*
174-
* @return void
175144
*/
176145
public function prepare(): void
177146
{
@@ -182,7 +151,6 @@ public function prepare(): void
182151
* Validates the inputs against the defined rules.
183152
*
184153
* @throws \Illuminate\Validation\ValidationException
185-
* @return void
186154
*/
187155
protected function validateInputs(): void
188156
{
@@ -192,11 +160,9 @@ protected function validateInputs(): void
192160
)->validate();
193161
}
194162

195-
196163
/**
197164
* Execute the action with preparation, validation, and data processing.
198165
*
199-
* @return Model
200166
* @throws \Illuminate\Validation\ValidationException
201167
*/
202168
public function execute(): Model
@@ -207,7 +173,7 @@ public function execute(): Model
207173
$this->removeConfirmationFields();
208174

209175
return $this->record = DB::transaction(function () {
210-
return !empty($this->constrainedBy)
176+
return ! empty($this->constrainedBy)
211177
? $this->model()::updateOrCreate($this->constrainedBy, $this->inputs)
212178
: $this->model()::create($this->inputs);
213179
});

tests/ResourceActionTest.php

Lines changed: 95 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@
5454
];
5555

5656
// Stub a class without a model definition
57-
$stubAction = new class($inputs) extends CreateUserAction {
57+
$stubAction = new class($inputs) extends CreateUserAction
58+
{
5859
protected string $model = ''; // Intentionally leave model empty
5960
};
6061

@@ -81,6 +82,84 @@
8182
expect(Hash::check('secretpassword', $record->password))->toBeTrue();
8283
});
8384

85+
// it applies both hashing and encryption to different fields
86+
it('applies both hashing and encryption to specified fields', function () {
87+
// Arrange
88+
$inputs = [
89+
'name' => 'John Doe',
90+
'email' => 'johndoe@example.com',
91+
'password' => 'secretpassword',
92+
'ssn' => '123-45-6789',
93+
];
94+
95+
$action = new CreateUserAction($inputs);
96+
$action->setProperty('hashFields', ['password']);
97+
$action->setProperty('encryptFields', ['ssn']);
98+
99+
// Act
100+
$record = $action->execute();
101+
102+
// Assert
103+
expect(Hash::check('secretpassword', $record->password))->toBeTrue();
104+
expect(decrypt($record->ssn))->toBe('123-45-6789');
105+
});
106+
107+
// it handles multiple fields for hashing and encryption
108+
it('handles multiple fields for hashing and encryption', function () {
109+
// Arrange
110+
$inputs = [
111+
'name' => 'Jane Doe',
112+
'email' => 'janedoe@example.com',
113+
'password' => 'anotherpassword',
114+
'ssn' => '987-65-4321',
115+
'security_answer' => 'My first car',
116+
];
117+
118+
$action = new CreateUserAction($inputs);
119+
$action->setProperty('hashFields', ['password', 'security_answer']);
120+
$action->setProperty('encryptFields', ['ssn', 'email']);
121+
122+
// Act
123+
$record = $action->execute();
124+
125+
// Assert
126+
// Only use Hash::check on fields known to be hashed
127+
expect(Hash::check('anotherpassword', $record->password))->toBeTrue();
128+
expect(Hash::check('My first car', $record->security_answer))->toBeTrue();
129+
130+
// Decrypt and verify the encrypted fields
131+
expect(decrypt($record->ssn))->toBe('987-65-4321');
132+
expect(decrypt($record->email))->toBe('janedoe@example.com');
133+
});
134+
135+
// it applies constraints for update or create
136+
it('applies constraints for update or create', function () {
137+
// Arrange
138+
$inputs = [
139+
'name' => 'John Doe Updated',
140+
'email' => 'uniqueemail@example.com', // Use a unique email to avoid the validation error
141+
'password' => 'newpassword',
142+
];
143+
144+
// Pre-create a user with a different email to simulate an existing record
145+
$existingUser = User::create([
146+
'name' => 'Old Name',
147+
'email' => 'oldemail@example.com', // Different email to avoid triggering unique constraint
148+
'password' => Hash::make('oldpassword'),
149+
]);
150+
151+
// Now set the constraints to match the unique constraint check
152+
$action = new CreateUserAction($inputs);
153+
$action->setProperty('constrainedBy', ['id' => $existingUser->id]); // Use ID as a unique constraint for update
154+
155+
// Act
156+
$record = $action->execute();
157+
158+
// Assert
159+
expect($record->name)->toBe('John Doe Updated')
160+
->and(Hash::check('newpassword', $record->password))->toBeTrue();
161+
});
162+
84163
// it removes confirmation fields from inputs
85164
it('removes confirmation fields from inputs', function () {
86165
// Arrange
@@ -116,46 +195,44 @@
116195
$mockConnection = Mockery::mock();
117196
$mockQueryBuilder = Mockery::mock();
118197

119-
// Mock the chain of methods on the query builder
198+
// Set expectation for DB::connection()
199+
DB::shouldReceive('connection')->once()->andReturn($mockConnection);
200+
201+
// Set up transaction mock and method chaining on the query builder
202+
DB::shouldReceive('transaction')->once()->andReturnUsing(function ($callback) {
203+
return $callback();
204+
});
205+
206+
// Set up expectations for methods on the query builder
120207
$mockConnection->shouldReceive('table')->andReturn($mockQueryBuilder);
121208
$mockQueryBuilder->shouldReceive('useWritePdo')->andReturn($mockQueryBuilder);
122209
$mockQueryBuilder->shouldReceive('where')->andReturn($mockQueryBuilder);
123210
$mockQueryBuilder->shouldReceive('count')->andReturn(0);
124211
$mockQueryBuilder->shouldReceive('updateOrCreate')->andReturn(Mockery::mock(User::class));
125212

126-
// Mock the transaction flow
127-
DB::shouldReceive('connection')->andReturn($mockConnection);
128-
DB::shouldReceive('transaction')->once()->andReturnUsing(function ($callback) {
129-
return $callback();
130-
});
131-
132213
// Act
133214
$record = $action->execute();
134215

135216
// Assert
136217
expect($record)->toBeInstanceOf(User::class);
137218
});
138219

139-
// it applies encryption to specified fields
140-
it('applies encryption to specified fields', function () {
220+
// it does not apply transformation if optional field is missing
221+
it('does not apply transformation if optional field is missing', function () {
141222
// Arrange
142223
$inputs = [
143224
'name' => 'John Doe',
144225
'email' => 'johndoe@example.com',
145-
'password' => 'secretpassword', // password will be hashed
146-
'ssn' => '123-45-6789', // This is the field to be encrypted
226+
'password' => 'secretpassword', // Required field to satisfy validation
147227
];
148228

149229
$action = new CreateUserAction($inputs);
150-
$action->setProperty('encryptFields', ['ssn']); // Use setProperty to define encryption fields
230+
$action->setProperty('hashFields', ['security_answer']); // 'security_answer' is not present in inputs
151231

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

155235
// Assert
156-
expect($record)->toBeInstanceOf(User::class);
157-
158-
// Ensure 'ssn' field was encrypted
159-
$decryptedSSN = decrypt($record->ssn);
160-
expect($decryptedSSN)->toBe('123-45-6789');
236+
expect($record)->toBeInstanceOf(User::class); // Check the action executed successfully
237+
expect($action->inputs())->not->toHaveKey('security_answer'); // Ensure no transformation occurred on missing field
161238
});

tests/Stubs/Models/User.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class User extends Authenticatable
1212
* @var array
1313
*/
1414
protected $fillable = [
15-
'name', 'email', 'password', 'ssn',
15+
'name', 'email', 'password', 'ssn', 'security_answer',
1616
];
1717

1818
/**

tests/database/migrations/create_users_table.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ public function up()
1919
$table->string('email')->unique();
2020
$table->timestamp('email_verified_at')->nullable();
2121
$table->string('password');
22+
$table->string('security_answer')->nullable();
2223
$table->rememberToken();
2324
$table->string('ssn')->nullable(); // Adding an 'ssn' field for testing encryption
2425
$table->timestamps();

0 commit comments

Comments
 (0)