55namespace Tests \Lendable \Json \Unit ;
66
77use PHPUnit \Framework \Attributes \CoversClass ;
8+ use PHPUnit \Framework \Attributes \DataProvider ;
89use PHPUnit \Framework \Attributes \Test ;
910use Lendable \Json \DeserializationFailed ;
11+ use Lendable \Json \Failure ;
1012use Lendable \Json \InvalidDeserializedData ;
1113use Lendable \Json \SerializationFailed ;
1214use Lendable \Json \Serializer ;
1820#[CoversClass(InvalidDeserializedData::class)]
1921final class SerializerTest extends TestCase
2022{
23+ private const int MAX_NESTING_DEPTH = 511 ;
24+
2125 private Serializer $ serializer ;
2226
2327 #[Test]
@@ -28,6 +32,12 @@ public function it_can_serialize_an_array_of_scalars_to_json(): void
2832 $ this ->assertSame ('{"foo":"bar","baz":[1.03,true,"foobar"],"emoji":"😃","with_forward_slash":"foo/bar"} ' , $ result );
2933 }
3034
35+ #[Test]
36+ public function it_can_serialize_an_empty_array_to_json (): void
37+ {
38+ $ this ->assertSame ('[] ' , $ this ->serializer ->serialize ([]));
39+ }
40+
3141 #[Test]
3242 public function it_throws_when_serializing_if_an_error_encountered (): void
3343 {
@@ -37,6 +47,29 @@ public function it_throws_when_serializing_if_an_error_encountered(): void
3747 $ this ->serializer ->serialize (["\xf0\x28\x8c\xbc" => 'bar ' ]);
3848 }
3949
50+ /**
51+ * @param array<mixed> $data
52+ */
53+ #[Test]
54+ #[DataProvider('provideUnrepresentableFloats ' )]
55+ public function it_throws_when_serializing_a_float_json_cannot_represent (array $ data ): void
56+ {
57+ $ this ->expectException (SerializationFailed::class);
58+ $ this ->expectExceptionMessageIs ('Failed to serialize data to JSON. Error code: 7, error message: Inf and NaN cannot be JSON encoded. ' );
59+
60+ $ this ->serializer ->serialize ($ data );
61+ }
62+
63+ /**
64+ * @return iterable<string, array{array<mixed>}>
65+ */
66+ public static function provideUnrepresentableFloats (): iterable
67+ {
68+ yield 'infinity ' => [[\INF ]];
69+ yield 'negative infinity ' => [[-\INF ]];
70+ yield 'not a number ' => [[\NAN ]];
71+ }
72+
4073 #[Test]
4174 public function it_can_deserialize_from_a_json_string_to_php_scalars (): void
4275 {
@@ -45,6 +78,25 @@ public function it_can_deserialize_from_a_json_string_to_php_scalars(): void
4578 $ this ->assertSame (['foo ' => 'bar ' , 'baz ' => [1.03 , true , 'foobar ' ]], $ result );
4679 }
4780
81+ /**
82+ * @param array<mixed> $expected
83+ */
84+ #[Test]
85+ #[DataProvider('provideEmptyStructures ' )]
86+ public function it_can_deserialize_an_empty_structure (string $ json , array $ expected ): void
87+ {
88+ $ this ->assertSame ($ expected , $ this ->serializer ->deserialize ($ json ));
89+ }
90+
91+ /**
92+ * @return iterable<string, array{string, array<mixed>}>
93+ */
94+ public static function provideEmptyStructures (): iterable
95+ {
96+ yield 'empty array ' => ['[] ' , []];
97+ yield 'empty object ' => ['{} ' , []];
98+ }
99+
48100 #[Test]
49101 public function it_throws_when_deserializing_if_an_error_encountered (): void
50102 {
@@ -55,14 +107,74 @@ public function it_throws_when_deserializing_if_an_error_encountered(): void
55107 }
56108
57109 #[Test]
58- public function it_throws_when_deserializing_if_the_result_is_not_an_array (): void
110+ public function it_can_deserialize_json_nested_to_the_maximum_supported_depth (): void
111+ {
112+ $ result = $ this ->serializer ->deserialize ($ this ->nestedArrays (self ::MAX_NESTING_DEPTH ));
113+
114+ $ this ->assertCount (1 , $ result );
115+ }
116+
117+ #[Test]
118+ public function it_throws_when_deserializing_json_nested_beyond_the_maximum_supported_depth (): void
119+ {
120+ $ this ->expectException (DeserializationFailed::class);
121+ $ this ->expectExceptionMessageIs ('Failed to deserialize data from JSON. Error code: 1, error message: Maximum stack depth exceeded. ' );
122+
123+ $ this ->serializer ->deserialize ($ this ->nestedArrays (self ::MAX_NESTING_DEPTH + 1 ));
124+ }
125+
126+ #[Test]
127+ #[DataProvider('provideNonArrayRoots ' )]
128+ public function it_throws_when_deserializing_if_the_result_is_not_an_array (string $ json , string $ expectedType ): void
59129 {
60130 $ this ->expectException (InvalidDeserializedData::class);
61- $ this ->expectExceptionMessageIs ('Expected array when deserializing JSON, got "boolean". ' );
131+ $ this ->expectExceptionMessageIs (\sprintf ('Expected array when deserializing JSON, got "%s". ' , $ expectedType ));
132+
133+ $ this ->serializer ->deserialize ($ json );
134+ }
135+
136+ /**
137+ * @return iterable<string, array{string, string}>
138+ */
139+ public static function provideNonArrayRoots (): iterable
140+ {
141+ yield 'true ' => ['true ' , 'boolean ' ];
142+ yield 'false ' => ['false ' , 'boolean ' ];
143+ yield 'null ' => ['null ' , 'NULL ' ];
144+ yield 'integer ' => ['5 ' , 'integer ' ];
145+ yield 'float ' => ['1.5 ' , 'double ' ];
146+ yield 'string ' => ['"foo" ' , 'string ' ];
147+ }
148+
149+ #[Test]
150+ public function a_serialization_error_can_be_caught_as_a_failure (): void
151+ {
152+ $ this ->expectException (Failure::class);
153+
154+ $ this ->serializer ->serialize (["\xf0\x28\x8c\xbc" => 'bar ' ]);
155+ }
156+
157+ #[Test]
158+ public function a_deserialization_error_can_be_caught_as_a_failure (): void
159+ {
160+ $ this ->expectException (Failure::class);
161+
162+ $ this ->serializer ->deserialize ('{"unclosed":"bad","object":"json" ' );
163+ }
164+
165+ #[Test]
166+ public function invalid_deserialized_data_can_be_caught_as_a_failure (): void
167+ {
168+ $ this ->expectException (Failure::class);
62169
63170 $ this ->serializer ->deserialize ('true ' );
64171 }
65172
173+ private function nestedArrays (int $ depth ): string
174+ {
175+ return \str_repeat ('[ ' , $ depth ).\str_repeat ('] ' , $ depth );
176+ }
177+
66178 protected function setUp (): void
67179 {
68180 $ this ->serializer = new Serializer ();
0 commit comments