@mlagie/sql-connector 2.0.8 → 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.
- package/package.json +23 -7
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -43
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -29
- package/.github/workflows/publish.yml +0 -80
- package/eslint.config.mjs +0 -37
- package/releases/1.4.8.md +0 -43
- package/releases/1.5.0.md +0 -127
- package/releases/2.0.0.md +0 -32
- package/releases/2.0.1.md +0 -23
- package/releases/2.0.2.md +0 -20
- package/releases/2.0.3.md +0 -20
- package/releases/2.0.4.md +0 -42
- package/releases/2.0.5.md +0 -65
- package/releases/2.0.6.md +0 -80
- package/releases/2.0.7.md +0 -38
- package/releases/2.0.8.md +0 -5
- package/socket.yml +0 -4
- package/tests/Model.test.js +0 -721
- package/tests/ModelInstance.test.js +0 -335
- package/tests/buildQuery.test.js +0 -217
- package/tests/connect.test.js +0 -44
- package/tests/formatObject.test.js +0 -46
- package/tests/generateCondition.test.js +0 -241
- package/tests/mysqlMock.js +0 -22
- package/tests/safe.test.js +0 -48
- package/tests/sql.test.js +0 -62
- package/tests/sqlTypeMap.test.js +0 -16
package/tests/Model.test.js
DELETED
|
@@ -1,721 +0,0 @@
|
|
|
1
|
-
// 1. LES MOCKS EN PREMIER
|
|
2
|
-
const { mockExecute, mockPool } = require('./mysqlMock');
|
|
3
|
-
const connexionManager = require("../src/db/connexion");
|
|
4
|
-
|
|
5
|
-
jest.mock('@mlagie/logger', () => ({
|
|
6
|
-
logs: jest.fn(),
|
|
7
|
-
error: jest.fn()
|
|
8
|
-
}));
|
|
9
|
-
|
|
10
|
-
// 2. LES IMPORTS DE L'APPLICATION
|
|
11
|
-
const { Model } = require('../src/models/Model');
|
|
12
|
-
const { Schema } = require('../src/models/Schema');
|
|
13
|
-
const { setConnexion } = require('../src/db/connexion');
|
|
14
|
-
const { sqlTypeMap } = require('../src/utils/sqlTypeMap');
|
|
15
|
-
const util = require("util");
|
|
16
|
-
|
|
17
|
-
describe('Tests unitaires avec Mock - Model.js (generate_uuid)', () => {
|
|
18
|
-
let testModel;
|
|
19
|
-
|
|
20
|
-
beforeAll(() => {
|
|
21
|
-
setConnexion(mockPool);
|
|
22
|
-
const userSchema = new Schema({
|
|
23
|
-
id: { type: Number },
|
|
24
|
-
uuid: { type: String }
|
|
25
|
-
});
|
|
26
|
-
testModel = new Model('users', userSchema);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
jest.clearAllMocks();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('generate_uuid() devrait renvoyer un UUID si unique', async () => {
|
|
34
|
-
mockExecute
|
|
35
|
-
.mockResolvedValueOnce([
|
|
36
|
-
[
|
|
37
|
-
{ "UUID()": "123e4567-e89b-12d3-a456-426614174000" }
|
|
38
|
-
]
|
|
39
|
-
])
|
|
40
|
-
.mockResolvedValueOnce([
|
|
41
|
-
[
|
|
42
|
-
{ "COUNT(*)": 0 }
|
|
43
|
-
]
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
const uuid = await testModel.generate_uuid('uuid');
|
|
47
|
-
expect(uuid).toBe('123e4567-e89b-12d3-a456-426614174000');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('generate_uuid() devrait gérer les rejets/erreurs de la base de données', async () => {
|
|
51
|
-
// On simule le crash de la base de données
|
|
52
|
-
mockExecute.mockRejectedValueOnce(new Error('Syntax Error ou Connexion perdue'));
|
|
53
|
-
|
|
54
|
-
const uuid = await testModel.generate_uuid('uuid');
|
|
55
|
-
|
|
56
|
-
// L'erreur est catchée par votre Model.js, qui log l'erreur et retourne null.
|
|
57
|
-
expect(uuid).toBeNull();
|
|
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
|
-
});
|
|
721
|
-
});
|