xdbc 1.0.217 → 1.0.218
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/.gitattributes +8 -0
- package/.vscode/settings.json +3 -3
- package/.vscode/tasks.json +23 -23
- package/ASSESSMENT.md +249 -0
- package/README.md +131 -1
- package/__tests__/DBC/AE.test.ts +62 -62
- package/__tests__/DBC/ARRAY.test.ts +91 -91
- package/__tests__/DBC/DEFINED.test.ts +53 -53
- package/__tests__/DBC/DOM.test.ts +481 -0
- package/__tests__/DBC/Decorators.test.ts +367 -367
- package/__tests__/DBC/EQ.test.ts +13 -13
- package/__tests__/DBC/GREATER.test.ts +31 -31
- package/__tests__/DBC/HasAttribute.test.ts +60 -60
- package/__tests__/DBC/IF.test.ts +62 -62
- package/__tests__/DBC/INSTANCE.test.ts +13 -13
- package/__tests__/DBC/JSON.OP.test.ts +47 -47
- package/__tests__/DBC/JSON.Parse.test.ts +17 -17
- package/__tests__/DBC/OR.test.ts +14 -14
- package/__tests__/DBC/PLAIN_OBJECT.test.ts +109 -109
- package/__tests__/DBC/REGEX.test.ts +17 -17
- package/__tests__/DBC/TYPE.test.ts +13 -13
- package/__tests__/DBC/UNDEFINED.test.ts +45 -45
- package/__tests__/DBC/ZOD.test.ts +54 -54
- package/__tests__/DBC/onInfringement.test.ts +262 -0
- package/biome.json +40 -40
- package/dist/DBC/AE.js +172 -0
- package/dist/DBC/ARR/PLAIN_OBJECT.d.ts +0 -3
- package/dist/DBC/ARR/PLAIN_OBJECT.js +95 -0
- package/dist/DBC/ARRAY.d.ts +0 -3
- package/dist/DBC/ARRAY.js +90 -0
- package/dist/DBC/COMPARISON/GREATER.js +21 -0
- package/dist/DBC/COMPARISON/GREATER_OR_EQUAL.js +21 -0
- package/dist/DBC/COMPARISON/LESS.js +21 -0
- package/dist/DBC/COMPARISON/LESS_OR_EQUAL.js +21 -0
- package/dist/DBC/COMPARISON.js +98 -0
- package/dist/DBC/DEFINED.js +87 -0
- package/dist/DBC/DOM.d.ts +87 -0
- package/dist/DBC/DOM.js +223 -0
- package/dist/DBC/EQ/DIFFERENT.js +34 -0
- package/dist/DBC/EQ.js +101 -0
- package/dist/DBC/HasAttribute.js +101 -0
- package/dist/DBC/IF.js +96 -0
- package/dist/DBC/INSTANCE.js +122 -0
- package/dist/DBC/JSON.OP.js +120 -0
- package/dist/DBC/JSON.Parse.js +104 -0
- package/dist/DBC/OR.js +125 -0
- package/dist/DBC/REGEX.js +136 -0
- package/dist/DBC/TYPE.js +112 -0
- package/dist/DBC/UNDEFINED.js +87 -0
- package/dist/DBC/ZOD.js +99 -0
- package/dist/DBC.d.ts +18 -4
- package/dist/DBC.js +645 -0
- package/dist/Demo.d.ts +10 -0
- package/dist/Demo.js +713 -0
- package/dist/bundle.js +6140 -405
- package/dist/index.d.ts +22 -0
- package/dist/index.js +22 -0
- package/jest.config.js +32 -32
- package/package.json +71 -55
- package/src/DBC/AE.ts +269 -288
- package/src/DBC/ARR/PLAIN_OBJECT.ts +122 -133
- package/src/DBC/ARRAY.ts +117 -127
- package/src/DBC/COMPARISON/GREATER.ts +41 -46
- package/src/DBC/COMPARISON/GREATER_OR_EQUAL.ts +41 -45
- package/src/DBC/COMPARISON/LESS.ts +41 -45
- package/src/DBC/COMPARISON/LESS_OR_EQUAL.ts +41 -45
- package/src/DBC/COMPARISON.ts +149 -159
- package/src/DBC/DEFINED.ts +117 -122
- package/src/DBC/DOM.ts +291 -0
- package/src/DBC/EQ/DIFFERENT.ts +51 -57
- package/src/DBC/EQ.ts +154 -163
- package/src/DBC/HasAttribute.ts +149 -154
- package/src/DBC/IF.ts +173 -179
- package/src/DBC/INSTANCE.ts +168 -171
- package/src/DBC/JSON.OP.ts +178 -186
- package/src/DBC/JSON.Parse.ts +150 -157
- package/src/DBC/OR.ts +183 -187
- package/src/DBC/REGEX.ts +195 -196
- package/src/DBC/TYPE.ts +142 -149
- package/src/DBC/UNDEFINED.ts +115 -117
- package/src/DBC/ZOD.ts +130 -135
- package/src/DBC.ts +902 -904
- package/src/Demo.ts +537 -404
- package/src/index.ts +22 -0
- package/tsconfig.json +18 -18
- package/tsconfig.test.json +7 -7
- package/typedoc.json +16 -16
- package/webpack.config.js +27 -27
- package/Assessment.md +0 -507
|
@@ -1,367 +1,367 @@
|
|
|
1
|
-
import "reflect-metadata";
|
|
2
|
-
import { DBC } from "../../src/DBC";
|
|
3
|
-
import { AE } from "../../src/DBC/AE";
|
|
4
|
-
import { GREATER } from "../../src/DBC/COMPARISON/GREATER";
|
|
5
|
-
import { GREATER_OR_EQUAL } from "../../src/DBC/COMPARISON/GREATER_OR_EQUAL";
|
|
6
|
-
import { LESS } from "../../src/DBC/COMPARISON/LESS";
|
|
7
|
-
import { LESS_OR_EQUAL } from "../../src/DBC/COMPARISON/LESS_OR_EQUAL";
|
|
8
|
-
import { EQ } from "../../src/DBC/EQ";
|
|
9
|
-
import { DIFFERENT } from "../../src/DBC/EQ/DIFFERENT";
|
|
10
|
-
import { INSTANCE } from "../../src/DBC/INSTANCE";
|
|
11
|
-
import { OR } from "../../src/DBC/OR";
|
|
12
|
-
import { REGEX } from "../../src/DBC/REGEX";
|
|
13
|
-
import { TYPE } from "../../src/DBC/TYPE";
|
|
14
|
-
|
|
15
|
-
// Ensure a DBC instance is registered (DBC.ts module-level code does this via DBC.register)
|
|
16
|
-
const dbc: DBC = (window as any).WaXCode?.DBC;
|
|
17
|
-
|
|
18
|
-
describe("Decorator: @PRE (Preconditions)", () => {
|
|
19
|
-
class PreTestSubject {
|
|
20
|
-
@DBC.ParamvalueProvider
|
|
21
|
-
public regexPre(@REGEX.PRE(/^[A-Z]+$/) input: string): string {
|
|
22
|
-
return input;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
@DBC.ParamvalueProvider
|
|
26
|
-
public typePre(@TYPE.PRE("string") input: unknown): unknown {
|
|
27
|
-
return input;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
@DBC.ParamvalueProvider
|
|
31
|
-
public eqPre(@EQ.PRE("hello") input: string): string {
|
|
32
|
-
return input;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
@DBC.ParamvalueProvider
|
|
36
|
-
public greaterPre(@GREATER.PRE(5) input: number): number {
|
|
37
|
-
return input;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@DBC.ParamvalueProvider
|
|
41
|
-
public greaterOrEqualPre(@GREATER_OR_EQUAL.PRE(5) input: number): number {
|
|
42
|
-
return input;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@DBC.ParamvalueProvider
|
|
46
|
-
public lessPre(@LESS.PRE(10) input: number): number {
|
|
47
|
-
return input;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@DBC.ParamvalueProvider
|
|
51
|
-
public lessOrEqualPre(@LESS_OR_EQUAL.PRE(10) input: number): number {
|
|
52
|
-
return input;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@DBC.ParamvalueProvider
|
|
56
|
-
public differentPre(
|
|
57
|
-
@DIFFERENT.PRE("forbidden", undefined) input: string,
|
|
58
|
-
): string {
|
|
59
|
-
return input;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
@DBC.ParamvalueProvider
|
|
63
|
-
public instancePre(@INSTANCE.PRE(Date) input: unknown): unknown {
|
|
64
|
-
return input;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
@DBC.ParamvalueProvider
|
|
68
|
-
public aePre(@AE.PRE([new TYPE("string")]) input: unknown[]): unknown[] {
|
|
69
|
-
return input;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
@DBC.ParamvalueProvider
|
|
73
|
-
public orPre(@OR.PRE([new EQ("a"), new EQ("b")]) input: string): string {
|
|
74
|
-
return input;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const subject = new PreTestSubject();
|
|
79
|
-
|
|
80
|
-
// REGEX.PRE
|
|
81
|
-
test("REGEX.PRE passes with matching value", () => {
|
|
82
|
-
expect(() => subject.regexPre("ABC")).not.toThrow();
|
|
83
|
-
});
|
|
84
|
-
test("REGEX.PRE throws on non-matching value", () => {
|
|
85
|
-
expect(() => subject.regexPre("abc123")).toThrow();
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// TYPE.PRE
|
|
89
|
-
test("TYPE.PRE passes with correct type", () => {
|
|
90
|
-
expect(() => subject.typePre("hello")).not.toThrow();
|
|
91
|
-
});
|
|
92
|
-
test("TYPE.PRE throws with wrong type", () => {
|
|
93
|
-
expect(() => subject.typePre(42)).toThrow();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// EQ.PRE
|
|
97
|
-
test("EQ.PRE passes with equal value", () => {
|
|
98
|
-
expect(() => subject.eqPre("hello")).not.toThrow();
|
|
99
|
-
});
|
|
100
|
-
test("EQ.PRE throws with non-equal value", () => {
|
|
101
|
-
expect(() => subject.eqPre("world")).toThrow();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// GREATER.PRE
|
|
105
|
-
test("GREATER.PRE passes with value > reference", () => {
|
|
106
|
-
expect(() => subject.greaterPre(10)).not.toThrow();
|
|
107
|
-
});
|
|
108
|
-
test("GREATER.PRE throws with value <= reference", () => {
|
|
109
|
-
expect(() => subject.greaterPre(5)).toThrow();
|
|
110
|
-
});
|
|
111
|
-
test("GREATER.PRE throws with value < reference", () => {
|
|
112
|
-
expect(() => subject.greaterPre(3)).toThrow();
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// GREATER_OR_EQUAL.PRE
|
|
116
|
-
test("GREATER_OR_EQUAL.PRE passes with value >= reference", () => {
|
|
117
|
-
expect(() => subject.greaterOrEqualPre(5)).not.toThrow();
|
|
118
|
-
});
|
|
119
|
-
test("GREATER_OR_EQUAL.PRE passes with value > reference", () => {
|
|
120
|
-
expect(() => subject.greaterOrEqualPre(10)).not.toThrow();
|
|
121
|
-
});
|
|
122
|
-
test("GREATER_OR_EQUAL.PRE throws with value < reference", () => {
|
|
123
|
-
expect(() => subject.greaterOrEqualPre(3)).toThrow();
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// LESS.PRE
|
|
127
|
-
test("LESS.PRE passes with value < reference", () => {
|
|
128
|
-
expect(() => subject.lessPre(5)).not.toThrow();
|
|
129
|
-
});
|
|
130
|
-
test("LESS.PRE throws with value >= reference", () => {
|
|
131
|
-
expect(() => subject.lessPre(10)).toThrow();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// LESS_OR_EQUAL.PRE
|
|
135
|
-
test("LESS_OR_EQUAL.PRE passes with value <= reference", () => {
|
|
136
|
-
expect(() => subject.lessOrEqualPre(10)).not.toThrow();
|
|
137
|
-
});
|
|
138
|
-
test("LESS_OR_EQUAL.PRE throws with value > reference", () => {
|
|
139
|
-
expect(() => subject.lessOrEqualPre(15)).toThrow();
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// DIFFERENT.PRE
|
|
143
|
-
test("DIFFERENT.PRE passes with different value", () => {
|
|
144
|
-
expect(() => subject.differentPre("allowed")).not.toThrow();
|
|
145
|
-
});
|
|
146
|
-
test("DIFFERENT.PRE throws with equal value", () => {
|
|
147
|
-
expect(() => subject.differentPre("forbidden")).toThrow();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
// INSTANCE.PRE
|
|
151
|
-
test("INSTANCE.PRE passes with correct instance", () => {
|
|
152
|
-
expect(() => subject.instancePre(new Date())).not.toThrow();
|
|
153
|
-
});
|
|
154
|
-
test("INSTANCE.PRE throws with wrong instance", () => {
|
|
155
|
-
expect(() => subject.instancePre("not a date")).toThrow();
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// AE.PRE
|
|
159
|
-
test("AE.PRE passes with all elements matching", () => {
|
|
160
|
-
expect(() => subject.aePre(["a", "b", "c"])).not.toThrow();
|
|
161
|
-
});
|
|
162
|
-
test("AE.PRE throws when an element does not match", () => {
|
|
163
|
-
expect(() => subject.aePre(["a", 42, "c"])).toThrow();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// OR.PRE
|
|
167
|
-
test("OR.PRE passes when one contract is satisfied", () => {
|
|
168
|
-
expect(() => subject.orPre("a")).not.toThrow();
|
|
169
|
-
});
|
|
170
|
-
test("OR.PRE throws when no contract is satisfied", () => {
|
|
171
|
-
expect(() => subject.orPre("c")).toThrow();
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
describe("Decorator: @POST (Postconditions)", () => {
|
|
176
|
-
class PostTestSubject {
|
|
177
|
-
@REGEX.POST(/^OK:.*$/)
|
|
178
|
-
@DBC.ParamvalueProvider
|
|
179
|
-
public formatResponse(@TYPE.PRE("string") input: string): string {
|
|
180
|
-
return `OK:${input}`;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
@REGEX.POST(/^OK:.*$/)
|
|
184
|
-
public failingPost(): string {
|
|
185
|
-
return "FAIL";
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
@EQ.POST("hello")
|
|
189
|
-
public eqPost(returnThis: string): string {
|
|
190
|
-
return returnThis;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const subject = new PostTestSubject();
|
|
195
|
-
|
|
196
|
-
test("POST passes when return value matches", () => {
|
|
197
|
-
expect(() => subject.formatResponse("test")).not.toThrow();
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test("POST throws when return value does not match", () => {
|
|
201
|
-
expect(() => subject.failingPost()).toThrow();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("EQ.POST passes with matching return value", () => {
|
|
205
|
-
expect(() => subject.eqPost("hello")).not.toThrow();
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
test("EQ.POST throws with non-matching return value", () => {
|
|
209
|
-
expect(() => subject.eqPost("world")).toThrow();
|
|
210
|
-
});
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
describe("Decorator: @INVARIANT (Field contracts)", () => {
|
|
214
|
-
test("INVARIANT allows valid initial value", () => {
|
|
215
|
-
expect(() => {
|
|
216
|
-
class InvariantSubject {
|
|
217
|
-
@REGEX.INVARIANT(/^[A-Z]+$/)
|
|
218
|
-
public code = "ABC";
|
|
219
|
-
}
|
|
220
|
-
new InvariantSubject();
|
|
221
|
-
}).not.toThrow();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("INVARIANT throws on invalid initial value", () => {
|
|
225
|
-
expect(() => {
|
|
226
|
-
class InvariantSubject {
|
|
227
|
-
@REGEX.INVARIANT(/^[A-Z]+$/)
|
|
228
|
-
public code = "abc123";
|
|
229
|
-
}
|
|
230
|
-
new InvariantSubject();
|
|
231
|
-
}).toThrow();
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test("INVARIANT throws on invalid reassignment", () => {
|
|
235
|
-
class InvariantSubject {
|
|
236
|
-
@REGEX.INVARIANT(/^[A-Z]+$/)
|
|
237
|
-
public code = "ABC";
|
|
238
|
-
}
|
|
239
|
-
const obj = new InvariantSubject();
|
|
240
|
-
expect(() => {
|
|
241
|
-
obj.code = "invalid!";
|
|
242
|
-
}).toThrow();
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("INVARIANT allows valid reassignment", () => {
|
|
246
|
-
class InvariantSubject {
|
|
247
|
-
@REGEX.INVARIANT(/^[A-Z]+$/)
|
|
248
|
-
public code = "ABC";
|
|
249
|
-
}
|
|
250
|
-
const obj = new InvariantSubject();
|
|
251
|
-
expect(() => {
|
|
252
|
-
obj.code = "XYZ";
|
|
253
|
-
}).not.toThrow();
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
describe("Decorator: @ParamvalueProvider", () => {
|
|
258
|
-
test("ParamvalueProvider passes multiple parameter contracts", () => {
|
|
259
|
-
class MultiParam {
|
|
260
|
-
@DBC.ParamvalueProvider
|
|
261
|
-
public method(
|
|
262
|
-
@TYPE.PRE("string") a: string,
|
|
263
|
-
@TYPE.PRE("number") b: number,
|
|
264
|
-
): string {
|
|
265
|
-
return `${a}:${b}`;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const obj = new MultiParam();
|
|
269
|
-
expect(() => obj.method("hello", 42)).not.toThrow();
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test("ParamvalueProvider catches second parameter violation", () => {
|
|
273
|
-
class MultiParam {
|
|
274
|
-
@DBC.ParamvalueProvider
|
|
275
|
-
public method(
|
|
276
|
-
@TYPE.PRE("string") a: string,
|
|
277
|
-
@TYPE.PRE("number") b: number,
|
|
278
|
-
): string {
|
|
279
|
-
return `${a}:${b}`;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
const obj = new MultiParam();
|
|
283
|
-
expect(() => obj.method("hello", "not a number" as any)).toThrow();
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
describe("DBC infringement settings", () => {
|
|
288
|
-
class InfringementTest {
|
|
289
|
-
@DBC.ParamvalueProvider
|
|
290
|
-
public method(@TYPE.PRE("string") input: unknown): unknown {
|
|
291
|
-
return input;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
test("Violations throw DBC.Infringement by default", () => {
|
|
296
|
-
const obj = new InfringementTest();
|
|
297
|
-
expect(() => obj.method(42)).toThrow(/XDBC Infringement/);
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
test("Error message includes class name, method name, and parameter info", () => {
|
|
301
|
-
const obj = new InfringementTest();
|
|
302
|
-
try {
|
|
303
|
-
obj.method(42);
|
|
304
|
-
fail("Should have thrown");
|
|
305
|
-
} catch (e: any) {
|
|
306
|
-
expect(e.message).toContain("InfringementTest");
|
|
307
|
-
expect(e.message).toContain("method");
|
|
308
|
-
expect(e.message).toContain("1st parameter");
|
|
309
|
-
}
|
|
310
|
-
});
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
describe("DBC.register()", () => {
|
|
314
|
-
test("registers an instance at the default path", () => {
|
|
315
|
-
const custom = new DBC();
|
|
316
|
-
DBC.register(custom);
|
|
317
|
-
expect((window as any).WaXCode.DBC).toBe(custom);
|
|
318
|
-
// Restore original
|
|
319
|
-
DBC.register(dbc);
|
|
320
|
-
});
|
|
321
|
-
|
|
322
|
-
test("registers an instance at a custom path", () => {
|
|
323
|
-
const custom = new DBC();
|
|
324
|
-
DBC.register(custom, "TestVendor.DBC");
|
|
325
|
-
expect((window as any).TestVendor.DBC).toBe(custom);
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
test("constructor does not auto-mount to globalThis", () => {
|
|
329
|
-
const original = (window as any).WaXCode.DBC;
|
|
330
|
-
const orphan = new DBC();
|
|
331
|
-
// Constructor should NOT have replaced the registered instance
|
|
332
|
-
expect((window as any).WaXCode.DBC).toBe(original);
|
|
333
|
-
expect(orphan).not.toBe(original);
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
describe("DBC.isolated()", () => {
|
|
338
|
-
test("provides a temporary DBC instance and restores the original", () => {
|
|
339
|
-
const original = (window as any).WaXCode.DBC;
|
|
340
|
-
let isolatedInstance: DBC | undefined;
|
|
341
|
-
DBC.isolated((tempDbc) => {
|
|
342
|
-
isolatedInstance = tempDbc;
|
|
343
|
-
expect(tempDbc).not.toBe(original);
|
|
344
|
-
expect((window as any).WaXCode.DBC).toBe(tempDbc);
|
|
345
|
-
});
|
|
346
|
-
// After isolated() returns, the original is restored
|
|
347
|
-
expect((window as any).WaXCode.DBC).toBe(original);
|
|
348
|
-
expect(isolatedInstance).toBeDefined();
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
test("restores original even if callback throws", () => {
|
|
352
|
-
const original = (window as any).WaXCode.DBC;
|
|
353
|
-
expect(() => {
|
|
354
|
-
DBC.isolated(() => {
|
|
355
|
-
throw new Error("test error");
|
|
356
|
-
});
|
|
357
|
-
}).toThrow("test error");
|
|
358
|
-
expect((window as any).WaXCode.DBC).toBe(original);
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
test("isolated instance has independent settings", () => {
|
|
362
|
-
DBC.isolated((tempDbc) => {
|
|
363
|
-
tempDbc.executionSettings.checkPreconditions = false;
|
|
364
|
-
expect(dbc.executionSettings.checkPreconditions).toBe(true);
|
|
365
|
-
});
|
|
366
|
-
});
|
|
367
|
-
});
|
|
1
|
+
import "reflect-metadata";
|
|
2
|
+
import { DBC } from "../../src/DBC";
|
|
3
|
+
import { AE } from "../../src/DBC/AE";
|
|
4
|
+
import { GREATER } from "../../src/DBC/COMPARISON/GREATER";
|
|
5
|
+
import { GREATER_OR_EQUAL } from "../../src/DBC/COMPARISON/GREATER_OR_EQUAL";
|
|
6
|
+
import { LESS } from "../../src/DBC/COMPARISON/LESS";
|
|
7
|
+
import { LESS_OR_EQUAL } from "../../src/DBC/COMPARISON/LESS_OR_EQUAL";
|
|
8
|
+
import { EQ } from "../../src/DBC/EQ";
|
|
9
|
+
import { DIFFERENT } from "../../src/DBC/EQ/DIFFERENT";
|
|
10
|
+
import { INSTANCE } from "../../src/DBC/INSTANCE";
|
|
11
|
+
import { OR } from "../../src/DBC/OR";
|
|
12
|
+
import { REGEX } from "../../src/DBC/REGEX";
|
|
13
|
+
import { TYPE } from "../../src/DBC/TYPE";
|
|
14
|
+
|
|
15
|
+
// Ensure a DBC instance is registered (DBC.ts module-level code does this via DBC.register)
|
|
16
|
+
const dbc: DBC = (window as any).WaXCode?.DBC;
|
|
17
|
+
|
|
18
|
+
describe("Decorator: @PRE (Preconditions)", () => {
|
|
19
|
+
class PreTestSubject {
|
|
20
|
+
@DBC.ParamvalueProvider
|
|
21
|
+
public regexPre(@REGEX.PRE(/^[A-Z]+$/) input: string): string {
|
|
22
|
+
return input;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@DBC.ParamvalueProvider
|
|
26
|
+
public typePre(@TYPE.PRE("string") input: unknown): unknown {
|
|
27
|
+
return input;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@DBC.ParamvalueProvider
|
|
31
|
+
public eqPre(@EQ.PRE("hello") input: string): string {
|
|
32
|
+
return input;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@DBC.ParamvalueProvider
|
|
36
|
+
public greaterPre(@GREATER.PRE(5) input: number): number {
|
|
37
|
+
return input;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@DBC.ParamvalueProvider
|
|
41
|
+
public greaterOrEqualPre(@GREATER_OR_EQUAL.PRE(5) input: number): number {
|
|
42
|
+
return input;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@DBC.ParamvalueProvider
|
|
46
|
+
public lessPre(@LESS.PRE(10) input: number): number {
|
|
47
|
+
return input;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@DBC.ParamvalueProvider
|
|
51
|
+
public lessOrEqualPre(@LESS_OR_EQUAL.PRE(10) input: number): number {
|
|
52
|
+
return input;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@DBC.ParamvalueProvider
|
|
56
|
+
public differentPre(
|
|
57
|
+
@DIFFERENT.PRE("forbidden", undefined) input: string,
|
|
58
|
+
): string {
|
|
59
|
+
return input;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@DBC.ParamvalueProvider
|
|
63
|
+
public instancePre(@INSTANCE.PRE(Date) input: unknown): unknown {
|
|
64
|
+
return input;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@DBC.ParamvalueProvider
|
|
68
|
+
public aePre(@AE.PRE([new TYPE("string")]) input: unknown[]): unknown[] {
|
|
69
|
+
return input;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@DBC.ParamvalueProvider
|
|
73
|
+
public orPre(@OR.PRE([new EQ("a"), new EQ("b")]) input: string): string {
|
|
74
|
+
return input;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const subject = new PreTestSubject();
|
|
79
|
+
|
|
80
|
+
// REGEX.PRE
|
|
81
|
+
test("REGEX.PRE passes with matching value", () => {
|
|
82
|
+
expect(() => subject.regexPre("ABC")).not.toThrow();
|
|
83
|
+
});
|
|
84
|
+
test("REGEX.PRE throws on non-matching value", () => {
|
|
85
|
+
expect(() => subject.regexPre("abc123")).toThrow();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// TYPE.PRE
|
|
89
|
+
test("TYPE.PRE passes with correct type", () => {
|
|
90
|
+
expect(() => subject.typePre("hello")).not.toThrow();
|
|
91
|
+
});
|
|
92
|
+
test("TYPE.PRE throws with wrong type", () => {
|
|
93
|
+
expect(() => subject.typePre(42)).toThrow();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// EQ.PRE
|
|
97
|
+
test("EQ.PRE passes with equal value", () => {
|
|
98
|
+
expect(() => subject.eqPre("hello")).not.toThrow();
|
|
99
|
+
});
|
|
100
|
+
test("EQ.PRE throws with non-equal value", () => {
|
|
101
|
+
expect(() => subject.eqPre("world")).toThrow();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// GREATER.PRE
|
|
105
|
+
test("GREATER.PRE passes with value > reference", () => {
|
|
106
|
+
expect(() => subject.greaterPre(10)).not.toThrow();
|
|
107
|
+
});
|
|
108
|
+
test("GREATER.PRE throws with value <= reference", () => {
|
|
109
|
+
expect(() => subject.greaterPre(5)).toThrow();
|
|
110
|
+
});
|
|
111
|
+
test("GREATER.PRE throws with value < reference", () => {
|
|
112
|
+
expect(() => subject.greaterPre(3)).toThrow();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// GREATER_OR_EQUAL.PRE
|
|
116
|
+
test("GREATER_OR_EQUAL.PRE passes with value >= reference", () => {
|
|
117
|
+
expect(() => subject.greaterOrEqualPre(5)).not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
test("GREATER_OR_EQUAL.PRE passes with value > reference", () => {
|
|
120
|
+
expect(() => subject.greaterOrEqualPre(10)).not.toThrow();
|
|
121
|
+
});
|
|
122
|
+
test("GREATER_OR_EQUAL.PRE throws with value < reference", () => {
|
|
123
|
+
expect(() => subject.greaterOrEqualPre(3)).toThrow();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// LESS.PRE
|
|
127
|
+
test("LESS.PRE passes with value < reference", () => {
|
|
128
|
+
expect(() => subject.lessPre(5)).not.toThrow();
|
|
129
|
+
});
|
|
130
|
+
test("LESS.PRE throws with value >= reference", () => {
|
|
131
|
+
expect(() => subject.lessPre(10)).toThrow();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// LESS_OR_EQUAL.PRE
|
|
135
|
+
test("LESS_OR_EQUAL.PRE passes with value <= reference", () => {
|
|
136
|
+
expect(() => subject.lessOrEqualPre(10)).not.toThrow();
|
|
137
|
+
});
|
|
138
|
+
test("LESS_OR_EQUAL.PRE throws with value > reference", () => {
|
|
139
|
+
expect(() => subject.lessOrEqualPre(15)).toThrow();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// DIFFERENT.PRE
|
|
143
|
+
test("DIFFERENT.PRE passes with different value", () => {
|
|
144
|
+
expect(() => subject.differentPre("allowed")).not.toThrow();
|
|
145
|
+
});
|
|
146
|
+
test("DIFFERENT.PRE throws with equal value", () => {
|
|
147
|
+
expect(() => subject.differentPre("forbidden")).toThrow();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// INSTANCE.PRE
|
|
151
|
+
test("INSTANCE.PRE passes with correct instance", () => {
|
|
152
|
+
expect(() => subject.instancePre(new Date())).not.toThrow();
|
|
153
|
+
});
|
|
154
|
+
test("INSTANCE.PRE throws with wrong instance", () => {
|
|
155
|
+
expect(() => subject.instancePre("not a date")).toThrow();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// AE.PRE
|
|
159
|
+
test("AE.PRE passes with all elements matching", () => {
|
|
160
|
+
expect(() => subject.aePre(["a", "b", "c"])).not.toThrow();
|
|
161
|
+
});
|
|
162
|
+
test("AE.PRE throws when an element does not match", () => {
|
|
163
|
+
expect(() => subject.aePre(["a", 42, "c"])).toThrow();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// OR.PRE
|
|
167
|
+
test("OR.PRE passes when one contract is satisfied", () => {
|
|
168
|
+
expect(() => subject.orPre("a")).not.toThrow();
|
|
169
|
+
});
|
|
170
|
+
test("OR.PRE throws when no contract is satisfied", () => {
|
|
171
|
+
expect(() => subject.orPre("c")).toThrow();
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe("Decorator: @POST (Postconditions)", () => {
|
|
176
|
+
class PostTestSubject {
|
|
177
|
+
@REGEX.POST(/^OK:.*$/)
|
|
178
|
+
@DBC.ParamvalueProvider
|
|
179
|
+
public formatResponse(@TYPE.PRE("string") input: string): string {
|
|
180
|
+
return `OK:${input}`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@REGEX.POST(/^OK:.*$/)
|
|
184
|
+
public failingPost(): string {
|
|
185
|
+
return "FAIL";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
@EQ.POST("hello")
|
|
189
|
+
public eqPost(returnThis: string): string {
|
|
190
|
+
return returnThis;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const subject = new PostTestSubject();
|
|
195
|
+
|
|
196
|
+
test("POST passes when return value matches", () => {
|
|
197
|
+
expect(() => subject.formatResponse("test")).not.toThrow();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("POST throws when return value does not match", () => {
|
|
201
|
+
expect(() => subject.failingPost()).toThrow();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("EQ.POST passes with matching return value", () => {
|
|
205
|
+
expect(() => subject.eqPost("hello")).not.toThrow();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("EQ.POST throws with non-matching return value", () => {
|
|
209
|
+
expect(() => subject.eqPost("world")).toThrow();
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("Decorator: @INVARIANT (Field contracts)", () => {
|
|
214
|
+
test("INVARIANT allows valid initial value", () => {
|
|
215
|
+
expect(() => {
|
|
216
|
+
class InvariantSubject {
|
|
217
|
+
@REGEX.INVARIANT(/^[A-Z]+$/)
|
|
218
|
+
public code = "ABC";
|
|
219
|
+
}
|
|
220
|
+
new InvariantSubject();
|
|
221
|
+
}).not.toThrow();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("INVARIANT throws on invalid initial value", () => {
|
|
225
|
+
expect(() => {
|
|
226
|
+
class InvariantSubject {
|
|
227
|
+
@REGEX.INVARIANT(/^[A-Z]+$/)
|
|
228
|
+
public code = "abc123";
|
|
229
|
+
}
|
|
230
|
+
new InvariantSubject();
|
|
231
|
+
}).toThrow();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("INVARIANT throws on invalid reassignment", () => {
|
|
235
|
+
class InvariantSubject {
|
|
236
|
+
@REGEX.INVARIANT(/^[A-Z]+$/)
|
|
237
|
+
public code = "ABC";
|
|
238
|
+
}
|
|
239
|
+
const obj = new InvariantSubject();
|
|
240
|
+
expect(() => {
|
|
241
|
+
obj.code = "invalid!";
|
|
242
|
+
}).toThrow();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("INVARIANT allows valid reassignment", () => {
|
|
246
|
+
class InvariantSubject {
|
|
247
|
+
@REGEX.INVARIANT(/^[A-Z]+$/)
|
|
248
|
+
public code = "ABC";
|
|
249
|
+
}
|
|
250
|
+
const obj = new InvariantSubject();
|
|
251
|
+
expect(() => {
|
|
252
|
+
obj.code = "XYZ";
|
|
253
|
+
}).not.toThrow();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
describe("Decorator: @ParamvalueProvider", () => {
|
|
258
|
+
test("ParamvalueProvider passes multiple parameter contracts", () => {
|
|
259
|
+
class MultiParam {
|
|
260
|
+
@DBC.ParamvalueProvider
|
|
261
|
+
public method(
|
|
262
|
+
@TYPE.PRE("string") a: string,
|
|
263
|
+
@TYPE.PRE("number") b: number,
|
|
264
|
+
): string {
|
|
265
|
+
return `${a}:${b}`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const obj = new MultiParam();
|
|
269
|
+
expect(() => obj.method("hello", 42)).not.toThrow();
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("ParamvalueProvider catches second parameter violation", () => {
|
|
273
|
+
class MultiParam {
|
|
274
|
+
@DBC.ParamvalueProvider
|
|
275
|
+
public method(
|
|
276
|
+
@TYPE.PRE("string") a: string,
|
|
277
|
+
@TYPE.PRE("number") b: number,
|
|
278
|
+
): string {
|
|
279
|
+
return `${a}:${b}`;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const obj = new MultiParam();
|
|
283
|
+
expect(() => obj.method("hello", "not a number" as any)).toThrow();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
describe("DBC infringement settings", () => {
|
|
288
|
+
class InfringementTest {
|
|
289
|
+
@DBC.ParamvalueProvider
|
|
290
|
+
public method(@TYPE.PRE("string") input: unknown): unknown {
|
|
291
|
+
return input;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
test("Violations throw DBC.Infringement by default", () => {
|
|
296
|
+
const obj = new InfringementTest();
|
|
297
|
+
expect(() => obj.method(42)).toThrow(/XDBC Infringement/);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("Error message includes class name, method name, and parameter info", () => {
|
|
301
|
+
const obj = new InfringementTest();
|
|
302
|
+
try {
|
|
303
|
+
obj.method(42);
|
|
304
|
+
fail("Should have thrown");
|
|
305
|
+
} catch (e: any) {
|
|
306
|
+
expect(e.message).toContain("InfringementTest");
|
|
307
|
+
expect(e.message).toContain("method");
|
|
308
|
+
expect(e.message).toContain("1st parameter");
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe("DBC.register()", () => {
|
|
314
|
+
test("registers an instance at the default path", () => {
|
|
315
|
+
const custom = new DBC();
|
|
316
|
+
DBC.register(custom);
|
|
317
|
+
expect((window as any).WaXCode.DBC).toBe(custom);
|
|
318
|
+
// Restore original
|
|
319
|
+
DBC.register(dbc);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("registers an instance at a custom path", () => {
|
|
323
|
+
const custom = new DBC();
|
|
324
|
+
DBC.register(custom, "TestVendor.DBC");
|
|
325
|
+
expect((window as any).TestVendor.DBC).toBe(custom);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("constructor does not auto-mount to globalThis", () => {
|
|
329
|
+
const original = (window as any).WaXCode.DBC;
|
|
330
|
+
const orphan = new DBC();
|
|
331
|
+
// Constructor should NOT have replaced the registered instance
|
|
332
|
+
expect((window as any).WaXCode.DBC).toBe(original);
|
|
333
|
+
expect(orphan).not.toBe(original);
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe("DBC.isolated()", () => {
|
|
338
|
+
test("provides a temporary DBC instance and restores the original", () => {
|
|
339
|
+
const original = (window as any).WaXCode.DBC;
|
|
340
|
+
let isolatedInstance: DBC | undefined;
|
|
341
|
+
DBC.isolated((tempDbc) => {
|
|
342
|
+
isolatedInstance = tempDbc;
|
|
343
|
+
expect(tempDbc).not.toBe(original);
|
|
344
|
+
expect((window as any).WaXCode.DBC).toBe(tempDbc);
|
|
345
|
+
});
|
|
346
|
+
// After isolated() returns, the original is restored
|
|
347
|
+
expect((window as any).WaXCode.DBC).toBe(original);
|
|
348
|
+
expect(isolatedInstance).toBeDefined();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
test("restores original even if callback throws", () => {
|
|
352
|
+
const original = (window as any).WaXCode.DBC;
|
|
353
|
+
expect(() => {
|
|
354
|
+
DBC.isolated(() => {
|
|
355
|
+
throw new Error("test error");
|
|
356
|
+
});
|
|
357
|
+
}).toThrow("test error");
|
|
358
|
+
expect((window as any).WaXCode.DBC).toBe(original);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("isolated instance has independent settings", () => {
|
|
362
|
+
DBC.isolated((tempDbc) => {
|
|
363
|
+
tempDbc.executionSettings.checkPreconditions = false;
|
|
364
|
+
expect(dbc.executionSettings.checkPreconditions).toBe(true);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|