@pdtf/schemas 3.4.1-5 → 3.4.1-6
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/index.js +85 -33
- package/package.json +1 -1
- package/src/schemas/v3/combined.json +5 -1
- package/src/schemas/v3/overlays/nts.json +4 -1
- package/src/schemas/v3/overlays/nts2.json +4 -1
- package/src/schemas/v3/overlays/ntsl.json +4 -1
- package/src/schemas/v3/overlays/ntsl2.json +4 -1
- package/src/tests/v3/caching.test.js +317 -0
- package/src/tests/v3/transactionSchema.test.js +78 -0
package/index.js
CHANGED
|
@@ -10,6 +10,9 @@ const ajv = new Ajv({
|
|
|
10
10
|
// Adds date formats among other types to the validator.
|
|
11
11
|
addFormats(ajv);
|
|
12
12
|
|
|
13
|
+
// Enhanced caching structure for subschemas and validators
|
|
14
|
+
const schemaCache = new Map();
|
|
15
|
+
|
|
13
16
|
const verifiedClaimsSchema = require("./src/schemas/verifiedClaims/pdtf-verified-claims.json");
|
|
14
17
|
const v2CoreSchema = require("./src/schemas/v2/pdtf-transaction.json");
|
|
15
18
|
const v3CoreSchema = require("./src/schemas/v3/pdtf-transaction.json");
|
|
@@ -180,32 +183,74 @@ const generateOverlayKey = (overlays) => {
|
|
|
180
183
|
return overlays.join(".");
|
|
181
184
|
};
|
|
182
185
|
|
|
186
|
+
// Enhanced caching function that stores both subschemas and validators
|
|
187
|
+
const getCachedSchemaData = (path, schemaId, overlays) => {
|
|
188
|
+
const overlayKey = generateOverlayKey(overlays);
|
|
189
|
+
const cacheKey = `${path}-${schemaId}-${overlayKey}`;
|
|
190
|
+
|
|
191
|
+
let cached = schemaCache.get(cacheKey);
|
|
192
|
+
if (!cached) {
|
|
193
|
+
// Compute subschema using the original logic
|
|
194
|
+
const sourceSchema = getTransactionSchema(schemaId, overlays);
|
|
195
|
+
const pathArray = path.split("/").slice(1);
|
|
196
|
+
let subSchema = sourceSchema;
|
|
197
|
+
|
|
198
|
+
if (pathArray.length >= 1) {
|
|
199
|
+
subSchema = pathArray.reduce((schema, pathElement) => {
|
|
200
|
+
if (!schema) return undefined;
|
|
201
|
+
const { type, items, properties, oneOf } = schema;
|
|
202
|
+
if (type === "array") return items;
|
|
203
|
+
if (properties?.[pathElement]) return properties[pathElement];
|
|
204
|
+
if (oneOf) {
|
|
205
|
+
let matchingProperty;
|
|
206
|
+
oneOf.forEach((aOneOf) => {
|
|
207
|
+
if (aOneOf.type === "array" && !Number.isNaN(pathElement)) {
|
|
208
|
+
matchingProperty = aOneOf.items;
|
|
209
|
+
} else if (aOneOf.properties?.[pathElement]) {
|
|
210
|
+
matchingProperty = aOneOf.properties?.[pathElement];
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
if (matchingProperty) return matchingProperty;
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}, sourceSchema);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Add schema to AJV and get validator
|
|
220
|
+
// Only add valid schemas to AJV
|
|
221
|
+
let validator;
|
|
222
|
+
if (subSchema && typeof subSchema === 'object') {
|
|
223
|
+
// Check if schema already exists in AJV to avoid duplicates
|
|
224
|
+
validator = ajv.getSchema(cacheKey);
|
|
225
|
+
if (!validator) {
|
|
226
|
+
ajv.addSchema(subSchema, cacheKey);
|
|
227
|
+
validator = ajv.getSchema(cacheKey);
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
// For invalid paths, create a validator that always fails
|
|
231
|
+
validator = () => false;
|
|
232
|
+
validator.errors = [`Invalid path: schema is ${subSchema}`];
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
cached = {
|
|
236
|
+
subSchema,
|
|
237
|
+
validator,
|
|
238
|
+
cacheKey
|
|
239
|
+
};
|
|
240
|
+
schemaCache.set(cacheKey, cached);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return cached;
|
|
244
|
+
};
|
|
245
|
+
|
|
183
246
|
const getValidator = (schemaId, overlays) => {
|
|
184
247
|
return getSubschemaValidator("", schemaId, overlays);
|
|
185
248
|
};
|
|
186
249
|
|
|
187
|
-
// common functions for v1 and v2
|
|
250
|
+
// common functions for v1 and v2 - now with caching
|
|
188
251
|
const getSubschema = (path, schemaId, overlays) => {
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
if (pathArray.length < 1) return sourceSchema;
|
|
192
|
-
return pathArray.reduce((schema, pathElement) => {
|
|
193
|
-
const { type, items, properties, oneOf } = schema;
|
|
194
|
-
if (type === "array") return items;
|
|
195
|
-
if (properties?.[pathElement]) return properties[pathElement];
|
|
196
|
-
if (oneOf) {
|
|
197
|
-
let matchingProperty;
|
|
198
|
-
oneOf.forEach((aOneOf) => {
|
|
199
|
-
if (aOneOf.type === "array" && !Number.isNaN(pathElement)) {
|
|
200
|
-
matchingProperty = aOneOf.items;
|
|
201
|
-
} else if (aOneOf.properties?.[pathElement]) {
|
|
202
|
-
matchingProperty = aOneOf.properties?.[pathElement];
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
if (matchingProperty) return matchingProperty;
|
|
206
|
-
}
|
|
207
|
-
return undefined;
|
|
208
|
-
}, sourceSchema);
|
|
252
|
+
const cached = getCachedSchemaData(path, schemaId, overlays);
|
|
253
|
+
return cached.subSchema;
|
|
209
254
|
};
|
|
210
255
|
|
|
211
256
|
const isPathValid = (path, schemaId, overlays) => {
|
|
@@ -217,18 +262,8 @@ const isPathValid = (path, schemaId, overlays) => {
|
|
|
217
262
|
};
|
|
218
263
|
|
|
219
264
|
const getSubschemaValidator = (path, schemaId, overlays) => {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
// see if we can retrieve the schema by path, schemaId and overlays
|
|
223
|
-
const cacheKey = `${path}-${schemaId}-${overlayKey}`;
|
|
224
|
-
let validator = ajv.getSchema(cacheKey);
|
|
225
|
-
// retrieve whole schema by $id if available
|
|
226
|
-
if (!validator && subSchema.$id) validator = ajv.getSchema(cacheKey);
|
|
227
|
-
if (!validator) {
|
|
228
|
-
ajv.addSchema(subSchema, cacheKey);
|
|
229
|
-
validator = ajv.getSchema(cacheKey);
|
|
230
|
-
}
|
|
231
|
-
return validator;
|
|
265
|
+
const cached = getCachedSchemaData(path, schemaId, overlays);
|
|
266
|
+
return cached.validator;
|
|
232
267
|
};
|
|
233
268
|
|
|
234
269
|
// v1, deprecated
|
|
@@ -312,6 +347,20 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
|
|
|
312
347
|
return validationErrorsArr;
|
|
313
348
|
};
|
|
314
349
|
|
|
350
|
+
// Cache management functions
|
|
351
|
+
const getCacheStats = () => ({
|
|
352
|
+
totalEntries: schemaCache.size,
|
|
353
|
+
cacheKeys: Array.from(schemaCache.keys())
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const clearSchemaCache = () => {
|
|
357
|
+
// Clear our cache
|
|
358
|
+
schemaCache.clear();
|
|
359
|
+
// Also clear AJV's internal cache to prevent duplicates
|
|
360
|
+
// Note: This removes all schemas from AJV, which is what we want for testing
|
|
361
|
+
ajv.removeSchema();
|
|
362
|
+
};
|
|
363
|
+
|
|
315
364
|
module.exports = {
|
|
316
365
|
ajv,
|
|
317
366
|
getTransactionSchema,
|
|
@@ -324,4 +373,7 @@ module.exports = {
|
|
|
324
373
|
validateVerifiedClaims,
|
|
325
374
|
overlaysMap,
|
|
326
375
|
extensionOverlays,
|
|
376
|
+
// New cache management functions
|
|
377
|
+
getCacheStats,
|
|
378
|
+
clearSchemaCache,
|
|
327
379
|
};
|
package/package.json
CHANGED
|
@@ -20071,7 +20071,11 @@
|
|
|
20071
20071
|
}
|
|
20072
20072
|
]
|
|
20073
20073
|
}
|
|
20074
|
-
}
|
|
20074
|
+
},
|
|
20075
|
+
"ntsRequired": ["offMainsDrainageSystem"],
|
|
20076
|
+
"nts2Required": ["offMainsDrainageSystem"],
|
|
20077
|
+
"ntslRequired": ["offMainsDrainageSystem"],
|
|
20078
|
+
"ntsl2Required": ["offMainsDrainageSystem"]
|
|
20075
20079
|
}
|
|
20076
20080
|
]
|
|
20077
20081
|
},
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
const {
|
|
2
|
+
getSubschema,
|
|
3
|
+
getSubschemaValidator,
|
|
4
|
+
getCacheStats,
|
|
5
|
+
clearSchemaCache,
|
|
6
|
+
isPathValid,
|
|
7
|
+
} = require("../../../index");
|
|
8
|
+
|
|
9
|
+
describe("Schema Caching System", () => {
|
|
10
|
+
const schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
|
|
11
|
+
const testPath = "/propertyPack/surveys";
|
|
12
|
+
const overlays = ["baspiV5", "ta6ed4"];
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
clearSchemaCache();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("Cache Hit/Miss Behavior", () => {
|
|
19
|
+
test("should start with empty cache", () => {
|
|
20
|
+
const stats = getCacheStats();
|
|
21
|
+
expect(stats.totalEntries).toBe(0);
|
|
22
|
+
expect(stats.cacheKeys).toEqual([]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("should cache subschema on first call", () => {
|
|
26
|
+
// First call should populate cache
|
|
27
|
+
const schema1 = getSubschema(testPath, schemaId, overlays);
|
|
28
|
+
|
|
29
|
+
const stats = getCacheStats();
|
|
30
|
+
expect(stats.totalEntries).toBe(1);
|
|
31
|
+
expect(stats.cacheKeys).toHaveLength(1);
|
|
32
|
+
expect(stats.cacheKeys[0]).toContain(testPath);
|
|
33
|
+
expect(stats.cacheKeys[0]).toContain("baspiV5.ta6ed4");
|
|
34
|
+
|
|
35
|
+
// Second call should return cached version
|
|
36
|
+
const schema2 = getSubschema(testPath, schemaId, overlays);
|
|
37
|
+
|
|
38
|
+
// Same object reference indicates cache hit
|
|
39
|
+
expect(schema1).toBe(schema2);
|
|
40
|
+
|
|
41
|
+
// Cache size shouldn't change
|
|
42
|
+
const stats2 = getCacheStats();
|
|
43
|
+
expect(stats2.totalEntries).toBe(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("should cache validator on first call", () => {
|
|
47
|
+
// First call should populate cache
|
|
48
|
+
const validator1 = getSubschemaValidator(testPath, schemaId, overlays);
|
|
49
|
+
|
|
50
|
+
const stats = getCacheStats();
|
|
51
|
+
expect(stats.totalEntries).toBe(1);
|
|
52
|
+
|
|
53
|
+
// Second call should return cached version
|
|
54
|
+
const validator2 = getSubschemaValidator(testPath, schemaId, overlays);
|
|
55
|
+
|
|
56
|
+
// Same object reference indicates cache hit
|
|
57
|
+
expect(validator1).toBe(validator2);
|
|
58
|
+
|
|
59
|
+
// Cache size shouldn't change
|
|
60
|
+
const stats2 = getCacheStats();
|
|
61
|
+
expect(stats2.totalEntries).toBe(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("should create separate cache entries for different paths", () => {
|
|
65
|
+
const path1 = "/participants";
|
|
66
|
+
const path2 = "/propertyPack/surveys";
|
|
67
|
+
const path3 = "/status";
|
|
68
|
+
|
|
69
|
+
getSubschema(path1, schemaId, overlays);
|
|
70
|
+
getSubschema(path2, schemaId, overlays);
|
|
71
|
+
getSubschema(path3, schemaId, overlays);
|
|
72
|
+
|
|
73
|
+
const stats = getCacheStats();
|
|
74
|
+
expect(stats.totalEntries).toBe(3);
|
|
75
|
+
expect(stats.cacheKeys.some(key => key.includes(path1))).toBe(true);
|
|
76
|
+
expect(stats.cacheKeys.some(key => key.includes(path2))).toBe(true);
|
|
77
|
+
expect(stats.cacheKeys.some(key => key.includes(path3))).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("should create separate cache entries for different overlays", () => {
|
|
81
|
+
const overlays1 = ["baspiV5"];
|
|
82
|
+
const overlays2 = ["ta6ed4"];
|
|
83
|
+
const overlays3 = ["baspiV5", "ta6ed4"];
|
|
84
|
+
|
|
85
|
+
getSubschema(testPath, schemaId, overlays1);
|
|
86
|
+
getSubschema(testPath, schemaId, overlays2);
|
|
87
|
+
getSubschema(testPath, schemaId, overlays3);
|
|
88
|
+
|
|
89
|
+
const stats = getCacheStats();
|
|
90
|
+
expect(stats.totalEntries).toBe(3);
|
|
91
|
+
expect(stats.cacheKeys.some(key => key.includes("baspiV5"))).toBe(true);
|
|
92
|
+
expect(stats.cacheKeys.some(key => key.includes("ta6ed4"))).toBe(true);
|
|
93
|
+
expect(stats.cacheKeys.some(key => key.includes("baspiV5.ta6ed4"))).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("should handle null/empty overlays consistently", () => {
|
|
97
|
+
getSubschema(testPath, schemaId, null);
|
|
98
|
+
getSubschema(testPath, schemaId, []);
|
|
99
|
+
getSubschema(testPath, schemaId, undefined);
|
|
100
|
+
|
|
101
|
+
const stats = getCacheStats();
|
|
102
|
+
// Should create separate entries for each null-equivalent
|
|
103
|
+
expect(stats.totalEntries).toBeGreaterThan(0);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("Cache Management Functions", () => {
|
|
108
|
+
test("getCacheStats should return accurate information", () => {
|
|
109
|
+
// Start with empty cache
|
|
110
|
+
let stats = getCacheStats();
|
|
111
|
+
expect(stats).toHaveProperty("totalEntries");
|
|
112
|
+
expect(stats).toHaveProperty("cacheKeys");
|
|
113
|
+
expect(stats.totalEntries).toBe(0);
|
|
114
|
+
expect(Array.isArray(stats.cacheKeys)).toBe(true);
|
|
115
|
+
|
|
116
|
+
// Add some entries
|
|
117
|
+
getSubschema("/property", schemaId, ["baspiV5"]);
|
|
118
|
+
getSubschema("/property/propertyPack", schemaId, ["ta6ed4"]);
|
|
119
|
+
|
|
120
|
+
stats = getCacheStats();
|
|
121
|
+
expect(stats.totalEntries).toBe(2);
|
|
122
|
+
expect(stats.cacheKeys).toHaveLength(2);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("clearSchemaCache should empty the cache", () => {
|
|
126
|
+
// Populate cache
|
|
127
|
+
getSubschema(testPath, schemaId, overlays);
|
|
128
|
+
getSubschemaValidator(testPath, schemaId, overlays);
|
|
129
|
+
|
|
130
|
+
let stats = getCacheStats();
|
|
131
|
+
expect(stats.totalEntries).toBe(1);
|
|
132
|
+
|
|
133
|
+
// Clear cache
|
|
134
|
+
clearSchemaCache();
|
|
135
|
+
|
|
136
|
+
stats = getCacheStats();
|
|
137
|
+
expect(stats.totalEntries).toBe(0);
|
|
138
|
+
expect(stats.cacheKeys).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("should rebuild cache after clearing", () => {
|
|
142
|
+
// Populate cache
|
|
143
|
+
const schema1 = getSubschema(testPath, schemaId, overlays);
|
|
144
|
+
|
|
145
|
+
// Clear cache
|
|
146
|
+
clearSchemaCache();
|
|
147
|
+
|
|
148
|
+
// Get schema again - should recompute
|
|
149
|
+
const schema2 = getSubschema(testPath, schemaId, overlays);
|
|
150
|
+
|
|
151
|
+
// Should be equivalent content
|
|
152
|
+
expect(schema1).toEqual(schema2);
|
|
153
|
+
// Note: The actual schema content may be the same object due to how schemas are stored,
|
|
154
|
+
// but cache entry should be recreated
|
|
155
|
+
|
|
156
|
+
const stats = getCacheStats();
|
|
157
|
+
expect(stats.totalEntries).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("Schema Identity and Validator Consistency", () => {
|
|
162
|
+
test("cached schemas should be identical objects", () => {
|
|
163
|
+
const schema1 = getSubschema(testPath, schemaId, overlays);
|
|
164
|
+
const schema2 = getSubschema(testPath, schemaId, overlays);
|
|
165
|
+
const schema3 = getSubschema(testPath, schemaId, overlays);
|
|
166
|
+
|
|
167
|
+
expect(schema1).toBe(schema2);
|
|
168
|
+
expect(schema2).toBe(schema3);
|
|
169
|
+
expect(schema1).toBe(schema3);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("cached validators should be identical objects", () => {
|
|
173
|
+
const validator1 = getSubschemaValidator(testPath, schemaId, overlays);
|
|
174
|
+
const validator2 = getSubschemaValidator(testPath, schemaId, overlays);
|
|
175
|
+
const validator3 = getSubschemaValidator(testPath, schemaId, overlays);
|
|
176
|
+
|
|
177
|
+
expect(validator1).toBe(validator2);
|
|
178
|
+
expect(validator2).toBe(validator3);
|
|
179
|
+
expect(validator1).toBe(validator3);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("mixed calls should use same cached data", () => {
|
|
183
|
+
// First get schema
|
|
184
|
+
const schema = getSubschema(testPath, schemaId, overlays);
|
|
185
|
+
|
|
186
|
+
// Then get validator - should use same cache entry
|
|
187
|
+
const validator = getSubschemaValidator(testPath, schemaId, overlays);
|
|
188
|
+
|
|
189
|
+
// Then get schema again
|
|
190
|
+
const schema2 = getSubschema(testPath, schemaId, overlays);
|
|
191
|
+
|
|
192
|
+
expect(schema).toBe(schema2);
|
|
193
|
+
expect(typeof validator).toBe("function");
|
|
194
|
+
|
|
195
|
+
// Should still be only one cache entry
|
|
196
|
+
const stats = getCacheStats();
|
|
197
|
+
expect(stats.totalEntries).toBe(1);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("validators should work correctly with cached schemas", () => {
|
|
201
|
+
// Use a path that exists - status
|
|
202
|
+
const validator = getSubschemaValidator("/status", schemaId, overlays);
|
|
203
|
+
|
|
204
|
+
// Test that the validator is a function
|
|
205
|
+
expect(typeof validator).toBe("function");
|
|
206
|
+
|
|
207
|
+
// Test with a valid status value
|
|
208
|
+
const result = validator("active");
|
|
209
|
+
|
|
210
|
+
// The validator should exist and be callable
|
|
211
|
+
expect(typeof result).toBe("boolean");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("Integration with Existing Functions", () => {
|
|
216
|
+
test("isPathValid should work with cached schemas", () => {
|
|
217
|
+
// Use a path that definitely exists
|
|
218
|
+
const validPath = "/participants";
|
|
219
|
+
|
|
220
|
+
// First call will populate cache
|
|
221
|
+
const isValid1 = isPathValid(validPath, schemaId, overlays);
|
|
222
|
+
|
|
223
|
+
// Second call should use cache
|
|
224
|
+
const isValid2 = isPathValid(validPath, schemaId, overlays);
|
|
225
|
+
|
|
226
|
+
expect(isValid1).toBe(isValid2);
|
|
227
|
+
expect(isValid1).toBe(true);
|
|
228
|
+
|
|
229
|
+
// Should have cached entry
|
|
230
|
+
const stats = getCacheStats();
|
|
231
|
+
expect(stats.totalEntries).toBeGreaterThan(0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
test("should handle invalid paths gracefully", () => {
|
|
235
|
+
const invalidPath = "/nonexistent/path/here";
|
|
236
|
+
|
|
237
|
+
const schema = getSubschema(invalidPath, schemaId, overlays);
|
|
238
|
+
const validator = getSubschemaValidator(invalidPath, schemaId, overlays);
|
|
239
|
+
const isValid = isPathValid(invalidPath, schemaId, overlays);
|
|
240
|
+
|
|
241
|
+
expect(schema).toBeUndefined();
|
|
242
|
+
expect(typeof validator).toBe("function");
|
|
243
|
+
expect(isValid).toBe(false);
|
|
244
|
+
|
|
245
|
+
// Should still cache the result
|
|
246
|
+
const stats = getCacheStats();
|
|
247
|
+
expect(stats.totalEntries).toBe(1);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("Performance Characteristics", () => {
|
|
252
|
+
test("should demonstrate significant speedup on cached calls", () => {
|
|
253
|
+
const iterations = 10;
|
|
254
|
+
|
|
255
|
+
// First call (cold cache)
|
|
256
|
+
const start1 = process.hrtime.bigint();
|
|
257
|
+
for (let i = 0; i < iterations; i++) {
|
|
258
|
+
clearSchemaCache();
|
|
259
|
+
getSubschema(testPath, schemaId, overlays);
|
|
260
|
+
}
|
|
261
|
+
const end1 = process.hrtime.bigint();
|
|
262
|
+
const coldTime = Number(end1 - start1) / 1_000_000; // Convert to milliseconds
|
|
263
|
+
|
|
264
|
+
// Warm up cache
|
|
265
|
+
getSubschema(testPath, schemaId, overlays);
|
|
266
|
+
|
|
267
|
+
// Subsequent calls (warm cache)
|
|
268
|
+
const start2 = process.hrtime.bigint();
|
|
269
|
+
for (let i = 0; i < iterations; i++) {
|
|
270
|
+
getSubschema(testPath, schemaId, overlays);
|
|
271
|
+
}
|
|
272
|
+
const end2 = process.hrtime.bigint();
|
|
273
|
+
const warmTime = Number(end2 - start2) / 1_000_000; // Convert to milliseconds
|
|
274
|
+
|
|
275
|
+
// Warm cache should be significantly faster
|
|
276
|
+
expect(warmTime).toBeLessThan(coldTime / 10); // At least 10x faster
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("cache should scale well with multiple entries", () => {
|
|
280
|
+
const paths = [
|
|
281
|
+
"/participants",
|
|
282
|
+
"/status",
|
|
283
|
+
"/propertyPack/surveys",
|
|
284
|
+
"/propertyPack/valuations",
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
const overlaySet = [
|
|
288
|
+
["baspiV5"],
|
|
289
|
+
["ta6ed4"],
|
|
290
|
+
["baspiV5", "ta6ed4"],
|
|
291
|
+
];
|
|
292
|
+
|
|
293
|
+
// Populate cache with multiple combinations
|
|
294
|
+
paths.forEach(path => {
|
|
295
|
+
overlaySet.forEach(overlays => {
|
|
296
|
+
getSubschema(path, schemaId, overlays);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const stats = getCacheStats();
|
|
301
|
+
expect(stats.totalEntries).toBe(paths.length * overlaySet.length);
|
|
302
|
+
|
|
303
|
+
// All subsequent calls should be fast
|
|
304
|
+
const start = process.hrtime.bigint();
|
|
305
|
+
paths.forEach(path => {
|
|
306
|
+
overlaySet.forEach(overlays => {
|
|
307
|
+
getSubschema(path, schemaId, overlays);
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
const end = process.hrtime.bigint();
|
|
311
|
+
const timeMs = Number(end - start) / 1_000_000;
|
|
312
|
+
|
|
313
|
+
// Should be very fast (less than 1ms total for all cached calls)
|
|
314
|
+
expect(timeMs).toBeLessThan(1);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
@@ -448,3 +448,81 @@ test("validates a valid contract", () => {
|
|
|
448
448
|
const isValid = validator(data);
|
|
449
449
|
expect(isValid).toBe(true);
|
|
450
450
|
});
|
|
451
|
+
|
|
452
|
+
test("waterAndDrainage state with mainsFoulDrainage No missing offMainsDrainageSystem is now invalid under nts2023 overlay", () => {
|
|
453
|
+
const validator = getValidator(schemaId, ["nts2023"]);
|
|
454
|
+
const clonedExampleTransaction = JSON.parse(
|
|
455
|
+
JSON.stringify(exampleTransaction)
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
// Set the waterAndDrainage to the state from the user request
|
|
459
|
+
// This SHOULD be invalid because offMainsDrainageSystem is required when mainsFoulDrainage.yesNo is "No"
|
|
460
|
+
clonedExampleTransaction.propertyPack.waterAndDrainage = {
|
|
461
|
+
"water": {
|
|
462
|
+
"mainsWater": {
|
|
463
|
+
"yesNo": "Yes",
|
|
464
|
+
"waterMeter": {
|
|
465
|
+
"isSupplyMetered": "No"
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
"drainage": {
|
|
470
|
+
"mainsSurfaceWaterDrainage": {
|
|
471
|
+
"yesNo": "Yes"
|
|
472
|
+
},
|
|
473
|
+
"mainsFoulDrainage": {
|
|
474
|
+
"yesNo": "No"
|
|
475
|
+
// Missing offMainsDrainageSystem - this should make validation fail
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const isValid = validator(clonedExampleTransaction);
|
|
481
|
+
expect(isValid).toBe(false);
|
|
482
|
+
|
|
483
|
+
// Check that the validation error is about missing offMainsDrainageSystem
|
|
484
|
+
const relevantError = validator.errors.find(error =>
|
|
485
|
+
error.instancePath.includes('mainsFoulDrainage') &&
|
|
486
|
+
error.message.includes('offMainsDrainageSystem')
|
|
487
|
+
);
|
|
488
|
+
expect(relevantError).toBeDefined();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
test("waterAndDrainage state is invalid under nts2023 overlay when mainsFoulDrainage yesNo is Not known", () => {
|
|
492
|
+
const validator = getValidator(schemaId, ["nts2023"]);
|
|
493
|
+
const clonedExampleTransaction = JSON.parse(
|
|
494
|
+
JSON.stringify(exampleTransaction)
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
// Set the waterAndDrainage to an actually invalid state for nts2023
|
|
498
|
+
// The nts overlay restricts mainsFoulDrainage.yesNo to only "Yes" or "No"
|
|
499
|
+
// (not "Not known" like the base schema allows)
|
|
500
|
+
clonedExampleTransaction.propertyPack.waterAndDrainage = {
|
|
501
|
+
"water": {
|
|
502
|
+
"mainsWater": {
|
|
503
|
+
"yesNo": "Yes",
|
|
504
|
+
"waterMeter": {
|
|
505
|
+
"isSupplyMetered": "No"
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
},
|
|
509
|
+
"drainage": {
|
|
510
|
+
"mainsSurfaceWaterDrainage": {
|
|
511
|
+
"yesNo": "Yes"
|
|
512
|
+
},
|
|
513
|
+
"mainsFoulDrainage": {
|
|
514
|
+
"yesNo": "Not known" // This is invalid under nts2023 overlay
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
const isValid = validator(clonedExampleTransaction);
|
|
520
|
+
expect(isValid).toBe(false);
|
|
521
|
+
|
|
522
|
+
// Check that the validation error is about the invalid enum value
|
|
523
|
+
const relevantError = validator.errors.find(error =>
|
|
524
|
+
error.instancePath.includes('mainsFoulDrainage') &&
|
|
525
|
+
error.message.includes('must be equal to one of the allowed values')
|
|
526
|
+
);
|
|
527
|
+
expect(relevantError).toBeDefined();
|
|
528
|
+
});
|