exisjs 0.7.12 → 0.7.14

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 (132) hide show
  1. package/README.md +195 -39
  2. package/dist/cli/commands/run.d.ts.map +1 -1
  3. package/dist/cli/commands/run.js +3 -1
  4. package/dist/cli/commands/run.js.map +1 -1
  5. package/dist/config/types.d.ts +2 -2
  6. package/dist/config/types.d.ts.map +1 -1
  7. package/dist/decorators/params.d.ts +21 -0
  8. package/dist/decorators/params.d.ts.map +1 -1
  9. package/dist/decorators/params.js +21 -1
  10. package/dist/decorators/params.js.map +1 -1
  11. package/dist/di/container.d.ts +15 -6
  12. package/dist/di/container.d.ts.map +1 -1
  13. package/dist/di/container.js +26 -3
  14. package/dist/di/container.js.map +1 -1
  15. package/dist/di/index.d.ts +1 -1
  16. package/dist/di/index.d.ts.map +1 -1
  17. package/dist/di/index.js.map +1 -1
  18. package/dist/di/inject.d.ts +2 -1
  19. package/dist/di/inject.d.ts.map +1 -1
  20. package/dist/di/inject.js +2 -1
  21. package/dist/di/inject.js.map +1 -1
  22. package/dist/docs/ARCHITECTURE.md +130 -78
  23. package/dist/docs/BENCHMARKS.md +4 -4
  24. package/dist/docs/content/README.md +43 -44
  25. package/dist/docs/content/boundaries.mdx +138 -61
  26. package/dist/docs/content/cli/commands.mdx +151 -128
  27. package/dist/docs/content/cli/overview.mdx +61 -47
  28. package/dist/docs/content/cli/usage.mdx +107 -30
  29. package/dist/docs/content/config.mdx +145 -158
  30. package/dist/docs/content/controllers.mdx +193 -464
  31. package/dist/docs/content/cron.mdx +183 -69
  32. package/dist/docs/content/decorators.mdx +449 -154
  33. package/dist/docs/content/errors.mdx +202 -305
  34. package/dist/docs/content/exisjs-mcp.mdx +59 -49
  35. package/dist/docs/content/installation.mdx +243 -157
  36. package/dist/docs/content/introduction.mdx +220 -48
  37. package/dist/docs/content/middleware.mdx +171 -497
  38. package/dist/docs/content/modules.mdx +141 -83
  39. package/dist/docs/content/providers.mdx +216 -180
  40. package/dist/docs/content/requests.mdx +168 -488
  41. package/dist/docs/content/responses.mdx +200 -533
  42. package/dist/docs/content/routing.mdx +235 -598
  43. package/dist/docs/content/security.mdx +233 -57
  44. package/dist/docs/content/structure.mdx +151 -240
  45. package/dist/docs/content/swagger.mdx +192 -91
  46. package/dist/docs/content/testing.mdx +205 -74
  47. package/dist/docs/content/validation.mdx +479 -341
  48. package/dist/docs/reference/README.md +34 -2
  49. package/dist/docs/reference/config.mdx +277 -75
  50. package/dist/docs/reference/context.mdx +197 -106
  51. package/dist/docs/reference/cron.mdx +266 -152
  52. package/dist/docs/reference/database.mdx +135 -53
  53. package/dist/docs/reference/decorators.mdx +234 -116
  54. package/dist/docs/reference/di.mdx +146 -112
  55. package/dist/docs/reference/error.mdx +155 -178
  56. package/dist/docs/reference/exis.mdx +141 -70
  57. package/dist/docs/reference/logger.mdx +110 -126
  58. package/dist/docs/reference/middleware.mdx +235 -158
  59. package/dist/docs/reference/plugin.mdx +136 -64
  60. package/dist/docs/reference/router.mdx +275 -293
  61. package/dist/docs/reference/sanitize.mdx +91 -87
  62. package/dist/docs/reference/swagger.mdx +280 -100
  63. package/dist/docs/reference/testing.mdx +223 -89
  64. package/dist/docs/reference/validator.mdx +194 -206
  65. package/dist/lib/start-server.js +22 -8
  66. package/dist/lib/start-server.js.map +1 -1
  67. package/dist/module/module.d.ts +6 -3
  68. package/dist/module/module.d.ts.map +1 -1
  69. package/dist/module/module.js +17 -3
  70. package/dist/module/module.js.map +1 -1
  71. package/dist/router/controller-registrar.d.ts.map +1 -1
  72. package/dist/router/controller-registrar.js +26 -4
  73. package/dist/router/controller-registrar.js.map +1 -1
  74. package/dist/router/scanner/fs-scanner.d.ts.map +1 -1
  75. package/dist/router/scanner/fs-scanner.js +22 -0
  76. package/dist/router/scanner/fs-scanner.js.map +1 -1
  77. package/dist/router/types.d.ts +6 -4
  78. package/dist/router/types.d.ts.map +1 -1
  79. package/dist/server/app.d.ts +3 -1
  80. package/dist/server/app.d.ts.map +1 -1
  81. package/dist/server/app.js +24 -8
  82. package/dist/server/app.js.map +1 -1
  83. package/dist/server/bootstrapper.d.ts.map +1 -1
  84. package/dist/server/bootstrapper.js +19 -0
  85. package/dist/server/bootstrapper.js.map +1 -1
  86. package/dist/server/request.d.ts +2 -0
  87. package/dist/server/request.d.ts.map +1 -1
  88. package/dist/server/request.js +11 -0
  89. package/dist/server/request.js.map +1 -1
  90. package/dist/server/response.d.ts +10 -5
  91. package/dist/server/response.d.ts.map +1 -1
  92. package/dist/server/response.js +65 -18
  93. package/dist/server/response.js.map +1 -1
  94. package/dist/swagger/generator.d.ts.map +1 -1
  95. package/dist/swagger/generator.js +130 -10
  96. package/dist/swagger/generator.js.map +1 -1
  97. package/dist/telemetry/index.d.ts +12 -0
  98. package/dist/telemetry/index.d.ts.map +1 -0
  99. package/dist/telemetry/index.js +121 -0
  100. package/dist/telemetry/index.js.map +1 -0
  101. package/dist/testing/client.d.ts +1 -0
  102. package/dist/testing/client.d.ts.map +1 -1
  103. package/dist/testing/client.js +4 -1
  104. package/dist/testing/client.js.map +1 -1
  105. package/dist/testing/index.d.ts.map +1 -1
  106. package/dist/testing/index.js +11 -4
  107. package/dist/testing/index.js.map +1 -1
  108. package/dist/validator/builder.d.ts +26 -1
  109. package/dist/validator/builder.d.ts.map +1 -1
  110. package/dist/validator/builder.js +31 -0
  111. package/dist/validator/builder.js.map +1 -1
  112. package/dist/validator/engine.d.ts +23 -0
  113. package/dist/validator/engine.d.ts.map +1 -1
  114. package/dist/validator/engine.js +228 -31
  115. package/dist/validator/engine.js.map +1 -1
  116. package/dist/validator/index.d.ts +8 -4
  117. package/dist/validator/index.d.ts.map +1 -1
  118. package/dist/validator/index.js +4 -1
  119. package/dist/validator/index.js.map +1 -1
  120. package/dist/validator/tex-types.d.ts +19 -4
  121. package/dist/validator/tex-types.d.ts.map +1 -1
  122. package/dist/validator/tex-types.js +102 -0
  123. package/dist/validator/tex-types.js.map +1 -1
  124. package/dist/validator/tex.d.ts +1 -1
  125. package/dist/validator/tex.d.ts.map +1 -1
  126. package/dist/validator/tex.js +3 -1
  127. package/dist/validator/tex.js.map +1 -1
  128. package/dist/validator/types.d.ts +1 -0
  129. package/dist/validator/types.d.ts.map +1 -1
  130. package/package.json +46 -14
  131. package/dist/docs/content/dataloader.mdx +0 -170
  132. package/dist/docs/reference/dataloader.mdx +0 -91
@@ -1,139 +1,270 @@
1
1
  ---
2
- nextUrl: "/docs/adapters"
3
- nextTitle: "Adapters"
2
+ title: "Testing"
3
+ description: "End-to-end HTTP integration testing, mocking, and unit test runner in ExisJS using Node.js native test runner."
4
+ nextUrl: "/docs/config"
5
+ nextTitle: "Configuration"
4
6
  ---
5
7
 
6
8
  # Testing
7
9
 
8
- ExisJS was built with testing as a first-class citizen. Instead of forcing you to configure bulky external test runners like Jest or Vitest, ExisJS natively wraps the highly optimized, built-in **Node.js Test Runner** (`node:test`).
10
+ ExisJS provides an integrated testing suite built on top of the native **Node.js Test Runner** (`node:test`) and TypeScript execution environment (`tsx`). You can execute unit tests, mock dependencies, and perform full-pipeline HTTP integration testing with zero external test runners.
9
11
 
10
- This means you get highly optimized, integrated testing out-of-the-box.
12
+ ---
13
+
14
+ ## The Testing Module (`exisjs/testing`)
15
+
16
+ The `exisjs/testing` module re-exports test runner primitives, assertions, mocking helpers, and the HTTP testing harness:
17
+
18
+ | Export | Source / Role | Description |
19
+ | :--- | :--- | :--- |
20
+ | `describe`, `test`, `it` | `node:test` | Test suite and test case definition functions with `.only`, `.skip`, `.todo`, and `.each`. |
21
+ | `before`, `after`, `beforeEach`, `afterEach` | `node:test` | Lifecycle execution hooks. |
22
+ | `expect` | ExisJS matcher engine | Jest-compatible fluent assertion matchers (`toBe`, `toEqual`, `toMatchObject`, `toThrow`, etc.). |
23
+ | `createTestContext(app)` | ExisJS HTTP harness | Boots an Exis application instance, sets up lifecycle hooks, and returns a fluent `TestApp` client. |
24
+ | `createTestApp(app)` | ExisJS HTTP harness | Wraps an existing `App` or `ExisAppDefinition` in a `TestApp` client without global test hooks. |
25
+ | `createMockRequest(opts)` | ExisJS mock helper | Creates a synthetic `ExisRequest` for unit-testing controllers or middlewares. |
26
+ | `createMockResponse()` | ExisJS mock helper | Creates a synthetic `ExisResponse` capturing status codes and output buffers. |
27
+ | `ex` | ExisJS mocking suite | Mock functions (`ex.fn`), method spies (`ex.spyOn`), module mocks (`ex.mock`), and fake timers (`ex.useFakeTimers`). |
28
+ | `registerCleanup(fn)` | ExisJS cleanup | Registers a teardown callback invoked after test context completion. |
11
29
 
12
30
  ---
13
31
 
14
- ## The Testing Module
32
+ ## Integration Testing with `createTestContext`
15
33
 
16
- ExisJS exports everything you need for testing directly from `exisjs/testing`. This includes the test runner functions (`test`, `describe`, `it`, `before`), assertions (`assert`), and the powerful ExisJS `createTestContext`!
34
+ `createTestContext` initializes your application definition, executes `onStart` hooks, establishes database connections (Mongoose, Prisma, or DatabaseManager), and tears down connections gracefully in `afterAll`.
17
35
 
18
- ### Creating a Test Context
36
+ ### Functional Paradigm Application Test
19
37
 
20
- When testing an API, you want to boot the app, run HTTP requests against it, and then gracefully shut it down (closing database connections so the test runner doesn't hang). `createTestContext` does exactly this for you automatically! It seamlessly handles both functional `exis({ ... })` applications and Object-Oriented `@Server` decorated classes.
38
+ Given a functional application defined in `src/http/server.ts`:
21
39
 
22
40
  ```typescript filename="tests/users.test.ts"
23
41
  import { describe, it, expect, createTestContext } from 'exisjs/testing'
24
- import app from '../src/http/server' // Import your Exis instance or @Server class
42
+ import app from '../src/http/server'
25
43
 
26
- describe('Users API', () => {
27
- // Automatically boots the app, runs `onStart`, and gracefully shuts down databases after tests!
44
+ describe('Users API Integration', () => {
45
+ // Automatically boots the app and manages database lifecycle
28
46
  const api = createTestContext(app)
29
47
 
30
- it('should create a new user', async () => {
31
- // `api` exposes a fast, fluid HTTP client
48
+ it('should create a new user record', async () => {
32
49
  const res = await api
33
50
  .post('/users')
34
- .send({ name: 'Alice', email: 'alice@example.com' })
35
- .execute()
36
-
37
- // Integrated Jest-like expectations
38
- expect(res.status).toBe(201)
39
- expect(res.body.success).toBe(true)
40
- expect(res.body.data.name).toBe('Alice')
51
+ .set('authorization', 'Bearer test-token')
52
+ .send({
53
+ name: 'Jane Doe',
54
+ email: 'jane@example.com'
55
+ })
56
+ .expect(201)
57
+
58
+ expect(res.body).toMatchObject({
59
+ success: true,
60
+ data: {
61
+ id: expect.any(String),
62
+ name: 'Jane Doe',
63
+ email: 'jane@example.com'
64
+ }
65
+ })
41
66
  })
42
67
 
43
- it('should return 400 on invalid input', async () => {
68
+ it('should return 400 when payload validation fails', async () => {
44
69
  const res = await api
45
70
  .post('/users')
46
- .send({ name: 'Bob' }) // Missing email
47
- .execute()
71
+ .send({ name: 'Jane Doe' }) // missing email
48
72
 
49
73
  expect(res.status).toBe(400)
50
74
  expect(res.body.success).toBe(false)
75
+ expect(res.body.error).toBe('Validation failed')
51
76
  })
52
77
  })
53
78
  ```
54
79
 
55
- ### Running Tests
80
+ ### Class-Based (OOP) Application Test
56
81
 
57
- Because ExisJS wraps the native Node.js test runner in its powerful CLI, you can run all your tests simply using the standard Exis command!
82
+ `createTestContext` natively supports `@Server` decorated entry classes:
58
83
 
59
- ```bash
60
- npx exisjs test
84
+ ```typescript filename="tests/server.test.ts"
85
+ import { describe, it, expect, createTestContext } from 'exisjs/testing'
86
+ import ServerApp from '../src/http/server'
87
+
88
+ describe('OOP Server API Integration', () => {
89
+ const api = createTestContext(ServerApp)
90
+
91
+ it('should retrieve status check', async () => {
92
+ const res = await api.get('/health').expect(200)
93
+
94
+ expect(res.body).toEqual({
95
+ status: 'ok',
96
+ uptime: expect.any(Number)
97
+ })
98
+ })
99
+ })
61
100
  ```
62
101
 
63
- Or configure it in your `package.json`:
102
+ ---
64
103
 
65
- ```json
66
- {
67
- "scripts": {
68
- "test": "exisjs test"
69
- }
104
+ ## Fluid Request Builder (`TestRequest`)
105
+
106
+ The client returned by `createTestContext` and `createTestApp` provides request builder methods for standard HTTP verbs:
107
+
108
+ ```typescript
109
+ const res = await api
110
+ .get(path) // .post(path), .put(path), .patch(path), .delete(path), .head(path), .options(path)
111
+ .set('x-api-key', 'secret-key')
112
+ .send({ key: 'value' })
113
+ .expect(200)
114
+ .expect('content-type', /json/)
115
+ .execute()
116
+ ```
117
+
118
+ ### Response Object Properties
119
+
120
+ The returned `TestResponse` contains:
121
+
122
+ ```typescript
123
+ export interface TestResponse {
124
+ status: number // HTTP response status code (e.g. 200, 404)
125
+ headers: http.IncomingHttpHeaders // Normalized response headers object
126
+ body: any // Parsed JSON payload or raw buffer/string
127
+ text: string // Raw response string representation
70
128
  }
71
129
  ```
72
130
 
73
- The `exisjs test` command automatically bootstraps your environment, loads your environment variables (`.env`), registers TypeScript (`tsx`), and recursively finds and executes all test files matching standard testing conventions (`*.test.ts`, `*.spec.ts`, etc.) regardless of where they are placed.
131
+ ---
132
+
133
+ ## Unit Testing Controllers & Middlewares
134
+
135
+ For fast unit tests that do not require booting a full HTTP server pipeline, use `createMockRequest` and `createMockResponse`:
136
+
137
+ ```typescript filename="tests/unit/guard.test.ts"
138
+ import { describe, it, expect } from 'exisjs/testing'
139
+ import { createMockRequest, createMockResponse, createMockNext } from 'exisjs/testing'
140
+ import { authGuard } from '../../src/common/guards/auth'
141
+
142
+ describe('Auth Guard Unit Test', () => {
143
+ it('should reject requests missing Authorization header', async () => {
144
+ const req = createMockRequest({
145
+ method: 'GET',
146
+ url: '/protected',
147
+ headers: {}
148
+ })
149
+ const res = createMockResponse()
150
+ const next = createMockNext()
151
+
152
+ await authGuard(req, res, next)
153
+
154
+ expect(res._statusCode).toBe(401)
155
+ expect(next.called).toBe(false)
156
+ })
74
157
 
75
- ### Test Configuration
158
+ it('should proceed when Authorization header is present', async () => {
159
+ const req = createMockRequest({
160
+ method: 'GET',
161
+ url: '/protected',
162
+ headers: {
163
+ authorization: 'Bearer valid-jwt-token'
164
+ }
165
+ })
166
+ const res = createMockResponse()
167
+ const next = createMockNext()
76
168
 
77
- ExisJS allows you to configure your testing environment directly. By default, it will use your project's TypeScript configuration, but you can override this by creating a specific `tsconfig.test.json` file in your project root. The test runner will automatically detect and prioritize it when running tests!
169
+ await authGuard(req, res, next)
170
+
171
+ expect(next.called).toBe(true)
172
+ })
173
+ })
174
+ ```
78
175
 
79
176
  ---
80
177
 
81
- ## Mocking (Dependency Injection)
178
+ ## Mocking & Spies (`ex`)
82
179
 
83
- If you are using ExisJS's built-in **Dependency Injection (DI)** or **Gateways**, mocking is incredibly easy. You don't need magic `jest.mock()` intercepts. You can simply override the providers in your test!
180
+ ExisJS includes a built-in mocking engine via `ex`:
84
181
 
85
- ```typescript filename="tests/billing.test.ts"
86
- import { describe, it, expect, createTestContext } from 'exisjs/testing'
87
- import app from '../src/http/server'
88
- import { BillingGateway } from '../src/http/billing/gateway'
182
+ ```typescript filename="tests/service.test.ts"
183
+ import { describe, it, expect, ex } from 'exisjs/testing'
184
+ import { PaymentService } from '../src/http/billing/service'
185
+
186
+ describe('PaymentService Unit Test', () => {
187
+ it('should record transactions through payment gateway', async () => {
188
+ const mockGateway = {
189
+ charge: ex.fn().mockResolvedValue({ id: 'tx_123', status: 'paid' })
190
+ }
191
+
192
+ const service = new PaymentService(mockGateway as any)
193
+ const result = await service.processOrder('order_456', 5000)
89
194
 
90
- describe('Billing API', () => {
91
-
92
- // Override the StripeService strictly for testing!
93
- BillingGateway.overrideProvider('StripeService', {
94
- charge: async (amount: number) => ({ success: true, transactionId: 'test_123' })
195
+ expect(mockGateway.charge).toHaveBeenCalledTimes(1)
196
+ expect(mockGateway.charge).toHaveBeenCalledWith(5000)
197
+ expect(result.id).toBe('tx_123')
95
198
  })
199
+ })
200
+ ```
96
201
 
97
- const api = createTestContext(app)
202
+ ### Fake Timers
98
203
 
99
- it('should process payment', async () => {
100
- const res = await api
101
- .post('/billing/charge')
102
- .send({ amount: 500 })
103
- .execute()
204
+ ```typescript
205
+ import { describe, it, expect, ex } from 'exisjs/testing'
104
206
 
105
- expect(res.status).toBe(200)
106
- expect(res.body.transactionId).toBe('test_123')
207
+ describe('Timer operations', () => {
208
+ it('advances timer controlled tasks', () => {
209
+ ex.useFakeTimers()
210
+ const spy = ex.fn()
211
+
212
+ setTimeout(spy, 10_000)
213
+
214
+ ex.advanceTimersByTime(10_000)
215
+ expect(spy).toHaveBeenCalledTimes(1)
216
+
217
+ ex.useRealTimers()
107
218
  })
108
219
  })
109
220
  ```
110
221
 
111
222
  ---
112
223
 
113
- ## Test Isolation
224
+ ## Running Tests via CLI
114
225
 
115
- One of the most common pitfalls when testing backend frameworks is **test bleed**—where the state from one test unexpectedly affects the next. ExisJS provides the tools to prevent this, but you must be intentional about state isolation.
226
+ Run your test suites with the built-in `exisjs test` command:
116
227
 
117
- ### 1. Dependency Injection Cleanup
118
- If you rely heavily on singletons in the ExisJS Dependency Injection container, you may want to reset the container's state between tests.
228
+ ```bash
229
+ # Run all test files matching tests/**/*.test.ts or tests/**/*.spec.ts
230
+ npx exisjs test
231
+
232
+ # Run a specific test file
233
+ npx exisjs test tests/users.test.ts
234
+
235
+ # Run tests in watch mode
236
+ npx exisjs test --watch
237
+
238
+ # Run with test coverage
239
+ npx exisjs test --coverage
240
+ ```
241
+
242
+ ### `package.json` Configuration
119
243
 
120
- ```typescript filename="tests/setup.ts"
121
- import { beforeEach } from 'exisjs/testing'
122
- import { getActiveApp } from 'exisjs/app'
244
+ Add the test command to your scripts:
123
245
 
124
- beforeEach(() => {
125
- const app = getActiveApp()
126
- if (app) {
127
- app.container.clearCache() // Purges all instantiated singletons
246
+ ```json filename="package.json"
247
+ {
248
+ "scripts": {
249
+ "test": "exisjs test",
250
+ "test:watch": "exisjs test --watch"
128
251
  }
129
- })
252
+ }
130
253
  ```
131
254
 
132
- ### 2. Zero-Config Singletons (Redis, Prisma, etc.)
133
- ExisJS provides zero-config integrations (`redis.ts`, `prisma.ts`, etc.) which act as **process-wide singletons**.
134
- Because they use proxy-based caching at the module level, running multiple test suites concurrently in the same Node.js process means they will all share the **exact same database connections and state**.
255
+ ### Test Configuration Options (`exis.config.ts`)
256
+
257
+ You can customize test runner behaviors in `exis.config.ts`:
258
+
259
+ ```typescript filename="exis.config.ts"
260
+ import { defineConfig } from 'exisjs/config'
135
261
 
136
- If your tests write to the database or queue, ensure you either:
137
- 1. Run your test runner in a mode that isolates processes per file.
138
- 2. Manually clear database tables or flush Redis in a `beforeEach` block.
139
- 3. Use database transactions that rollback after each test.
262
+ export default defineConfig({
263
+ test: {
264
+ include: ['tests/**/*.test.ts', 'src/**/*.spec.ts'],
265
+ concurrency: true,
266
+ coverage: false,
267
+ setupFiles: ['./tests/setup.ts']
268
+ }
269
+ })
270
+ ```