@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.
- package/package.json +1 -1
- package/wiki/best-practices/architectural-patterns.md +0 -2
- package/wiki/best-practices/architecture-decisions.md +0 -8
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/code-style-standards/index.md +0 -1
- package/wiki/best-practices/code-style-standards/tooling.md +0 -3
- package/wiki/best-practices/contribution-workflow.md +12 -12
- package/wiki/best-practices/index.md +4 -14
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/guides/core-concepts/application/bootstrapping.md +6 -7
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/components.md +2 -2
- package/wiki/guides/core-concepts/dependency-injection.md +4 -5
- package/wiki/guides/core-concepts/persistent/datasources.md +4 -5
- package/wiki/guides/core-concepts/services.md +1 -1
- package/wiki/guides/get-started/5-minute-quickstart.md +4 -5
- package/wiki/guides/get-started/philosophy.md +12 -24
- package/wiki/guides/index.md +2 -9
- package/wiki/guides/reference/mcp-docs-server.md +13 -13
- package/wiki/guides/tutorials/building-a-crud-api.md +10 -10
- package/wiki/guides/tutorials/complete-installation.md +11 -12
- package/wiki/guides/tutorials/ecommerce-api.md +3 -3
- package/wiki/guides/tutorials/realtime-chat.md +6 -6
- package/wiki/guides/tutorials/testing.md +4 -5
- package/wiki/index.md +8 -14
- package/wiki/references/base/bootstrapping.md +0 -3
- package/wiki/references/base/components.md +2 -2
- package/wiki/references/base/controllers.md +0 -1
- package/wiki/references/base/datasources.md +1 -1
- package/wiki/references/base/dependency-injection.md +2 -2
- package/wiki/references/base/filter-system/default-filter.md +2 -3
- package/wiki/references/base/filter-system/index.md +1 -1
- package/wiki/references/base/filter-system/quick-reference.md +0 -14
- package/wiki/references/base/middlewares.md +0 -8
- package/wiki/references/base/providers.md +0 -9
- package/wiki/references/base/repositories/advanced.md +1 -1
- package/wiki/references/base/repositories/mixins.md +2 -3
- package/wiki/references/base/services.md +0 -1
- package/wiki/references/components/authentication/api.md +444 -0
- package/wiki/references/components/authentication/errors.md +177 -0
- package/wiki/references/components/authentication/index.md +571 -0
- package/wiki/references/components/authentication/usage.md +781 -0
- package/wiki/references/components/health-check.md +292 -103
- package/wiki/references/components/index.md +14 -12
- package/wiki/references/components/mail/api.md +505 -0
- package/wiki/references/components/mail/errors.md +176 -0
- package/wiki/references/components/mail/index.md +535 -0
- package/wiki/references/components/mail/usage.md +404 -0
- package/wiki/references/components/request-tracker.md +229 -25
- package/wiki/references/components/socket-io/api.md +1051 -0
- package/wiki/references/components/socket-io/errors.md +119 -0
- package/wiki/references/components/socket-io/index.md +410 -0
- package/wiki/references/components/socket-io/usage.md +322 -0
- package/wiki/references/components/static-asset/api.md +261 -0
- package/wiki/references/components/static-asset/errors.md +89 -0
- package/wiki/references/components/static-asset/index.md +617 -0
- package/wiki/references/components/static-asset/usage.md +364 -0
- package/wiki/references/components/swagger.md +390 -110
- package/wiki/references/components/template/api-page.md +125 -0
- package/wiki/references/components/template/errors-page.md +100 -0
- package/wiki/references/components/template/index.md +104 -0
- package/wiki/references/components/template/setup-page.md +134 -0
- package/wiki/references/components/template/single-page.md +132 -0
- package/wiki/references/components/template/usage-page.md +127 -0
- package/wiki/references/components/websocket/api.md +508 -0
- package/wiki/references/components/websocket/errors.md +123 -0
- package/wiki/references/components/websocket/index.md +453 -0
- package/wiki/references/components/websocket/usage.md +475 -0
- package/wiki/references/helpers/cron/index.md +224 -0
- package/wiki/references/helpers/crypto/index.md +537 -0
- package/wiki/references/helpers/env/index.md +214 -0
- package/wiki/references/helpers/error/index.md +232 -0
- package/wiki/references/helpers/index.md +16 -15
- package/wiki/references/helpers/inversion/index.md +608 -0
- package/wiki/references/helpers/logger/index.md +600 -0
- package/wiki/references/helpers/network/api.md +986 -0
- package/wiki/references/helpers/network/index.md +620 -0
- package/wiki/references/helpers/queue/index.md +589 -0
- package/wiki/references/helpers/redis/index.md +495 -0
- package/wiki/references/helpers/socket-io/api.md +497 -0
- package/wiki/references/helpers/socket-io/index.md +513 -0
- package/wiki/references/helpers/storage/api.md +705 -0
- package/wiki/references/helpers/storage/index.md +583 -0
- package/wiki/references/helpers/template/index.md +66 -0
- package/wiki/references/helpers/template/single-page.md +126 -0
- package/wiki/references/helpers/testing/index.md +510 -0
- package/wiki/references/helpers/types/index.md +512 -0
- package/wiki/references/helpers/uid/index.md +272 -0
- package/wiki/references/helpers/websocket/api.md +736 -0
- package/wiki/references/helpers/websocket/index.md +574 -0
- package/wiki/references/helpers/worker-thread/index.md +470 -0
- package/wiki/references/index.md +2 -9
- package/wiki/references/quick-reference.md +3 -18
- package/wiki/references/utilities/jsx.md +1 -8
- package/wiki/references/utilities/statuses.md +0 -7
- package/wiki/references/components/authentication.md +0 -476
- package/wiki/references/components/mail.md +0 -687
- package/wiki/references/components/socket-io.md +0 -562
- package/wiki/references/components/static-asset.md +0 -1277
- package/wiki/references/helpers/cron.md +0 -108
- package/wiki/references/helpers/crypto.md +0 -132
- package/wiki/references/helpers/env.md +0 -83
- package/wiki/references/helpers/error.md +0 -97
- package/wiki/references/helpers/inversion.md +0 -176
- package/wiki/references/helpers/logger.md +0 -296
- package/wiki/references/helpers/network.md +0 -396
- package/wiki/references/helpers/queue.md +0 -150
- package/wiki/references/helpers/redis.md +0 -142
- package/wiki/references/helpers/socket-io.md +0 -932
- package/wiki/references/helpers/storage.md +0 -665
- package/wiki/references/helpers/testing.md +0 -133
- package/wiki/references/helpers/types.md +0 -167
- package/wiki/references/helpers/uid.md +0 -167
- package/wiki/references/helpers/worker-thread.md +0 -178
- package/wiki/references/src-details/boot.md +0 -379
- package/wiki/references/src-details/core.md +0 -263
- package/wiki/references/src-details/dev-configs.md +0 -298
- package/wiki/references/src-details/docs.md +0 -71
- package/wiki/references/src-details/helpers.md +0 -211
- package/wiki/references/src-details/index.md +0 -86
- 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
|