1414namespace CodeIgniter \Models ;
1515
1616use CodeIgniter \Database \Exceptions \DatabaseException ;
17+ use CodeIgniter \Exceptions \InvalidArgumentException ;
1718use CodeIgniter \Exceptions \ModelException ;
1819use PHPUnit \Framework \Attributes \DataProvider ;
1920use PHPUnit \Framework \Attributes \Group ;
@@ -152,29 +153,37 @@ public function testOnlyDeleted(): void
152153
153154 /**
154155 * Given an explicit empty value in the WHERE condition
155- * When executing a soft delete
156+ * When executing a soft delete with where() clause
156157 * Then an exception should not be thrown
157158 *
159+ * This test uses where() so values go into WHERE clause, not through validateID().
160+ *
158161 * @param int|string|null $emptyValue
159162 */
160- #[DataProvider('emptyPkValues ' )]
163+ #[DataProvider('emptyPkValuesWithWhereClause ' )]
161164 public function testDontThrowExceptionWhenSoftDeleteConditionIsSetWithEmptyValue ($ emptyValue ): void
162165 {
163166 $ this ->createModel (UserModel::class);
164167 $ this ->seeInDatabase ('user ' , ['name ' => 'Derek Jones ' , 'deleted_at IS NULL ' => null ]);
165168
166169 $ this ->model ->where ('id ' , $ emptyValue )->delete ();
167- $ this ->seeInDatabase ('user ' , ['name ' => 'Derek Jones ' , 'deleted_at IS NULL ' => null ]);
170+ // Special case: true converted to 1
171+ if ($ emptyValue === true ) {
172+ $ this ->seeInDatabase ('user ' , ['name ' => 'Derek Jones ' , 'deleted_at IS NOT NULL ' => null ]);
173+ } else {
174+ $ this ->seeInDatabase ('user ' , ['name ' => 'Derek Jones ' , 'deleted_at IS NULL ' => null ]);
175+ }
168176 }
169177
170178 /**
171179 * @param int|string|null $emptyValue
180+ * @param class-string $exception
172181 */
173182 #[DataProvider('emptyPkValues ' )]
174- public function testThrowExceptionWhenSoftDeleteParamIsEmptyValue ($ emptyValue ): void
183+ public function testThrowExceptionWhenSoftDeleteParamIsEmptyValue ($ emptyValue, string $ exception , string $ exceptionMessage ): void
175184 {
176- $ this ->expectException (DatabaseException::class );
177- $ this ->expectExceptionMessage (' Deletes are not allowed unless they contain a "where" or "like" clause. ' );
185+ $ this ->expectException ($ exception );
186+ $ this ->expectExceptionMessage ($ exceptionMessage );
178187
179188 $ this ->seeInDatabase ('user ' , ['name ' => 'Derek Jones ' , 'deleted_at IS NULL ' => null ]);
180189
@@ -183,16 +192,17 @@ public function testThrowExceptionWhenSoftDeleteParamIsEmptyValue($emptyValue):
183192
184193 /**
185194 * @param int|string|null $emptyValue
195+ * @param class-string $exception
186196 */
187197 #[DataProvider('emptyPkValues ' )]
188- public function testDontDeleteRowsWhenSoftDeleteParamIsEmpty ($ emptyValue ): void
198+ public function testDontDeleteRowsWhenSoftDeleteParamIsEmpty ($ emptyValue, string $ exception , string $ exceptionMessage ): void
189199 {
190200 $ this ->seeInDatabase ('user ' , ['name ' => 'Derek Jones ' , 'deleted_at IS NULL ' => null ]);
191201
192202 try {
193203 $ this ->createModel (UserModel::class)->delete ($ emptyValue );
194- } catch (DatabaseException ) {
195- // Do nothing.
204+ } catch (DatabaseException | InvalidArgumentException ) {
205+ // Do nothing - both exceptions are expected for different values .
196206 }
197207
198208 $ this ->seeInDatabase ('user ' , ['name ' => 'Derek Jones ' , 'deleted_at IS NULL ' => null ]);
@@ -233,15 +243,13 @@ public function testPurgeDeletedWithSoftDeleteFalse(): void
233243
234244 /**
235245 * @param int|string|null $id
246+ * @param class-string $exception
236247 */
237248 #[DataProvider('emptyPkValues ' )]
238- public function testDeleteThrowDatabaseExceptionWithoutWhereClause ($ id ): void
249+ public function testDeleteThrowDatabaseExceptionWithoutWhereClause ($ id, string $ exception , string $ exceptionMessage ): void
239250 {
240- // BaseBuilder throws Exception.
241- $ this ->expectException (DatabaseException::class);
242- $ this ->expectExceptionMessage (
243- 'Deletes are not allowed unless they contain a "where" or "like" clause. ' ,
244- );
251+ $ this ->expectException ($ exception );
252+ $ this ->expectExceptionMessage ($ exceptionMessage );
245253
246254 // $useSoftDeletes = false
247255 $ this ->createModel (JobModel::class);
@@ -251,15 +259,13 @@ public function testDeleteThrowDatabaseExceptionWithoutWhereClause($id): void
251259
252260 /**
253261 * @param int|string|null $id
262+ * @param class-string $exception
254263 */
255264 #[DataProvider('emptyPkValues ' )]
256- public function testDeleteWithSoftDeleteThrowDatabaseExceptionWithoutWhereClause ($ id ): void
265+ public function testDeleteWithSoftDeleteThrowDatabaseExceptionWithoutWhereClause ($ id, string $ exception , string $ exceptionMessage ): void
257266 {
258- // Model throws Exception.
259- $ this ->expectException (DatabaseException::class);
260- $ this ->expectExceptionMessage (
261- 'Deletes are not allowed unless they contain a "where" or "like" clause. ' ,
262- );
267+ $ this ->expectException ($ exception );
268+ $ this ->expectExceptionMessage ($ exceptionMessage );
263269
264270 // $useSoftDeletes = true
265271 $ this ->createModel (UserModel::class);
@@ -268,11 +274,91 @@ public function testDeleteWithSoftDeleteThrowDatabaseExceptionWithoutWhereClause
268274 }
269275
270276 public static function emptyPkValues (): iterable
277+ {
278+ return [
279+ 'null ' => [
280+ null ,
281+ DatabaseException::class,
282+ 'Deletes are not allowed unless they contain a "where" or "like" clause. ' ,
283+ ],
284+ 'false ' => [
285+ false ,
286+ InvalidArgumentException::class,
287+ 'Invalid primary key: boolean false is not allowed. ' ,
288+ ],
289+ '0 integer ' => [
290+ 0 ,
291+ InvalidArgumentException::class,
292+ 'Invalid primary key: 0 is not allowed. ' ,
293+ ],
294+ "'0' string " => [
295+ '0 ' ,
296+ InvalidArgumentException::class,
297+ "Invalid primary key: '0' is not allowed. " ,
298+ ],
299+ 'empty string ' => [
300+ '' ,
301+ InvalidArgumentException::class,
302+ "Invalid primary key: '' is not allowed. " ,
303+ ],
304+ 'true ' => [
305+ true ,
306+ InvalidArgumentException::class,
307+ 'Invalid primary key: boolean true is not allowed. ' ,
308+ ],
309+ 'empty array ' => [
310+ [],
311+ InvalidArgumentException::class,
312+ 'Invalid primary key: cannot be an empty array. ' ,
313+ ],
314+ 'nested array ' => [
315+ [[1 , 2 ]],
316+ InvalidArgumentException::class,
317+ 'Invalid primary key at index 0: nested arrays are not allowed. ' ,
318+ ],
319+ 'array with null ' => [
320+ [1 , null , 3 ],
321+ InvalidArgumentException::class,
322+ 'Invalid primary key: NULL is not allowed. ' ,
323+ ],
324+ 'array with 0 ' => [
325+ [1 , 0 , 3 ],
326+ InvalidArgumentException::class,
327+ 'Invalid primary key: 0 is not allowed. ' ,
328+ ],
329+ "array with '0' " => [
330+ [1 , '0 ' , 3 ],
331+ InvalidArgumentException::class,
332+ "Invalid primary key: '0' is not allowed. " ,
333+ ],
334+ 'array with empty string ' => [
335+ [1 , '' , 3 ],
336+ InvalidArgumentException::class,
337+ "Invalid primary key: '' is not allowed. " ,
338+ ],
339+ 'array with boolean ' => [
340+ [1 , false , 3 ],
341+ InvalidArgumentException::class,
342+ 'Invalid primary key: boolean false is not allowed. ' ,
343+ ],
344+ ];
345+ }
346+
347+ /**
348+ * Data provider for tests using where() clause.
349+ * These values go into WHERE clause, not through validateID().
350+ *
351+ * @return iterable<array{bool|int|string|null}>
352+ */
353+ public static function emptyPkValuesWithWhereClause (): iterable
271354 {
272355 return [
273356 [0 ],
274357 [null ],
275358 ['0 ' ],
359+ ['' ],
360+ [true ],
361+ [false ],
276362 ];
277363 }
278364}
0 commit comments