@mlagie/sql-connector 2.0.5 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,7 @@
1
1
  // 1. LES MOCKS EN PREMIER
2
2
  const { mockExecute, mockPool } = require('./mysqlMock');
3
+ const connexionManager = require("../src/db/connexion");
4
+
3
5
  jest.mock('@mlagie/logger', () => ({
4
6
  logs: jest.fn(),
5
7
  error: jest.fn()
@@ -9,6 +11,8 @@ jest.mock('@mlagie/logger', () => ({
9
11
  const { Model } = require('../src/models/Model');
10
12
  const { Schema } = require('../src/models/Schema');
11
13
  const { setConnexion } = require('../src/db/connexion');
14
+ const { sqlTypeMap } = require('../src/utils/sqlTypeMap');
15
+ const util = require("util");
12
16
 
13
17
  describe('Tests unitaires avec Mock - Model.js (generate_uuid)', () => {
14
18
  let testModel;
@@ -52,4 +56,666 @@ describe('Tests unitaires avec Mock - Model.js (generate_uuid)', () => {
52
56
  // L'erreur est catchée par votre Model.js, qui log l'erreur et retourne null.
53
57
  expect(uuid).toBeNull();
54
58
  });
59
+
60
+ test("Le constructeur doit empiler le modèle dans pendingModels", () => {
61
+ const initialLength = Model.pendingModels.length;
62
+ const testModel = new Model("DummyTable", { schemaDict: {} });
63
+
64
+ expect(Model.pendingModels.length).toBe(initialLength + 1);
65
+ expect(Model.pendingModels[Model.pendingModels.length - 1]).toBe(testModel);
66
+ });
67
+
68
+ test("save() doit générer un INSERT INTO correct", async () => {
69
+ const pipelineModel = new Model("ProjectPipeline", { schemaDict: {} });
70
+ mockExecute.mockResolvedValue([{ insertId: 12, affectedRows: 1 }]);
71
+
72
+ const payload = { project_id: 3, name: "start_olm", source: "jenkins" };
73
+ const result = await pipelineModel.save(payload);
74
+
75
+ expect(result.insertId).toBe(12);
76
+ expect(mockExecute).toHaveBeenCalled();
77
+
78
+ const sqlGenere = mockExecute.mock.calls[0][0];
79
+ const valeursGenerees = mockExecute.mock.calls[0][1];
80
+
81
+ expect(sqlGenere).toContain("INSERT INTO `ProjectPipeline`");
82
+ expect(valeursGenerees).toEqual([3, "start_olm", "jenkins"]);
83
+ });
84
+
85
+ test("find() doit retourner une liste d'instances hydratées", async () => {
86
+ const pipelineModel = new Model("ProjectPipeline", { schemaDict: {} });
87
+
88
+ // On mock la réponse sous forme de lignes (rows) renvoyées par mysql2
89
+ mockExecute.mockResolvedValue([
90
+ [
91
+ { id: 1, name: "start_olm", source: "jenkins" },
92
+ { id: 2, name: "build-job", source: "jenkins" }
93
+ ]
94
+ ]);
95
+
96
+ const results = await pipelineModel.find({ where: { source: "jenkins" } });
97
+
98
+ expect(results).toHaveLength(2);
99
+ // On vérifie que la ligne a bien été hydratée en tant que ModelInstance
100
+ expect(results[0]._name).toBe("ProjectPipeline");
101
+ expect(results[0].name).toBe("start_olm");
102
+ });
103
+
104
+ test("count() doit renvoyer le nombre d'enregistrements", async () => {
105
+ const pipelineModel = new Model("ProjectPipeline", { schemaDict: {} });
106
+
107
+ // Simule le résultat d'un SELECT COUNT(*)
108
+ mockExecute.mockResolvedValue([[{ count: 5 }]]);
109
+
110
+ const total = await pipelineModel.count({ source: "jenkins" });
111
+ expect(total).not.toBeNull();
112
+ });
113
+
114
+ test("save() doit propager une erreur SQL", async () => {
115
+ const model = new Model("users", { schemaDict: {} });
116
+
117
+ mockExecute.mockRejectedValue(new Error("INSERT ERROR"));
118
+
119
+ await expect(
120
+ model.save({ name: "john" })
121
+ ).rejects.toThrow("INSERT ERROR");
122
+ });
123
+
124
+ test("find() retourne un tableau vide", async () => {
125
+ const model = new Model("users", { schemaDict: {} });
126
+
127
+ mockExecute.mockResolvedValue([[]]);
128
+
129
+ const result = await model.find();
130
+
131
+ expect(result).toEqual([]);
132
+ });
133
+
134
+ test("find() propage une erreur SQL", async () => {
135
+ const model = new Model("users", { schemaDict: {} });
136
+
137
+ mockExecute.mockRejectedValue(new Error("SELECT ERROR"));
138
+
139
+ await expect(
140
+ model.find()
141
+ ).rejects.toThrow("SELECT ERROR");
142
+ });
143
+
144
+ test("find() génère un INNER JOIN", async () => {
145
+ const model = new Model("users", { schemaDict: {} });
146
+
147
+ mockExecute.mockResolvedValue([
148
+ [{ id: 1, name: "john" }]
149
+ ]);
150
+
151
+ await model.find({
152
+ select: ["id", "name"],
153
+ join: {
154
+ table: "roles",
155
+ on: "`users`.`role_id` = `roles`.`id`"
156
+ }
157
+ });
158
+
159
+ const sql = mockExecute.mock.calls[0][0];
160
+
161
+ expect(sql).toContain("INNER JOIN");
162
+ });
163
+
164
+ test("delete() doit propager une erreur SQL", async () => {
165
+ const model = new Model("users", { schemaDict: {} });
166
+
167
+ mockExecute.mockRejectedValue(new Error("DELETE ERROR"));
168
+
169
+ await expect(
170
+ model.delete({ id: 1 })
171
+ ).rejects.toThrow(Error);
172
+ });
173
+
174
+ test("customRequest() retourne une instance", async () => {
175
+ const model = new Model("users", { schemaDict: {} });
176
+
177
+ mockExecute.mockResolvedValue([
178
+ [{ id: 1 }]
179
+ ]);
180
+
181
+ const result = await model.customRequest(
182
+ "SELECT * FROM users"
183
+ );
184
+
185
+ expect(result).toBeDefined();
186
+ });
187
+
188
+ test("customRequest() retourne 0 si vide", async () => {
189
+ const model = new Model("users", { schemaDict: {} });
190
+
191
+ mockExecute.mockResolvedValue([[]]);
192
+
193
+ const result = await model.customRequest(
194
+ "SELECT * FROM users"
195
+ );
196
+
197
+ expect(result).toBe(0);
198
+ });
199
+
200
+ test("customRequest() propage une erreur SQL", async () => {
201
+ const model = new Model("users", { schemaDict: {} });
202
+
203
+ mockExecute.mockRejectedValue(
204
+ new Error("CUSTOM ERROR")
205
+ );
206
+
207
+ await expect(
208
+ model.customRequest("SELECT")
209
+ ).rejects.toThrow("CUSTOM ERROR");
210
+ });
211
+
212
+ test("dropTable() exécute la requête", async () => {
213
+ const model = new Model("users", { schemaDict: {} });
214
+
215
+ mockExecute.mockResolvedValue([{}]);
216
+
217
+ await model.dropTable();
218
+
219
+ expect(mockExecute).toHaveBeenCalled();
220
+ });
221
+
222
+ test("dropTable() propage une erreur SQL", async () => {
223
+ const model = new Model("users", { schemaDict: {} });
224
+
225
+ mockExecute.mockRejectedValue(
226
+ new Error("DROP ERROR")
227
+ );
228
+
229
+ await expect(
230
+ model.dropTable()
231
+ ).rejects.toThrow("DROP ERROR");
232
+ });
233
+
234
+ test("generateCreateTableStatement() génère un CREATE TABLE", () => {
235
+ const model = new Model("users", {
236
+ schemaDict: {}
237
+ });
238
+
239
+ const sql = model.generateCreateTableStatement({
240
+ id: {
241
+ type: Number,
242
+ primary_key: true
243
+ }
244
+ });
245
+
246
+ expect(sql).toContain("CREATE TABLE");
247
+ });
248
+
249
+ test("generateCreateTableStatement() refuse SELECT comme nom de table", () => {
250
+ const model = new Model("SELECT", {
251
+ schemaDict: {}
252
+ });
253
+
254
+ const result = model.generateCreateTableStatement({
255
+ id: { type: Number }
256
+ });
257
+
258
+ expect(result).toBeUndefined();
259
+ });
260
+
261
+ test("syncAllTables() traite les modèles en attente", async () => {
262
+ Model.pendingModels = [];
263
+
264
+ new Model("users", {
265
+ schemaDict: {
266
+ id: {
267
+ type: Number
268
+ }
269
+ }
270
+ });
271
+
272
+ mockExecute.mockResolvedValue([{}]);
273
+
274
+ await Model.syncAllTables();
275
+
276
+ expect(mockExecute).toHaveBeenCalled();
277
+ });
278
+
279
+ test("generateCreateTableStatement() couvre les types spéciaux", () => {
280
+ const model = new Model("users", { schemaDict: {} });
281
+
282
+ const sql = model.generateCreateTableStatement({
283
+ active: {
284
+ type: Boolean,
285
+ default: true
286
+ },
287
+ score: {
288
+ type: "Float",
289
+ default: 12.5
290
+ },
291
+ metadata: {
292
+ type: Object,
293
+ default: { test: true }
294
+ },
295
+ created_at: {
296
+ type: Date,
297
+ default: "CURRENT_TIMESTAMP"
298
+ }
299
+ });
300
+
301
+ expect(sql).toContain("BOOLEAN");
302
+ expect(sql).toContain("FLOAT");
303
+ expect(sql).toContain("JSON");
304
+ expect(sql).toContain("CURRENT_TIMESTAMP");
305
+ });
306
+
307
+ test("generateCreateTableStatement() refuse les types inconnus", () => {
308
+ const model = new Model("users", { schemaDict: {} });
309
+
310
+ expect(() => {
311
+ model.generateCreateTableStatement({
312
+ test: {
313
+ type: "BananaType"
314
+ }
315
+ });
316
+ }).toThrow();
317
+ });
318
+
319
+ test("generateCreateTableStatement() couvre field.type qui est une string", () => {
320
+ const model = new Model("users", { schemaDict: {} });
321
+
322
+ const sql = model.generateCreateTableStatement({
323
+ age: {
324
+ type: "Number"
325
+ }
326
+ });
327
+
328
+ expect(sql).toContain("INT");
329
+ });
330
+
331
+ test("generateCreateTableStatement() couvre field.name", () => {
332
+ const model = new Model("users", { schemaDict: {} });
333
+
334
+ const sql = model.generateCreateTableStatement({
335
+ created_at: Date
336
+ });
337
+
338
+ expect(sql).toContain("DATETIME");
339
+ });
340
+
341
+ test("syncAllTables avec foreign key", async () => {
342
+ Model.pendingModels = [];
343
+
344
+ new Model("roles", {
345
+ schemaDict: {
346
+ id: {
347
+ type: Number
348
+ }
349
+ }
350
+ });
351
+
352
+ new Model("users", {
353
+ schemaDict: {
354
+ role_id: {
355
+ type: Number,
356
+ foreignKey: "roles(id)"
357
+ }
358
+ }
359
+ });
360
+
361
+ mockExecute.mockResolvedValue([{}]);
362
+
363
+ await Model.syncAllTables();
364
+
365
+ expect(mockExecute).toHaveBeenCalled();
366
+ });
367
+
368
+ test("syncAllTables propage les erreurs SQL", async () => {
369
+ Model.pendingModels = [];
370
+
371
+ new Model("users", {
372
+ schemaDict: {
373
+ id: {
374
+ type: Number
375
+ }
376
+ }
377
+ });
378
+
379
+ mockExecute.mockRejectedValue(
380
+ new Error("CREATE ERROR")
381
+ );
382
+
383
+ await expect(
384
+ Model.syncAllTables()
385
+ ).rejects.toThrow("CREATE ERROR");
386
+ });
387
+
388
+ test("ENUM sans type", () => {
389
+ const model = new Model("users", { schemaDict: {} });
390
+
391
+ const sql = model.generateCreateTableStatement({
392
+ status: {
393
+ enum: ["OPEN", "CLOSED"]
394
+ }
395
+ });
396
+
397
+ expect(sql).toContain("status ENUM");
398
+ });
399
+
400
+ test("find conserve les objets select", async () => {
401
+ const model = new Model("users", { schemaDict: {} });
402
+
403
+ mockExecute.mockResolvedValue([[{ id: 1 }]]);
404
+
405
+ await model.find({
406
+ select: [
407
+ {
408
+ count: "*",
409
+ as: "total"
410
+ }
411
+ ]
412
+ });
413
+
414
+ expect(mockExecute).toHaveBeenCalled();
415
+ });
416
+ test("delete retourne 1 si suppression", async () => {
417
+ const model = new Model("users", { schemaDict: {} });
418
+
419
+ mockExecute.mockResolvedValue([
420
+ {
421
+ affectedRows: 1
422
+ }
423
+ ]);
424
+
425
+ const result = await model.delete({
426
+ id: 1
427
+ });
428
+
429
+ expect(result).toBe(1);
430
+ });
431
+
432
+ test("generate_uuid retourne un uuid unique", async () => {
433
+ mockExecute
434
+ .mockResolvedValueOnce([
435
+ [{ "UUID()": "uuid-test" }]
436
+ ])
437
+ .mockResolvedValueOnce([
438
+ [{ "COUNT(*)": 0 }]
439
+ ]);
440
+
441
+ const result = await testModel.generate_uuid();
442
+
443
+ expect(result).toBe("uuid-test");
444
+ });
445
+
446
+ test("generateCreateTableStatement supporte un constructeur JS", () => {
447
+ const model = new Model("users", { schemaDict: {} });
448
+
449
+ const sql = model.generateCreateTableStatement({
450
+ created_at: Date
451
+ });
452
+
453
+ expect(sql).toContain("DATETIME");
454
+ });
455
+
456
+ test("default fonction sur un type Date", () => {
457
+ const model = new Model("users", { schemaDict: {} });
458
+
459
+ const sql = model.generateCreateTableStatement({
460
+ created_at: {
461
+ type: Date,
462
+ default: () => new Date()
463
+ }
464
+ });
465
+
466
+ expect(sql).toContain("DEFAULT CURRENT_TIMESTAMP");
467
+ });
468
+
469
+ test("default Date", () => {
470
+ const model = new Model("users", { schemaDict: {} });
471
+
472
+ const sql = model.generateCreateTableStatement({
473
+ created_at: {
474
+ type: Date,
475
+ default: new Date("2024-01-01T12:00:00Z")
476
+ }
477
+ });
478
+
479
+ expect(sql).toContain("2024-01-01");
480
+ });
481
+
482
+ test("default avec un symbole", () => {
483
+ const model = new Model("users", { schemaDict: {} });
484
+
485
+ const sql = model.generateCreateTableStatement({
486
+ test: {
487
+ type: String,
488
+ default: Symbol.for("hello")
489
+ }
490
+ });
491
+
492
+ expect(sql).toContain("hello");
493
+ });
494
+
495
+ test("primary_key et unique sont incompatibles", () => {
496
+ const model = new Model("users", { schemaDict: {} });
497
+
498
+ expect(() =>
499
+ model.generateCreateTableStatement({
500
+ id: {
501
+ type: Number,
502
+ primary_key: true,
503
+ unique: true
504
+ }
505
+ })
506
+ ).toThrow();
507
+ });
508
+
509
+ test("enum avec type défini", () => {
510
+ const model = new Model("users", { schemaDict: {} });
511
+
512
+ const sql = model.generateCreateTableStatement({
513
+ role: {
514
+ type: String,
515
+ enum: ["ADMIN", "USER"]
516
+ }
517
+ });
518
+
519
+ expect(sql).toContain("ENUM");
520
+ });
521
+
522
+ test("save retourne le premier résultat mysql", async () => {
523
+ const model = new Model("users", { schemaDict: {} });
524
+
525
+ mockExecute.mockResolvedValue([
526
+ {
527
+ insertId: 123,
528
+ affectedRows: 1
529
+ }
530
+ ]);
531
+
532
+ const result = await model.save({
533
+ name: "john"
534
+ });
535
+
536
+ expect(result).toEqual({
537
+ insertId: 123,
538
+ affectedRows: 1
539
+ });
540
+ });
541
+
542
+ test("generate_uuid retourne un uuid unique", async () => {
543
+ mockExecute
544
+ .mockResolvedValueOnce([
545
+ [{ "UUID()": "uuid-123" }]
546
+ ])
547
+ .mockResolvedValueOnce([
548
+ [{ "COUNT(*)": 0 }]
549
+ ]);
550
+
551
+ const result = await testModel.generate_uuid();
552
+
553
+ expect(result).toBe("uuid-123");
554
+ });
555
+
556
+ test("find() - Options Advanced - Doit gérer les jointures de table et ignorer les éléments du select qui ne sont pas des chaînes", async () => {
557
+ // 1. Initialisation de votre modèle
558
+ const pipelineModel = new Model("ProjectPipeline", { schemaDict: {} });
559
+
560
+ // Simule une réponse MySQL nominale pour que la méthode find() aille jusqu'au bout
561
+ mockExecute.mockResolvedValue([[{ id: 1 }]]);
562
+
563
+ // 2. On configure des options avec un 'join' et un tableau 'select' mixte
564
+ await pipelineModel.find({
565
+ select: [
566
+ "id", // Chaîne standard (sera préfixée par la table principale)
567
+ "name_pipeline", // Chaîne commençant par 'name' (sera préfixée par la table de jointure)
568
+ 12345, // 🔥 UN NOMBRE (Pas une string) : déclenchera le 'return item' direct
569
+ { raw: "NOW()" } // 🔥 UN OBJET (Pas une string) : déclenchera également le 'return item'
570
+ ],
571
+ join: {
572
+ table: "Users",
573
+ on: { "ProjectPipeline.user_id": "Users.id" }
574
+ }
575
+ });
576
+
577
+ // 3. Validation de la requête SQL générée en arrière-plan
578
+ const lastQuery = mockExecute.mock.calls[0][0];
579
+
580
+ // Vérifie que les chaînes ont bien été traitées avec leur table respective
581
+ expect(lastQuery).toContain("`ProjectPipeline`.`id`");
582
+ expect(lastQuery).toContain("`Users`.`name_pipeline`");
583
+
584
+ // Le test passe avec succès et la ligne de couverture "return item" est validée !
585
+ });
586
+
587
+ test("generate_uuid() - Doit renvoyer null si l'UUID généré existe déjà en base de données (Collision)", async () => {
588
+ // 1. Initialisation de votre modèle
589
+ const pipelineModel = new Model("ProjectPipeline", { schemaDict: {} });
590
+
591
+ // 2. Configuration des réponses successives de la base de données (mocking séquentiel)
592
+ mockExecute
593
+ // Premier appel MySQL : La fonction native SELECT UUID(); génère un UUID virtuel
594
+ .mockResolvedValueOnce([[{ "UUID()": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" }]])
595
+ // Deuxième appel MySQL : La requête de vérification COUNT(*) indique que cet UUID existe déjà (COUNT = 1)
596
+ .mockResolvedValueOnce([[{ "COUNT(*)": 1 }]]);
597
+
598
+ // 3. Exécution de la méthode : comme COUNT(*) vaut 1, la condition == 0 est FAUSSE
599
+ const uuidResult = await pipelineModel.generate_uuid("uuid_field");
600
+
601
+ // 4. Vérification que la méthode a bien retourné null suite à la collision
602
+ expect(uuidResult).toBeNull();
603
+
604
+ // Optionnel : On s'assure que la requête générée ciblait bien le bon champ de condition
605
+ const lastSqlQuery = mockExecute.mock.calls[1][0];
606
+ expect(lastSqlQuery).toContain("WHERE `uuid_field` = ?");
607
+ });
608
+ });
609
+
610
+ describe("Model Unit Tests - Deep Coverage", () => {
611
+
612
+ beforeAll(() => {
613
+ connexionManager.setConnexion(mockPool);
614
+ });
615
+
616
+ beforeEach(() => {
617
+ jest.clearAllMocks();
618
+ });
619
+
620
+ test("getColumnDefinition & formatDefaultSql - Traitement des cas limites et d'erreurs", () => {
621
+ const myModel = new Model("Users", {});
622
+
623
+ // Lignes 92-94 : Conflit Primary Key ET Unique
624
+ expect(() => {
625
+ myModel.generateCreateTableStatement({
626
+ id: { type: "INT", primary_key: true, unique: true }
627
+ });
628
+ }).toThrow("Field 'id' cannot be both PRIMARY KEY and UNIQUE.");
629
+
630
+ // Formatage de valeurs par défaut exotiques (Fonction date, Date object, Objets complexes)
631
+ // Note: Pour tester indirectement getColumnDefinition, on l'appelle via generateCreateTableStatement
632
+ const complexSchema = {
633
+ created_at: { type: Date, default: () => new Date() }, // Ligne 34
634
+ updated_at: { type: "Timestamp", default: "NOW()" }, // Ligne 37
635
+ metadata: { type: Boolean, default: false } // Ligne 49
636
+ };
637
+ const sql = myModel.generateCreateTableStatement(complexSchema);
638
+ expect(sql).toContain("DEFAULT CURRENT_TIMESTAMP");
639
+ expect(sql).toContain('CREATE TABLE IF NOT EXISTS `Users` (`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, `metadata` BOOLEAN DEFAULT 0) ENGINE=InnoDB');
640
+
641
+ expect(() => {
642
+ myModel.generateCreateTableStatement({
643
+ ghost: { type: "UNKNOWN_TYPE" }
644
+ });
645
+ }).toThrow("Field ghost has unsupported type UNKNOWN_TYPE."); // Si non bloqué par l'étape 100, l'étape 210 lèvera l'exception d'un type invalide
646
+ });
647
+
648
+ test("syncAllTables() - Gestion nominale de création et détection des cycles de clés étrangères", async () => {
649
+ Model.pendingModels = []; // On nettoie les modèles empilés
650
+
651
+ // Création de deux modèles interdépendants pour forcer la détection cyclique (Ligne 163)
652
+ const modelA = new Model("TableA", { schemaDict: { b_id: { foreignKey: "TableB(id)" } } });
653
+ const modelB = new Model("TableB", { schemaDict: { a_id: { foreignKey: "TableA(id)" } } });
654
+
655
+ // Ligne 163 : Doit lever une erreur à cause de la boucle TableA -> TableB -> TableA
656
+ await expect(Model.syncAllTables()).rejects.toThrow("Cyclic foreign key dependency detected");
657
+
658
+ // Cas nominal : Nettoyage et ré-empilement de modèles sans cycle
659
+ Model.pendingModels = [];
660
+ const modelSingle = new Model("TableNominale", { schemaDict: { id: { type: "Number" } } });
661
+
662
+ mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
663
+ await Model.syncAllTables();
664
+
665
+ expect(mockExecute).toHaveBeenCalled();
666
+ });
667
+
668
+ test("generateCreateTableStatement() - Doit rejeter les structures vides et les mots réservés", () => {
669
+ // Champ sans aucun type défini
670
+ const modelInvalid = new Model("TestTable", {});
671
+ expect(() => {
672
+ modelInvalid.generateCreateTableStatement({ undefined_field: {} });
673
+ }).toThrow("Field undefined_field has no type defined.");
674
+
675
+ // Type invalide ou non supporté passé sous forme de string directe
676
+ expect(() => {
677
+ modelInvalid.generateCreateTableStatement({ invalid_field: "BAD_TYPE_STRING" });
678
+ }).toThrow("Field invalid_field has unsupported type");
679
+
680
+ // Utilisation d'un mot-clé réservé SQL comme nom de table (ex: SELECT, ALTER)
681
+ const modelReserved = new Model("SELECT", {});
682
+ const result = modelReserved.generateCreateTableStatement({ id: { type: "Number" } });
683
+ expect(result).toBeUndefined(); // Renvoie undefined car le log d'erreur bloque la requête
684
+ });
685
+
686
+ test("find() - Ligne 259 - Doit intercepter et propager les erreurs SQL", async () => {
687
+ const myModel = new Model("Users", { schemaDict: {} });
688
+ mockExecute.mockRejectedValue(new Error("Syntax Error near WHERE"));
689
+
690
+ await expect(myModel.find({ select: ["id"] })).rejects.toThrow("Syntax Error near WHERE");
691
+ });
692
+
693
+ test("count() - Ligne 278 - Doit générer la requête COUNT globale sans clause WHERE si aucun filtre n'est fourni", async () => {
694
+ const myModel = new Model("Users", { schemaDict: {} });
695
+
696
+ // Simule un retour valide pour l'instanciation de l'instance
697
+ mockExecute.mockResolvedValue([[{ count: 42 }]]);
698
+
699
+ // On appelle count() sans paramètre (filter = undefined)
700
+ await myModel.count();
701
+
702
+ const sqlRequest = mockExecute.mock.calls[0][0];
703
+ // La requête ne doit pas avoir de mot clé WHERE attaché
704
+ expect(sqlRequest).not.toContain("WHERE");
705
+ expect(sqlRequest).toContain("SELECT COUNT(*) as count FROM `Users`");
706
+ });
707
+
708
+ test("customRequest() - Ligne 295 - Doit lever une exception typée en cas de crash SQL", async () => {
709
+ const myModel = new Model("Users", { schemaDict: {} });
710
+ mockExecute.mockRejectedValue(new Error("Table doesn't exist"));
711
+
712
+ await expect(myModel.customRequest("SELECT * FROM non_existent", "CRITICAL_GET")).rejects.toThrow("Table doesn't exist");
713
+ });
714
+
715
+ test("dropTable() - Ligne 329 - Doit rejeter la promesse si l'instruction DROP échoue", async () => {
716
+ const myModel = new Model("Users", { schemaDict: {} });
717
+ mockExecute.mockRejectedValue(new Error("Lock wait timeout exceeded"));
718
+
719
+ await expect(myModel.dropTable()).rejects.toThrow("Lock wait timeout exceeded");
720
+ });
55
721
  });