@venizia/ignis-docs 0.0.8 → 0.2.0

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 (180) hide show
  1. package/README.md +7 -7
  2. package/content/best-practices/api-usage-examples.md +15 -12
  3. package/content/best-practices/architectural-patterns.md +70 -78
  4. package/content/best-practices/architecture-decisions.md +91 -60
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +56 -44
  6. package/content/best-practices/code-style-standards/constants-configuration.md +11 -11
  7. package/content/best-practices/code-style-standards/control-flow.md +5 -2
  8. package/content/best-practices/code-style-standards/documentation.md +13 -13
  9. package/content/best-practices/code-style-standards/function-patterns.md +9 -10
  10. package/content/best-practices/code-style-standards/index.md +1 -1
  11. package/content/best-practices/code-style-standards/naming-conventions.md +10 -8
  12. package/content/best-practices/code-style-standards/route-definitions.md +30 -12
  13. package/content/best-practices/code-style-standards/tooling.md +8 -5
  14. package/content/best-practices/code-style-standards/type-safety.md +13 -12
  15. package/content/best-practices/common-pitfalls.md +56 -37
  16. package/content/best-practices/contribution-workflow.md +13 -14
  17. package/content/best-practices/data-modeling.md +46 -22
  18. package/content/best-practices/deployment-strategies.md +28 -27
  19. package/content/best-practices/error-handling.md +48 -24
  20. package/content/best-practices/index.md +5 -5
  21. package/content/best-practices/performance-optimization.md +40 -31
  22. package/content/best-practices/security-guidelines.md +52 -23
  23. package/content/best-practices/testing-strategies.md +65 -51
  24. package/content/best-practices/troubleshooting-tips.md +24 -24
  25. package/content/extensions/components/{swagger.md → api-reference.md} +40 -31
  26. package/content/extensions/components/authentication/api.md +19 -19
  27. package/content/extensions/components/authentication/errors.md +7 -7
  28. package/content/extensions/components/authentication/index.md +10 -8
  29. package/content/extensions/components/authentication/usage.md +101 -6
  30. package/content/extensions/components/authorization/api.md +45 -25
  31. package/content/extensions/components/authorization/errors.md +6 -6
  32. package/content/extensions/components/authorization/index.md +11 -10
  33. package/content/extensions/components/authorization/usage.md +21 -21
  34. package/content/extensions/components/health-check.md +1 -1
  35. package/content/extensions/components/index.md +5 -5
  36. package/content/extensions/components/mail/errors.md +15 -15
  37. package/content/extensions/components/mail/index.md +1 -2
  38. package/content/extensions/components/mail/usage.md +1 -1
  39. package/content/extensions/components/request-tracker.md +1 -1
  40. package/content/extensions/components/socket-io/api.md +9 -9
  41. package/content/extensions/components/socket-io/errors.md +5 -5
  42. package/content/extensions/components/socket-io/index.md +8 -8
  43. package/content/extensions/components/socket-io/usage.md +1 -1
  44. package/content/extensions/components/static-asset/api.md +17 -4
  45. package/content/extensions/components/static-asset/errors.md +4 -4
  46. package/content/extensions/components/static-asset/index.md +26 -28
  47. package/content/extensions/components/static-asset/usage.md +13 -12
  48. package/content/extensions/components/template/index.md +2 -2
  49. package/content/extensions/components/template/setup-page.md +1 -1
  50. package/content/extensions/components/websocket/api.md +3 -3
  51. package/content/extensions/components/websocket/errors.md +5 -5
  52. package/content/extensions/components/websocket/index.md +5 -5
  53. package/content/extensions/components/websocket/usage.md +3 -3
  54. package/content/extensions/helpers/cron/index.md +2 -2
  55. package/content/extensions/helpers/crypto/index.md +1 -1
  56. package/content/extensions/helpers/env/index.md +27 -12
  57. package/content/extensions/helpers/error/index.md +81 -25
  58. package/content/extensions/helpers/index.md +2 -3
  59. package/content/extensions/helpers/inversion/index.md +15 -7
  60. package/content/extensions/helpers/kafka/compile-binary.md +92 -0
  61. package/content/extensions/helpers/kafka/examples.md +1 -1
  62. package/content/extensions/helpers/kafka/index.md +3 -0
  63. package/content/extensions/helpers/logger/index.md +32 -2
  64. package/content/extensions/helpers/network/index.md +6 -0
  65. package/content/extensions/helpers/queue/index.md +14 -17
  66. package/content/extensions/helpers/redis/index.md +548 -323
  67. package/content/extensions/helpers/socket-io/index.md +14 -10
  68. package/content/extensions/helpers/storage/api.md +44 -8
  69. package/content/extensions/helpers/storage/index.md +43 -7
  70. package/content/extensions/helpers/template/index.md +6 -3
  71. package/content/extensions/helpers/types/index.md +11 -8
  72. package/content/extensions/helpers/websocket/api.md +9 -9
  73. package/content/extensions/helpers/websocket/index.md +7 -7
  74. package/content/extensions/helpers/worker-thread/index.md +2 -2
  75. package/content/extensions/index.md +3 -4
  76. package/content/extensions/src-details/mcp-server.md +18 -24
  77. package/content/guides/core-concepts/application/bootstrapping.md +11 -14
  78. package/content/guides/core-concepts/application/index.md +3 -3
  79. package/content/guides/core-concepts/components.md +19 -10
  80. package/content/guides/core-concepts/dependency-injection.md +6 -3
  81. package/content/guides/core-concepts/grpc-controllers.md +6 -5
  82. package/content/guides/core-concepts/persistent/datasources.md +42 -43
  83. package/content/guides/core-concepts/persistent/index.md +16 -7
  84. package/content/guides/core-concepts/persistent/models.md +24 -20
  85. package/content/guides/core-concepts/persistent/postgres-drivers.md +201 -0
  86. package/content/guides/core-concepts/persistent/repositories.md +40 -23
  87. package/content/guides/core-concepts/persistent/search-meilisearch.md +185 -0
  88. package/content/guides/core-concepts/persistent/search-typesense.md +431 -0
  89. package/content/guides/core-concepts/persistent/transactions.md +61 -25
  90. package/content/guides/core-concepts/rest-controllers.md +12 -9
  91. package/content/guides/core-concepts/services.md +330 -60
  92. package/content/guides/get-started/5-minute-quickstart.md +15 -15
  93. package/content/guides/get-started/philosophy.md +36 -36
  94. package/content/guides/get-started/setup.md +3 -3
  95. package/content/guides/index.md +3 -3
  96. package/content/guides/migrations/redis-helpers-migration.md +177 -0
  97. package/content/guides/migrations/scoped-rbac-migration.md +17 -17
  98. package/content/guides/migrations/unified-connectors-migration.md +113 -0
  99. package/content/guides/reference/glossary.md +19 -12
  100. package/content/guides/reference/mcp-docs-server.md +22 -18
  101. package/content/guides/tutorials/building-a-crud-api.md +37 -44
  102. package/content/guides/tutorials/complete-installation.md +17 -17
  103. package/content/guides/tutorials/ecommerce-api.md +163 -124
  104. package/content/guides/tutorials/realtime-chat.md +181 -135
  105. package/content/guides/tutorials/testing.md +65 -523
  106. package/content/index.md +2 -180
  107. package/content/public/apple-touch-icon.png +0 -0
  108. package/content/public/og-image.png +0 -0
  109. package/content/public/site.webmanifest +11 -0
  110. package/content/references/base/application.md +4 -5
  111. package/content/references/base/bootstrapping.md +18 -5
  112. package/content/references/base/components.md +149 -120
  113. package/content/references/base/connectors.md +178 -0
  114. package/content/references/base/controllers.md +41 -30
  115. package/content/references/base/datasources.md +163 -92
  116. package/content/references/base/dependency-injection.md +34 -22
  117. package/content/references/base/filter-system/application-usage.md +17 -14
  118. package/content/references/base/filter-system/array-operators.md +7 -2
  119. package/content/references/base/filter-system/comparison-operators.md +3 -0
  120. package/content/references/base/filter-system/default-filter.md +89 -71
  121. package/content/references/base/filter-system/fields-order-pagination.md +22 -22
  122. package/content/references/base/filter-system/index.md +6 -3
  123. package/content/references/base/filter-system/json-filtering.md +20 -1
  124. package/content/references/base/filter-system/list-operators.md +1 -1
  125. package/content/references/base/filter-system/logical-operators.md +33 -1
  126. package/content/references/base/filter-system/null-operators.md +30 -1
  127. package/content/references/base/filter-system/quick-reference.md +23 -4
  128. package/content/references/base/filter-system/tips.md +5 -5
  129. package/content/references/base/filter-system/use-cases.md +12 -12
  130. package/content/references/base/grpc-controllers.md +13 -13
  131. package/content/references/base/index.md +24 -12
  132. package/content/references/base/middlewares.md +265 -327
  133. package/content/references/base/models.md +63 -49
  134. package/content/references/base/providers.md +136 -130
  135. package/content/references/base/repositories/advanced.md +59 -58
  136. package/content/references/base/repositories/index.md +115 -91
  137. package/content/references/base/repositories/mixins.md +55 -291
  138. package/content/references/base/repositories/relations.md +54 -64
  139. package/content/references/base/repositories/soft-deletable.md +31 -30
  140. package/content/references/base/services.md +296 -93
  141. package/content/references/configuration/environment-variables.md +49 -31
  142. package/content/references/configuration/index.md +6 -6
  143. package/content/references/index.md +17 -12
  144. package/content/references/quick-reference.md +65 -106
  145. package/content/references/utilities/crypto.md +65 -23
  146. package/content/references/utilities/index.md +3 -3
  147. package/content/references/utilities/jsx.md +6 -4
  148. package/content/references/utilities/module.md +68 -20
  149. package/content/references/utilities/parse.md +4 -14
  150. package/content/references/utilities/promise.md +9 -7
  151. package/content/references/utilities/schema.md +5 -3
  152. package/dist/mcp-server/common/guards.d.ts +8 -0
  153. package/dist/mcp-server/common/guards.d.ts.map +1 -0
  154. package/dist/mcp-server/common/guards.js +14 -0
  155. package/dist/mcp-server/common/guards.js.map +1 -0
  156. package/dist/mcp-server/common/index.d.ts +1 -0
  157. package/dist/mcp-server/common/index.d.ts.map +1 -1
  158. package/dist/mcp-server/common/index.js +1 -0
  159. package/dist/mcp-server/common/index.js.map +1 -1
  160. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  161. package/dist/mcp-server/helpers/docs.helper.js +4 -2
  162. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  163. package/dist/mcp-server/helpers/github.helper.js +1 -1
  164. package/dist/mcp-server/index.js +7 -2
  165. package/dist/mcp-server/index.js.map +1 -1
  166. package/dist/mcp-server/tools/base.tool.d.ts +6 -2
  167. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  168. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  169. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  171. package/dist/mcp-server/tools/github/search-code.tool.d.ts +1 -1
  172. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  173. package/dist/mcp-server/tools/github/search-code.tool.js +4 -1
  174. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  175. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  176. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +3 -1
  177. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  178. package/package.json +9 -9
  179. package/content/extensions/helpers/testing/index.md +0 -510
  180. package/content/references/base/middleware.md +0 -347
@@ -1,12 +1,12 @@
1
- # Testing Your Ignis Application
1
+ # Testing Your IGNIS Application
2
2
 
3
- This guide shows you how to write tests for your Ignis application.
3
+ This guide shows you how to write tests for your IGNIS application.
4
4
 
5
5
  **Time to Complete:** ~30 minutes
6
6
 
7
7
  ## Choose Your Test Framework
8
8
 
9
- **Ignis works with any test framework.** You can use whichever testing tool you prefer:
9
+ **IGNIS works with any test framework.** You can use whichever testing tool you prefer:
10
10
 
11
11
  | Framework | Description |
12
12
  |-----------|-------------|
@@ -16,47 +16,67 @@ This guide shows you how to write tests for your Ignis application.
16
16
  | **Playwright** | End-to-end testing for web applications |
17
17
  | **node:test** | Node.js native test module |
18
18
  | **Mocha** | Flexible testing framework |
19
- | **Any other** | All test frameworks work with Ignis |
19
+ | **Any other** | All test frameworks work with IGNIS |
20
20
 
21
- Since Ignis is just a TypeScript/JavaScript application framework, you can test it with any tool that supports TypeScript.
21
+ Since IGNIS is just a TypeScript/JavaScript application framework, you can test it with any tool that supports TypeScript.
22
22
 
23
23
  > [!TIP] IGNIS Testing Extension
24
- > IGNIS provides its own testing utilities built on `node:test`. These utilities (`TestPlan`, `TestCase`, `TestCaseHandler`) offer a structured approach for organizing tests with lifecycle hooks and shared context. This is optional use it if you prefer this pattern, or use your favorite test framework directly.
24
+ > IGNIS does not ship its own test framework - use the runner your project already standardizes on (the framework itself is tested with Bun Test).
25
25
 
26
26
  ## Prerequisites
27
27
 
28
28
  Before starting, ensure you have:
29
- - A working Ignis application (see [Building a CRUD API](./building-a-crud-api.md))
29
+ - A working IGNIS application (see [Building a CRUD API](./building-a-crud-api.md))
30
30
  - Basic understanding of [Controllers](../core-concepts/rest-controllers.md) and [Repositories](../core-concepts/persistent/)
31
31
 
32
32
  ## Quick Examples with Popular Frameworks
33
33
 
34
+ ### Shared Test App Helper
35
+
36
+ `BaseApplication` has no `request()` method of its own, and `getServer()` has no routes mounted on it until `start()` runs `server.route(basePath, rootRouter)`. The router that actually carries your bound controllers is `getRootRouter()` (an `OpenAPIHono` instance, which has Hono's in-process `request()` testing helper). Start the real application once and reuse it across test files:
37
+
38
+ ```typescript
39
+ // __tests__/helpers/test-app.ts
40
+ import { Application, appConfigs } from '../../src/application';
41
+
42
+ export const testApp = new Application({ scope: 'TestApp', config: appConfigs });
43
+
44
+ // getRootRouter() carries the bound controllers - request() exercises them in-process,
45
+ // with no network socket involved.
46
+ export const testServer = () => testApp.getRootRouter();
47
+ ```
48
+
34
49
  ### Using Vitest
35
50
 
36
51
  ```typescript
37
52
  // __tests__/todo.test.ts
38
53
  import { describe, it, expect, beforeAll, afterAll } from 'vitest';
39
- import { app } from '../src/application';
54
+ import { testApp, testServer } from './helpers/test-app';
40
55
 
41
56
  describe('Todo API', () => {
42
57
  beforeAll(async () => {
43
- // Setup: start server, seed database, etc.
58
+ // start() runs the full lifecycle (preConfigure -> registerDataSources ->
59
+ // registerComponents -> registerControllers) and opens the HTTP server.
60
+ await testApp.start();
44
61
  });
45
62
 
46
63
  afterAll(async () => {
47
- // Cleanup: close connections
64
+ await testApp.stop();
48
65
  });
49
66
 
50
67
  it('should return list of todos', async () => {
51
- const response = await app.request('/api/todos', { method: 'GET' });
68
+ const response = await testServer().request('/api/todos', { method: 'GET' });
52
69
 
53
70
  expect(response.status).toBe(200);
71
+ // ControllerFactory's generated GET / wraps reads in { count, data } by default too
72
+ // (unless the caller sends `x-request-count-data: false`) - unlike the repository API,
73
+ // where find()/findOne()/findById() return rows directly.
54
74
  const body = await response.json();
55
- expect(Array.isArray(body)).toBe(true);
75
+ expect(Array.isArray(body.data)).toBe(true);
56
76
  });
57
77
 
58
78
  it('should create a new todo', async () => {
59
- const response = await app.request('/api/todos', {
79
+ const response = await testServer().request('/api/todos', {
60
80
  method: 'POST',
61
81
  headers: { 'Content-Type': 'application/json' },
62
82
  body: JSON.stringify({ title: 'Test Todo' }),
@@ -64,7 +84,7 @@ describe('Todo API', () => {
64
84
 
65
85
  expect(response.status).toBe(201);
66
86
  const body = await response.json();
67
- expect(body.title).toBe('Test Todo');
87
+ expect(body.data.title).toBe('Test Todo');
68
88
  });
69
89
  });
70
90
  ```
@@ -73,15 +93,23 @@ describe('Todo API', () => {
73
93
 
74
94
  ```typescript
75
95
  // __tests__/todo.test.ts
76
- import { app } from '../src/application';
96
+ import { testApp, testServer } from './helpers/test-app';
77
97
 
78
98
  describe('Todo API', () => {
99
+ beforeAll(async () => {
100
+ await testApp.start();
101
+ });
102
+
103
+ afterAll(async () => {
104
+ await testApp.stop();
105
+ });
106
+
79
107
  it('should return list of todos', async () => {
80
- const response = await app.request('/api/todos', { method: 'GET' });
108
+ const response = await testServer().request('/api/todos', { method: 'GET' });
81
109
 
82
110
  expect(response.status).toBe(200);
83
111
  const body = await response.json();
84
- expect(Array.isArray(body)).toBe(true);
112
+ expect(Array.isArray(body.data)).toBe(true);
85
113
  });
86
114
  });
87
115
  ```
@@ -90,16 +118,24 @@ describe('Todo API', () => {
90
118
 
91
119
  ```typescript
92
120
  // __tests__/todo.test.ts
93
- import { describe, it, expect } from 'bun:test';
94
- import { app } from '../src/application';
121
+ import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
122
+ import { testApp, testServer } from './helpers/test-app';
95
123
 
96
124
  describe('Todo API', () => {
125
+ beforeAll(async () => {
126
+ await testApp.start();
127
+ });
128
+
129
+ afterAll(async () => {
130
+ await testApp.stop();
131
+ });
132
+
97
133
  it('should return list of todos', async () => {
98
- const response = await app.request('/api/todos', { method: 'GET' });
134
+ const response = await testServer().request('/api/todos', { method: 'GET' });
99
135
 
100
136
  expect(response.status).toBe(200);
101
137
  const body = await response.json();
102
- expect(Array.isArray(body)).toBe(true);
138
+ expect(Array.isArray(body.data)).toBe(true);
103
139
  });
104
140
  });
105
141
  ```
@@ -112,459 +148,15 @@ import { test, expect } from '@playwright/test';
112
148
 
113
149
  test.describe('Todo Application', () => {
114
150
  test('should display todo list', async ({ request }) => {
115
- const response = await request.get('http://localhost:3000/api/todos');
151
+ const response = await request.get(`http://localhost:3000/api/todos`);
116
152
 
117
153
  expect(response.ok()).toBeTruthy();
118
154
  const todos = await response.json();
119
- expect(Array.isArray(todos)).toBe(true);
155
+ expect(Array.isArray(todos.data)).toBe(true);
120
156
  });
121
157
  });
122
158
  ```
123
159
 
124
- ## Using IGNIS Testing Extension
125
-
126
- IGNIS provides its own testing utilities built on `node:test` for a more structured approach.
127
-
128
- ### 1. Create Your First Test
129
-
130
- Create a test file in your project:
131
-
132
- ```typescript
133
- // __tests__/hello.test.ts
134
- import {
135
- TestPlan,
136
- TestDescribe,
137
- TestCase,
138
- TestCaseHandler,
139
- TestCaseDecisions,
140
- } from '@venizia/ignis-helpers';
141
-
142
- // Step 1: Define a Test Handler
143
- class HelloHandler extends TestCaseHandler {
144
- async execute() {
145
- // The action to test
146
- const message = 'Hello, Ignis!';
147
- return { message };
148
- }
149
-
150
- getValidator() {
151
- // Validate the result
152
- return (result: { message: string }) => {
153
- if (result.message === 'Hello, Ignis!') {
154
- return TestCaseDecisions.SUCCESS;
155
- }
156
- return TestCaseDecisions.FAIL;
157
- };
158
- }
159
- }
160
-
161
- // Step 2: Create a Test Plan
162
- const helloTestPlan = TestPlan.newInstance({
163
- scope: 'Hello World Tests',
164
- testCases: [
165
- TestCase.withOptions({
166
- code: 'HELLO-001',
167
- description: 'Should return greeting message',
168
- expectation: 'Message equals "Hello, Ignis!"',
169
- handler: new HelloHandler({ context: {} as any }),
170
- }),
171
- ],
172
- });
173
-
174
- // Step 3: Run the Test
175
- TestDescribe.withTestPlan({ testPlan: helloTestPlan }).run();
176
- ```
177
-
178
- ### 2. Run Tests
179
-
180
- ```bash
181
- # Using Bun
182
- bun test
183
-
184
- # Using Node.js
185
- node --test __tests__/*.test.ts
186
- ```
187
-
188
- ## Core Concepts
189
-
190
- ### Test Framework Components
191
-
192
- | Component | Purpose |
193
- |-----------|---------|
194
- | **TestPlan** | Organizes a test suite with lifecycle hooks and shared context |
195
- | **TestCase** | A single test unit with code, description, and handler |
196
- | **TestCaseHandler** | Encapsulates test execution and validation logic |
197
- | **TestDescribe** | Runs test plans using `node:test` |
198
-
199
- ### Test Case Decisions
200
-
201
- | Decision | Meaning |
202
- |----------|---------|
203
- | `TestCaseDecisions.SUCCESS` | Test passed |
204
- | `TestCaseDecisions.FAIL` | Test failed |
205
- | `TestCaseDecisions.UNKNOWN` | Result undetermined |
206
-
207
- ### Lifecycle Hooks
208
-
209
- | Hook | When | Use Case |
210
- |------|------|----------|
211
- | `before` | Before all tests | Start server, seed database |
212
- | `after` | After all tests | Close connections, cleanup |
213
- | `beforeEach` | Before each test | Reset state |
214
- | `afterEach` | After each test | Clear test data |
215
-
216
- ## Testing Controllers
217
-
218
- Here's how to test an HTTP controller:
219
-
220
- ```typescript
221
- // __tests__/todo.controller.test.ts
222
- import {
223
- TestPlan,
224
- TestDescribe,
225
- TestCase,
226
- TestCaseHandler,
227
- TestCaseDecisions,
228
- } from '@venizia/ignis-helpers';
229
- import { app } from '../src/application'; // Your Ignis app
230
-
231
- // Handler for testing GET /todos
232
- class GetTodosHandler extends TestCaseHandler {
233
- async execute() {
234
- // Make HTTP request to your app
235
- const response = await app.request('/api/todos', {
236
- method: 'GET',
237
- });
238
-
239
- return {
240
- status: response.status,
241
- body: await response.json(),
242
- };
243
- }
244
-
245
- getValidator() {
246
- return (result: { status: number; body: any }) => {
247
- // Validate status code
248
- if (result.status !== 200) {
249
- return TestCaseDecisions.FAIL;
250
- }
251
-
252
- // Validate response is an array
253
- if (!Array.isArray(result.body)) {
254
- return TestCaseDecisions.FAIL;
255
- }
256
-
257
- return TestCaseDecisions.SUCCESS;
258
- };
259
- }
260
- }
261
-
262
- // Handler for testing POST /todos
263
- class CreateTodoHandler extends TestCaseHandler {
264
- async execute() {
265
- const response = await app.request('/api/todos', {
266
- method: 'POST',
267
- headers: { 'Content-Type': 'application/json' },
268
- body: JSON.stringify({
269
- title: 'Test Todo',
270
- description: 'Created by test',
271
- }),
272
- });
273
-
274
- return {
275
- status: response.status,
276
- body: await response.json(),
277
- };
278
- }
279
-
280
- getValidator() {
281
- return (result: { status: number; body: any }) => {
282
- if (result.status !== 201) {
283
- return TestCaseDecisions.FAIL;
284
- }
285
-
286
- if (result.body.title !== 'Test Todo') {
287
- return TestCaseDecisions.FAIL;
288
- }
289
-
290
- return TestCaseDecisions.SUCCESS;
291
- };
292
- }
293
- }
294
-
295
- // Create test plan
296
- const todoControllerTests = TestPlan.newInstance({
297
- scope: 'Todo Controller',
298
- hooks: {
299
- before: async () => {
300
- console.log('Setting up Todo controller tests...');
301
- // Start server or setup test database
302
- },
303
- after: async () => {
304
- console.log('Cleaning up...');
305
- // Cleanup resources
306
- },
307
- },
308
- testCases: [
309
- TestCase.withOptions({
310
- code: 'TODO-001',
311
- description: 'GET /todos returns list of todos',
312
- expectation: 'Status 200 with array response',
313
- handler: new GetTodosHandler({ context: {} as any }),
314
- }),
315
- TestCase.withOptions({
316
- code: 'TODO-002',
317
- description: 'POST /todos creates a new todo',
318
- expectation: 'Status 201 with created todo',
319
- handler: new CreateTodoHandler({ context: {} as any }),
320
- }),
321
- ],
322
- });
323
-
324
- TestDescribe.withTestPlan({ testPlan: todoControllerTests }).run();
325
- ```
326
-
327
- ## Testing with Shared Context
328
-
329
- Use the test plan's context to share data between tests (like authentication tokens):
330
-
331
- ```typescript
332
- // __tests__/auth.test.ts
333
- import {
334
- TestPlan,
335
- TestDescribe,
336
- TestCase,
337
- TestCaseHandler,
338
- TestCaseDecisions,
339
- ITestContext,
340
- } from '@venizia/ignis-helpers';
341
-
342
- // Define context shape
343
- interface AuthContext {
344
- token: string;
345
- userId: string;
346
- }
347
-
348
- // Handler that uses shared context
349
- class SecureEndpointHandler extends TestCaseHandler<AuthContext> {
350
- async execute() {
351
- // Get token from context (set in before hook)
352
- const token = this.context.getSync<string>({ key: 'token' });
353
-
354
- const response = await app.request('/api/profile', {
355
- method: 'GET',
356
- headers: {
357
- Authorization: `Bearer ${token}`,
358
- },
359
- });
360
-
361
- return {
362
- status: response.status,
363
- body: await response.json(),
364
- };
365
- }
366
-
367
- getValidator() {
368
- return (result: { status: number; body: any }) => {
369
- if (result.status === 200 && result.body.id) {
370
- return TestCaseDecisions.SUCCESS;
371
- }
372
- return TestCaseDecisions.FAIL;
373
- };
374
- }
375
- }
376
-
377
- const authTests = TestPlan.newInstance<AuthContext>({
378
- scope: 'Authentication Tests',
379
- hooks: {
380
- before: async (testPlan: ITestContext<AuthContext>) => {
381
- // Login and store token in context
382
- const loginResponse = await app.request('/api/auth/login', {
383
- method: 'POST',
384
- headers: { 'Content-Type': 'application/json' },
385
- body: JSON.stringify({
386
- email: 'test@example.com',
387
- password: 'password123',
388
- }),
389
- });
390
-
391
- const { token, userId } = await loginResponse.json();
392
-
393
- // Bind to context for use in test cases
394
- testPlan.bind({ key: 'token', value: token });
395
- testPlan.bind({ key: 'userId', value: userId });
396
- },
397
- },
398
- testCases: [
399
- TestCase.withOptions({
400
- code: 'AUTH-001',
401
- description: 'Authenticated user can access profile',
402
- expectation: 'Returns user profile with status 200',
403
- handler: new SecureEndpointHandler({ context: {} as any }),
404
- }),
405
- ],
406
- });
407
-
408
- TestDescribe.withTestPlan({ testPlan: authTests }).run();
409
- ```
410
-
411
- ## Testing Repositories
412
-
413
- Test your data access layer directly:
414
-
415
- ```typescript
416
- // __tests__/todo.repository.test.ts
417
- import {
418
- TestPlan,
419
- TestDescribe,
420
- TestCase,
421
- TestCaseHandler,
422
- TestCaseDecisions,
423
- } from '@venizia/ignis-helpers';
424
- import { TodoRepository } from '../src/repositories/todo.repository';
425
- import { Container } from '@venizia/ignis-inversion';
426
-
427
- // Setup container for DI
428
- const container = new Container();
429
-
430
- class CreateTodoRepoHandler extends TestCaseHandler {
431
- async execute() {
432
- const todoRepo = container.get<TodoRepository>('repositories.TodoRepository');
433
-
434
- const created = await todoRepo.create({
435
- title: 'Repository Test',
436
- description: 'Testing repository layer',
437
- isCompleted: false,
438
- });
439
-
440
- return { todo: created };
441
- }
442
-
443
- getValidator() {
444
- return (result: { todo: any }) => {
445
- if (result.todo && result.todo.id && result.todo.title === 'Repository Test') {
446
- return TestCaseDecisions.SUCCESS;
447
- }
448
- return TestCaseDecisions.FAIL;
449
- };
450
- }
451
- }
452
-
453
- class FindTodoRepoHandler extends TestCaseHandler {
454
- async execute() {
455
- const todoRepo = container.get<TodoRepository>('repositories.TodoRepository');
456
-
457
- const todos = await todoRepo.find({
458
- where: { isCompleted: false },
459
- limit: 10,
460
- });
461
-
462
- return { todos, count: todos.length };
463
- }
464
-
465
- getValidator() {
466
- return (result: { todos: any[]; count: number }) => {
467
- if (Array.isArray(result.todos) && result.count >= 0) {
468
- return TestCaseDecisions.SUCCESS;
469
- }
470
- return TestCaseDecisions.FAIL;
471
- };
472
- }
473
- }
474
-
475
- const repoTests = TestPlan.newInstance({
476
- scope: 'Todo Repository',
477
- hooks: {
478
- before: async () => {
479
- // Setup DI container and database connection
480
- container.bind('repositories.TodoRepository').toClass(TodoRepository);
481
- },
482
- after: async () => {
483
- // Cleanup test data
484
- },
485
- },
486
- testCases: [
487
- TestCase.withOptions({
488
- code: 'REPO-001',
489
- description: 'Can create a todo via repository',
490
- expectation: 'Returns created todo with ID',
491
- handler: new CreateTodoRepoHandler({ context: {} as any }),
492
- }),
493
- TestCase.withOptions({
494
- code: 'REPO-002',
495
- description: 'Can find todos with filters',
496
- expectation: 'Returns array of matching todos',
497
- handler: new FindTodoRepoHandler({ context: {} as any }),
498
- }),
499
- ],
500
- });
501
-
502
- TestDescribe.withTestPlan({ testPlan: repoTests }).run();
503
- ```
504
-
505
- ## Testing Services
506
-
507
- Test business logic in isolation:
508
-
509
- ```typescript
510
- // __tests__/todo.service.test.ts
511
- import {
512
- TestPlan,
513
- TestDescribe,
514
- TestCase,
515
- TestCaseHandler,
516
- TestCaseDecisions,
517
- } from '@venizia/ignis-helpers';
518
- import { TodoService } from '../src/services/todo.service';
519
-
520
- class CompleteTodoHandler extends TestCaseHandler {
521
- async execute() {
522
- const todoService = new TodoService();
523
-
524
- // Create a todo first
525
- const todo = await todoService.create({
526
- title: 'Test completion',
527
- isCompleted: false,
528
- });
529
-
530
- // Mark as complete
531
- const completed = await todoService.markAsComplete(todo.id);
532
-
533
- return { original: todo, completed };
534
- }
535
-
536
- getValidator() {
537
- return (result: { original: any; completed: any }) => {
538
- // Original should be incomplete
539
- if (result.original.isCompleted !== false) {
540
- return TestCaseDecisions.FAIL;
541
- }
542
-
543
- // Completed should be complete
544
- if (result.completed.isCompleted !== true) {
545
- return TestCaseDecisions.FAIL;
546
- }
547
-
548
- return TestCaseDecisions.SUCCESS;
549
- };
550
- }
551
- }
552
-
553
- const serviceTests = TestPlan.newInstance({
554
- scope: 'Todo Service',
555
- testCases: [
556
- TestCase.withOptions({
557
- code: 'SVC-001',
558
- description: 'Can mark todo as complete',
559
- expectation: 'Todo isCompleted changes from false to true',
560
- handler: new CompleteTodoHandler({ context: {} as any }),
561
- }),
562
- ],
563
- });
564
-
565
- TestDescribe.withTestPlan({ testPlan: serviceTests }).run();
566
- ```
567
-
568
160
  ## Project Structure
569
161
 
570
162
  Organize your tests alongside your source code:
@@ -636,73 +228,24 @@ Choose scripts based on your preferred test framework:
636
228
 
637
229
  ## Best Practices
638
230
 
639
- ### 1. Use Descriptive Test Codes
231
+ ### 1. Use Descriptive Test Names
640
232
 
641
233
  ```typescript
642
- TestCase.withOptions({
643
- code: 'AUTH-LOGIN-001', // Feature-Action-Number
644
- description: 'User can login with valid credentials',
645
- expectation: 'Returns JWT token and user ID',
234
+ test('login with valid credentials returns a JWT and the user id', async () => {
646
235
  // ...
647
236
  });
648
237
  ```
649
238
 
650
239
  ### 2. Isolate Test Data
651
240
 
652
- ```typescript
653
- hooks: {
654
- beforeEach: async (testPlan) => {
655
- // Create fresh test data for each test
656
- const testTodo = await createTestTodo();
657
- testPlan.bind({ key: 'testTodoId', value: testTodo.id });
658
- },
659
- afterEach: async (testPlan) => {
660
- // Clean up after each test
661
- const todoId = testPlan.getSync({ key: 'testTodoId' });
662
- await deleteTestTodo(todoId);
663
- },
664
- }
665
- ```
241
+ Give every test its own fixtures - unique ids, fresh records - so tests never depend on execution order.
666
242
 
667
243
  ### 3. Test Edge Cases
668
244
 
669
- ```typescript
670
- // Test empty results
671
- TestCase.withOptions({
672
- code: 'TODO-FIND-002',
673
- description: 'Returns empty array when no todos match filter',
674
- expectation: 'Empty array with status 200',
675
- handler: new FindNonExistentHandler({ context: {} as any }),
676
- });
677
-
678
- // Test validation errors
679
- TestCase.withOptions({
680
- code: 'TODO-CREATE-003',
681
- description: 'Rejects todo without title',
682
- expectation: 'Status 400 with validation error',
683
- handler: new CreateInvalidTodoHandler({ context: {} as any }),
684
- });
685
- ```
686
-
687
- ### 4. Keep Handlers Focused
688
-
689
- Each handler should test one specific behavior:
690
-
691
- ```typescript
692
- // Good: Focused on one behavior
693
- class CreateTodoHandler extends TestCaseHandler {
694
- async execute() { /* only create logic */ }
695
- }
696
-
697
- // Avoid: Multiple behaviors in one handler
698
- class CreateAndUpdateAndDeleteHandler extends TestCaseHandler {
699
- async execute() { /* too many things */ }
700
- }
701
- ```
245
+ Cover the boundaries alongside the happy path: empty input, duplicate ids, missing records, unauthorized callers.
702
246
 
703
247
  ## Next Steps
704
248
 
705
- - [Testing Reference](../../extensions/helpers/testing/) - Complete API documentation
706
249
  - [Best Practices](../../best-practices/code-style-standards/) - Code quality standards
707
250
  - [Troubleshooting](../../best-practices/troubleshooting-tips.md) - Common issues
708
251
 
@@ -710,7 +253,7 @@ class CreateAndUpdateAndDeleteHandler extends TestCaseHandler {
710
253
 
711
254
  | What to Test | How |
712
255
  |--------------|-----|
713
- | **Controllers** | Use `app.request()` to make HTTP calls |
256
+ | **Controllers** | Use `getRootRouter().request()` to make in-process HTTP calls |
714
257
  | **Services** | Instantiate and call methods directly |
715
258
  | **Repositories** | Use DI container, test with real/mock DB |
716
259
  | **Integration** | Chain multiple operations with shared context |
@@ -718,5 +261,4 @@ class CreateAndUpdateAndDeleteHandler extends TestCaseHandler {
718
261
 
719
262
  **Key Takeaways:**
720
263
  - Use any test framework you prefer (Jest, Vitest, Bun Test, Playwright, etc.)
721
- - IGNIS provides optional testing utilities (`TestPlan`, `TestCase`, `TestCaseHandler`) built on `node:test`
722
- - All frameworks work seamlessly with Ignis applications
264
+ - All frameworks work seamlessly with IGNIS applications