@venizia/ignis-docs 0.0.5 → 0.0.6-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 (123) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/architectural-patterns.md +0 -2
  3. package/wiki/best-practices/architecture-decisions.md +0 -8
  4. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  5. package/wiki/best-practices/code-style-standards/index.md +0 -1
  6. package/wiki/best-practices/code-style-standards/tooling.md +0 -3
  7. package/wiki/best-practices/contribution-workflow.md +12 -12
  8. package/wiki/best-practices/index.md +4 -14
  9. package/wiki/best-practices/performance-optimization.md +3 -3
  10. package/wiki/best-practices/security-guidelines.md +2 -2
  11. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  12. package/wiki/guides/core-concepts/application/bootstrapping.md +6 -7
  13. package/wiki/guides/core-concepts/components-guide.md +1 -1
  14. package/wiki/guides/core-concepts/components.md +2 -2
  15. package/wiki/guides/core-concepts/dependency-injection.md +4 -5
  16. package/wiki/guides/core-concepts/persistent/datasources.md +4 -5
  17. package/wiki/guides/core-concepts/services.md +1 -1
  18. package/wiki/guides/get-started/5-minute-quickstart.md +4 -5
  19. package/wiki/guides/get-started/philosophy.md +12 -24
  20. package/wiki/guides/index.md +2 -9
  21. package/wiki/guides/reference/mcp-docs-server.md +13 -13
  22. package/wiki/guides/tutorials/building-a-crud-api.md +10 -10
  23. package/wiki/guides/tutorials/complete-installation.md +11 -12
  24. package/wiki/guides/tutorials/ecommerce-api.md +3 -3
  25. package/wiki/guides/tutorials/realtime-chat.md +6 -6
  26. package/wiki/guides/tutorials/testing.md +4 -5
  27. package/wiki/index.md +8 -14
  28. package/wiki/references/base/bootstrapping.md +0 -3
  29. package/wiki/references/base/components.md +2 -2
  30. package/wiki/references/base/controllers.md +0 -1
  31. package/wiki/references/base/datasources.md +1 -1
  32. package/wiki/references/base/dependency-injection.md +2 -2
  33. package/wiki/references/base/filter-system/default-filter.md +2 -3
  34. package/wiki/references/base/filter-system/index.md +1 -1
  35. package/wiki/references/base/filter-system/quick-reference.md +0 -14
  36. package/wiki/references/base/middlewares.md +0 -8
  37. package/wiki/references/base/providers.md +0 -9
  38. package/wiki/references/base/repositories/advanced.md +1 -1
  39. package/wiki/references/base/repositories/mixins.md +2 -3
  40. package/wiki/references/base/services.md +0 -1
  41. package/wiki/references/components/authentication/api.md +444 -0
  42. package/wiki/references/components/authentication/errors.md +177 -0
  43. package/wiki/references/components/authentication/index.md +571 -0
  44. package/wiki/references/components/authentication/usage.md +781 -0
  45. package/wiki/references/components/health-check.md +292 -103
  46. package/wiki/references/components/index.md +14 -12
  47. package/wiki/references/components/mail/api.md +505 -0
  48. package/wiki/references/components/mail/errors.md +176 -0
  49. package/wiki/references/components/mail/index.md +535 -0
  50. package/wiki/references/components/mail/usage.md +404 -0
  51. package/wiki/references/components/request-tracker.md +229 -25
  52. package/wiki/references/components/socket-io/api.md +1051 -0
  53. package/wiki/references/components/socket-io/errors.md +119 -0
  54. package/wiki/references/components/socket-io/index.md +410 -0
  55. package/wiki/references/components/socket-io/usage.md +322 -0
  56. package/wiki/references/components/static-asset/api.md +261 -0
  57. package/wiki/references/components/static-asset/errors.md +89 -0
  58. package/wiki/references/components/static-asset/index.md +617 -0
  59. package/wiki/references/components/static-asset/usage.md +364 -0
  60. package/wiki/references/components/swagger.md +390 -110
  61. package/wiki/references/components/template/api-page.md +125 -0
  62. package/wiki/references/components/template/errors-page.md +100 -0
  63. package/wiki/references/components/template/index.md +104 -0
  64. package/wiki/references/components/template/setup-page.md +134 -0
  65. package/wiki/references/components/template/single-page.md +132 -0
  66. package/wiki/references/components/template/usage-page.md +127 -0
  67. package/wiki/references/components/websocket/api.md +508 -0
  68. package/wiki/references/components/websocket/errors.md +123 -0
  69. package/wiki/references/components/websocket/index.md +453 -0
  70. package/wiki/references/components/websocket/usage.md +475 -0
  71. package/wiki/references/helpers/cron/index.md +224 -0
  72. package/wiki/references/helpers/crypto/index.md +537 -0
  73. package/wiki/references/helpers/env/index.md +214 -0
  74. package/wiki/references/helpers/error/index.md +232 -0
  75. package/wiki/references/helpers/index.md +16 -15
  76. package/wiki/references/helpers/inversion/index.md +608 -0
  77. package/wiki/references/helpers/logger/index.md +600 -0
  78. package/wiki/references/helpers/network/api.md +986 -0
  79. package/wiki/references/helpers/network/index.md +620 -0
  80. package/wiki/references/helpers/queue/index.md +589 -0
  81. package/wiki/references/helpers/redis/index.md +495 -0
  82. package/wiki/references/helpers/socket-io/api.md +497 -0
  83. package/wiki/references/helpers/socket-io/index.md +513 -0
  84. package/wiki/references/helpers/storage/api.md +705 -0
  85. package/wiki/references/helpers/storage/index.md +583 -0
  86. package/wiki/references/helpers/template/index.md +66 -0
  87. package/wiki/references/helpers/template/single-page.md +126 -0
  88. package/wiki/references/helpers/testing/index.md +510 -0
  89. package/wiki/references/helpers/types/index.md +512 -0
  90. package/wiki/references/helpers/uid/index.md +272 -0
  91. package/wiki/references/helpers/websocket/api.md +736 -0
  92. package/wiki/references/helpers/websocket/index.md +574 -0
  93. package/wiki/references/helpers/worker-thread/index.md +470 -0
  94. package/wiki/references/index.md +2 -9
  95. package/wiki/references/quick-reference.md +3 -18
  96. package/wiki/references/utilities/jsx.md +1 -8
  97. package/wiki/references/utilities/statuses.md +0 -7
  98. package/wiki/references/components/authentication.md +0 -476
  99. package/wiki/references/components/mail.md +0 -687
  100. package/wiki/references/components/socket-io.md +0 -562
  101. package/wiki/references/components/static-asset.md +0 -1277
  102. package/wiki/references/helpers/cron.md +0 -108
  103. package/wiki/references/helpers/crypto.md +0 -132
  104. package/wiki/references/helpers/env.md +0 -83
  105. package/wiki/references/helpers/error.md +0 -97
  106. package/wiki/references/helpers/inversion.md +0 -176
  107. package/wiki/references/helpers/logger.md +0 -296
  108. package/wiki/references/helpers/network.md +0 -396
  109. package/wiki/references/helpers/queue.md +0 -150
  110. package/wiki/references/helpers/redis.md +0 -142
  111. package/wiki/references/helpers/socket-io.md +0 -932
  112. package/wiki/references/helpers/storage.md +0 -665
  113. package/wiki/references/helpers/testing.md +0 -133
  114. package/wiki/references/helpers/types.md +0 -167
  115. package/wiki/references/helpers/uid.md +0 -167
  116. package/wiki/references/helpers/worker-thread.md +0 -178
  117. package/wiki/references/src-details/boot.md +0 -379
  118. package/wiki/references/src-details/core.md +0 -263
  119. package/wiki/references/src-details/dev-configs.md +0 -298
  120. package/wiki/references/src-details/docs.md +0 -71
  121. package/wiki/references/src-details/helpers.md +0 -211
  122. package/wiki/references/src-details/index.md +0 -86
  123. package/wiki/references/src-details/inversion.md +0 -340
@@ -0,0 +1,126 @@
1
+ # Single-Page Helper Template (Tier 1)
2
+
3
+ Use this template for Tier 1 helpers -- one `index.md` file per helper directory.
4
+
5
+ ---
6
+
7
+ ## Template
8
+
9
+ ````markdown
10
+ # {Helper Name}
11
+
12
+ One-line description sourced from the class JSDoc or module purpose.
13
+
14
+ ## Quick Reference
15
+
16
+ | Item | Value |
17
+ |------|-------|
18
+ | **Package** | `@venizia/ignis-helpers` |
19
+ | **Class** | `HelperClass` |
20
+ | **Extends** | `BaseHelper` |
21
+ | **Runtimes** | Both / Bun only |
22
+
23
+ For multi-class helpers, use a class table instead:
24
+
25
+ | Class | Extends | Use Case |
26
+ |-------|---------|----------|
27
+ | **FooHelper** | BaseFoo | Primary use case |
28
+ | **BarHelper** | BaseFoo | Alternative use case |
29
+
30
+ #### Import Paths
31
+
32
+ ```typescript
33
+ import { HelperClass } from '@venizia/ignis-helpers';
34
+ import type { IHelperOptions } from '@venizia/ignis-helpers';
35
+ ```
36
+
37
+ ## Creating an Instance
38
+
39
+ ```typescript
40
+ const helper = new HelperClass({
41
+ option1: 'value',
42
+ option2: true,
43
+ });
44
+ ```
45
+
46
+ #### Options
47
+
48
+ | Option | Type | Default | Description |
49
+ |--------|------|---------|-------------|
50
+ | `option1` | `string` | -- | Required. Description |
51
+ | `option2` | `boolean` | `true` | Optional. Description |
52
+
53
+ > [!NOTE]
54
+ > Add notes about required dependencies, environment variables, or runtime constraints here.
55
+
56
+ ## Usage
57
+
58
+ ### {Feature Area 1}
59
+
60
+ ```typescript
61
+ // Realistic usage example
62
+ await helper.doSomething({ param: 'value' });
63
+ ```
64
+
65
+ ### {Feature Area 2}
66
+
67
+ ```typescript
68
+ // Another realistic usage example
69
+ const result = helper.anotherMethod({ key: 'value' });
70
+ ```
71
+
72
+ ## API Summary
73
+
74
+ > Only include this section if the helper has 8+ public methods.
75
+
76
+ | Method | Parameters | Returns | Description |
77
+ |--------|-----------|---------|-------------|
78
+ | `method1()` | `opts: { key: string }` | `Promise<void>` | Does X |
79
+ | `method2()` | `opts: { id: number }` | `Result` | Does Y |
80
+
81
+ ## Troubleshooting
82
+
83
+ ### "Exact error message from source code"
84
+
85
+ **Cause:** What triggers this error.
86
+
87
+ **Fix:**
88
+ ```typescript
89
+ // Corrected code or configuration
90
+ ```
91
+
92
+ ### Another common issue
93
+
94
+ **Cause:** Explanation.
95
+
96
+ **Fix:** Steps or code to resolve.
97
+
98
+ ## See Also
99
+
100
+ - [Related Helper](../related-helper/) -- Brief description
101
+ - [Guide Topic](/guides/core-concepts/relevant-guide) -- Brief description
102
+ - [External Link](https://example.com) -- Brief description
103
+ ````
104
+
105
+ ---
106
+
107
+ ## Section Rules
108
+
109
+ | Section | Required | Notes |
110
+ |---------|----------|-------|
111
+ | Quick Reference | Yes | Always first after title |
112
+ | Import Paths | Yes | As `####` sub-heading under Quick Reference |
113
+ | Creating an Instance | Yes | Constructor + full options table |
114
+ | Usage | Yes | Organized by feature area with `###` sub-headings |
115
+ | API Summary | No | Only for helpers with 8+ public methods |
116
+ | Troubleshooting | Yes | 2-5 common errors from source `throw` statements |
117
+ | See Also | Yes | Categorized links to related docs |
118
+
119
+ ## Content Rules
120
+
121
+ 1. All content sourced from actual source code -- no inventing methods, options, or behaviors
122
+ 2. Show realistic parameter examples (not `'foo'` or `'bar'`)
123
+ 3. Use `<code v-pre>` for any `{{ }}` template patterns
124
+ 4. No `::: details` containers -- use `####` sub-headings
125
+ 5. No emojis
126
+ 6. GitHub-style callouts only: `> [!NOTE]`, `> [!TIP]`, `> [!WARNING]`, `> [!IMPORTANT]`
@@ -0,0 +1,510 @@
1
+ # Testing
2
+
3
+ Structured test framework built on Node.js's native `node:test` module that organizes tests into plans, cases, and handlers with lifecycle hooks and shared context.
4
+
5
+ ## Quick Reference
6
+
7
+ | Item | Value |
8
+ |------|-------|
9
+ | **Package** | `@venizia/ignis-helpers` |
10
+ | **Classes** | `TestPlan`, `BaseTestPlan`, `TestCase`, `TestCaseHandler`, `BaseTestCaseHandler`, `TestDescribe`, `AppTestDescribe`, `TestCaseDecisions` |
11
+ | **Extends** | `BaseTestPlan` (uses `Logger` + `MemoryStorageHelper`, does not extend `BaseHelper`) |
12
+ | **Runtimes** | Both |
13
+
14
+ #### Import Paths
15
+
16
+ ```typescript
17
+ import {
18
+ TestPlan,
19
+ BaseTestPlan,
20
+ TestCase,
21
+ TestCaseHandler,
22
+ BaseTestCaseHandler,
23
+ TestDescribe,
24
+ AppTestDescribe,
25
+ TestCaseDecisions,
26
+ } from '@venizia/ignis-helpers';
27
+
28
+ import type {
29
+ ITestContext,
30
+ ITestPlan,
31
+ ITestPlanOptions,
32
+ ITestHooks,
33
+ TTestHook,
34
+ ITestCase,
35
+ ITestCaseHandler,
36
+ ITestCaseInput,
37
+ ITestCaseHandlerOptions,
38
+ ITestCaseOptions,
39
+ TTestCaseDecision,
40
+ } from '@venizia/ignis-helpers';
41
+ ```
42
+
43
+ ## Creating an Instance
44
+
45
+ A test suite is assembled from three layers: a **TestCaseHandler** (execution + validation logic), a **TestCase** (metadata wrapper), and a **TestPlan** (orchestrator with hooks and shared context). The plan is then executed via **TestDescribe**.
46
+
47
+ ```typescript
48
+ import {
49
+ TestPlan,
50
+ TestDescribe,
51
+ TestCase,
52
+ TestCaseHandler,
53
+ TestCaseDecisions,
54
+ } from '@venizia/ignis-helpers';
55
+ import type { ITestContext, TTestCaseDecision } from '@venizia/ignis-helpers';
56
+
57
+ // 1. Define a handler
58
+ class MyTestHandler extends TestCaseHandler {
59
+ async execute() {
60
+ return { result: 'some-value' };
61
+ }
62
+
63
+ getValidator() {
64
+ return (opts: { result: string }): TTestCaseDecision => {
65
+ if (opts.result === 'some-value') {
66
+ return TestCaseDecisions.SUCCESS;
67
+ }
68
+ return TestCaseDecisions.FAIL;
69
+ };
70
+ }
71
+ }
72
+
73
+ // 2. Create a test plan
74
+ const myTestPlan = TestPlan.newInstance({
75
+ scope: 'My Feature',
76
+ hooks: {
77
+ before: async (testPlan) => console.log('Starting tests for:', testPlan.scope),
78
+ after: async () => console.log('Finished tests.'),
79
+ },
80
+ testCases: [
81
+ TestCase.withOptions({
82
+ code: 'MY-FEATURE-001',
83
+ description: 'It should return the correct value',
84
+ expectation: 'The result should be "some-value"',
85
+ handler: new MyTestHandler({ context: {} as any }),
86
+ }),
87
+ ],
88
+ });
89
+
90
+ // 3. Run the test plan
91
+ TestDescribe.withTestPlan({ testPlan: myTestPlan }).run();
92
+ ```
93
+
94
+ ## Usage
95
+
96
+ ### Shared Context
97
+
98
+ `TestPlan` implements `ITestContext`, providing `bind()` and `getSync()` methods backed by a `MemoryStorageHelper` registry. Use this to share data between lifecycle hooks and test case handlers.
99
+
100
+ ```typescript
101
+ import {
102
+ TestPlan,
103
+ TestDescribe,
104
+ TestCase,
105
+ TestCaseHandler,
106
+ TestCaseDecisions,
107
+ } from '@venizia/ignis-helpers';
108
+ import type { ITestPlan, TTestCaseDecision } from '@venizia/ignis-helpers';
109
+
110
+ class SecureApiHandler extends TestCaseHandler<{ token: string }> {
111
+ async execute() {
112
+ const token = this.context.getSync<string>({ key: 'token' });
113
+ const response = await app.request('/api/secure-data', {
114
+ headers: { Authorization: `Bearer ${token}` },
115
+ });
116
+ return { status: response.status };
117
+ }
118
+
119
+ getValidator() {
120
+ return (opts: { status: number }): TTestCaseDecision => {
121
+ return opts.status === 200
122
+ ? TestCaseDecisions.SUCCESS
123
+ : TestCaseDecisions.FAIL;
124
+ };
125
+ }
126
+ }
127
+
128
+ const authTestPlan = TestPlan.newInstance<{ token: string }>({
129
+ scope: 'Authentication',
130
+ hooks: {
131
+ before: async (testPlan: ITestPlan<{ token: string }>) => {
132
+ const token = await generateTestToken();
133
+ testPlan.bind({ key: 'token', value: token });
134
+ },
135
+ },
136
+ testCases: [
137
+ TestCase.withOptions({
138
+ code: 'AUTH-001',
139
+ description: 'Secure endpoint returns 200 with valid token',
140
+ expectation: 'Response status is 200',
141
+ handler: new SecureApiHandler({ context: {} as any }),
142
+ }),
143
+ ],
144
+ });
145
+
146
+ TestDescribe.withTestPlan({ testPlan: authTestPlan }).run();
147
+ ```
148
+
149
+ ### Test Case Resolver
150
+
151
+ Instead of (or in addition to) providing `testCases` directly, supply a `testCaseResolver` function that dynamically generates test cases at plan construction time. The resolver receives the plan context. Both `testCases` and `testCaseResolver` results are concatenated.
152
+
153
+ ```typescript
154
+ const plan = TestPlan.newInstance({
155
+ scope: 'Dynamic Tests',
156
+ testCaseResolver: ({ context }) => {
157
+ return endpoints.map((endpoint) =>
158
+ TestCase.withOptions({
159
+ code: `EP-${endpoint.name}`,
160
+ description: `Test ${endpoint.name}`,
161
+ expectation: 'Returns 200',
162
+ handler: new EndpointHandler({ context }),
163
+ }),
164
+ );
165
+ },
166
+ });
167
+ ```
168
+
169
+ ### Handler Arguments
170
+
171
+ Handlers support `args` (static) and `argResolver` (dynamic) for injecting test-specific input data. If both are omitted, `getArguments()` returns `null`. If both are provided, `args` takes priority.
172
+
173
+ ```typescript
174
+ class CreateUserHandler extends TestCaseHandler<{}, { name: string }> {
175
+ async execute() {
176
+ const args = this.getArguments(); // { name: 'Alice' }
177
+ return await userService.create(args!);
178
+ }
179
+
180
+ getValidator() {
181
+ return (user: { id: string; name: string }): TTestCaseDecision => {
182
+ return user.name === 'Alice'
183
+ ? TestCaseDecisions.SUCCESS
184
+ : TestCaseDecisions.FAIL;
185
+ };
186
+ }
187
+ }
188
+
189
+ // Static args
190
+ new CreateUserHandler({ context: {} as any, args: { name: 'Alice' } });
191
+
192
+ // Dynamic args via resolver
193
+ new CreateUserHandler({
194
+ context: {} as any,
195
+ argResolver: () => ({ name: 'Alice' }),
196
+ });
197
+ ```
198
+
199
+ ### Lifecycle Hooks
200
+
201
+ Hooks are registered via `ITestPlanOptions.hooks` and executed by `TestDescribe` using `node:test`'s `before`, `beforeEach`, `after`, and `afterEach` functions.
202
+
203
+ | Hook | When | Purpose |
204
+ |------|------|---------|
205
+ | `before` | Before all tests | Setup (e.g., start server, seed database) |
206
+ | `beforeEach` | Before each test | Reset state |
207
+ | `afterEach` | After each test | Cleanup per test |
208
+ | `after` | After all tests | Cleanup (e.g., close connections) |
209
+
210
+ > [!NOTE]
211
+ > Hook callbacks receive the full `ITestPlan` instance (not just the context), giving access to `bind()`, `getSync()`, `getTestCases()`, `getHooks()`, and `getRegistry()`.
212
+
213
+ ```typescript
214
+ const plan = TestPlan.newInstance<{ db: Database }>({
215
+ scope: 'With Hooks',
216
+ hooks: {
217
+ before: async (testPlan) => {
218
+ const db = await connectDatabase();
219
+ testPlan.bind({ key: 'db', value: db });
220
+ },
221
+ afterEach: async (testPlan) => {
222
+ const db = testPlan.getSync<Database>({ key: 'db' });
223
+ await db.truncateAll();
224
+ },
225
+ after: async (testPlan) => {
226
+ const db = testPlan.getSync<Database>({ key: 'db' });
227
+ await db.close();
228
+ },
229
+ },
230
+ testCases: [/* ... */],
231
+ });
232
+ ```
233
+
234
+ ### Modifying Test Cases After Construction
235
+
236
+ `BaseTestPlan` exposes `withTestCases()` for replacing the test case array after construction. This returns `this` for chaining.
237
+
238
+ ```typescript
239
+ const plan = TestPlan.newInstance({ scope: 'Mutable' });
240
+ plan.withTestCases({
241
+ testCases: [
242
+ TestCase.withOptions({
243
+ code: 'TC-001',
244
+ description: 'Added after construction',
245
+ expectation: 'Should pass',
246
+ handler: myHandler,
247
+ }),
248
+ ],
249
+ });
250
+ ```
251
+
252
+ > [!WARNING]
253
+ > `withTestCases()` fully replaces the existing test case array rather than appending to it.
254
+
255
+ ### TestCaseDecisions
256
+
257
+ Test case validators must return one of these decision constants:
258
+
259
+ | Decision | Value | Meaning |
260
+ |----------|-------|---------|
261
+ | `SUCCESS` | `'200_SUCCESS'` | Test passed |
262
+ | `FAIL` | `'000_FAIL'` | Test failed |
263
+ | `UNKNOWN` | `'000_UNKNOWN'` | No decision reached (treated as failure by `_execute()`) |
264
+
265
+ The `_execute()` method on `TestCaseHandler` calls `assert.equal(validateRs, TestCaseDecisions.SUCCESS)`, so any value other than `'200_SUCCESS'` causes the test to fail.
266
+
267
+ ## API Summary
268
+
269
+ ### Class Hierarchy
270
+
271
+ ```
272
+ BaseTestCaseHandler (abstract)
273
+ └── TestCaseHandler (abstract) -- execute(), getValidator(), validate()
274
+ └── Your concrete handler
275
+
276
+ BaseTestPlan (abstract)
277
+ └── TestPlan -- newInstance()
278
+
279
+ TestDescribe -- withTestPlan(), run()
280
+ └── AppTestDescribe
281
+ ```
282
+
283
+ ### ITestPlanOptions
284
+
285
+ | Option | Type | Default | Description |
286
+ |--------|------|---------|-------------|
287
+ | `scope` | `string` | -- | Name for the test suite (used as the `describe()` label). Required. |
288
+ | `hooks` | `ITestHooks<R>` | `{}` | Lifecycle hooks (`before`, `beforeEach`, `after`, `afterEach`). |
289
+ | `testCases` | `Array<ITestCase<R>>` | `[]` | Static list of test cases. |
290
+ | `testCaseResolver` | `(opts: { context: ITestContext<R> }) => Array<ITestCase<R>>` | `undefined` | Dynamic test case generator, receives the plan context. |
291
+
292
+ ### BaseTestPlan / TestPlan Methods
293
+
294
+ | Method | Returns | Description |
295
+ |--------|---------|-------------|
296
+ | `TestPlan.newInstance(opts)` | `TestPlan<R>` | Static factory method. |
297
+ | `withTestCases({ testCases })` | `this` | Replace the plan's test case array. |
298
+ | `getTestCases()` | `Array<ITestCase<R>>` | Get all registered test cases. |
299
+ | `getHooks()` | `ITestHooks<R>` | Get all lifecycle hooks. |
300
+ | `getHook({ key })` | `TTestHook<R> \| null` | Get a specific hook by name. |
301
+ | `getRegistry()` | `MemoryStorageHelper<R>` | Get the backing context registry. |
302
+ | `getContext()` | `ITestContext<R>` | Returns `this` (the plan is the context). |
303
+ | `bind({ key, value })` | `void` | Store a value in the context registry. |
304
+ | `getSync({ key })` | `T` | Retrieve a value from the context registry. |
305
+ | `execute()` | `void` | Run all test cases via `node:test` `it()` blocks. |
306
+
307
+ ### ITestCaseOptions
308
+
309
+ | Option | Type | Default | Description |
310
+ |--------|------|---------|-------------|
311
+ | `code` | `string` | -- | Unique test case identifier (e.g., `'AUTH-001'`). Required, must be non-empty. |
312
+ | `name` | `string` | `undefined` | Optional short name for the test case. |
313
+ | `description` | `string` | -- | What the test case does. Required, must be non-empty. |
314
+ | `expectation` | `string` | `undefined` | Expected outcome description. Validated as required and non-empty by constructor. |
315
+ | `handler` | `TestCaseHandler<R, I>` | -- | The handler that executes and validates the test. Required. |
316
+
317
+ ### TestCase Methods
318
+
319
+ | Method | Returns | Description |
320
+ |--------|---------|-------------|
321
+ | `TestCase.withOptions(opts)` | `TestCase<R, I>` | Static factory. Validates `code`, `description`, `expectation` are non-empty. |
322
+ | `run()` | `Promise<void>` | Delegates to `handler._execute()`. |
323
+
324
+ ### ITestCaseHandlerOptions
325
+
326
+ | Option | Type | Default | Description |
327
+ |--------|------|---------|-------------|
328
+ | `scope` | `string` | `'TestCaseHandler'` | Logger scope. |
329
+ | `context` | `ITestContext<R>` | -- | The test plan context for shared state. Required. |
330
+ | `args` | `I \| null` | `null` | Static arguments for the handler. |
331
+ | `argResolver` | `(...args: any[]) => I \| null` | `undefined` | Dynamic argument resolver, called once at construction. |
332
+ | `validator` | `(opts: any) => ValueOrPromise<TTestCaseDecision>` | `undefined` | Validator function. Overrides `getValidator()` if provided. |
333
+
334
+ ### TestCaseHandler Methods
335
+
336
+ | Method | Returns | Description |
337
+ |--------|---------|-------------|
338
+ | `execute()` | `ValueOrPromise<any>` | **Abstract.** Perform the action under test. |
339
+ | `getValidator()` | `((opts) => ValueOrPromise<TTestCaseDecision>) \| null` | **Abstract.** Return a validator function or `null`. |
340
+ | `validate(opts)` | `ValueOrPromise<TTestCaseDecision>` | Runs the validator (from `this.validator` or `getValidator()`). |
341
+ | `getArguments()` | `I \| null` | Returns the handler's `args`. |
342
+ | `_execute()` | `Promise<void>` | Internal. Calls `execute()`, then `validate()`, then `assert.equal(result, SUCCESS)`. |
343
+
344
+ ### TestDescribe Methods
345
+
346
+ | Method | Returns | Description |
347
+ |--------|---------|-------------|
348
+ | `TestDescribe.withTestPlan({ testPlan })` | `TestDescribe<R>` | Static factory method. |
349
+ | `run()` | `void` | Wraps the test plan in a `node:test` `describe()` block with all lifecycle hooks wired up. Throws if `testPlan` is not set. |
350
+
351
+ ### Type Definitions
352
+
353
+ #### ITestContext
354
+
355
+ ```typescript
356
+ interface ITestContext<R extends object> {
357
+ scope: string;
358
+ getRegistry: () => MemoryStorageHelper<R>;
359
+ bind: <T>(opts: { key: string; value: T }) => void;
360
+ getSync: <E = AnyType>(opts: { key: keyof R }) => E;
361
+ }
362
+ ```
363
+
364
+ #### ITestPlan
365
+
366
+ ```typescript
367
+ interface ITestPlan<R extends object = {}> extends ITestContext<R> {
368
+ getTestCases: () => Array<ITestCase<R>>;
369
+ getContext: () => ITestContext<R>;
370
+ getHooks: () => ITestHooks<R>;
371
+ getHook: (opts: { key: keyof ITestHooks<R> }) => TTestHook<R> | null;
372
+ execute: () => ValueOrPromise<void>;
373
+ }
374
+ ```
375
+
376
+ #### ITestHooks / TTestHook
377
+
378
+ ```typescript
379
+ type TTestHook<R extends object> = (testPlan: ITestPlan<R>) => ValueOrPromise<void>;
380
+
381
+ interface ITestHooks<R extends object> {
382
+ before?: TTestHook<R>;
383
+ beforeEach?: TTestHook<R>;
384
+ after?: TTestHook<R>;
385
+ afterEach?: TTestHook<R>;
386
+ }
387
+ ```
388
+
389
+ #### ITestCase
390
+
391
+ ```typescript
392
+ interface ITestCase<R extends object = {}, I extends object = {}> {
393
+ code: string;
394
+ name?: string;
395
+ description: string;
396
+ expectation?: string;
397
+ handler: ITestCaseHandler<R, I>;
398
+ run: () => ValueOrPromise<void>;
399
+ }
400
+ ```
401
+
402
+ #### ITestCaseHandler
403
+
404
+ ```typescript
405
+ interface ITestCaseHandler<R extends object = {}, I extends object = {}> {
406
+ context: ITestContext<R>;
407
+ args: I | null;
408
+ validator?: (args: AnyObject) => ValueOrPromise<TTestCaseDecision>;
409
+ }
410
+ ```
411
+
412
+ #### TTestCaseDecision
413
+
414
+ ```typescript
415
+ type TTestCaseDecision = '000_UNKNOWN' | '000_FAIL' | '200_SUCCESS';
416
+ ```
417
+
418
+ ## Troubleshooting
419
+
420
+ ### "[validate] Invalid test case validator!"
421
+
422
+ **Cause:** `TestCaseHandler.validate()` is called but neither a `validator` was passed in the constructor options nor does `getValidator()` return a function.
423
+
424
+ **Fix:** Implement `getValidator()` to return a validation function, or pass a `validator` in the handler options:
425
+
426
+ ```typescript
427
+ // Option 1: Implement getValidator()
428
+ class MyHandler extends TestCaseHandler {
429
+ execute() { return { ok: true }; }
430
+ getValidator() {
431
+ return (opts: { ok: boolean }) =>
432
+ opts.ok ? TestCaseDecisions.SUCCESS : TestCaseDecisions.FAIL;
433
+ }
434
+ }
435
+
436
+ // Option 2: Pass validator in constructor options
437
+ new MyHandler({
438
+ context: {} as any,
439
+ validator: (opts) => opts.ok ? TestCaseDecisions.SUCCESS : TestCaseDecisions.FAIL,
440
+ });
441
+ ```
442
+
443
+ ### "[TestCase] Invalid value for key: \<key\> | value: \<value\> | Opts: ..."
444
+
445
+ **Cause:** `TestCase.withOptions()` validates that `code`, `description`, and `expectation` are all non-empty strings. If any is missing or empty, this error is thrown.
446
+
447
+ **Fix:** Ensure all three required fields are provided:
448
+
449
+ ```typescript
450
+ // Wrong -- missing expectation
451
+ TestCase.withOptions({
452
+ code: 'TC-001',
453
+ description: 'Some test',
454
+ handler: myHandler,
455
+ });
456
+
457
+ // Correct
458
+ TestCase.withOptions({
459
+ code: 'TC-001',
460
+ description: 'Some test',
461
+ expectation: 'Should return 200',
462
+ handler: myHandler,
463
+ });
464
+ ```
465
+
466
+ ### "[run] Invalid test plan!"
467
+
468
+ **Cause:** `TestDescribe.run()` was called but `this.testPlan` is falsy. This happens if the `TestDescribe` instance was constructed without a valid test plan.
469
+
470
+ **Fix:** Ensure a valid `ITestPlan` is provided via the constructor or `withTestPlan()`:
471
+
472
+ ```typescript
473
+ const describe = TestDescribe.withTestPlan({ testPlan: myTestPlan });
474
+ describe.run();
475
+ ```
476
+
477
+ ### Tests run but always fail with assertion error
478
+
479
+ **Cause:** The `_execute()` method on `TestCaseHandler` asserts that the validation result equals `TestCaseDecisions.SUCCESS` (`'200_SUCCESS'`). If your validator returns `undefined`, `null`, or a string that is not exactly `'200_SUCCESS'`, the assertion fails.
480
+
481
+ **Fix:** Ensure your validator always returns one of the `TestCaseDecisions` constants and that the success path returns `TestCaseDecisions.SUCCESS` explicitly:
482
+
483
+ ```typescript
484
+ getValidator() {
485
+ return (opts: { value: number }): TTestCaseDecision => {
486
+ // Always return an explicit decision constant
487
+ return opts.value > 0
488
+ ? TestCaseDecisions.SUCCESS
489
+ : TestCaseDecisions.FAIL;
490
+ };
491
+ }
492
+ ```
493
+
494
+ ### "Failed to execute test handler | Error: ..."
495
+
496
+ **Cause:** An unhandled exception was thrown inside `execute()` or `validate()` within `_execute()`. The error is caught and logged, but `validateRs` remains `TestCaseDecisions.UNKNOWN`, causing the subsequent `assert.equal` to fail.
497
+
498
+ **Fix:** Check the logged error message for the root cause. Common issues include missing context values (calling `getSync()` for a key that was never `bind()`-ed) or network/database errors in the handler's `execute()` method.
499
+
500
+ ## See Also
501
+
502
+ - **Related Concepts:**
503
+ - [Dependency Injection](/guides/core-concepts/dependency-injection) -- Testing with DI
504
+ - [Application](/guides/core-concepts/application/) -- Application lifecycle in tests
505
+
506
+ - **Other Helpers:**
507
+ - [Helpers Index](../index) -- All available helpers
508
+
509
+ - **External Resources:**
510
+ - [Node.js Test Runner](https://nodejs.org/api/test.html) -- Native `node:test` module documentation