@unrdf/kgc-runtime 26.4.2
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/IMPLEMENTATION_SUMMARY.json +150 -0
- package/PLUGIN_SYSTEM_SUMMARY.json +149 -0
- package/README.md +98 -0
- package/TRANSACTION_IMPLEMENTATION.json +119 -0
- package/capability-map.md +93 -0
- package/docs/api-stability.md +269 -0
- package/docs/extensions/plugin-development.md +382 -0
- package/package.json +40 -0
- package/plugins/registry.json +35 -0
- package/src/admission-gate.mjs +414 -0
- package/src/api-version.mjs +373 -0
- package/src/atomic-admission.mjs +310 -0
- package/src/bounds.mjs +289 -0
- package/src/bulkhead-manager.mjs +280 -0
- package/src/capsule.mjs +524 -0
- package/src/crdt.mjs +361 -0
- package/src/enhanced-bounds.mjs +614 -0
- package/src/executor.mjs +73 -0
- package/src/freeze-restore.mjs +521 -0
- package/src/index.mjs +62 -0
- package/src/materialized-views.mjs +371 -0
- package/src/merge.mjs +472 -0
- package/src/plugin-isolation.mjs +392 -0
- package/src/plugin-manager.mjs +441 -0
- package/src/projections-api.mjs +336 -0
- package/src/projections-cli.mjs +238 -0
- package/src/projections-docs.mjs +300 -0
- package/src/projections-ide.mjs +278 -0
- package/src/receipt.mjs +340 -0
- package/src/rollback.mjs +258 -0
- package/src/saga-orchestrator.mjs +355 -0
- package/src/schemas.mjs +1330 -0
- package/src/storage-optimization.mjs +359 -0
- package/src/tool-registry.mjs +272 -0
- package/src/transaction.mjs +466 -0
- package/src/validators.mjs +485 -0
- package/src/work-item.mjs +449 -0
- package/templates/plugin-template/README.md +58 -0
- package/templates/plugin-template/index.mjs +162 -0
- package/templates/plugin-template/plugin.json +19 -0
- package/test/admission-gate.test.mjs +583 -0
- package/test/api-version.test.mjs +74 -0
- package/test/atomic-admission.test.mjs +155 -0
- package/test/bounds.test.mjs +341 -0
- package/test/bulkhead-manager.test.mjs +236 -0
- package/test/capsule.test.mjs +625 -0
- package/test/crdt.test.mjs +215 -0
- package/test/enhanced-bounds.test.mjs +487 -0
- package/test/freeze-restore.test.mjs +472 -0
- package/test/materialized-views.test.mjs +243 -0
- package/test/merge.test.mjs +665 -0
- package/test/plugin-isolation.test.mjs +109 -0
- package/test/plugin-manager.test.mjs +208 -0
- package/test/projections-api.test.mjs +293 -0
- package/test/projections-cli.test.mjs +204 -0
- package/test/projections-docs.test.mjs +173 -0
- package/test/projections-ide.test.mjs +230 -0
- package/test/receipt.test.mjs +295 -0
- package/test/rollback.test.mjs +132 -0
- package/test/saga-orchestrator.test.mjs +279 -0
- package/test/schemas.test.mjs +716 -0
- package/test/storage-optimization.test.mjs +503 -0
- package/test/tool-registry.test.mjs +341 -0
- package/test/transaction.test.mjs +189 -0
- package/test/validators.test.mjs +463 -0
- package/test/work-item.test.mjs +548 -0
- package/test/work-item.test.mjs.bak +548 -0
- package/var/kgc/test-atomic-log.json +519 -0
- package/var/kgc/test-cascading-log.json +145 -0
- package/vitest.config.mjs +18 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Custom validators tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { blake3 } from 'hash-wasm';
|
|
8
|
+
import {
|
|
9
|
+
validateReceiptChainIntegrity,
|
|
10
|
+
ReceiptChainSchema,
|
|
11
|
+
validateTemporalConsistency,
|
|
12
|
+
TemporallyOrderedSchema,
|
|
13
|
+
validateArtifactHash,
|
|
14
|
+
ArtifactSchema,
|
|
15
|
+
validateDependencyDAG,
|
|
16
|
+
WorkItemDependencySchema,
|
|
17
|
+
detectCycle,
|
|
18
|
+
validateAsyncPolicy,
|
|
19
|
+
createAsyncPolicySchema,
|
|
20
|
+
validateTimeRange,
|
|
21
|
+
RunCapsuleTimeRangeSchema,
|
|
22
|
+
combineValidators,
|
|
23
|
+
createValidationResult,
|
|
24
|
+
} from '../src/validators.mjs';
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Receipt Chain Integrity Tests
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
describe('Receipt Chain Integrity Validator', () => {
|
|
31
|
+
it('should validate valid receipt chain with proper linkage', () => {
|
|
32
|
+
const receipts = [
|
|
33
|
+
{
|
|
34
|
+
id: 'receipt-1',
|
|
35
|
+
hash: 'abc123',
|
|
36
|
+
parentHash: null,
|
|
37
|
+
timestamp: 1000,
|
|
38
|
+
operation: 'add',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 'receipt-2',
|
|
42
|
+
hash: 'def456',
|
|
43
|
+
parentHash: 'abc123',
|
|
44
|
+
timestamp: 2000,
|
|
45
|
+
operation: 'update',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'receipt-3',
|
|
49
|
+
hash: 'ghi789',
|
|
50
|
+
parentHash: 'def456',
|
|
51
|
+
timestamp: 3000,
|
|
52
|
+
operation: 'delete',
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const isValid = validateReceiptChainIntegrity(receipts);
|
|
57
|
+
assert.equal(isValid, true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should reject chain with broken parentHash linkage', () => {
|
|
61
|
+
const receipts = [
|
|
62
|
+
{
|
|
63
|
+
id: 'receipt-1',
|
|
64
|
+
hash: 'abc123',
|
|
65
|
+
parentHash: null,
|
|
66
|
+
timestamp: 1000,
|
|
67
|
+
operation: 'add',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'receipt-2',
|
|
71
|
+
hash: 'def456',
|
|
72
|
+
parentHash: 'wrong-hash', // Should be 'abc123'
|
|
73
|
+
timestamp: 2000,
|
|
74
|
+
operation: 'update',
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const isValid = validateReceiptChainIntegrity(receipts);
|
|
79
|
+
assert.equal(isValid, false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should reject chain where first receipt has parent', () => {
|
|
83
|
+
const receipts = [
|
|
84
|
+
{
|
|
85
|
+
id: 'receipt-1',
|
|
86
|
+
hash: 'abc123',
|
|
87
|
+
parentHash: 'should-be-null', // First receipt should have null parent
|
|
88
|
+
timestamp: 1000,
|
|
89
|
+
operation: 'add',
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const isValid = validateReceiptChainIntegrity(receipts);
|
|
94
|
+
assert.equal(isValid, false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should validate empty chain', () => {
|
|
98
|
+
const isValid = validateReceiptChainIntegrity([]);
|
|
99
|
+
assert.equal(isValid, true);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should validate chain using ReceiptChainSchema', async () => {
|
|
103
|
+
const receipts = [
|
|
104
|
+
{
|
|
105
|
+
id: 'receipt-1',
|
|
106
|
+
hash: 'abc123',
|
|
107
|
+
parentHash: null,
|
|
108
|
+
timestamp: 1000,
|
|
109
|
+
operation: 'add',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'receipt-2',
|
|
113
|
+
hash: 'def456',
|
|
114
|
+
parentHash: 'abc123',
|
|
115
|
+
timestamp: 2000,
|
|
116
|
+
operation: 'update',
|
|
117
|
+
},
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const result = ReceiptChainSchema.parse(receipts);
|
|
121
|
+
assert.equal(result.length, 2);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// =============================================================================
|
|
126
|
+
// Temporal Consistency Tests
|
|
127
|
+
// =============================================================================
|
|
128
|
+
|
|
129
|
+
describe('Temporal Consistency Validator', () => {
|
|
130
|
+
it('should validate monotonically increasing timestamps', () => {
|
|
131
|
+
const items = [
|
|
132
|
+
{ timestamp: 1000 },
|
|
133
|
+
{ timestamp: 2000 },
|
|
134
|
+
{ timestamp: 3000 },
|
|
135
|
+
{ timestamp: 4000 },
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const isValid = validateTemporalConsistency(items);
|
|
139
|
+
assert.equal(isValid, true);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should reject non-increasing timestamps', () => {
|
|
143
|
+
const items = [
|
|
144
|
+
{ timestamp: 1000 },
|
|
145
|
+
{ timestamp: 2000 },
|
|
146
|
+
{ timestamp: 1500 }, // Goes backwards
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
const isValid = validateTemporalConsistency(items);
|
|
150
|
+
assert.equal(isValid, false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should reject equal timestamps', () => {
|
|
154
|
+
const items = [
|
|
155
|
+
{ timestamp: 1000 },
|
|
156
|
+
{ timestamp: 2000 },
|
|
157
|
+
{ timestamp: 2000 }, // Duplicate
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const isValid = validateTemporalConsistency(items);
|
|
161
|
+
assert.equal(isValid, false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should validate single item', () => {
|
|
165
|
+
const items = [{ timestamp: 1000 }];
|
|
166
|
+
|
|
167
|
+
const isValid = validateTemporalConsistency(items);
|
|
168
|
+
assert.equal(isValid, true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should validate using TemporallyOrderedSchema', () => {
|
|
172
|
+
const items = [
|
|
173
|
+
{ timestamp: 1000, data: 'test1' },
|
|
174
|
+
{ timestamp: 2000, data: 'test2' },
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const result = TemporallyOrderedSchema.parse(items);
|
|
178
|
+
assert.equal(result.length, 2);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// =============================================================================
|
|
183
|
+
// Artifact Hash Validation Tests
|
|
184
|
+
// =============================================================================
|
|
185
|
+
|
|
186
|
+
describe('Artifact Hash Validator', () => {
|
|
187
|
+
it('should validate artifact with correct hash', async () => {
|
|
188
|
+
const content = 'Hello World';
|
|
189
|
+
const hash = await blake3(content);
|
|
190
|
+
|
|
191
|
+
const artifact = {
|
|
192
|
+
type: 'file',
|
|
193
|
+
content,
|
|
194
|
+
hash,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const isValid = await validateArtifactHash(artifact);
|
|
198
|
+
assert.equal(isValid, true);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should reject artifact with incorrect hash', async () => {
|
|
202
|
+
const artifact = {
|
|
203
|
+
type: 'file',
|
|
204
|
+
content: 'Hello World',
|
|
205
|
+
hash: 'incorrect-hash',
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const isValid = await validateArtifactHash(artifact);
|
|
209
|
+
assert.equal(isValid, false);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should validate artifact without content or hash', async () => {
|
|
213
|
+
const artifact = {
|
|
214
|
+
type: 'file',
|
|
215
|
+
path: '/test/file.txt',
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const isValid = await validateArtifactHash(artifact);
|
|
219
|
+
assert.equal(isValid, true);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should validate using ArtifactSchema', async () => {
|
|
223
|
+
const content = 'Test Content';
|
|
224
|
+
const hash = await blake3(content);
|
|
225
|
+
|
|
226
|
+
const artifact = {
|
|
227
|
+
type: 'file',
|
|
228
|
+
path: '/test.txt',
|
|
229
|
+
content,
|
|
230
|
+
hash,
|
|
231
|
+
size: 12,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const result = await ArtifactSchema.parseAsync(artifact);
|
|
235
|
+
assert.equal(result.type, 'file');
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// =============================================================================
|
|
240
|
+
// Dependency DAG Validation Tests
|
|
241
|
+
// =============================================================================
|
|
242
|
+
|
|
243
|
+
describe('Dependency DAG Validator', () => {
|
|
244
|
+
it('should validate acyclic dependency graph', () => {
|
|
245
|
+
const workItems = [
|
|
246
|
+
{ id: 'A', dependencies: ['B', 'C'] },
|
|
247
|
+
{ id: 'B', dependencies: ['D'] },
|
|
248
|
+
{ id: 'C', dependencies: ['D'] },
|
|
249
|
+
{ id: 'D', dependencies: [] },
|
|
250
|
+
];
|
|
251
|
+
|
|
252
|
+
const result = validateDependencyDAG(workItems);
|
|
253
|
+
assert.equal(result.valid, true);
|
|
254
|
+
assert.equal(result.cycle.length, 0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('should detect simple cycle', () => {
|
|
258
|
+
const workItems = [
|
|
259
|
+
{ id: 'A', dependencies: ['B'] },
|
|
260
|
+
{ id: 'B', dependencies: ['C'] },
|
|
261
|
+
{ id: 'C', dependencies: ['A'] }, // Cycle: A -> B -> C -> A
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
const result = validateDependencyDAG(workItems);
|
|
265
|
+
assert.equal(result.valid, false);
|
|
266
|
+
assert.ok(result.cycle.length > 0);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should detect self-loop', () => {
|
|
270
|
+
const workItems = [
|
|
271
|
+
{ id: 'A', dependencies: ['A'] }, // Self-loop
|
|
272
|
+
];
|
|
273
|
+
|
|
274
|
+
const result = validateDependencyDAG(workItems);
|
|
275
|
+
assert.equal(result.valid, false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should validate empty graph', () => {
|
|
279
|
+
const result = validateDependencyDAG([]);
|
|
280
|
+
assert.equal(result.valid, true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('should detect non-existent dependency', () => {
|
|
284
|
+
const workItems = [
|
|
285
|
+
{ id: 'A', dependencies: ['B'] },
|
|
286
|
+
// 'B' does not exist
|
|
287
|
+
];
|
|
288
|
+
|
|
289
|
+
const result = validateDependencyDAG(workItems);
|
|
290
|
+
assert.equal(result.valid, false);
|
|
291
|
+
assert.ok(result.error);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('should validate using WorkItemDependencySchema', async () => {
|
|
295
|
+
const workItems = [
|
|
296
|
+
{ id: 'A', dependencies: ['B'] },
|
|
297
|
+
{ id: 'B', dependencies: [] },
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
const result = WorkItemDependencySchema.parse(workItems);
|
|
301
|
+
assert.equal(result.length, 2);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should reject cycle using WorkItemDependencySchema', async () => {
|
|
305
|
+
const workItems = [
|
|
306
|
+
{ id: 'A', dependencies: ['B'] },
|
|
307
|
+
{ id: 'B', dependencies: ['A'] },
|
|
308
|
+
];
|
|
309
|
+
|
|
310
|
+
assert.throws(() => {
|
|
311
|
+
WorkItemDependencySchema.parse(workItems);
|
|
312
|
+
}, /cycle detected/i);
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe('Cycle Detection', () => {
|
|
317
|
+
it('should detect cycle in graph', () => {
|
|
318
|
+
const graph = new Map([
|
|
319
|
+
['A', ['B']],
|
|
320
|
+
['B', ['C']],
|
|
321
|
+
['C', ['A']],
|
|
322
|
+
]);
|
|
323
|
+
|
|
324
|
+
const result = detectCycle(graph);
|
|
325
|
+
assert.equal(result.hasCycle, true);
|
|
326
|
+
assert.ok(result.cycle.length > 0);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should return no cycle for DAG', () => {
|
|
330
|
+
const graph = new Map([
|
|
331
|
+
['A', ['B', 'C']],
|
|
332
|
+
['B', ['D']],
|
|
333
|
+
['C', ['D']],
|
|
334
|
+
['D', []],
|
|
335
|
+
]);
|
|
336
|
+
|
|
337
|
+
const result = detectCycle(graph);
|
|
338
|
+
assert.equal(result.hasCycle, false);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// =============================================================================
|
|
343
|
+
// Async Validator Tests
|
|
344
|
+
// =============================================================================
|
|
345
|
+
|
|
346
|
+
describe('Async Policy Validator', () => {
|
|
347
|
+
it('should validate operation allowed by policy', async () => {
|
|
348
|
+
const policyCheck = async (op) => op.type === 'safe_operation';
|
|
349
|
+
|
|
350
|
+
const operation = { type: 'safe_operation', data: 'test' };
|
|
351
|
+
const isAllowed = await validateAsyncPolicy(operation, policyCheck);
|
|
352
|
+
|
|
353
|
+
assert.equal(isAllowed, true);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should reject operation denied by policy', async () => {
|
|
357
|
+
const policyCheck = async (op) => op.type !== 'forbidden_operation';
|
|
358
|
+
|
|
359
|
+
const operation = { type: 'forbidden_operation', data: 'test' };
|
|
360
|
+
const isAllowed = await validateAsyncPolicy(operation, policyCheck);
|
|
361
|
+
|
|
362
|
+
assert.equal(isAllowed, false);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it('should handle async policy check errors', async () => {
|
|
366
|
+
const policyCheck = async () => {
|
|
367
|
+
throw new Error('Policy service unavailable');
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const operation = { type: 'test', data: 'test' };
|
|
371
|
+
const isAllowed = await validateAsyncPolicy(operation, policyCheck);
|
|
372
|
+
|
|
373
|
+
assert.equal(isAllowed, false);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should validate using createAsyncPolicySchema', async () => {
|
|
377
|
+
const policyCheck = async (op) => op.type === 'allowed';
|
|
378
|
+
|
|
379
|
+
const schema = createAsyncPolicySchema(policyCheck);
|
|
380
|
+
const result = await schema.parseAsync({ type: 'allowed', data: 'test' });
|
|
381
|
+
|
|
382
|
+
assert.equal(result.type, 'allowed');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should reject using createAsyncPolicySchema', async () => {
|
|
386
|
+
const policyCheck = async (op) => op.type === 'allowed';
|
|
387
|
+
|
|
388
|
+
const schema = createAsyncPolicySchema(policyCheck);
|
|
389
|
+
|
|
390
|
+
await assert.rejects(async () => {
|
|
391
|
+
await schema.parseAsync({ type: 'denied', data: 'test' });
|
|
392
|
+
}, /denied by policy/i);
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
// =============================================================================
|
|
397
|
+
// Time Range Validation Tests
|
|
398
|
+
// =============================================================================
|
|
399
|
+
|
|
400
|
+
describe('Time Range Validator', () => {
|
|
401
|
+
it('should validate valid time range', () => {
|
|
402
|
+
const capsule = {
|
|
403
|
+
startTime: 1000,
|
|
404
|
+
endTime: 2000,
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const isValid = validateTimeRange(capsule);
|
|
408
|
+
assert.equal(isValid, true);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it('should reject invalid time range', () => {
|
|
412
|
+
const capsule = {
|
|
413
|
+
startTime: 2000,
|
|
414
|
+
endTime: 1000, // End before start
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
const isValid = validateTimeRange(capsule);
|
|
418
|
+
assert.equal(isValid, false);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should validate using RunCapsuleTimeRangeSchema', () => {
|
|
422
|
+
const capsule = {
|
|
423
|
+
startTime: 1000,
|
|
424
|
+
endTime: 2000,
|
|
425
|
+
other: 'data',
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const result = RunCapsuleTimeRangeSchema.parse(capsule);
|
|
429
|
+
assert.equal(result.startTime, 1000);
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
// =============================================================================
|
|
434
|
+
// Validator Utilities Tests
|
|
435
|
+
// =============================================================================
|
|
436
|
+
|
|
437
|
+
describe('Validator Utilities', () => {
|
|
438
|
+
it('should combine multiple validators', () => {
|
|
439
|
+
const validators = [
|
|
440
|
+
(data) => data.value > 0 || 'Value must be positive',
|
|
441
|
+
(data) => data.value < 100 || 'Value must be less than 100',
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
const validator = combineValidators(validators);
|
|
445
|
+
|
|
446
|
+
const result1 = validator({ value: 50 });
|
|
447
|
+
assert.equal(result1.valid, true);
|
|
448
|
+
assert.equal(result1.errors.length, 0);
|
|
449
|
+
|
|
450
|
+
const result2 = validator({ value: -10 });
|
|
451
|
+
assert.equal(result2.valid, false);
|
|
452
|
+
assert.ok(result2.errors.length > 0);
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('should create validation result', () => {
|
|
456
|
+
const result = createValidationResult(true, 'Success', { detail: 'test' });
|
|
457
|
+
|
|
458
|
+
assert.equal(result.valid, true);
|
|
459
|
+
assert.equal(result.message, 'Success');
|
|
460
|
+
assert.equal(result.context.detail, 'test');
|
|
461
|
+
assert.ok(result.timestamp);
|
|
462
|
+
});
|
|
463
|
+
});
|