@mlagie/sql-connector 2.0.7 → 2.0.9

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,335 +0,0 @@
1
- const { ModelInstance } = require("../src/models/ModelInstance");
2
- // 1. Importez le vrai gestionnaire de connexion de votre ORM
3
- const connexionManager = require("../src/db/connexion");
4
- const util = require("util");
5
- // 2. Définissez votre structure de mock locale
6
- const mockExecute = jest.fn();
7
- const mockPool = {
8
- promise: () => ({
9
- execute: mockExecute
10
- })
11
- };
12
-
13
- // Empêche les logs d'erreurs volontaires des tests de polluer le terminal
14
- jest.mock('@mlagie/logger', () => ({
15
- error: jest.fn(),
16
- logs: jest.fn(),
17
- serveur: jest.fn()
18
- }));
19
-
20
- describe("ModelInstance Unit Tests - v2.0.6", () => {
21
-
22
- beforeAll(() => {
23
- // 3. 🔥 LE FIX MAGIQUE : On force l'ORM à utiliser le mock localement
24
- // Cela écrase la connexion réelle par votre pool simulé sans dépendre du comportement de jest.mock()
25
- connexionManager.setConnexion(mockPool);
26
- });
27
-
28
- beforeEach(() => {
29
- jest.clearAllMocks();
30
- });
31
-
32
- test("updateOne() doit exécuter une requête UPDATE valide", async () => {
33
- const mockData = { id: 1, name: "callisto-report" };
34
- const mockSchema = { schemaDict: { id: { primary_key: true } } };
35
- const instance = new ModelInstance("ProjectPipeline", mockData, mockSchema);
36
-
37
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
38
-
39
- // Appel de la méthode
40
- await instance.updateOne({ name: "nouveau-nom" });
41
-
42
- expect(mockExecute).toHaveBeenCalled();
43
- });
44
-
45
- test("Hydratation - _getTargetRow doit gérer un tableau de données imbriqué", () => {
46
- // Lignes 47, 52 : Couvre le cas où rows est un tableau de lignes
47
- const instance = new ModelInstance("ProjectPipeline", [[{ id: 99, name: "nested" }]]);
48
- expect(instance.id).toBe(99);
49
- });
50
-
51
- test("updateOne() - Doit lever une erreur et la logger en cas d'échec SQL", async () => {
52
- // Lignes 129-130 : Bloc .catch de updateOne
53
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
54
- mockExecute.mockRejectedValue(new Error("MySQL Down"));
55
-
56
- await expect(instance.updateOne({ name: "fail" })).rejects.toThrow("MySQL Down");
57
- });
58
-
59
- test("delete() - Doit exécuter une suppression groupée et gérer le succès/échec", async () => {
60
- // Lignes 138 - 154 : Méthode delete(filter) globale
61
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
62
-
63
- // Cas 1 : Succès avec lignes affectées
64
- mockExecute.mockResolvedValue([{ affectedRows: 2 }]);
65
- let result = await instance.delete({ status: "old" });
66
- expect(result).toBe(1);
67
-
68
- // Cas 2 : Aucune ligne affectée
69
- mockExecute.mockResolvedValue([{ affectedRows: 0 }]);
70
- result = await instance.delete({ status: "old" });
71
- expect(result).toBe(0);
72
-
73
- // Cas 3 : Échec SQL (Couvre le bloc .catch de delete)
74
- mockExecute.mockRejectedValue(new Error("Delete Failed"));
75
- await expect(instance.delete({ status: "old" })).rejects.toThrow("Delete Failed");
76
- });
77
-
78
- test("deleteOne() - Doit lever une erreur en cas d'échec SQL", async () => {
79
- // Lignes 173-176 : Bloc .catch de deleteOne
80
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
81
- mockExecute.mockRejectedValue(new Error("MySQL Fatal Delete"));
82
-
83
- await expect(instance.deleteOne()).rejects.toThrow("MySQL Fatal Delete");
84
- });
85
-
86
- test("customRequest() - Doit exécuter du SQL brut et hydrater ou renvoyer 0", async () => {
87
- // Lignes 185-195 : Méthode customRequest
88
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
89
-
90
- // Cas 1 : La requête renvoie des données
91
- mockExecute.mockResolvedValue([[{ id: 5, custom_field: "value" }]]);
92
- const resData = await instance.customRequest("SELECT * FROM optimization");
93
- expect(resData).toBeDefined();
94
-
95
- // Cas 2 : La requête renvoie un tableau vide (Ligne 192 : rows[0].length == 0)
96
- mockExecute.mockResolvedValue([[]]);
97
- const resEmpty = await instance.customRequest("SELECT * FROM empty");
98
- expect(resEmpty).toBe(0);
99
-
100
- // Cas 3 : Échec de la requête (Couvre le bloc .catch de customRequest)
101
- mockExecute.mockRejectedValue(new Error("Query Bad Syntax"));
102
- await expect(instance.customRequest("SELECT BAD")).rejects.toThrow("Query Bad Syntax");
103
- });
104
-
105
- // === COUVRE LIGNES 47, 52 (Setters & Échec JSON.parse) ===
106
- test("Hydratation - Doit exécuter les setters dynamiques et capter les faux JSON", () => {
107
- const mockData = { id: 1, config: "{malformed json" };
108
- const instance = new ModelInstance("ProjectPipeline", mockData);
109
-
110
- // Ligne 52 : Déclenche le setter dynamique (setSafe)
111
- instance.id = 45;
112
- expect(instance.id).toBe(45);
113
- });
114
-
115
- // === COUVRE LIGNES 75-79 (JSON parsing dans getRecordData) ===
116
- test("getRecordData - Doit analyser les chaînes JSON ou renvoyer le tableau brut", () => {
117
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
118
-
119
- // On simule une structure où _data est un tableau contenant une chaîne JSON
120
- instance._data = ["[{\"id\": 10, \"name\": \"json-extracted\"}]"];
121
-
122
- const record = instance.getRecordData();
123
- expect(record).toBeDefined();
124
- });
125
-
126
- // === COUVRE LIGNES 99-102 (Extraction de Clé Primaire avec succès) ===
127
- test("updateOne() - Doit extraire la clé primaire depuis le dictionnaire de schéma", async () => {
128
- const mockData = { id: 77, name: "pipeline-target" };
129
- const mockSchema = { schemaDict: { id: { primary_key: true } } };
130
- const instance = new ModelInstance("ProjectPipeline", mockData, mockSchema);
131
-
132
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
133
-
134
- // Lignes 99-102 : Utilise 'id' comme critère WHERE unique
135
- const affected = await instance.updateOne({ name: "pipeline-updated" });
136
- expect(affected).toBe(1);
137
-
138
- const sqlGenerated = mockExecute.mock.calls[0][0];
139
- expect(sqlGenerated).toContain("UPDATE ProjectPipeline SET `name` = 'pipeline-updated' WHERE `id` = 77");
140
- });
141
-
142
- // === COUVRE LIGNES 120-124 (Bloc catch de repli de updateOne) ===
143
- test("updateOne() - Doit basculer sur le fallback d'évaluation si le parsing crash", async () => {
144
- const mockData = { id: 1, name: "pipeline-fallback-test" };
145
- const instance = new ModelInstance("ProjectPipeline", mockData);
146
-
147
- // 🔥 LE FIX : On passe un dictionnaire de schéma structurellement valide (un objet)
148
- // mais totalement vide. L'évaluation des clés primaires échouera proprement,
149
- // ce qui forcera l'activation du bloc catch de repli (fallback) de updateOne
150
- // sans provoquer de panne sur Reflect.get !
151
- instance._schema = { schemaDict: {} };
152
-
153
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
154
-
155
- // Exécution de la méthode
156
- const affected = await instance.updateOne({ status: "patched" });
157
- expect(affected).toBe(1);
158
-
159
- const sqlGenerated = mockExecute.mock.calls[0][0];
160
- // On valide que le traitement s'est terminé avec succès
161
- expect(sqlGenerated).toContain("UPDATE ProjectPipeline SET `status` = 'patched' WHERE `id` = 1 AND `name` = 'pipeline-fallback-test'");
162
- });
163
-
164
- // === COUVRE LIGNE 138 (delete avec affectedRows > 0) ===
165
- test("delete() - Doit renvoyer 1 si des éléments ont été affectés par la suppression", async () => {
166
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
167
-
168
- // Ligne 138 (Cas affecté)
169
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
170
- const result = await instance.delete({ status: "archived" });
171
- expect(result).toBe(1);
172
- });
173
-
174
- // === COUVRE LIGNE 178 (deleteOne avec affectedRows > 0) ===
175
- test("deleteOne() - Doit renvoyer 1 si l'élément courant a été supprimé", async () => {
176
- const instance = new ModelInstance("ProjectPipeline", { id: 200 });
177
-
178
- // Ligne 178 (Cas affecté)
179
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
180
- const result = await instance.deleteOne();
181
- expect(result).toBe(1);
182
- });
183
-
184
- // === COUVRE LES BLOCS D'ERREURS DÉJÀ EXISTANTS ===
185
- test("updateOne() - Doit lever une erreur et la logger en cas d'échec SQL", async () => {
186
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
187
- mockExecute.mockRejectedValue(new Error("MySQL Down"));
188
-
189
- await expect(instance.updateOne({ name: "fail" })).rejects.toThrow("MySQL Down");
190
- });
191
-
192
- test("delete() - Doit propager l'erreur en cas de rejet SQL", async () => {
193
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
194
- mockExecute.mockRejectedValue(new Error("Delete Failed"));
195
-
196
- await expect(instance.delete({ status: "old" })).rejects.toThrow("Delete Failed");
197
- });
198
-
199
- test("deleteOne() - Doit lever une erreur en cas d'échec SQL", async () => {
200
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
201
- mockExecute.mockRejectedValue(new Error("MySQL Fatal Delete"));
202
-
203
- await expect(instance.deleteOne()).rejects.toThrow("MySQL Fatal Delete");
204
- });
205
-
206
- test("customRequest() - Doit exécuter du SQL brut et hydrater ou renvoyer 0", async () => {
207
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
208
-
209
- mockExecute.mockResolvedValue([[{ id: 5, custom_field: "value" }]]);
210
- const resData = await instance.customRequest("SELECT * FROM optimization");
211
- expect(resData).toBeDefined();
212
-
213
- mockExecute.mockResolvedValue([[]]);
214
- const resEmpty = await instance.customRequest("SELECT * FROM empty");
215
- expect(resEmpty).toBe(0);
216
-
217
- mockExecute.mockRejectedValue(new Error("Query Bad Syntax"));
218
- await expect(instance.customRequest("SELECT BAD")).rejects.toThrow("Query Bad Syntax");
219
- });
220
-
221
- test("Getter dynamique - retourne la valeur brute si JSON.parse échoue", () => {
222
- const instance = new ModelInstance("ProjectPipeline", {
223
- config: "{invalid-json}"
224
- });
225
-
226
- expect(instance.config).toBe("{invalid-json}");
227
- });
228
-
229
- test("updateOne() met à jour les données locales après succès", async () => {
230
- const instance = new ModelInstance(
231
- "ProjectPipeline",
232
- { id: 1, name: "before" }
233
- );
234
-
235
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
236
-
237
- await instance.updateOne({ name: "after" });
238
-
239
- expect(instance._data.name).toBe("after");
240
- });
241
-
242
- test("updateOne() met à jour le premier élément d'un tableau", async () => {
243
- const instance = new ModelInstance(
244
- "ProjectPipeline",
245
- [{ id: 1, name: "before" }]
246
- );
247
-
248
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
249
-
250
- await instance.updateOne({ name: "after" });
251
-
252
- expect(instance._data[0].name).toBe("after");
253
- });
254
-
255
- test("delete() retourne 0 quand aucune ligne n'est supprimée", async () => {
256
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
257
-
258
- mockExecute.mockResolvedValue([{ affectedRows: 0 }]);
259
-
260
- expect(await instance.delete({ id: 1 })).toBe(0);
261
- });
262
-
263
- test("delete() retourne 1 quand une ligne est supprimée", async () => {
264
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
265
-
266
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
267
-
268
- expect(await instance.delete({ id: 1 })).toBe(1);
269
- });
270
-
271
- test("customRequest() retourne les données hydratées", async () => {
272
- const instance = new ModelInstance("ProjectPipeline", { id: 1 });
273
-
274
- mockExecute.mockResolvedValue([
275
- [{ id: 123, name: "test" }]
276
- ]);
277
-
278
- const result = await instance.customRequest("SELECT * FROM test");
279
-
280
- expect(result).toEqual([{ id: 123, name: "test" }]);
281
- });
282
-
283
- test("toJSON() retourne les données locales", () => {
284
- const instance = new ModelInstance("ProjectPipeline", { id: 1, name: "test" });
285
-
286
- expect(instance.toJSON()).toEqual({ id: 1, name: "test" });
287
- });
288
-
289
- test("updateOne parse une chaîne JSON", async () => {
290
- const instance = new ModelInstance(
291
- "users",
292
- '{"id":1,"name":"john"}'
293
- );
294
-
295
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
296
-
297
- await instance.updateOne({
298
- name: "updated"
299
- });
300
-
301
- expect(mockExecute).toHaveBeenCalled();
302
- });
303
-
304
- test("updateOne() - Doit basculer sur le bloc catch global et exécuter le fallback", async () => {
305
- // 1. On instancie normalement un ModelInstance avec un objet JSON valide
306
- const mockData = { id: 10, status: "active" };
307
- const instance = new ModelInstance("ProjectPipeline", mockData);
308
-
309
- // 2. On espionne la méthode getRecordData() de cette instance spécifique
310
- const spy = jest.spyOn(instance, 'getRecordData');
311
-
312
- // 3. Premier appel (dans le try principal) : On jette une erreur pour forcer le passage au catch
313
- // Deuxième appel (dans le bloc catch) : On renvoie l'objet de secours valide mockData
314
- spy.mockImplementationOnce(() => {
315
- throw new Error("Forced exception to enter catch block");
316
- }).mockImplementationOnce(() => {
317
- return mockData;
318
- });
319
-
320
- // Simule un retour positif de la base de données
321
- mockExecute.mockResolvedValue([{ affectedRows: 1 }]);
322
-
323
- // 4. Exécution de la méthode
324
- const affected = await instance.updateOne({ status: "patched" });
325
-
326
- expect(affected).toBe(1);
327
- expect(mockExecute).toHaveBeenCalled();
328
-
329
- const sqlGenerated = mockExecute.mock.calls[0][0];
330
- expect(sqlGenerated).toContain("UPDATE ProjectPipeline SET `status` = 'patched' WHERE `id` = 10 AND `status` = 'active'");
331
-
332
- // 5. On nettoie le spy pour ne pas impacter les autres tests
333
- spy.mockRestore();
334
- });
335
- });
@@ -1,217 +0,0 @@
1
- const { buildSelect, buildQueryParts } = require('../src/utils/buildQuery/buildQuery');
2
-
3
- describe('Utils - buildQuery.js', () => {
4
- describe('buildSelect', () => {
5
- test('Devrait renvoyer "*" si le select est absent ou vide', () => {
6
- expect(buildSelect()).toBe('*');
7
- expect(buildSelect([])).toBe('*');
8
- });
9
-
10
- test('Devrait concaténer les colonnes simples séparées par des retours à la ligne', () => {
11
- expect(buildSelect(['id', 'name'])).toBe('`id`,\n`name`');
12
- });
13
-
14
- test('Devrait gérer l\'agrégation SUM avec alias optionnel', () => {
15
- expect(buildSelect([{ sum: 'price' }])).toBe('SUM(`price`) AS `price`');
16
- expect(buildSelect([{ sum: 'price', as: 'total' }])).toBe('SUM(`price`) AS `total`');
17
- });
18
-
19
- test('Devrait gérer DATE_FORMAT et appliquer l\'échappement sur la colonne', () => {
20
- const select = [{ dateFormat: ['createdAt', '%Y-%m'], as: 'month' }];
21
- expect(buildSelect(select)).toBe("DATE_FORMAT(`createdAt`, '%Y-%m') AS `month`");
22
- });
23
-
24
- test('Devrait gérer une colonne simple déclarée via un objet avec alias', () => {
25
- expect(buildSelect([{ col: 'role', as: 'user_role' }])).toBe('`role` AS `user_role`');
26
- });
27
- });
28
-
29
- describe('buildQueryParts', () => {
30
-
31
- test('Devrait traiter correctement la clause WHERE sous forme d\'objet structuré (Cas nominal sécurisé)', () => {
32
- const options = { where: { status: 'active', role: 'admin' } };
33
-
34
- // L'objet est nettoyé et les colonnes sont échappées avec des backticks
35
- expect(buildQueryParts(options)).toEqual("WHERE `status` = 'active' AND `role` = 'admin'");
36
- });
37
-
38
- test('Devrait lever une erreur si la clause WHERE est passée sous forme de chaîne brute (Protection Injection SQL)', () => {
39
- const optionsInvalides = { where: 'WHERE id = 1 OR 1=1' };
40
-
41
- expect(() => buildQueryParts(optionsInvalides)).toThrow(
42
- 'Raw string WHERE clauses are not allowed. Use a structured filter instead.'
43
- );
44
- });
45
- test('Devrait traiter la clause WHERE (chaîne brute ou objet)', () => {
46
- expect(buildQueryParts({ where: 'id = 1' })).toEqual('WHERE id = 1');
47
- expect(buildQueryParts({ where: { status: 'ok' } })).toEqual("WHERE `status` = 'ok'");
48
- });
49
-
50
- test('Devrait générer la clause GROUP BY et parser correctement DATE_FORMAT', () => {
51
- const options = { groupBy: ['role', "DATE_FORMAT(createdAt, '%Y')"] };
52
- expect(buildQueryParts(options)).toEqual("GROUP BY `role`, DATE_FORMAT(`createdAt`, '%Y')");
53
- });
54
-
55
- test('Devrait lever une erreur si la clause HAVING est passée sous forme de chaîne brute', () => {
56
- expect(() => buildQueryParts({ having: 'count > 1' })).toThrow(
57
- 'Raw string HAVING clauses are not allowed. Use a structured filter instead.'
58
- );
59
- });
60
-
61
- test('Devrait traiter la clause ORDER BY (chaîne simple ou objet structuré)', () => {
62
- const options = {
63
- orderBy: ['name', { field: 'id', direction: 'DESC' }]
64
- };
65
- expect(buildQueryParts(options)).toEqual('ORDER BY `name`, `id` DESC');
66
- });
67
-
68
- test('Devrait accepter la clause LIMIT si elle est un entier valide', () => {
69
- expect(buildQueryParts({ limit: 10 })).toEqual("LIMIT 10");
70
- expect(buildQueryParts({ limit: 0 })).toEqual("");
71
- });
72
-
73
- test('Devrait lever une erreur si la clause LIMIT est invalide', () => {
74
- expect(() => buildQueryParts({ limit: -5 })).toThrow('Invalid LIMIT value');
75
- expect(() => buildQueryParts({ limit: 10.5 })).toThrow('Invalid LIMIT value');
76
- expect(() => buildQueryParts({ limit: 'abc' })).toThrow('Invalid LIMIT value');
77
- });
78
- });
79
- describe('buildQuery - Advanced Fields & Aggregations', () => {
80
-
81
- test('Should safely compile COUNT(*) aggregated calculations with an expression alias', () => {
82
- const fields = [{ count: '*', as: 'total_user' }];
83
- expect(buildSelect(fields)).toBe('COUNT(*) AS `total_user`');
84
- });
85
-
86
- test('Should safely compile COUNT(column) on structured column identifiers', () => {
87
- const fields = [{ count: 'id', as: 'unique_ids' }];
88
- expect(buildSelect(fields)).toBe('COUNT(`id`) AS `unique_ids`');
89
- });
90
-
91
- test('Should correctly process multi-column GROUP BY arrays to avoid ONLY_FULL_GROUP_BY validation issues', () => {
92
- const options = {
93
- select: ['status', 'email', { count: '*', as: 'total_user' }],
94
- groupBy: ['status', 'email']
95
- };
96
- const parts = buildQueryParts(options);
97
-
98
- expect(parts).toContain('GROUP BY `status`, `email`');
99
- });
100
-
101
- test("COUNT DISTINCT sur plusieurs colonnes", () => {
102
- expect(
103
- buildSelect([
104
- {
105
- count: ["truc", "bidule"],
106
- as: "total"
107
- }
108
- ])
109
- ).toBe("COUNT(`truc`) + COUNT(`bidule`) AS `total`");
110
- });
111
-
112
- test("COUNT conditionnel avec NULL", () => {
113
- expect(
114
- buildSelect([
115
- {
116
- count: {
117
- truc: null
118
- },
119
- as: "total"
120
- }
121
- ])
122
- ).toBe("COUNT(CASE WHEN `truc` = NULL THEN 1 END) AS `total`");
123
- });
124
-
125
- test("COUNT conditionnel avec NOT NULL", () => {
126
- expect(
127
- buildSelect([
128
- {
129
- count: {
130
- truc: "NOT NULL"
131
- },
132
- as: "total"
133
- }
134
- ])
135
- ).toBe("COUNT(CASE WHEN `truc` = 'NOT NULL' THEN 1 END) AS `total`");
136
- });
137
-
138
- test("COUNT refuse un nombre", () => {
139
- expect(() =>
140
- buildSelect([
141
- {
142
- count: 1515
143
- }
144
- ])
145
- ).toThrow(
146
- "Invalid field type for COUNT. Must be a string, array, or object."
147
- );
148
- });
149
-
150
- });
151
-
152
- describe("buildQuery - buildGroupByItem", () => {
153
- test('Return directly an error if group is not a string.', () => {
154
- const options = {
155
- select: ['status', 'email', { count: '*', as: 'total_user' }],
156
- groupBy: ['status', 1255]
157
- };
158
-
159
- expect(() => buildQueryParts(options)).toThrow(
160
- "Group by items must be strings"
161
- );
162
- })
163
- })
164
-
165
- test('Devrait gérer DISTINCT avec alias', () => {
166
- const fields = [
167
- {
168
- distinct: 'email',
169
- as: 'unique_email'
170
- }
171
- ];
172
-
173
- expect(buildSelect(fields))
174
- .toBe('DISTINCT `email` AS `unique_email`');
175
- });
176
-
177
- test("GROUP BY simple", () => {
178
- expect(
179
- buildQueryParts({
180
- groupBy: ["status"]
181
- })
182
- ).toBe("GROUP BY `status`");
183
- });
184
-
185
- test("count simple", () => {
186
- expect(
187
- buildSelect([
188
- { count: "id" }
189
- ])
190
- ).toBe("COUNT(`id`)");
191
- });
192
-
193
-
194
- test("buildSelect supporte *", () => {
195
- expect(
196
- buildSelect(["*"])
197
- ).toBe("*");
198
- });
199
-
200
- test("SUM sans alias génère un alias automatique", () => {
201
- expect(
202
- buildSelect([
203
- {
204
- sum: "price"
205
- }
206
- ])
207
- ).toBe("SUM(`price`) AS `price`");
208
- });
209
-
210
- test("LIMIT décimal invalide", () => {
211
- expect(() =>
212
- buildQueryParts({
213
- limit: 10.5
214
- })
215
- ).toThrow();
216
- });
217
- });
@@ -1,44 +0,0 @@
1
- // 1. CHARGER LE MOCK EN PREMIER
2
- const { mockPool, mockEnd } = require('./mysqlMock');
3
-
4
- // 2. CHARGER LES MODULES DU PROJET ENSUITE
5
- const { connect, logout } = require('../src/db/connect'); // Ajustez le chemin vers votre dossier src
6
- const { getConnexion } = require('../src/db/connexion');
7
- const mysql = require('mysql2');
8
-
9
- describe("Tests unitaires avec Mock - connect.js", () => {
10
-
11
- beforeEach(() => {
12
- jest.clearAllMocks();
13
- });
14
-
15
- test("connect() devrait créer un pool mysql2 et définir la connexion globale", async () => {
16
- const config = { host: 'localhost', user: 'root', database: 'test' };
17
-
18
- await connect(config);
19
-
20
- // Maintenant mysql.createPool est bien un mock Jest !
21
- expect(mysql.createPool).toHaveBeenCalledWith(config);
22
- expect(getConnexion()).toBe(mockPool);
23
- });
24
-
25
- test("logout() devrait fermer proprement la connexion du pool", async () => {
26
- // On simule une connexion active d'abord
27
- const { setConnexion } = require('../src/db/connexion');
28
- setConnexion(mockPool);
29
-
30
- await logout();
31
-
32
- expect(mockEnd).toHaveBeenCalled();
33
- });
34
-
35
- test("logout() doit gérer une erreur lors de la fermeture", async () => {
36
- mockPool.end.mockImplementation((callback) => {
37
- callback(new Error("Close Error"));
38
- });
39
-
40
- await logout();
41
-
42
- expect(mockPool.end).toHaveBeenCalled();
43
- });
44
- });
@@ -1,46 +0,0 @@
1
- const formatObject = require('../src/utils/formatObject');
2
-
3
- describe('Utils - formatObject.js', () => {
4
- test('Devrait convertir les instances de Date au format DATETIME MySQL local', () => {
5
- const date = new Date('2026-07-03T12:34:56.789Z');
6
- const result = formatObject({ createdAt: date });
7
- expect(result.createdAt).toBe('2026-07-03 12:34:56');
8
- });
9
-
10
- test('Devrait supprimer les quotes enveloppantes des chaînes de caractères', () => {
11
- const result = formatObject({
12
- single: "'hello'",
13
- double: '"world"',
14
- normal: 'text'
15
- });
16
- expect(result.single).toBe('hello');
17
- expect(result.double).toBe('world');
18
- expect(result.normal).toBe('text');
19
- });
20
-
21
- test('Devrait déséchapper les quotes protégées d’une chaîne', () => {
22
- const result = formatObject({ text: "John\\'s corporate \\\"value\\\"" });
23
- expect(result.text).toBe('John\'s corporate "value"');
24
- });
25
-
26
- test('Devrait sérialiser les objets imbriqués en JSON et échapper les single quotes', () => {
27
- const result = formatObject({ meta: { role: "owner's", active: true } });
28
- // L'objet devient une chaîne JSON avec les single quotes échappées par un double antislash
29
- expect(result.meta).toBe('{"role":"owner\\\'s","active":true}');
30
- });
31
-
32
- test('Devrait ignorer et laisser inchangés les types primitifs non ciblés (nombres, booléens)', () => {
33
- const result = formatObject({ age: 25, isValid: true });
34
- expect(result.age).toBe(25);
35
- expect(result.isValid).toBe(true);
36
- });
37
-
38
- test("formatObject couvre hasOwnProperty", () => {
39
- const parent = { test: 1 };
40
- const obj = Object.create(parent);
41
-
42
- obj.name = "john";
43
-
44
- formatObject(obj);
45
- });
46
- });