@@ -386,7 +386,7 @@ public function testLinkedJsonModelValidatesBeforeSave(): void
386
386
{
387
387
[$ model , $ jsonmodel ] = $ this ->mockLinkedValidatedJsonModel ();
388
388
SchemaValidator::shouldReceive ('validateOrThrow ' )
389
- ->once ()
389
+ ->atLeast ()-> once ()
390
390
->with (
391
391
$ jsonmodel ,
392
392
$ jsonmodel ::SCHEMA ,
@@ -808,17 +808,12 @@ public function testSafeUpdate(): void
808
808
'first_name ' => 'Jeremy ' ,
809
809
];
810
810
811
- $ model = mock (Model::class)-> makePartial ();
811
+ $ model = $ this -> makeMockModel ();
812
812
$ jsonModel = new class ($ model , 'data ' ) extends EventedJsonModel {
813
813
const SCHEMA = 'person.json ' ;
814
814
};
815
815
$ jsonModel ->mobile_phone = '8885551111 ' ;
816
816
817
- $ model
818
- ->shouldReceive ('save ' )
819
- ->once ()
820
- ->andReturn (true );
821
-
822
817
$ jsonModel ->safeUpdate ($ mixedChanges );
823
818
self ::assertFalse (isset ($ jsonModel ->email )); // attribute is invalid, revert to unset
824
819
self ::assertSame ('8885551111 ' , $ jsonModel ->mobile_phone ); // attribute is invalid, revert to previous value
@@ -835,21 +830,59 @@ public function testSafeUpdateRecursive(): void
835
830
],
836
831
];
837
832
838
- $ model = mock (Model::class)-> makePartial ();
833
+ $ model = $ this -> makeMockModel ();
839
834
$ jsonModel = new Person ($ model , 'data ' );
840
835
$ jsonModel ->address ->country = 'CA ' ;
841
836
842
- $ model
843
- ->shouldReceive ('save ' )
844
- ->once ()
845
- ->andReturn (true );
846
-
847
837
$ jsonModel ->safeUpdateRecursive ($ mixedChanges );
848
838
self ::assertFalse (isset ($ jsonModel ->email ), 'attribute is invalid, should revert to unset ' );
849
839
self ::assertSame ('CA ' , $ jsonModel ->address ->country , 'attribute is invalid, revert to previous value ' );
850
840
self ::assertSame ('Jeremy ' , $ jsonModel ->first_name , 'attribute is valid, set ' );
851
841
}
842
+
843
+ public function makeMockModel (): Model
844
+ {
845
+ $ model = mock (Model::class)->makePartial ();
846
+ $ model
847
+ ->shouldReceive ('save ' )
848
+ ->andReturn (true );
849
+ return $ model ;
850
+ }
851
+
852
+ public function testSafeUpdateRecursiveIncludesSavingHandlers (): void
853
+ {
854
+ $ model = $ this ->makeMockModel ();
855
+ $ jsonModel = new CustomSavingHandler ($ model , 'data ' );
856
+ $ caughtExceptions = [];
857
+ $ jsonModel ->safeUpdateRecursive (['shirt ' => 'red ' , 'bestCaptain ' => 'Solo ' ], caughtExceptions: $ caughtExceptions );
858
+ self ::assertCanonicallySame (['shirt ' => 'red ' ], $ jsonModel );
859
+ self ::assertCanonicallySame (["Sorry, Solo is not a valid choice for Best Star Trek Captain. " ], array_map (fn ($ e ) => $ e ->getMessage (), $ caughtExceptions ));
860
+
861
+ // can mix schema and custom handler problems
862
+ $ caughtExceptions = [];
863
+ $ jsonModel ->safeUpdateRecursive (['shirt ' => 'orange ' , 'bestCaptain ' => 'Starbuck ' , 'tribbles ' => 14 ], caughtExceptions: $ caughtExceptions );
864
+ self ::assertCanonicallySame ([
865
+ 'shirt ' => 'red ' , // orange is invalid in the schema, reverted
866
+ 'tribbles ' => 14
867
+ ], $ jsonModel );
868
+ self ::assertCanonicallySame ([
869
+ "The properties must match schema: shirt \nThe data should match one item from enum " ,
870
+ "Sorry, Starbuck is not a valid choice for Best Star Trek Captain. "
871
+ ], array_map (fn ($ e ) => ($ e instanceof JsonSchemaValidationException) ? $ e ->errorsAsMultilineString () : $ e ->getMessage (), $ caughtExceptions ));
872
+
873
+ // False saving handler is also a problem.
874
+ $ caughtExceptions = [];
875
+ $ jsonModel ->safeUpdateRecursive (['tribbles ' => 101 ], caughtExceptions: $ caughtExceptions );
876
+ self ::assertCanonicallySame ([
877
+ 'shirt ' => 'red ' ,
878
+ 'tribbles ' => 14 , // excess tribbles caused saving to return false, that gets reverted but no message
879
+ ], $ jsonModel );
880
+ self ::assertCanonicallySame ([
881
+ "A saving handler on Custom Saving Handler returned false but provided no reason. "
882
+ ], array_map (fn ($ e ) => ($ e instanceof JsonSchemaValidationException) ? $ e ->errorsAsMultilineString () : $ e ->getMessage (), $ caughtExceptions ));
883
+ }
852
884
}
885
+
853
886
/**
854
887
* These are classes and models needed to test inheritance, etc.
855
888
*/
@@ -863,6 +896,21 @@ class UpstreamModel extends JsonModel
863
896
];
864
897
}
865
898
899
+ class CustomSavingHandler extends JsonModel
900
+ {
901
+ public const SCHEMA = '{"properties":{"shirt":{"enum":["red", "gold"]}}} ' ;
902
+ protected static function boot ()
903
+ {
904
+ parent ::boot ();
905
+ static ::saving (function (self $ model ) {
906
+ if ($ model ->isDirty ('bestCaptain ' ) && $ model ->bestCaptain !== 'Saru ' ) {
907
+ throw new DomainException ("Sorry, {$ model ->bestCaptain } is not a valid choice for Best Star Trek Captain. " );
908
+ }
909
+ return $ model ->tribbles < 99 ;
910
+ });
911
+ }
912
+ }
913
+
866
914
class DownstreamModel extends JsonModel
867
915
{
868
916
use HasJsonModelAttributes;
0 commit comments