arckode-framework 1.3.2 → 1.4.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.
Files changed (64) hide show
  1. package/adapters/jwt.ts +6 -4
  2. package/adapters/mysql.ts +7 -2
  3. package/adapters/postgres.ts +37 -0
  4. package/adapters/sqlite.ts +7 -1
  5. package/adapters/vendor.d.ts +48 -0
  6. package/cli/analyze/checks.ts +333 -0
  7. package/cli/analyze/index.ts +44 -0
  8. package/cli/analyze/report.ts +107 -0
  9. package/cli/analyze/types.ts +46 -0
  10. package/cli/analyze/utils.ts +36 -0
  11. package/cli/analyze.ts +2 -647
  12. package/cli/commands/db-migrate.ts +213 -89
  13. package/cli/commands/db-seed.ts +97 -32
  14. package/cli/commands/db-utils.ts +192 -0
  15. package/cli/commands/new.ts +175 -0
  16. package/cli/commands/routes.ts +94 -0
  17. package/cli/index.ts +57 -404
  18. package/cli/stubs/module/core.ts +162 -0
  19. package/cli/stubs/module/data.ts +171 -0
  20. package/cli/stubs/module/index.ts +5 -0
  21. package/cli/stubs/module/service.ts +198 -0
  22. package/cli/stubs/module/types.ts +12 -0
  23. package/cli/stubs/module-stub.ts +2 -552
  24. package/kernel/auth.ts +114 -0
  25. package/kernel/cache.ts +37 -0
  26. package/kernel/config.ts +129 -0
  27. package/kernel/container.ts +64 -0
  28. package/kernel/db/orm-migrate.ts +136 -0
  29. package/kernel/db/orm-repository.ts +45 -0
  30. package/kernel/db/orm-utils.ts +93 -0
  31. package/kernel/db/orm.ts +254 -0
  32. package/kernel/db/transactor.ts +17 -0
  33. package/kernel/db/types.ts +72 -0
  34. package/kernel/errors.ts +102 -0
  35. package/kernel/framework.default.ts +41 -0
  36. package/kernel/framework.ts +8 -2144
  37. package/kernel/http/router.ts +131 -0
  38. package/kernel/http/server.ts +303 -0
  39. package/kernel/http/types.ts +56 -0
  40. package/kernel/index.ts +25 -0
  41. package/kernel/logger.ts +50 -0
  42. package/kernel/middlewares.ts +19 -7
  43. package/kernel/modules/create-module.ts +5 -0
  44. package/kernel/modules/system.ts +149 -0
  45. package/kernel/modules/types.ts +46 -0
  46. package/kernel/seeds.ts +48 -0
  47. package/kernel/static.ts +11 -2
  48. package/kernel/testing.ts +8 -3
  49. package/kernel/validator.ts +116 -0
  50. package/modules/events/index.ts +19 -3
  51. package/modules/mail/index.ts +14 -2
  52. package/modules/storage/local-adapter.ts +19 -5
  53. package/modules/ws/index.ts +123 -18
  54. package/package.json +8 -11
  55. package/skills/auth/SKILL.md +36 -220
  56. package/skills/cli/SKILL.md +32 -251
  57. package/skills/config/SKILL.md +30 -239
  58. package/skills/connectors/SKILL.md +32 -295
  59. package/skills/helpers/SKILL.md +26 -195
  60. package/skills/middlewares/SKILL.md +30 -280
  61. package/skills/orm/SKILL.md +42 -349
  62. package/skills/realtime/SKILL.md +22 -297
  63. package/skills/services/SKILL.md +40 -183
  64. package/skills/testing/SKILL.md +34 -266
@@ -1,280 +1,48 @@
1
- # SKILL: Arckode Testing — Patrones, Utilidades y Estructura
1
+ # Testing — Bun Test + Patterns
2
2
 
3
- > Activar cuando: escribir tests, configurar test suite, debuggear tests fallidos, agregar cobertura.
3
+ ## Stack
4
4
 
5
- ---
5
+ `bun test` con `describe`/`it`/`expect` global.
6
6
 
7
- ## 1. SETUP DEL TEST (sin BD real)
7
+ ## Ejecución
8
8
 
9
- ```ts
10
- import { describe, test, expect, beforeEach, mock } from 'bun:test'
11
- // NOMBRES REALES: silentLogger (factory), createRecordingDb, createTestClient
12
- // createRecordingOrm / createSilentLogger / createNullCache NO existen — nunca usar esos nombres
13
- import { silentLogger, createRecordingDb } from 'arckode-framework/testing'
14
- import type { RepositoryAdapter, CacheAdapter } from 'arckode-framework'
15
- import { ProductosService } from '../service'
16
- import type { ProductoDTO } from '../types'
17
-
18
- // silentLogger es factory function — SIEMPRE llamar con ()
19
- const log = silentLogger()
20
- const silentCache: CacheAdapter = { get: async () => null, set: async () => {}, delete: async () => {}, flush: async () => {} }
21
-
22
- function makeRepo(overrides: Partial<RepositoryAdapter<ProductoDTO>> = {}): RepositoryAdapter<ProductoDTO> {
23
- return {
24
- findMany: async () => [],
25
- findById: async () => null,
26
- findOne: async () => null,
27
- create: async (data) => ({ id: 'test-id', ...data } as ProductoDTO),
28
- update: async (id, data) => ({ id, ...data } as ProductoDTO),
29
- delete: async () => true,
30
- count: async () => 0,
31
- paginate: async () => ({ data: [], total: 0, limit: 20, offset: 0, pages: 0 }),
32
- ...overrides,
33
- }
34
- }
35
-
36
- describe('ProductosService', () => {
37
- let service: ProductosService
38
-
39
- beforeEach(() => {
40
- service = new ProductosService(makeRepo(), log, silentCache)
41
- })
42
- })
43
- ```
44
-
45
- **Regla de oro:** Testear el service, no el ORM. El ORM ya tiene sus propios tests.
46
-
47
- ---
48
-
49
- ## 2. UTILIDADES DE TESTING
50
-
51
- ```ts
52
- // Exports REALES del módulo arckode-framework/testing:
53
- import {
54
- silentLogger, // factory: silentLogger() → Logger (NO usarla sin ())
55
- createRecordingDb, // captura SQL sin ejecutar BD real → db.calls, db.lastSql()
56
- createTestClient, // cliente HTTP sin levantar servidor
57
- createIntegrationClient, // cliente HTTP con servidor real (puerto aleatorio)
58
- mockAuth, // middleware mock para simular autenticación
59
- } from 'arckode-framework/testing'
60
-
61
- // ❌ ESTOS NO EXISTEN — nunca usar:
62
- // createRecordingOrm, createSilentLogger, createNullCache
63
-
64
- // silentLogger — factory, siempre con ()
65
- const log = silentLogger()
66
-
67
- // createRecordingDb — captura SQL sin ejecutar nada
68
- const db = createRecordingDb({ queryData: [{ id: '1', nombre: 'Laptop' }] })
69
- // db.calls → array de { sql, params, type }
70
- // db.lastSql() → último SQL ejecutado
71
- // db.reset() → limpia el historial
72
-
73
- // TestClient — router real, sin HTTP
74
- const client = createTestClient(router)
75
- const res = await client.get('/productos')
76
- expect(res.status).toBe(200)
77
- ```
78
-
79
- ---
80
-
81
- ## 3. ESTRUCTURA MÍNIMA OBLIGATORIA
82
-
83
- Cada módulo necesita al menos 2 casos:
84
-
85
- ```ts
86
- describe('ProductosService', () => {
87
-
88
- // CASO 1: Happy path
89
- test('listar retorna array (vacío si no hay datos)', async () => {
90
- const result = await service.listar()
91
- expect(Array.isArray(result)).toBe(true)
92
- })
93
-
94
- // CASO 2: Error esperado (validación, not found, conflict, etc.)
95
- test('crear lanza error si el nombre está vacío', async () => {
96
- await expect(service.crear({ nombre: '', precio: 100 }))
97
- .rejects.toThrow()
98
- })
99
-
100
- // CASO 3 (recomendado): IDOR / autorización
101
- test('getById lanza NotFoundError si no existe', async () => {
102
- await expect(service.getById('id-inexistente', { id: 'u1', role: 'user' }))
103
- .rejects.toThrow('no encontrado')
104
- })
105
- })
106
- ```
107
-
108
- **`arckode analyze` detecta:** `TESTS_WITHOUT_CASES` — archivo test sin ningún `test()`.
109
-
110
- ---
111
-
112
- ## 4. MOCKEAR DEPENDENCIAS EXTERNAS
113
-
114
- ```ts
115
- import { mock } from 'bun:test'
116
-
117
- // Mockear el mail service en tests que lo usan
118
- const mockMail = {
119
- send: mock(() => Promise.resolve()),
120
- }
121
-
122
- const service = new AuthService(repo, auth, mockMail as any)
123
-
124
- test('registro envía email de bienvenida', async () => {
125
- await service.registrar({ email: 'test@test.com', password: '12345678' })
126
- expect(mockMail.send).toHaveBeenCalledTimes(1)
127
- expect(mockMail.send).toHaveBeenCalledWith(
128
- expect.objectContaining({ to: 'test@test.com' })
129
- )
130
- })
131
- ```
9
+ | Comando | Scope |
10
+ |---------|-------|
11
+ | `bun test` | All |
12
+ | `bun test tests/modules/users.test.ts` | Archivo |
13
+ | `bun test --filter="users"` | Match |
14
+ | `bun run typecheck` | tsc --noEmit |
132
15
 
133
- ---
16
+ ## Reglas
134
17
 
135
- ## 5. INTEGRATION TESTS (router + controller)
18
+ 1. Test por módulo: `tests/modules/{modulo}.test.ts`
19
+ 2. Service test: mock RepositoryAdapter, NUNCA ORM real
20
+ 3. Controller test: mock Service, solo valida HTTP contract
21
+ 4. Integration test: DB real con `beforeAll` setup + `afterAll` cleanup
22
+ 5. NO testear helpers ni config (salvo lógica crítica)
23
+ 6. Nombre: `UserService.create → crea usuario con datos válidos`
136
24
 
137
- ```ts
138
- import { Router } from 'arckode-framework'
139
- import { createTestClient } from 'arckode-framework/testing'
140
-
141
- describe('Productos API', () => {
142
- let client: ReturnType<typeof createTestClient>
143
-
144
- beforeEach(() => {
145
- const router = new Router()
146
- // montar el módulo de prueba
147
- const service = new ProductosService(makeRepo(), log, silentCache)
148
- const controller = new ProductosController(service)
149
-
150
- router.get('/productos', req => controller.index(req))
151
- router.post('/productos', req => controller.store(req))
152
- router.delete('/productos/:id', req => controller.destroy(req))
153
-
154
- client = createTestClient(router)
155
- })
156
-
157
- test('GET /productos → 200', async () => {
158
- const res = await client.get('/productos')
159
- expect(res.status).toBe(200)
160
- expect(Array.isArray(res.body)).toBe(true)
161
- })
162
-
163
- test('POST /productos sin nombre → 400', async () => {
164
- const res = await client.post('/productos', { body: { precio: 100 } })
165
- expect(res.status).toBe(400)
166
- expect(res.body).toHaveProperty('errors')
167
- })
168
-
169
- test('POST /productos con datos válidos → 201', async () => {
170
- const res = await client.post('/productos', {
171
- body: { nombre: 'Zapato', precio: 999 }
172
- })
173
- expect(res.status).toBe(201)
174
- expect(res.body).toHaveProperty('id')
175
- })
176
- })
177
- ```
178
-
179
- ---
180
-
181
- ## 6. TESTEAR AUTH (con middleware)
182
-
183
- ```ts
184
- import { Auth } from 'arckode-framework'
185
- import { jwtTokenAdapter } from 'arckode-framework/adapters/jwt'
186
-
187
- const auth = new Auth(jwtTokenAdapter, 'test-secret-12345678901234567890', silentLogger())
188
- const token = auth.createToken({ id: 'user-1', role: 'user' })
189
- const adminToken = auth.createToken({ id: 'admin-1', role: 'admin' })
190
-
191
- test('GET /pedidos sin token → 401', async () => {
192
- const res = await client.get('/pedidos')
193
- expect(res.status).toBe(401)
194
- })
195
-
196
- test('GET /pedidos con token → 200', async () => {
197
- const res = await client.get('/pedidos', {
198
- headers: { Authorization: `Bearer ${token}` }
199
- })
200
- expect(res.status).toBe(200)
201
- })
202
-
203
- test('DELETE /admin/algo sin admin → 403', async () => {
204
- const res = await client.delete('/admin/algo', {
205
- headers: { Authorization: `Bearer ${token}` } // user, no admin
206
- })
207
- expect(res.status).toBe(403)
208
- })
209
- ```
210
-
211
- ---
212
-
213
- ## 7. CORRER TESTS
214
-
215
- ```bash
216
- # Todos los tests
217
- bun test
218
-
219
- # Tests de un módulo específico
220
- bun test modules/productos
221
-
222
- # Un archivo específico
223
- bun test modules/productos/tests/service.test.ts
224
-
225
- # Con watch (re-corre al cambiar archivos)
226
- bun test --watch
227
-
228
- # Con cobertura
229
- bun test --coverage
230
- ```
231
-
232
- ---
233
-
234
- ## 8. QUÉ NO TESTEAR
235
-
236
- - **Los adapters del framework** (SqliteAdapter, jwtTokenAdapter): ya tienen tests propios
237
- - **El ORM** directamente: testealo via RepositoryAdapter en tu service
238
- - **El router**: testear el controller con `createTestClient`, no el router directamente
239
- - **Casos imposibles**: no testear `if` que nunca puede ser `false` con TypeScript strict
240
-
241
- ---
242
-
243
- ## 9. ERRORES COMUNES EN TESTS
25
+ ## Patrón
244
26
 
245
27
  ```ts
246
- // ❌ Olvidar el await — el test pasa siempre (false positive)
247
- test('lanza error', () => {
248
- expect(service.crear({})).rejects.toThrow() // sin await → siempre pasa
249
- })
250
-
251
- // ✅ Siempre await en expects async
252
- test('lanza error', async () => {
253
- await expect(service.crear({})).rejects.toThrow()
254
- })
28
+ // Arrange
29
+ const mockRepo = new MockRepositoryAdapter<UserDTO>() // implementa RepositoryAdapter<T>
30
+ mockRepo.setData(mockUsers)
31
+ const service = new UsersService(mockRepo)
255
32
 
256
- // ❌ Describir implementación en lugar de comportamiento
257
- test('llama a repo.create con los datos correctos', ...)
33
+ // Act
34
+ const result = await service.findById('1')
258
35
 
259
- // ✅ Describir el comportamiento observable
260
- test('crear retorna el producto con id asignado', ...)
261
-
262
- // ❌ Test que testea el ORM en lugar del service
263
- test('ORM recibe query INSERT', async () => {
264
- const orm = createRecordingOrm()
265
- await service.crear(...)
266
- expect(orm.getRecordedQueries()[0]).toContain('INSERT') // testear el framework, no tu código
267
- })
36
+ // Assert
37
+ expect(result).toEqual(mockUsers[0])
268
38
  ```
269
39
 
270
- ---
271
-
272
- ## 10. CHECKLIST TESTING
40
+ ## Errores silenciosos
273
41
 
274
- - [ ] Mínimo 2 test cases: happy path + error esperado
275
- - [ ] `silentLogger()` — con paréntesis, es factory function (no objeto)
276
- - [ ] Cache mock inline (`get: async () => null`, etc.) no hay createNullCache
277
- - [ ] `RepositoryAdapter<T>` mock inline con `makeRepo()` no hay createRecordingOrm
278
- - [ ] Todo `expect(...).rejects` tiene `await`
279
- - [ ] Los nombres de test describen comportamiento, no implementación
280
- - [ ] `bun test` pasa sin errores antes del commit
42
+ | Error | Señal | Fix |
43
+ |-------|-------|-----|
44
+ | Service test con ORM real | DB connection en test | Mock RepositoryAdapter |
45
+ | Test sin cleanup | Datos entre tests | `afterEach` reset |
46
+ | Mock sin tipo | `as any` en todo | Usar `MockRepositoryAdapter<T>` |
47
+ | Cobertura baja | <70% branch | `bun run analyze` |
48
+ | `bun test` no encuentra tests | Archivos sin `.test.ts` | Renombrar |