arckode-framework 1.0.0 → 1.0.1

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,617 +0,0 @@
1
- // kernel/__tests__/framework.test.ts — Tests del kernel
2
- // Bun:test. Cubre: Config, Logger, Container, ORM, Router, Auth, Cache, Validator, System
3
-
4
- import { describe, it, expect, beforeAll, beforeEach } from 'bun:test'
5
- import {
6
- ConfigStore, Logger, Container, ORM, Router, NodeServer,
7
- MemoryCache, Auth, validateSchema, System, createModule,
8
- NotFoundError, ValidationError, AuthError,
9
- } from '../framework'
10
- import { jwtTokenAdapter } from '../../adapters/jwt'
11
-
12
- // ─── MOCK DB — simula almacenamiento para tests de comportamiento ──────────
13
- const mockDb = {
14
- _tables: new Map<string, any[]>(),
15
- query: async (sql: string, params?: unknown[]) => {
16
- const table = sql.match(/FROM (\w+)/i)?.[1] ?? ''
17
- const data = mockDb._tables.get(table) ?? []
18
- if (sql.includes('SELECT') && sql.includes('WHERE') && params && params.length > 0) {
19
- // params es un array: [id] para findById, o [valor, ...] para filtros
20
- const whereClause = sql.match(/WHERE (.+)/i)?.[1] ?? ''
21
- const conditions = whereClause.split(' AND ')
22
- return data.filter(row => {
23
- return conditions.every((cond, i) => {
24
- const col = cond.split(' ')[0] ?? ''
25
- return params![i] !== undefined && row[col] === params![i]
26
- })
27
- })
28
- }
29
- return data
30
- },
31
- run: async (sql: string, params?: unknown[]) => {
32
- if (sql.includes('CREATE TABLE')) return { changes: 0 }
33
- if (sql.includes('INSERT INTO')) {
34
- const table = sql.match(/INTO (\w+)/i)?.[1] ?? ''
35
- const data = mockDb._tables.get(table) ?? []
36
- const id = params?.[0] ?? crypto.randomUUID()
37
- const row = { id } as any
38
- const keys = sql.match(/\(([^)]+)\)/g)?.[0]?.replace(/[()]/g, '')?.split(', ').map(s => s.trim()) ?? []
39
- keys.forEach((k, i) => { row[k] = params?.[i] })
40
- data.push(row)
41
- mockDb._tables.set(table, data)
42
- return { changes: 1, lastId: id }
43
- }
44
- if (sql.includes('DELETE')) {
45
- const table = sql.match(/FROM (\w+)/i)?.[1] ?? ''
46
- const id = params?.[0]
47
- const data = mockDb._tables.get(table) ?? []
48
- mockDb._tables.set(table, data.filter((r: any) => r.id !== id))
49
- return { changes: 1 }
50
- }
51
- return { changes: 0 }
52
- },
53
- close: async () => {},
54
- }
55
-
56
- // ─── RECORDING MOCK — graba SQL generado para verificar contratos del ORM ──
57
- // El ORM es una abstracción. Sus tests validan qué SQL genera y delega al adapter,
58
- // no cómo lo ejecuta (eso es responsabilidad de cada adapter: SQLite, Postgres, etc.)
59
- function createRecordingDb(opts: { countValue?: number; queryData?: unknown[] } = {}) {
60
- const calls: { sql: string; params: unknown[] }[] = []
61
- const countValue = opts.countValue ?? 0
62
- const queryData = opts.queryData ?? []
63
-
64
- return {
65
- calls,
66
- lastSql: () => calls.at(-1)?.sql ?? '',
67
- lastParams: () => calls.at(-1)?.params ?? [],
68
- reset: () => { calls.length = 0 },
69
-
70
- query: async (sql: string, params: unknown[] = []) => {
71
- calls.push({ sql, params })
72
- // COUNT(*) → devuelve el total configurado
73
- if (sql.includes('COUNT(*)')) return [{ n: countValue }]
74
- return queryData
75
- },
76
- run: async (sql: string, params: unknown[] = []) => {
77
- calls.push({ sql, params })
78
- return { changes: 1 }
79
- },
80
- close: async () => {},
81
- }
82
- }
83
-
84
- // ═════════════════════════════════════════════════════
85
- // 1. CONFIG
86
- // ═════════════════════════════════════════════════════
87
- describe('ConfigStore', () => {
88
- it('define y carga valores correctamente', () => {
89
- const c = new ConfigStore()
90
- c.define({ PORT: { type: 'number', default: 3000 }, JWT_SECRET: { type: 'string', required: true } })
91
- c.load({ JWT_SECRET: 'test123' })
92
- expect(c.get<number>('PORT')).toBe(3000)
93
- expect(c.get<string>('JWT_SECRET')).toBe('test123')
94
- })
95
-
96
- it('fail fast si falta required', () => {
97
- const c = new ConfigStore()
98
- c.define({ REQUIRED: { type: 'string', required: true } })
99
- expect(() => c.load({})).toThrow('CONFIG')
100
- })
101
-
102
- it('falla si no se llamó load()', () => {
103
- const c = new ConfigStore()
104
- c.define({ X: { type: 'string' } })
105
- expect(() => c.get('X')).toThrow('Config no cargada')
106
- })
107
-
108
- it('falla si la key no existe', () => {
109
- const c = new ConfigStore()
110
- expect(() => c.get('NOEXISTE')).toThrow()
111
- })
112
-
113
- it('type: url valida URLs correctas', () => {
114
- const c = new ConfigStore()
115
- c.define({ URL: { type: 'url', required: true } })
116
- c.load({ URL: 'https://example.com' })
117
- expect(c.get<string>('URL')).toBe('https://example.com')
118
- })
119
-
120
- it('type: url rechaza URLs inválidas', () => {
121
- const c = new ConfigStore()
122
- c.define({ URL: { type: 'url', required: true } })
123
- expect(() => c.load({ URL: 'no-es-url' })).toThrow()
124
- })
125
- })
126
-
127
- // ═════════════════════════════════════════════════════
128
- // 2. LOGGER
129
- // ═════════════════════════════════════════════════════
130
- describe('Logger', () => {
131
- it('crea logger con source', () => {
132
- const l = new Logger('test')
133
- expect(l.source).toBe('test')
134
- })
135
-
136
- it('child hereda source', () => {
137
- const l = new Logger('app').child('modulo')
138
- expect(l.source).toBe('app.modulo')
139
- })
140
-
141
- it('no rompe al loguear', () => {
142
- const l = new Logger('test', 'error')
143
- l.info('no debería aparecer')
144
- l.error('debería aparecer')
145
- })
146
- })
147
-
148
- // ═════════════════════════════════════════════════════
149
- // 3. CONTAINER
150
- // ═════════════════════════════════════════════════════
151
- describe('Container', () => {
152
- it('registra y resuelve servicios', () => {
153
- const c = new Container()
154
- c.register('num', () => 42)
155
- c.init()
156
- expect(c.get<number>('num')).toBe(42)
157
- })
158
-
159
- it('tira error si no existe', () => {
160
- const c = new Container()
161
- c.init()
162
- expect(() => c.get('no-existe')).toThrow('no registrado')
163
- })
164
-
165
- it('tira error si se registra después de init', () => {
166
- const c = new Container()
167
- c.init()
168
- expect(() => c.register('x', () => 1)).toThrow('registered after init')
169
- })
170
-
171
- it('singleton: misma instancia', () => {
172
- const c = new Container()
173
- c.register('obj', () => ({ a: 1 }))
174
- c.init()
175
- const a = c.get<{ a: number }>('obj')
176
- const b = c.get<{ a: number }>('obj')
177
- expect(a).toBe(b)
178
- })
179
- })
180
-
181
- // ═════════════════════════════════════════════════════
182
- // 4. ORM
183
- // ═════════════════════════════════════════════════════
184
- describe('ORM', () => {
185
- let orm: ORM
186
-
187
- beforeAll(() => {
188
- orm = new ORM(mockDb as any)
189
- orm.define('Producto', {
190
- table: 'productos',
191
- fields: { nombre: { type: 'string', required: true }, precio: { type: 'number' } },
192
- timestamps: true,
193
- })
194
- })
195
-
196
- it('findMany devuelve array vacío al inicio', async () => {
197
- const items = await orm.findMany('Producto')
198
- expect(items).toEqual([])
199
- })
200
-
201
- it('create y findById funcionan', async () => {
202
- const created = await orm.create('Producto', { nombre: 'Test', precio: 100 })
203
- expect(created.id).toBeDefined()
204
- expect(created.nombre).toBe('Test')
205
- expect(created.createdAt).toBeDefined()
206
-
207
- const found = await orm.findById('Producto', created.id)
208
- expect(found).not.toBeNull()
209
- expect(found?.nombre).toBe('Test')
210
- })
211
-
212
- it('findById devuelve null si no existe', async () => {
213
- const found = await orm.findById('Producto', 'no-existe')
214
- expect(found).toBeNull()
215
- })
216
-
217
- it('delete devuelve true si existía', async () => {
218
- const created = await orm.create('Producto', { nombre: 'Borrar' })
219
- const deleted = await orm.delete('Producto', created.id)
220
- expect(deleted).toBe(true)
221
-
222
- const found = await orm.findById('Producto', created.id)
223
- expect(found).toBeNull()
224
- })
225
-
226
- it('tira error si modelo no existe', async () => {
227
- await expect(orm.findMany('NoExiste')).rejects.toThrow(NotFoundError)
228
- })
229
- })
230
-
231
- // ═════════════════════════════════════════════════════
232
- // 4b. ORM — GENERACIÓN DE SQL (Recording Mock)
233
- // Verifica el contrato del ORM con el adapter.
234
- // Independiente de SQLite, Postgres o cualquier implementación.
235
- // ═════════════════════════════════════════════════════
236
- describe('ORM — Generación de SQL', () => {
237
- const FIELDS = { nombre: { type: 'string' as const }, precio: { type: 'number' as const } }
238
- const fakeItem = { id: 'uuid-1', nombre: 'Laptop', precio: 1500 }
239
-
240
- function makeOrm(db: ReturnType<typeof createRecordingDb>) {
241
- const orm = new ORM(db as any)
242
- orm.define('Producto', { table: 'productos', fields: FIELDS })
243
- return orm
244
- }
245
-
246
- it('findMany sin opciones genera SELECT * FROM tabla', async () => {
247
- const db = createRecordingDb({ queryData: [] })
248
- await makeOrm(db).findMany('Producto')
249
- expect(db.lastSql()).toBe('SELECT * FROM productos')
250
- })
251
-
252
- it('findMany con filtro genera WHERE con params', async () => {
253
- const db = createRecordingDb({ queryData: [fakeItem] })
254
- await makeOrm(db).findMany('Producto', { nombre: 'Laptop' })
255
- expect(db.lastSql()).toContain('WHERE nombre = ?')
256
- expect(db.lastParams()).toEqual(['Laptop'])
257
- })
258
-
259
- it('findMany con orderBy DESC genera ORDER BY campo DESC', async () => {
260
- const db = createRecordingDb({ queryData: [fakeItem] })
261
- await makeOrm(db).findMany('Producto', {}, { orderBy: { field: 'precio', dir: 'DESC' } })
262
- expect(db.lastSql()).toContain('ORDER BY precio DESC')
263
- })
264
-
265
- it('findMany con orderBy ASC por defecto', async () => {
266
- const db = createRecordingDb({ queryData: [fakeItem] })
267
- await makeOrm(db).findMany('Producto', {}, { orderBy: { field: 'nombre' } })
268
- expect(db.lastSql()).toContain('ORDER BY nombre ASC')
269
- })
270
-
271
- it('findMany con múltiples orderBy genera cláusula compuesta', async () => {
272
- const db = createRecordingDb({ queryData: [fakeItem] })
273
- await makeOrm(db).findMany('Producto', {}, {
274
- orderBy: [{ field: 'precio', dir: 'DESC' }, { field: 'nombre', dir: 'ASC' }],
275
- })
276
- expect(db.lastSql()).toContain('ORDER BY precio DESC, nombre ASC')
277
- })
278
-
279
- it('findMany con limit genera LIMIT N', async () => {
280
- const db = createRecordingDb({ queryData: [fakeItem] })
281
- await makeOrm(db).findMany('Producto', {}, { limit: 10 })
282
- expect(db.lastSql()).toContain('LIMIT 10')
283
- })
284
-
285
- it('findMany con limit y offset genera LIMIT N OFFSET M', async () => {
286
- const db = createRecordingDb({ queryData: [fakeItem] })
287
- await makeOrm(db).findMany('Producto', {}, { limit: 10, offset: 20 })
288
- const sql = db.lastSql()
289
- expect(sql).toContain('LIMIT 10')
290
- expect(sql).toContain('OFFSET 20')
291
- })
292
-
293
- it('findMany combina filtro + orderBy + limit en el orden correcto', async () => {
294
- const db = createRecordingDb({ queryData: [fakeItem] })
295
- await makeOrm(db).findMany('Producto', { nombre: 'X' }, {
296
- orderBy: { field: 'precio', dir: 'ASC' },
297
- limit: 5,
298
- offset: 10,
299
- })
300
- const sql = db.lastSql()
301
- // Orden SQL: WHERE → ORDER BY → LIMIT → OFFSET
302
- expect(sql.indexOf('WHERE')).toBeLessThan(sql.indexOf('ORDER BY'))
303
- expect(sql.indexOf('ORDER BY')).toBeLessThan(sql.indexOf('LIMIT'))
304
- expect(sql.indexOf('LIMIT')).toBeLessThan(sql.indexOf('OFFSET'))
305
- })
306
-
307
- it('findMany rechaza campo orderBy no definido en el modelo', async () => {
308
- const db = createRecordingDb()
309
- await expect(
310
- makeOrm(db).findMany('Producto', {}, { orderBy: { field: 'campoFalso', dir: 'DESC' } })
311
- ).rejects.toThrow(ValidationError)
312
- })
313
-
314
- it('findMany rechaza filtro con campo no definido en el modelo', async () => {
315
- const db = createRecordingDb()
316
- await expect(
317
- makeOrm(db).findMany('Producto', { campoFalso: 'x' })
318
- ).rejects.toThrow(ValidationError)
319
- })
320
-
321
- it('count genera SELECT COUNT(*) FROM tabla', async () => {
322
- const db = createRecordingDb({ countValue: 7 })
323
- const total = await makeOrm(db).count('Producto')
324
- expect(total).toBe(7)
325
- expect(db.lastSql()).toMatch(/SELECT COUNT\(\*\) as n FROM productos/)
326
- })
327
-
328
- it('count con filtros agrega WHERE', async () => {
329
- const db = createRecordingDb({ countValue: 3 })
330
- await makeOrm(db).count('Producto', { nombre: 'X' })
331
- expect(db.lastSql()).toContain('WHERE nombre = ?')
332
- })
333
-
334
- it('paginate devuelve { data, total, limit, offset, pages }', async () => {
335
- const db = createRecordingDb({ countValue: 47, queryData: [fakeItem] })
336
- const result = await makeOrm(db).paginate('Producto', {}, { limit: 10 })
337
- expect(result.total).toBe(47)
338
- expect(result.limit).toBe(10)
339
- expect(result.offset).toBe(0)
340
- expect(result.pages).toBe(5) // ceil(47/10)
341
- expect(Array.isArray(result.data)).toBe(true)
342
- })
343
-
344
- it('paginate calcula pages redondeando para arriba', async () => {
345
- const db = createRecordingDb({ countValue: 21, queryData: [fakeItem] })
346
- const result = await makeOrm(db).paginate('Producto', {}, { limit: 10, offset: 10 })
347
- expect(result.pages).toBe(3) // ceil(21/10)
348
- expect(result.offset).toBe(10)
349
- })
350
-
351
- it('paginate con 0 resultados devuelve pages: 0', async () => {
352
- const db = createRecordingDb({ countValue: 0, queryData: [] })
353
- const result = await makeOrm(db).paginate('Producto', {}, { limit: 10 })
354
- expect(result.total).toBe(0)
355
- expect(result.pages).toBe(0)
356
- expect(result.data).toEqual([])
357
- })
358
- })
359
-
360
- // ═════════════════════════════════════════════════════
361
- // 5. ROUTER
362
- // ═════════════════════════════════════════════════════
363
- describe('Router', () => {
364
- it('resuelve rutas GET', async () => {
365
- const r = new Router()
366
- r.get('/test', () => ({ status: 200, body: { ok: true } }))
367
- const res = await r.resolve('GET', '/test')
368
- expect(res.status).toBe(200)
369
- expect(res.body).toEqual({ ok: true })
370
- })
371
-
372
- it('resuelve params en ruta', async () => {
373
- const r = new Router()
374
- r.get('/users/:id', (req) => ({ status: 200, body: { id: req.params.id } }))
375
- const res = await r.resolve('GET', '/users/42')
376
- expect(res.body).toEqual({ id: '42' })
377
- })
378
-
379
- it('devuelve 404 si no hay match', async () => {
380
- const r = new Router()
381
- const res = await r.resolve('GET', '/no-existe')
382
- expect(res.status).toBe(404)
383
- })
384
-
385
- it('ejecuta middlewares en orden', async () => {
386
- const r = new Router()
387
- const order: string[] = []
388
-
389
- r.use(async (req, next) => { order.push('global'); return next() })
390
- r.get('/test', () => { order.push('handler'); return { status: 200, body: {} } }, [
391
- async (req, next) => { order.push('route'); return next() },
392
- ])
393
-
394
- await r.resolve('GET', '/test')
395
- expect(order).toEqual(['global', 'route', 'handler'])
396
- })
397
-
398
- it('POST con body', async () => {
399
- const r = new Router()
400
- r.post('/data', (req) => ({ status: 201, body: req.body }))
401
- const res = await r.resolve('POST', '/data', { body: { nombre: 'test' } })
402
- expect(res.status).toBe(201)
403
- expect(res.body).toEqual({ nombre: 'test' })
404
- })
405
- })
406
-
407
- // ═════════════════════════════════════════════════════
408
- // 6. AUTH
409
- // ═════════════════════════════════════════════════════
410
- describe('Auth', () => {
411
- const auth = new Auth(jwtTokenAdapter, 'test-secret', new Logger('auth', 'error'))
412
-
413
- it('createToken y verifyToken funcionan', () => {
414
- const token = auth.createToken({ id: '1', role: 'admin' })
415
- const payload = auth.verifyToken(token)
416
- expect(payload.id).toBe('1')
417
- expect(payload.role).toBe('admin')
418
- })
419
-
420
- it('verifyToken rechaza token inválido', () => {
421
- expect(() => auth.verifyToken('token-invalido')).toThrow(AuthError)
422
- })
423
-
424
- it('authenticate middleware funciona', async () => {
425
- const token = auth.createToken({ id: '1', role: 'admin' })
426
- const mw = auth.authenticate('admin')
427
- const req = { headers: { authorization: `Bearer ${token}` } } as any
428
- let called = false
429
- await mw(req, async () => { called = true; return { status: 200, body: {} } })
430
- expect(called).toBe(true)
431
- expect(req.user?.id).toBe('1')
432
- })
433
-
434
- it('authenticate rechaza si falta token', async () => {
435
- const mw = auth.authenticate()
436
- const req = { headers: {} } as any
437
- await expect(mw(req, async () => ({ status: 200, body: {} }))).rejects.toThrow(AuthError)
438
- })
439
-
440
- it('authenticate rechaza si rol no coincide', async () => {
441
- const token = auth.createToken({ id: '1', role: 'user' })
442
- const mw = auth.authenticate('admin')
443
- const req = { headers: { authorization: `Bearer ${token}` } } as any
444
- await expect(mw(req, async () => ({ status: 200, body: {} }))).rejects.toThrow(/required role/i)
445
- })
446
- })
447
-
448
- // ═════════════════════════════════════════════════════
449
- // 7. CACHE
450
- // ═════════════════════════════════════════════════════
451
- describe('MemoryCache', () => {
452
- it('set y get', async () => {
453
- const c = new MemoryCache()
454
- await c.set('key', { data: 42 })
455
- const val = await c.get('key')
456
- expect(val).toEqual({ data: 42 })
457
- })
458
-
459
- it('get devuelve null si no existe', async () => {
460
- const c = new MemoryCache()
461
- const val = await c.get('no-existe')
462
- expect(val).toBeNull()
463
- })
464
-
465
- it('delete elimina', async () => {
466
- const c = new MemoryCache()
467
- await c.set('key', 'valor')
468
- await c.delete('key')
469
- const val = await c.get('key')
470
- expect(val).toBeNull()
471
- })
472
-
473
- it('flush limpia todo', async () => {
474
- const c = new MemoryCache()
475
- await c.set('a', 1)
476
- await c.set('b', 2)
477
- await c.flush()
478
- expect(await c.get('a')).toBeNull()
479
- expect(await c.get('b')).toBeNull()
480
- })
481
-
482
- it('expira después del TTL', async () => {
483
- const c = new MemoryCache()
484
- await c.set('key', 'valor', 0) // 0 segundos = expira inmediato
485
- await new Promise(r => setTimeout(r, 10))
486
- const val = await c.get('key')
487
- expect(val).toBeNull()
488
- })
489
- })
490
-
491
- // ═════════════════════════════════════════════════════
492
- // 8. VALIDATOR
493
- // ═════════════════════════════════════════════════════
494
- describe('validateSchema', () => {
495
- it('valida campos requeridos', () => {
496
- const data = validateSchema({
497
- nombre: { type: 'string', required: true },
498
- edad: { type: 'number', required: true },
499
- }, { nombre: 'Juan', edad: 30 })
500
- expect(data.nombre).toBe('Juan')
501
- expect(data.edad).toBe(30)
502
- })
503
-
504
- it('tira error si falta required', () => {
505
- expect(() => validateSchema({
506
- nombre: { type: 'string', required: true },
507
- }, {})).toThrow(ValidationError)
508
- })
509
-
510
- it('valida email', () => {
511
- expect(() => validateSchema({
512
- email: { type: 'email', required: true },
513
- }, { email: 'no-es-email' })).toThrow(ValidationError)
514
-
515
- const data = validateSchema({
516
- email: { type: 'email', required: true },
517
- }, { email: 'test@correo.com' })
518
- expect(data.email).toBe('test@correo.com')
519
- })
520
-
521
- it('valida min/max en strings', () => {
522
- expect(() => validateSchema({
523
- name: { type: 'string', min: 3, max: 10 },
524
- }, { name: 'ab' })).toThrow()
525
-
526
- const data = validateSchema({
527
- name: { type: 'string', min: 3, max: 10 },
528
- }, { name: 'Juan Pérez' })
529
- expect(data.name).toBe('Juan Pérez')
530
- })
531
-
532
- it('valida enum', () => {
533
- const schema = { rol: { type: 'string', enum: ['admin', 'user'] } as any }
534
-
535
- expect(() => validateSchema(schema, { rol: 'superadmin' })).toThrow()
536
-
537
- const data = validateSchema(schema, { rol: 'admin' })
538
- expect(data.rol).toBe('admin')
539
- })
540
-
541
- it('tira error si body no es objeto', () => {
542
- expect(() => validateSchema({}, 'texto')).toThrow(ValidationError)
543
- })
544
- })
545
-
546
- // ═════════════════════════════════════════════════════
547
- // 9. SYSTEM (módulos + conectores)
548
- // ═════════════════════════════════════════════════════
549
- describe('System', () => {
550
- it('inicializa módulos y ejecuta conectores', async () => {
551
- const config = new ConfigStore()
552
- config.define({ TEST: { type: 'string', default: 'ok' } }).load({})
553
- const container = new Container()
554
- container.register('cfg', () => config).init()
555
- const orm = new ORM(mockDb as any)
556
- const router = new Router()
557
- const http = { start: async () => {}, stop: async () => {} }
558
- const cache = new MemoryCache()
559
-
560
- const TestModule = createModule({
561
- name: 'test',
562
- version: '1.0.0',
563
- description: 'Test',
564
- contract: { name: 'test', version: '1', description: '', actions: [], events: [], tables: [], dependencies: [], rules: [] },
565
- create: ({ logger }) => ({ ok: true, logger }),
566
- })
567
-
568
- const system = new System({ config, container, logger: new Logger('test', 'error'), orm, router, http, cache })
569
- system.addModule(TestModule)
570
- system.addConnector('test-connector', (ctx) => {
571
- const mod = ctx.resolveModule<any>('test')
572
- expect(mod.ok).toBe(true)
573
- })
574
- system.init()
575
-
576
- const mod = system.getModule<any>('test')
577
- expect(mod.ok).toBe(true)
578
- })
579
-
580
- it('tira error si módulo no existe', async () => {
581
- const config = new ConfigStore()
582
- config.define({}).load({})
583
- const container = new Container()
584
- container.register('c', () => config).init()
585
-
586
- const system = new System({
587
- config, container, logger: new Logger('test', 'error'),
588
- orm: new ORM(mockDb as any), router: new Router(),
589
- http: { start: async () => {}, stop: async () => {} },
590
- cache: new MemoryCache(),
591
- })
592
- expect(() => system.getModule('no-existe')).toThrow(NotFoundError)
593
- })
594
- })
595
-
596
- // ═════════════════════════════════════════════════════
597
- // 10. ERRORES
598
- // ═════════════════════════════════════════════════════
599
- describe('Errors', () => {
600
- it('NotFoundError tiene httpStatus 404', () => {
601
- const e = new NotFoundError('test')
602
- expect(e.httpStatus).toBe(404)
603
- expect(e.isExpected).toBe(true)
604
- expect(e.toJSON()).toHaveProperty('error')
605
- })
606
-
607
- it('ValidationError acepta fields', () => {
608
- const e = new ValidationError('error', { campo: ['requerido'] })
609
- expect(e.httpStatus).toBe(400)
610
- expect(e.toJSON().details).toBeDefined()
611
- })
612
-
613
- it('AuthError tiene httpStatus 401', () => {
614
- const e = new AuthError()
615
- expect(e.httpStatus).toBe(401)
616
- })
617
- })