|
1 | 1 | <?php |
2 | 2 |
|
| 3 | +use Illuminate\Database\QueryException; |
| 4 | +use Illuminate\Support\Facades\Date; |
3 | 5 | use Illuminate\Support\Facades\Event; |
| 6 | +use Illuminate\Validation\Validator; |
4 | 7 | use StevenFox\LaravelModelValidation\Exceptions\ModelValidationException; |
5 | 8 | use StevenFox\LaravelModelValidation\Listeners\ValidateModel; |
6 | 9 | use StevenFox\LaravelModelValidation\Tests\Fixtures\ValidatesWhenSavingModel; |
7 | 10 | use StevenFox\LaravelModelValidation\Tests\Fixtures\ValidatingModel; |
8 | 11 |
|
| 12 | +beforeEach(function() { |
| 13 | + // Reset the static variable on the trait prior to each test |
| 14 | + // to avoid state corruption. |
| 15 | + ValidatingModel::enableValidationWhenSaving(); |
| 16 | +}); |
| 17 | + |
9 | 18 | it('registers a "creating" model event listeners when shouldValidateWhenSaving() is true', function () { |
10 | 19 | Event::forget('*'); |
11 | 20 | Event::fake(); |
|
55 | 64 | $m = new ValidatesWhenSavingModel(); |
56 | 65 | $modelClassName = $m::class; |
57 | 66 |
|
58 | | - ValidatingModel::reactivateValidationWhenSaving(); |
| 67 | + ValidatingModel::enableValidationWhenSaving(); |
59 | 68 |
|
60 | 69 | expect($m::shouldValidateWhenSaving())->toBeTrue() |
61 | 70 | ->and(Event::hasListeners("eloquent.updating: {$modelClassName}")) |
|
89 | 98 | $m->validate(); |
90 | 99 | }); |
91 | 100 |
|
92 | | -it('will use database-prepared attribute values for validation by default', function () { |
93 | | - $m = new ValidatingModel([ |
| 101 | +it('will validate a model during the save process when applicable', function () { |
| 102 | + $this->expectException(ModelValidationException::class); |
| 103 | + $this->expectExceptionMessage('The required string field is required.'); |
| 104 | + |
| 105 | + $m = new ValidatesWhenSavingModel(); |
| 106 | + |
| 107 | + $m->save(); |
| 108 | + // Exception should be thrown |
| 109 | +}); |
| 110 | + |
| 111 | +it('will NOT validate a model during the save process when inapplicable', function () { |
| 112 | + $this->expectException(QueryException::class); |
| 113 | + |
| 114 | + $m = new ValidatingModel(); |
| 115 | + |
| 116 | + $m->save(); |
| 117 | + // Exception should be thrown |
| 118 | +}); |
| 119 | + |
| 120 | +it('has a passesValidation method', function () { |
| 121 | + $m = new ValidatingModel(); |
| 122 | + |
| 123 | + expect($m->passesValidation())->toBeFalse(); |
| 124 | + |
| 125 | + $m->fill([ |
| 126 | + 'required_string' => 'foo', |
94 | 127 | 'stringable' => str('foo'), |
95 | | - 'datetime' => \Illuminate\Support\Facades\Date::create(2000, 1, 1), |
| 128 | + 'datetime' => Date::create(2000, 1, 1), |
96 | 129 | 'json' => ['foo' => 'bar'], |
97 | 130 | 'array_object' => ['foo' => 'bar'], |
98 | 131 | 'collection' => collect(['foo' => 'bar']), |
99 | 132 | ]); |
100 | 133 |
|
| 134 | + $m->validate(); |
| 135 | + |
| 136 | + expect($m->passesValidation())->toBeTrue(); |
| 137 | +}); |
| 138 | + |
| 139 | +it('will use database-prepared attribute values for validation by default', function () { |
| 140 | + $m = new ValidatingModel([ |
| 141 | + 'stringable' => str('foo'), |
| 142 | + 'datetime' => Date::create(2000, 1, 1), |
| 143 | + 'json' => ['foo' => 'bar'], |
| 144 | + ]); |
| 145 | + |
101 | 146 | expect($m->validationData())->toBe([ |
102 | 147 | 'stringable' => 'foo', |
103 | 148 | 'datetime' => '2000-01-01 00:00:00', |
104 | 149 | 'json' => '{"foo":"bar"}', |
105 | | - 'array_object' => '{"foo":"bar"}', |
106 | | - 'collection' => '{"foo":"bar"}', |
| 150 | + 'array_object' => null, |
| 151 | + 'collection' => null, |
| 152 | + ]); |
| 153 | +}); |
| 154 | + |
| 155 | +it('can prepare the validation data with the prepareAttributesForValidation method', function () { |
| 156 | + $m = new ValidatingModel([ |
| 157 | + 'array_object' => ['foo' => 'bar'], |
| 158 | + 'collection' => ['foo' => 'bar'], |
| 159 | + ]); |
| 160 | + |
| 161 | + /** @see ValidatingModel::prepareAttributesForValidation() */ |
| 162 | + expect($m->validationData())->toBe([ |
| 163 | + 'array_object' => ['foo' => 'bar'], |
| 164 | + 'collection' => ['foo' => 'bar'], |
107 | 165 | ]); |
108 | 166 | }); |
109 | 167 |
|
|
140 | 198 | ->and($m->makeValidator()->getRules())->not()->toBe(['required_string' => ['nullable', 'string']]); |
141 | 199 | }); |
142 | 200 |
|
| 201 | +it('can use mixin validation rules', function () { |
| 202 | + $m = new ValidatingModel([ |
| 203 | + 'required_string' => null, |
| 204 | + ]); |
| 205 | + |
| 206 | + $m->addMixinValidationRules([ |
| 207 | + 'required_string' => 'nullable|string', |
| 208 | + 'array_object' => 'nullable|array', |
| 209 | + 'collection' => 'nullable|array', |
| 210 | + ]); |
| 211 | + |
| 212 | + expect($m->getMixinValidationRules()) |
| 213 | + ->toBe([ |
| 214 | + 'required_string' => 'nullable|string', |
| 215 | + 'array_object' => 'nullable|array', |
| 216 | + 'collection' => 'nullable|array', |
| 217 | + ]) |
| 218 | + ->and($m->makeValidator()->getRules()) |
| 219 | + ->toBe([ |
| 220 | + 'required_string' => ['nullable', 'string'], |
| 221 | + 'stringable' => ['string'], |
| 222 | + 'unique_column' => ['unique:validating_models,NULL,NULL,id'], |
| 223 | + 'datetime' => ['date'], |
| 224 | + 'json' => ['json'], |
| 225 | + 'array_object' => ['nullable', 'array'], |
| 226 | + 'collection' => ['nullable', 'array'], |
| 227 | + 'encrypted_object' => ['encrypted'], |
| 228 | + ]) |
| 229 | + ->and($m->validate()->fails()) |
| 230 | + ->toBeFalse(); |
| 231 | +}); |
| 232 | + |
| 233 | +it('can clear the mixin validation rules', function () { |
| 234 | + $m = new ValidatingModel(); |
| 235 | + |
| 236 | + $m->addMixinValidationRules([ |
| 237 | + 'required_string' => 'nullable|string', |
| 238 | + ]); |
| 239 | + |
| 240 | + expect($m->getMixinValidationRules())->not()->toBeEmpty() |
| 241 | + ->and($m->makeValidator()->getRules())->toBe([ |
| 242 | + 'required_string' => ['nullable', 'string'], |
| 243 | + 'stringable' => ['string'], |
| 244 | + 'unique_column' => ['unique:validating_models,NULL,NULL,id'], |
| 245 | + 'datetime' => ['date'], |
| 246 | + 'json' => ['json'], |
| 247 | + 'array_object' => ['array'], |
| 248 | + 'collection' => ['array'], |
| 249 | + 'encrypted_object' => ['encrypted'], |
| 250 | + ]); |
| 251 | + |
| 252 | + $m->clearMixinValidationRules(); |
| 253 | + |
| 254 | + expect($m->getMixinValidationRules())->toBeEmpty() |
| 255 | + ->and($m->makeValidator()->getRules())->toBe([ |
| 256 | + 'required_string' => ['required', 'string'], |
| 257 | + 'stringable' => ['string'], |
| 258 | + 'unique_column' => ['unique:validating_models,NULL,NULL,id'], |
| 259 | + 'datetime' => ['date'], |
| 260 | + 'json' => ['json'], |
| 261 | + 'array_object' => ['array'], |
| 262 | + 'collection' => ['array'], |
| 263 | + 'encrypted_object' => ['encrypted'], |
| 264 | + ]); |
| 265 | +}); |
| 266 | + |
143 | 267 | it('uses independent rules for updating vs creating', function () { |
144 | 268 | $m = new ValidatingModel(); |
145 | 269 |
|
|
154 | 278 | ->and($m->makeValidator()->getRules()['unique_column']) |
155 | 279 | ->toBe(['unique:validating_models,NULL,"127",id']); // The model key is now a part of the rule for ignoring |
156 | 280 | }); |
| 281 | + |
| 282 | +it('can use custom validation messages', function () { |
| 283 | + /** @see ValidatingModel::customValidationMessages() */ |
| 284 | + $m = new ValidatingModel([ |
| 285 | + 'required_string' => 123, |
| 286 | + ]); |
| 287 | + |
| 288 | + $m->passesValidation(); |
| 289 | + |
| 290 | + expect($m->validator()->errors()->first('required_string')) |
| 291 | + ->toBe('This is a custom message for the required_string.string rule'); |
| 292 | +}); |
| 293 | + |
| 294 | +it('can use custom validation attribute names', function () { |
| 295 | + /** @see ValidatingModel::customValidationMessages() */ |
| 296 | + $m = new ValidatingModel([ |
| 297 | + 'datetime' => Date::create(2000, 1, 1), |
| 298 | + ]); |
| 299 | + |
| 300 | + // Only test the datetime attribute... |
| 301 | + $m->setSupersedingValidationRules([ |
| 302 | + 'datetime' => ['date', 'after:2000-01-01'], |
| 303 | + ]); |
| 304 | + |
| 305 | + $m->passesValidation(); |
| 306 | + |
| 307 | + expect($m->validator()->errors()->first('datetime')) |
| 308 | + ->toBe('The custom datetime attribute name field must be a date after 2000-01-01.'); |
| 309 | +}); |
| 310 | + |
| 311 | +it('provides public access to the validation configuration', function () { |
| 312 | + $m = new ValidatingModel([ |
| 313 | + 'required_string' => 'foo', |
| 314 | + 'stringable' => 'bar', |
| 315 | + ]); |
| 316 | + |
| 317 | + $rules = $m->validationRules(); |
| 318 | + |
| 319 | + expect($rules['required_string']) |
| 320 | + ->toBe(['required', 'string']) |
| 321 | + ->and($rules['stringable']) |
| 322 | + ->toBe(['string']) |
| 323 | + ->and($m->validationData()) |
| 324 | + ->toMatchArray([ |
| 325 | + 'required_string' => 'foo', |
| 326 | + 'stringable' => 'bar', |
| 327 | + ]) |
| 328 | + ->and($m->customValidationMessages()) |
| 329 | + ->toBe(['required_string.string' => 'This is a custom message for the required_string.string rule']) |
| 330 | + ->and($m->customValidationAttributeNames()) |
| 331 | + ->toBe(['datetime' => 'custom datetime attribute name']); |
| 332 | +}); |
| 333 | + |
| 334 | +it('provides a static validating method to register an event hook', function () { |
| 335 | + ValidatingModel::validating(function (ValidatingModel $model, Validator $validator) { |
| 336 | + expect($model->id)->toBe(123) |
| 337 | + ->and($validator->failed())->toBeEmpty(); |
| 338 | + }); |
| 339 | + |
| 340 | + $m = new ValidatingModel([ |
| 341 | + 'id' => 123, |
| 342 | + ]); |
| 343 | + |
| 344 | + // The following should trigger the event. |
| 345 | + $m->passesValidation(); |
| 346 | +}); |
| 347 | + |
| 348 | +it('provides a static validated method to register an event hook', function () { |
| 349 | + ValidatingModel::validated(function (ValidatingModel $model, Validator $validator) { |
| 350 | + expect($model->id)->toBe(123) |
| 351 | + ->and($validator->failed())->not()->toBeEmpty(); |
| 352 | + }); |
| 353 | + |
| 354 | + $m = new ValidatingModel([ |
| 355 | + 'id' => 123, |
| 356 | + ]); |
| 357 | + |
| 358 | + // The following should trigger the event. |
| 359 | + $m->passesValidation(); |
| 360 | +}); |
| 361 | + |
| 362 | +it('registers observable events', function () { |
| 363 | + $m = new ValidatingModel(); |
| 364 | + |
| 365 | + expect($m->getObservableEvents()) |
| 366 | + ->toContain('validating', 'validated'); |
| 367 | +}); |
| 368 | + |
| 369 | +it('throws a ModelValidationException', function () { |
| 370 | + $m = new ValidatingModel(); |
| 371 | + |
| 372 | + try { |
| 373 | + $m->validate(); |
| 374 | + } catch (ModelValidationException $e) { |
| 375 | + expect($e->model)->toBe($m); |
| 376 | + } |
| 377 | +}); |
0 commit comments