@pdtf/schemas 3.4.1-6 → 3.4.1-7

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.
@@ -8,7 +8,8 @@
8
8
  "Bash(jq:*)",
9
9
  "Bash(grep:*)",
10
10
  "Bash(rm:*)",
11
- "Bash(npm test:*)"
11
+ "Bash(npm test:*)",
12
+ "Bash(npm install:*)"
12
13
  ],
13
14
  "deny": []
14
15
  }
package/index.js CHANGED
@@ -10,9 +10,6 @@ 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
-
16
13
  const verifiedClaimsSchema = require("./src/schemas/verifiedClaims/pdtf-verified-claims.json");
17
14
  const v2CoreSchema = require("./src/schemas/v2/pdtf-transaction.json");
18
15
  const v3CoreSchema = require("./src/schemas/v3/pdtf-transaction.json");
@@ -183,74 +180,32 @@ const generateOverlayKey = (overlays) => {
183
180
  return overlays.join(".");
184
181
  };
185
182
 
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
-
246
183
  const getValidator = (schemaId, overlays) => {
247
184
  return getSubschemaValidator("", schemaId, overlays);
248
185
  };
249
186
 
250
- // common functions for v1 and v2 - now with caching
187
+ // common functions for v1 and v2
251
188
  const getSubschema = (path, schemaId, overlays) => {
252
- const cached = getCachedSchemaData(path, schemaId, overlays);
253
- return cached.subSchema;
189
+ const sourceSchema = getTransactionSchema(schemaId, overlays);
190
+ const pathArray = path.split("/").slice(1);
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);
254
209
  };
255
210
 
256
211
  const isPathValid = (path, schemaId, overlays) => {
@@ -262,8 +217,18 @@ const isPathValid = (path, schemaId, overlays) => {
262
217
  };
263
218
 
264
219
  const getSubschemaValidator = (path, schemaId, overlays) => {
265
- const cached = getCachedSchemaData(path, schemaId, overlays);
266
- return cached.validator;
220
+ const subSchema = getSubschema(path, schemaId, overlays);
221
+ const overlayKey = generateOverlayKey(overlays);
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;
267
232
  };
268
233
 
269
234
  // v1, deprecated
@@ -347,20 +312,6 @@ const validateVerifiedClaims = (verifiedClaims, schemaId, overlays) => {
347
312
  return validationErrorsArr;
348
313
  };
349
314
 
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
-
364
315
  module.exports = {
365
316
  ajv,
366
317
  getTransactionSchema,
@@ -373,7 +324,4 @@ module.exports = {
373
324
  validateVerifiedClaims,
374
325
  overlaysMap,
375
326
  extensionOverlays,
376
- // New cache management functions
377
- getCacheStats,
378
- clearSchemaCache,
379
327
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pdtf/schemas",
3
- "version": "3.4.1-6",
3
+ "version": "3.4.1-7",
4
4
  "description": "Property Data Trust Framework Schemas and Utilities",
5
5
  "main": "index.js",
6
6
  "directories": {
@@ -9228,6 +9228,12 @@
9228
9228
  "controlledParking",
9229
9229
  "electricVehicleChargingPoint"
9230
9230
  ],
9231
+ "ntsl2Required": [
9232
+ "parkingArrangements",
9233
+ "disabledParking",
9234
+ "controlledParking",
9235
+ "electricVehicleChargingPoint"
9236
+ ],
9231
9237
  "ta6Required": ["parkingArrangements", "controlledParking"],
9232
9238
  "properties": {
9233
9239
  "parkingArrangements": {
@@ -9456,6 +9462,11 @@
9456
9462
  "isConservationArea",
9457
9463
  "hasTreePreservationOrder"
9458
9464
  ],
9465
+ "ntsl2Required": [
9466
+ "isListed",
9467
+ "isConservationArea",
9468
+ "hasTreePreservationOrder"
9469
+ ],
9459
9470
  "baspi4Required": [
9460
9471
  "isListed",
9461
9472
  "isConservationArea",
@@ -20071,11 +20082,7 @@
20071
20082
  }
20072
20083
  ]
20073
20084
  }
20074
- },
20075
- "ntsRequired": ["offMainsDrainageSystem"],
20076
- "nts2Required": ["offMainsDrainageSystem"],
20077
- "ntslRequired": ["offMainsDrainageSystem"],
20078
- "ntsl2Required": ["offMainsDrainageSystem"]
20085
+ }
20079
20086
  }
20080
20087
  ]
20081
20088
  },
@@ -1568,10 +1568,7 @@
1568
1568
  }
1569
1569
  }
1570
1570
  }
1571
- },
1572
- "required": [
1573
- "offMainsDrainageSystem"
1574
- ]
1571
+ }
1575
1572
  }
1576
1573
  ],
1577
1574
  "required": [
@@ -2072,10 +2072,7 @@
2072
2072
  }
2073
2073
  }
2074
2074
  }
2075
- },
2076
- "required": [
2077
- "offMainsDrainageSystem"
2078
- ]
2075
+ }
2079
2076
  }
2080
2077
  ],
2081
2078
  "required": [
@@ -1229,10 +1229,7 @@
1229
1229
  }
1230
1230
  }
1231
1231
  }
1232
- },
1233
- "required": [
1234
- "offMainsDrainageSystem"
1235
- ]
1232
+ }
1236
1233
  }
1237
1234
  ],
1238
1235
  "required": [
@@ -252,6 +252,12 @@
252
252
  },
253
253
  "parking": {
254
254
  "ntsl2Ref": "B4",
255
+ "required": [
256
+ "parkingArrangements",
257
+ "disabledParking",
258
+ "controlledParking",
259
+ "electricVehicleChargingPoint"
260
+ ],
255
261
  "properties": {
256
262
  "parkingArrangements": {
257
263
  "ntsl2Ref": "B4.1",
@@ -320,6 +326,11 @@
320
326
  },
321
327
  "listingAndConservation": {
322
328
  "ntsl2Ref": "C2.1",
329
+ "required": [
330
+ "isListed",
331
+ "isConservationArea",
332
+ "hasTreePreservationOrder"
333
+ ],
323
334
  "properties": {
324
335
  "isListed": {
325
336
  "ntsl2Ref": "C2.1.1",
@@ -1580,10 +1591,7 @@
1580
1591
  }
1581
1592
  }
1582
1593
  }
1583
- },
1584
- "required": [
1585
- "offMainsDrainageSystem"
1586
- ]
1594
+ }
1587
1595
  }
1588
1596
  ],
1589
1597
  "required": [
@@ -449,80 +449,48 @@ test("validates a valid contract", () => {
449
449
  expect(isValid).toBe(true);
450
450
  });
451
451
 
452
- test("waterAndDrainage state with mainsFoulDrainage No missing offMainsDrainageSystem is now invalid under nts2023 overlay", () => {
453
- const validator = getValidator(schemaId, ["nts2023"]);
452
+ test("ntsl2025 requires parking and listingAndConservation fields", () => {
453
+ // This test validates that the ntsl2025 overlay correctly enforces required fields
454
+ // for the parking and listingAndConservation sections.
455
+ // It should fail validation when these fields are missing.
456
+ // NOTE: This test will fail until overlays are regenerated from combined.json
457
+ const validator = getValidator(schemaId, ["ntsl2025"]);
454
458
  const clonedExampleTransaction = JSON.parse(
455
459
  JSON.stringify(exampleTransaction)
456
460
  );
457
461
 
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
- }
462
+ // Set up lettings-specific fields
463
+ clonedExampleTransaction.propertyPack.lettingInformation = {
464
+ rent: 3500,
465
+ rentFrequency: "Monthly",
466
+ securityDeposit: 5000,
478
467
  };
468
+ delete clonedExampleTransaction.propertyPack.priceInformation;
469
+ delete clonedExampleTransaction.propertyPack.ownership;
479
470
 
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
- );
471
+ // Remove ALL parking fields - this should make validation fail
472
+ delete clonedExampleTransaction.propertyPack.parking.parkingArrangements;
473
+ delete clonedExampleTransaction.propertyPack.parking.disabledParking;
474
+ delete clonedExampleTransaction.propertyPack.parking.controlledParking;
475
+ delete clonedExampleTransaction.propertyPack.parking.electricVehicleChargingPoint;
496
476
 
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
- };
477
+ // Remove ALL listingAndConservation fields - this should make validation fail
478
+ delete clonedExampleTransaction.propertyPack.listingAndConservation.isListed;
479
+ delete clonedExampleTransaction.propertyPack.listingAndConservation.isConservationArea;
480
+ delete clonedExampleTransaction.propertyPack.listingAndConservation.hasTreePreservationOrder;
518
481
 
519
482
  const isValid = validator(clonedExampleTransaction);
483
+
484
+ // The data should be INVALID because required fields are missing
520
485
  expect(isValid).toBe(false);
521
486
 
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();
487
+ // Specifically check that parking and listingAndConservation fields are reported as missing
488
+ const errorMessages = validator.errors.map(e => e.message);
489
+ expect(errorMessages).toContain("must have required property 'parkingArrangements'");
490
+ expect(errorMessages).toContain("must have required property 'disabledParking'");
491
+ expect(errorMessages).toContain("must have required property 'controlledParking'");
492
+ expect(errorMessages).toContain("must have required property 'electricVehicleChargingPoint'");
493
+ expect(errorMessages).toContain("must have required property 'isListed'");
494
+ expect(errorMessages).toContain("must have required property 'isConservationArea'");
495
+ expect(errorMessages).toContain("must have required property 'hasTreePreservationOrder'");
528
496
  });
@@ -1,317 +0,0 @@
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
- });