@pdtf/schemas 3.4.1-2 → 3.4.1-4
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 +62 -167
- package/package.json +1 -1
- package/src/examples/v3/exampleTransaction.json +1 -5
- package/src/schemas/v3/combined.json +2 -1
- package/src/schemas/v3/overlays/extensions/as.json +15 -0
- package/src/schemas/v3/overlays/extensions/dr.json +14 -1
- package/src/schemas/v3/overlays/extensions/er.json +13 -1
- package/src/schemas/v3/overlays/extensions/fd.json +9 -0
- package/src/schemas/v3/overlays/extensions/hi.json +4 -0
- package/src/schemas/v3/overlays/extensions/hs.json +15 -0
- package/src/schemas/v3/overlays/extensions/jk.json +22 -1
- package/src/schemas/v3/overlays/extensions/la.json +19 -2
- package/src/schemas/v3/overlays/extensions/oc.json +20 -0
- package/src/schemas/v3/overlays/extensions/sb.json +15 -0
- package/src/schemas/v3/overlays/extensions/sf.json +15 -0
- package/src/schemas/v3/overlays/extensions/sl.json +20 -0
- package/src/schemas/v3/overlays/extensions/tf.json +9 -0
- package/src/tests/v3/extensionOverlays.test.js +138 -10
- package/src/tests/v3/transactionSchema.test.js +0 -228
- package/src/utils/extractExtensionOverlays.js +51 -7
package/index.js
CHANGED
|
@@ -92,197 +92,92 @@ const transactionSchemas = {
|
|
|
92
92
|
v3CoreSchema,
|
|
93
93
|
};
|
|
94
94
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
// Custom array merge function for oneOf arrays
|
|
96
|
+
const arrayMerge = (target, source) => {
|
|
97
|
+
// For string arrays (enum, required, etc.)
|
|
98
|
+
if (
|
|
99
|
+
target.length > 0 &&
|
|
100
|
+
source.length > 0 &&
|
|
101
|
+
target[0] &&
|
|
102
|
+
typeof target[0] === "string" &&
|
|
103
|
+
source[0] &&
|
|
104
|
+
typeof source[0] === "string"
|
|
105
|
+
) {
|
|
106
|
+
// For required arrays, combine them (remove duplicates)
|
|
107
|
+
// Check if this looks like a required array (contains field names, not enum values)
|
|
108
|
+
const isRequiredArray = target.some(
|
|
109
|
+
(item) =>
|
|
110
|
+
typeof item === "string" &&
|
|
111
|
+
item.length > 0 &&
|
|
112
|
+
!item.includes("Yes") &&
|
|
113
|
+
!item.includes("No") &&
|
|
114
|
+
!item.includes("Not known") &&
|
|
115
|
+
!item.includes("To be connected")
|
|
116
|
+
);
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
sourceSchema.properties?.[discriminator]?.enum || [];
|
|
121
|
-
// Check if there's any overlap between the enum values
|
|
122
|
-
return sourceEnum.some((value) => targetEnum.includes(value));
|
|
123
|
-
});
|
|
124
|
-
if (matchingSourceSchema) {
|
|
125
|
-
return merge(targetSchema, matchingSourceSchema, options);
|
|
118
|
+
if (isRequiredArray) {
|
|
119
|
+
// Combine required arrays
|
|
120
|
+
const combined = [...target];
|
|
121
|
+
source.forEach((item) => {
|
|
122
|
+
if (!combined.includes(item)) {
|
|
123
|
+
combined.push(item);
|
|
126
124
|
}
|
|
127
|
-
return targetSchema;
|
|
128
125
|
});
|
|
126
|
+
return combined;
|
|
129
127
|
}
|
|
128
|
+
|
|
129
|
+
// For other string arrays (like enum), use source if it exists, otherwise use target
|
|
130
|
+
return source.length > 0 ? source : target;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// If target is empty but source has values, use source
|
|
134
|
+
if (target.length === 0 && source.length > 0) {
|
|
135
|
+
return source;
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
source
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
// Ensure we only process arrays of the same length
|
|
139
|
+
// If source is shorter, pad with nulls
|
|
140
|
+
// If source is longer, truncate to target length
|
|
141
|
+
const paddedSource = [...source];
|
|
142
|
+
while (paddedSource.length < target.length) {
|
|
143
|
+
paddedSource.push(null);
|
|
144
|
+
}
|
|
145
|
+
paddedSource.splice(target.length);
|
|
146
|
+
|
|
147
|
+
return target.map((item, index) => {
|
|
148
|
+
if (paddedSource[index] === null || paddedSource[index] === undefined) {
|
|
149
|
+
return item;
|
|
139
150
|
}
|
|
151
|
+
if (item === null || item === undefined) {
|
|
152
|
+
return paddedSource[index];
|
|
153
|
+
}
|
|
154
|
+
// Recursively merge nested objects and arrays
|
|
155
|
+
return merge(item, paddedSource[index], { arrayMerge });
|
|
140
156
|
});
|
|
141
|
-
return destination;
|
|
142
157
|
};
|
|
143
158
|
|
|
144
|
-
const mergeEnums = (target, source) => source;
|
|
145
|
-
|
|
146
159
|
const getTransactionSchema = (
|
|
147
160
|
schemaId = "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json",
|
|
148
|
-
overlays
|
|
161
|
+
overlays
|
|
149
162
|
) => {
|
|
150
163
|
const sourceSchema = transactionSchemas[schemaId];
|
|
151
164
|
if (!overlays || overlays.length < 1) return sourceSchema;
|
|
152
|
-
let mergedSchema = sourceSchema;
|
|
153
|
-
overlays.forEach((overlay) => {
|
|
154
|
-
// Handle both string keys and direct overlay objects
|
|
155
|
-
const overlaySchema =
|
|
156
|
-
typeof overlay === "string"
|
|
157
|
-
? overlaysMap[schemaId][overlay] || {}
|
|
158
|
-
: overlay;
|
|
159
|
-
|
|
160
|
-
mergedSchema = merge(mergedSchema, overlaySchema, {
|
|
161
|
-
customMerge: (key) => {
|
|
162
|
-
if (key === "enum") {
|
|
163
|
-
return mergeEnums;
|
|
164
|
-
}
|
|
165
|
-
},
|
|
166
|
-
arrayMerge: combineMerge,
|
|
167
|
-
});
|
|
168
165
|
|
|
169
|
-
|
|
170
|
-
|
|
166
|
+
let mergedSchema = JSON.parse(JSON.stringify(sourceSchema)); // Deep copy
|
|
167
|
+
overlays.forEach((overlay) => {
|
|
168
|
+
const overlaySchema = JSON.parse(
|
|
169
|
+
JSON.stringify(overlaysMap[schemaId][overlay] || {})
|
|
170
|
+
); // Deep copy
|
|
171
|
+
mergedSchema = merge(mergedSchema, overlaySchema, { arrayMerge });
|
|
171
172
|
});
|
|
172
|
-
return mergedSchema;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// Function to ensure overlay metadata properties are preserved after merging
|
|
176
|
-
const preserveOverlayMetadata = (mergedSchema, overlaySchema) => {
|
|
177
|
-
const metadataKeys = [
|
|
178
|
-
"ntsRef",
|
|
179
|
-
"nts2Ref",
|
|
180
|
-
"baspi4Ref",
|
|
181
|
-
"baspi5Ref",
|
|
182
|
-
"ta6Ref",
|
|
183
|
-
"ta7Ref",
|
|
184
|
-
"ta10Ref",
|
|
185
|
-
"lpe1Ref",
|
|
186
|
-
"fme1Ref",
|
|
187
|
-
"con29RRef",
|
|
188
|
-
"con29DWRef",
|
|
189
|
-
"llc1Ref",
|
|
190
|
-
"rdsRef",
|
|
191
|
-
"oc1Ref",
|
|
192
|
-
"piqRef",
|
|
193
|
-
"sr24Ref",
|
|
194
|
-
"discriminator",
|
|
195
|
-
];
|
|
196
|
-
|
|
197
|
-
// Helper to get a unique key signature for a schema branch (for matching oneOf branches)
|
|
198
|
-
const getBranchSignature = (obj) => {
|
|
199
|
-
if (!obj || typeof obj !== "object") return "";
|
|
200
|
-
if (obj.properties) {
|
|
201
|
-
return Object.keys(obj.properties).sort().join(",");
|
|
202
|
-
}
|
|
203
|
-
return Object.keys(obj).sort().join(",");
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const traverseAndPreserve = (merged, overlay, path = "") => {
|
|
207
|
-
if (!overlay || typeof overlay !== "object") return;
|
|
208
|
-
|
|
209
|
-
// If both are arrays, try to match by signature, else fallback to index
|
|
210
|
-
if (Array.isArray(overlay) && Array.isArray(merged)) {
|
|
211
|
-
// Build a map of overlay signatures
|
|
212
|
-
const overlaySignatures = overlay.map(getBranchSignature);
|
|
213
|
-
const mergedSignatures = merged.map(getBranchSignature);
|
|
214
|
-
for (let i = 0; i < overlay.length; i++) {
|
|
215
|
-
const oSig = overlaySignatures[i];
|
|
216
|
-
// Try to find a matching branch in merged by signature
|
|
217
|
-
let mIdx = mergedSignatures.indexOf(oSig);
|
|
218
|
-
if (mIdx === -1) mIdx = i; // fallback to index if not found
|
|
219
|
-
if (merged[mIdx] !== undefined) {
|
|
220
|
-
traverseAndPreserve(merged[mIdx], overlay[i], path + `[${mIdx}]`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
Object.keys(overlay).forEach((key) => {
|
|
227
|
-
const overlayValue = overlay[key];
|
|
228
|
-
const mergedValue = merged[key];
|
|
229
|
-
|
|
230
|
-
if (metadataKeys.includes(key) && overlayValue !== undefined) {
|
|
231
|
-
// For metadata keys, preserve the overlay value
|
|
232
|
-
merged[key] = overlayValue;
|
|
233
|
-
} else if (
|
|
234
|
-
key === "required" &&
|
|
235
|
-
Array.isArray(overlayValue) &&
|
|
236
|
-
Array.isArray(mergedValue)
|
|
237
|
-
) {
|
|
238
|
-
// For required arrays, merge them uniquely (don't overwrite)
|
|
239
|
-
merged[key] = [...new Set([...mergedValue, ...overlayValue])];
|
|
240
|
-
} else if (
|
|
241
|
-
key === "required" &&
|
|
242
|
-
Array.isArray(overlayValue) &&
|
|
243
|
-
!Array.isArray(mergedValue)
|
|
244
|
-
) {
|
|
245
|
-
// If merged doesn't have required array but overlay does, use overlay's
|
|
246
|
-
merged[key] = overlayValue;
|
|
247
|
-
} else if (
|
|
248
|
-
overlayValue &&
|
|
249
|
-
typeof overlayValue === "object" &&
|
|
250
|
-
!Array.isArray(overlayValue) &&
|
|
251
|
-
mergedValue &&
|
|
252
|
-
typeof mergedValue === "object" &&
|
|
253
|
-
!Array.isArray(mergedValue)
|
|
254
|
-
) {
|
|
255
|
-
traverseAndPreserve(mergedValue, overlayValue, path + "/" + key);
|
|
256
|
-
} else if (
|
|
257
|
-
Array.isArray(overlayValue) &&
|
|
258
|
-
Array.isArray(mergedValue) &&
|
|
259
|
-
["oneOf", "anyOf", "allOf"].includes(key)
|
|
260
|
-
) {
|
|
261
|
-
// Recursively handle schema composition arrays (handled above)
|
|
262
|
-
traverseAndPreserve(mergedValue, overlayValue, path + `/${key}`);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
};
|
|
266
173
|
|
|
267
|
-
traverseAndPreserve(mergedSchema, overlaySchema);
|
|
268
174
|
return mergedSchema;
|
|
269
175
|
};
|
|
270
176
|
|
|
271
177
|
// Add helper function to generate cache key
|
|
272
178
|
const generateOverlayKey = (overlays) => {
|
|
273
179
|
if (!overlays) return "";
|
|
274
|
-
return overlays
|
|
275
|
-
.map((overlay) => {
|
|
276
|
-
if (typeof overlay === "string") return overlay;
|
|
277
|
-
// Generate a simple hash of the object for caching
|
|
278
|
-
return JSON.stringify(overlay)
|
|
279
|
-
.split("")
|
|
280
|
-
.reduce((hash, char) => {
|
|
281
|
-
return ((hash << 5) - hash + char.charCodeAt(0)) | 0;
|
|
282
|
-
}, 0)
|
|
283
|
-
.toString(36);
|
|
284
|
-
})
|
|
285
|
-
.join(".");
|
|
180
|
+
return overlays.join(".");
|
|
286
181
|
};
|
|
287
182
|
|
|
288
183
|
const getValidator = (schemaId, overlays) => {
|
package/package.json
CHANGED
|
@@ -18,11 +18,7 @@
|
|
|
18
18
|
"countryCode": "GBR"
|
|
19
19
|
},
|
|
20
20
|
"role": "Seller",
|
|
21
|
-
"sellersCapacity": {
|
|
22
|
-
"capacity": "Legal Owner",
|
|
23
|
-
"sellersCapacityDetails": "Selling as the legal owner of the property",
|
|
24
|
-
"attachments": "To follow"
|
|
25
|
-
},
|
|
21
|
+
"sellersCapacity": { "capacity": "Legal Owner" },
|
|
26
22
|
"externalIds": { "Moverly": "123434" }
|
|
27
23
|
},
|
|
28
24
|
{
|
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"oneOf": [
|
|
22
|
+
{
|
|
23
|
+
"properties": {
|
|
24
|
+
"yesNo": {
|
|
25
|
+
"enum": [
|
|
26
|
+
"No"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
22
31
|
{
|
|
23
32
|
"required": [
|
|
24
33
|
"details"
|
|
@@ -34,6 +43,12 @@
|
|
|
34
43
|
"enum": [
|
|
35
44
|
"Yes"
|
|
36
45
|
]
|
|
46
|
+
},
|
|
47
|
+
"attachments": {
|
|
48
|
+
"enum": [
|
|
49
|
+
"Attached",
|
|
50
|
+
"To follow"
|
|
51
|
+
]
|
|
37
52
|
}
|
|
38
53
|
}
|
|
39
54
|
}
|
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"oneOf": [
|
|
22
|
+
{
|
|
23
|
+
"properties": {
|
|
24
|
+
"yesNo": {
|
|
25
|
+
"enum": [
|
|
26
|
+
"No"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
22
31
|
{
|
|
23
32
|
"required": [
|
|
24
33
|
"details"
|
|
@@ -31,7 +40,11 @@
|
|
|
31
40
|
},
|
|
32
41
|
"attachments": {
|
|
33
42
|
"ntsRef": "A5.1.3",
|
|
34
|
-
"type": "string"
|
|
43
|
+
"type": "string",
|
|
44
|
+
"enum": [
|
|
45
|
+
"Attached",
|
|
46
|
+
"To follow"
|
|
47
|
+
]
|
|
35
48
|
},
|
|
36
49
|
"yesNo": {
|
|
37
50
|
"enum": [
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"oneOf": [
|
|
27
|
+
{
|
|
28
|
+
"properties": {
|
|
29
|
+
"yesNo": {
|
|
30
|
+
"enum": [
|
|
31
|
+
"No"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
27
36
|
{
|
|
28
37
|
"required": [
|
|
29
38
|
"details",
|
|
@@ -49,7 +58,10 @@
|
|
|
49
58
|
],
|
|
50
59
|
"type": "object"
|
|
51
60
|
}
|
|
52
|
-
}
|
|
61
|
+
},
|
|
62
|
+
"required": [
|
|
63
|
+
"estateRentcharges"
|
|
64
|
+
]
|
|
53
65
|
}
|
|
54
66
|
]
|
|
55
67
|
}
|
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"oneOf": [
|
|
22
|
+
{
|
|
23
|
+
"properties": {
|
|
24
|
+
"yesNo": {
|
|
25
|
+
"enum": [
|
|
26
|
+
"No"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
22
31
|
{
|
|
23
32
|
"required": [
|
|
24
33
|
"details"
|
|
@@ -33,6 +42,12 @@
|
|
|
33
42
|
"enum": [
|
|
34
43
|
"Yes"
|
|
35
44
|
]
|
|
45
|
+
},
|
|
46
|
+
"attachments": {
|
|
47
|
+
"enum": [
|
|
48
|
+
"Attached",
|
|
49
|
+
"To follow"
|
|
50
|
+
]
|
|
36
51
|
}
|
|
37
52
|
}
|
|
38
53
|
}
|
|
@@ -19,6 +19,16 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"oneOf": [
|
|
22
|
+
{
|
|
23
|
+
"properties": {
|
|
24
|
+
"yesNo": {
|
|
25
|
+
"enum": [
|
|
26
|
+
"No",
|
|
27
|
+
"Not known"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
22
32
|
{
|
|
23
33
|
"required": [
|
|
24
34
|
"managementPlanInPlace"
|
|
@@ -26,12 +36,23 @@
|
|
|
26
36
|
"properties": {
|
|
27
37
|
"managementPlanInPlace": {
|
|
28
38
|
"ntsRef": "A5.3.2",
|
|
29
|
-
"type": "string"
|
|
39
|
+
"type": "string",
|
|
40
|
+
"enum": [
|
|
41
|
+
"Yes",
|
|
42
|
+
"No",
|
|
43
|
+
"Not known"
|
|
44
|
+
]
|
|
30
45
|
},
|
|
31
46
|
"yesNo": {
|
|
32
47
|
"enum": [
|
|
33
48
|
"Yes"
|
|
34
49
|
]
|
|
50
|
+
},
|
|
51
|
+
"attachments": {
|
|
52
|
+
"enum": [
|
|
53
|
+
"Attached",
|
|
54
|
+
"To follow"
|
|
55
|
+
]
|
|
35
56
|
}
|
|
36
57
|
}
|
|
37
58
|
}
|
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"oneOf": [
|
|
22
|
+
{
|
|
23
|
+
"properties": {
|
|
24
|
+
"loftAccess": {
|
|
25
|
+
"enum": [
|
|
26
|
+
"No"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
22
31
|
{
|
|
23
32
|
"required": [
|
|
24
33
|
"details",
|
|
@@ -33,11 +42,19 @@
|
|
|
33
42
|
},
|
|
34
43
|
"loftBoarded": {
|
|
35
44
|
"ntsRef": "A1.2.4.3",
|
|
36
|
-
"type": "string"
|
|
45
|
+
"type": "string",
|
|
46
|
+
"enum": [
|
|
47
|
+
"Yes",
|
|
48
|
+
"No"
|
|
49
|
+
]
|
|
37
50
|
},
|
|
38
51
|
"loftInsulated": {
|
|
39
52
|
"ntsRef": "A1.2.4.4",
|
|
40
|
-
"type": "string"
|
|
53
|
+
"type": "string",
|
|
54
|
+
"enum": [
|
|
55
|
+
"Yes",
|
|
56
|
+
"No"
|
|
57
|
+
]
|
|
41
58
|
},
|
|
42
59
|
"loftAccess": {
|
|
43
60
|
"enum": [
|
|
@@ -19,6 +19,26 @@
|
|
|
19
19
|
"type": "string"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
|
+
"oneOf": [
|
|
23
|
+
{
|
|
24
|
+
"properties": {
|
|
25
|
+
"yesNo": {
|
|
26
|
+
"enum": [
|
|
27
|
+
"No"
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"properties": {
|
|
34
|
+
"yesNo": {
|
|
35
|
+
"enum": [
|
|
36
|
+
"Yes"
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
],
|
|
22
42
|
"type": "object"
|
|
23
43
|
}
|
|
24
44
|
},
|
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"oneOf": [
|
|
22
|
+
{
|
|
23
|
+
"properties": {
|
|
24
|
+
"yesNo": {
|
|
25
|
+
"enum": [
|
|
26
|
+
"No"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
22
31
|
{
|
|
23
32
|
"required": [
|
|
24
33
|
"details"
|
|
@@ -33,6 +42,12 @@
|
|
|
33
42
|
"enum": [
|
|
34
43
|
"Yes"
|
|
35
44
|
]
|
|
45
|
+
},
|
|
46
|
+
"attachments": {
|
|
47
|
+
"enum": [
|
|
48
|
+
"Attached",
|
|
49
|
+
"To follow"
|
|
50
|
+
]
|
|
36
51
|
}
|
|
37
52
|
}
|
|
38
53
|
}
|
|
@@ -19,6 +19,15 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"oneOf": [
|
|
22
|
+
{
|
|
23
|
+
"properties": {
|
|
24
|
+
"hasSprayFoamInstalled": {
|
|
25
|
+
"enum": [
|
|
26
|
+
"No"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
22
31
|
{
|
|
23
32
|
"required": [
|
|
24
33
|
"details"
|
|
@@ -34,6 +43,12 @@
|
|
|
34
43
|
"enum": [
|
|
35
44
|
"Yes"
|
|
36
45
|
]
|
|
46
|
+
},
|
|
47
|
+
"attachments": {
|
|
48
|
+
"enum": [
|
|
49
|
+
"Attached",
|
|
50
|
+
"To follow"
|
|
51
|
+
]
|
|
37
52
|
}
|
|
38
53
|
}
|
|
39
54
|
}
|
|
@@ -23,6 +23,26 @@
|
|
|
23
23
|
"type": "string"
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
+
"oneOf": [
|
|
27
|
+
{
|
|
28
|
+
"properties": {
|
|
29
|
+
"yesNo": {
|
|
30
|
+
"enum": [
|
|
31
|
+
"Yes"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"properties": {
|
|
38
|
+
"yesNo": {
|
|
39
|
+
"enum": [
|
|
40
|
+
"No"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
],
|
|
26
46
|
"type": "object"
|
|
27
47
|
}
|
|
28
48
|
}
|
|
@@ -50,16 +50,6 @@ describe("Extension Overlays", () => {
|
|
|
50
50
|
expect(maProp?.ntsRef).toBe("A1.5.4");
|
|
51
51
|
expect(erProp?.ntsRef).toBe("A3.4");
|
|
52
52
|
});
|
|
53
|
-
|
|
54
|
-
test("should be equivalent to direct object access", () => {
|
|
55
|
-
const schemaString = getTransactionSchema(schemaId, ["nts2023", "jk"]);
|
|
56
|
-
const schemaDirect = getTransactionSchema(schemaId, [
|
|
57
|
-
"nts2023",
|
|
58
|
-
extensionOverlays.jk,
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
expect(schemaString).toEqual(schemaDirect);
|
|
62
|
-
});
|
|
63
53
|
});
|
|
64
54
|
|
|
65
55
|
describe("Individual extension functionality", () => {
|
|
@@ -70,6 +60,7 @@ describe("Extension Overlays", () => {
|
|
|
70
60
|
?.properties?.japaneseKnotweed;
|
|
71
61
|
|
|
72
62
|
expect(jkProp).toBeDefined();
|
|
63
|
+
|
|
73
64
|
expect(jkProp.ntsRef).toBe("A5.3");
|
|
74
65
|
expect(jkProp.required).toContain("yesNo");
|
|
75
66
|
expect(jkProp.discriminator?.propertyName).toBe("yesNo");
|
|
@@ -131,6 +122,7 @@ describe("Extension Overlays", () => {
|
|
|
131
122
|
?.estateRentcharges;
|
|
132
123
|
|
|
133
124
|
expect(erProp).toBeDefined();
|
|
125
|
+
|
|
134
126
|
expect(erProp.ntsRef).toBe("A3.4");
|
|
135
127
|
expect(erProp.required).toContain("yesNo");
|
|
136
128
|
expect(erProp.discriminator?.propertyName).toBe("yesNo");
|
|
@@ -359,6 +351,89 @@ describe("Extension Overlays", () => {
|
|
|
359
351
|
expect(siSection.required).toContain("containsAsbestos");
|
|
360
352
|
});
|
|
361
353
|
|
|
354
|
+
test("should make estateRentcharges required when er extension is applied", () => {
|
|
355
|
+
const schema = getTransactionSchema(schemaId, ["nts2023", "er"]);
|
|
356
|
+
|
|
357
|
+
// Check that estateRentcharges is required at the oneOf branch level
|
|
358
|
+
const freeholdBranch =
|
|
359
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
360
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[0];
|
|
361
|
+
|
|
362
|
+
expect(freeholdBranch).toBeDefined();
|
|
363
|
+
expect(freeholdBranch.required).toBeDefined();
|
|
364
|
+
expect(freeholdBranch.required).toContain("estateRentcharges");
|
|
365
|
+
|
|
366
|
+
// Verify the estateRentcharges property itself is properly structured
|
|
367
|
+
const erProp = freeholdBranch.properties?.estateRentcharges;
|
|
368
|
+
expect(erProp).toBeDefined();
|
|
369
|
+
expect(erProp.ntsRef).toBe("A3.4");
|
|
370
|
+
expect(erProp.required).toContain("yesNo");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("should consistently apply required fields at parent level for extension properties", () => {
|
|
374
|
+
// Test multiple extensions that should have required fields at parent level
|
|
375
|
+
const schema = getTransactionSchema(schemaId, [
|
|
376
|
+
"nts2023",
|
|
377
|
+
"jk",
|
|
378
|
+
"er",
|
|
379
|
+
"ma",
|
|
380
|
+
"tf",
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
// Check specialist issues - japaneseKnotweed should be required
|
|
384
|
+
const siSection =
|
|
385
|
+
schema.properties?.propertyPack?.properties?.specialistIssues;
|
|
386
|
+
expect(siSection?.required).toContain("japaneseKnotweed");
|
|
387
|
+
|
|
388
|
+
// Check ownership - estateRentcharges should be required in freehold branch
|
|
389
|
+
const freeholdBranch =
|
|
390
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
391
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[0];
|
|
392
|
+
expect(freeholdBranch?.required).toContain("estateRentcharges");
|
|
393
|
+
|
|
394
|
+
// Check leasehold - leaseholdInformation should be required in leasehold branch
|
|
395
|
+
const leaseholdBranch =
|
|
396
|
+
schema.properties?.propertyPack?.properties?.ownership?.properties
|
|
397
|
+
?.ownershipsToBeTransferred?.items?.oneOf?.[2];
|
|
398
|
+
expect(leaseholdBranch?.required).toContain("leaseholdInformation");
|
|
399
|
+
|
|
400
|
+
// Verify that the required arrays are properly merged (not overwritten)
|
|
401
|
+
// Note: The length check might fail if only one extension is applied to specialistIssues
|
|
402
|
+
// Let's check what's actually in the required arrays
|
|
403
|
+
console.log("SpecialistIssues required:", siSection?.required);
|
|
404
|
+
console.log("FreeholdBranch required:", freeholdBranch?.required);
|
|
405
|
+
console.log("LeaseholdBranch required:", leaseholdBranch?.required);
|
|
406
|
+
|
|
407
|
+
expect(siSection.required.length).toBeGreaterThanOrEqual(1);
|
|
408
|
+
expect(freeholdBranch.required.length).toBeGreaterThan(0);
|
|
409
|
+
expect(leaseholdBranch.required.length).toBeGreaterThan(0);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("should merge required arrays for multiple specialist issues overlays", () => {
|
|
413
|
+
// Apply multiple specialist issues overlays
|
|
414
|
+
const overlays = ["nts2023", "jk", "as", "dr", "sb", "hs"];
|
|
415
|
+
const schema = getTransactionSchema(schemaId, overlays);
|
|
416
|
+
const siSection =
|
|
417
|
+
schema.properties?.propertyPack?.properties?.specialistIssues;
|
|
418
|
+
|
|
419
|
+
// The overlays correspond to these property keys
|
|
420
|
+
const expectedRequired = [
|
|
421
|
+
"japaneseKnotweed",
|
|
422
|
+
"containsAsbestos",
|
|
423
|
+
"dryRotEtcTreatment",
|
|
424
|
+
"subsidenceOrStructuralFault",
|
|
425
|
+
"ongoingHealthOrSafetyIssue",
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
expect(siSection?.required).toBeDefined();
|
|
429
|
+
// Should contain all expected keys
|
|
430
|
+
expectedRequired.forEach((key) => {
|
|
431
|
+
expect(siSection.required).toContain(key);
|
|
432
|
+
});
|
|
433
|
+
// Should be the same length as the number of overlays (excluding nts2023)
|
|
434
|
+
expect(siSection.required.length).toBe(expectedRequired.length);
|
|
435
|
+
});
|
|
436
|
+
|
|
362
437
|
test("should handle discriminator patterns correctly", () => {
|
|
363
438
|
const schema = getTransactionSchema(schemaId, ["nts2023", "jk"]);
|
|
364
439
|
const jkProp =
|
|
@@ -393,5 +468,58 @@ describe("Extension Overlays", () => {
|
|
|
393
468
|
conditionalBranch.required?.includes("details")
|
|
394
469
|
).toBeTruthy();
|
|
395
470
|
});
|
|
471
|
+
|
|
472
|
+
test("should not require additional fields when 'No' is selected in extension overlays", () => {
|
|
473
|
+
// Test the 'as' (additional searches) extension overlay
|
|
474
|
+
const schema = getTransactionSchema(schemaId, ["as"]);
|
|
475
|
+
|
|
476
|
+
const containsAsbestos =
|
|
477
|
+
schema.properties?.propertyPack?.properties?.specialistIssues
|
|
478
|
+
?.properties?.containsAsbestos;
|
|
479
|
+
|
|
480
|
+
expect(containsAsbestos).toBeDefined();
|
|
481
|
+
expect(containsAsbestos.discriminator?.propertyName).toBe("yesNo");
|
|
482
|
+
expect(containsAsbestos.oneOf).toBeDefined();
|
|
483
|
+
expect(containsAsbestos.oneOf).toHaveLength(2); // Both "No" and "Yes" branches
|
|
484
|
+
|
|
485
|
+
// Find the "No" branch - it should not have additional required fields
|
|
486
|
+
const noBranch = containsAsbestos.oneOf.find((branch) =>
|
|
487
|
+
branch.properties?.yesNo?.enum?.includes("No")
|
|
488
|
+
);
|
|
489
|
+
expect(noBranch).toBeDefined();
|
|
490
|
+
expect(noBranch.properties.yesNo.enum).toEqual(["No"]);
|
|
491
|
+
// The "No" branch should not have additional required fields beyond "yesNo"
|
|
492
|
+
expect(noBranch.required).toBeUndefined();
|
|
493
|
+
|
|
494
|
+
// Find the "Yes" branch - it should have additional required fields
|
|
495
|
+
const yesBranch = containsAsbestos.oneOf.find((branch) =>
|
|
496
|
+
branch.properties?.yesNo?.enum?.includes("Yes")
|
|
497
|
+
);
|
|
498
|
+
expect(yesBranch).toBeDefined();
|
|
499
|
+
expect(yesBranch.properties.yesNo.enum).toEqual(["Yes"]);
|
|
500
|
+
expect(yesBranch.required).toBeDefined();
|
|
501
|
+
expect(yesBranch.required).toContain("details");
|
|
502
|
+
|
|
503
|
+
// Test that data with "No" answer validates correctly using subschema validation
|
|
504
|
+
const { getSubschemaValidator } = require("../../../index.js");
|
|
505
|
+
const subschemaValidator = getSubschemaValidator(
|
|
506
|
+
"/propertyPack/specialistIssues/containsAsbestos",
|
|
507
|
+
schemaId,
|
|
508
|
+
["as"]
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
const validDataWithNo = {
|
|
512
|
+
yesNo: "No",
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const result = subschemaValidator(validDataWithNo);
|
|
516
|
+
// Handle both boolean and object return types
|
|
517
|
+
if (typeof result === "boolean") {
|
|
518
|
+
expect(result).toBe(true);
|
|
519
|
+
} else {
|
|
520
|
+
expect(result.valid).toBe(true);
|
|
521
|
+
expect(result.errors).toHaveLength(0);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
396
524
|
});
|
|
397
525
|
});
|
|
@@ -8,7 +8,6 @@ const {
|
|
|
8
8
|
getValidator,
|
|
9
9
|
getSubschemaValidator,
|
|
10
10
|
getTitleAtPath,
|
|
11
|
-
overlaysMap,
|
|
12
11
|
} = require("../../../index.js");
|
|
13
12
|
|
|
14
13
|
const exampleTransaction = require("../../examples/v3/exampleTransaction.json");
|
|
@@ -33,14 +32,12 @@ test("sample is valid BASPI", () => {
|
|
|
33
32
|
expect(testSchema.properties.propertyPack.baspi4Ref).toEqual("0");
|
|
34
33
|
const validator = getValidator(schemaId, ["baspiV4"]);
|
|
35
34
|
const isValid = validator(exampleTransaction);
|
|
36
|
-
if (!isValid) console.log(validator.errors);
|
|
37
35
|
expect(isValid).toBe(true);
|
|
38
36
|
});
|
|
39
37
|
|
|
40
38
|
test("sample is valid NTS", () => {
|
|
41
39
|
const validator = getValidator(schemaId, ["nts2023"]);
|
|
42
40
|
const isValid = validator(exampleTransaction);
|
|
43
|
-
if (!isValid) console.log(validator.errors);
|
|
44
41
|
expect(isValid).toBe(true);
|
|
45
42
|
});
|
|
46
43
|
|
|
@@ -102,7 +99,6 @@ test("sample is valid NTSL if we change it accordingly", () => {
|
|
|
102
99
|
delete clonedExampleTransaction.propertyPack.priceInformation;
|
|
103
100
|
delete clonedExampleTransaction.propertyPack.ownership;
|
|
104
101
|
const isValid = validator(clonedExampleTransaction);
|
|
105
|
-
if (!isValid) console.log(validator.errors);
|
|
106
102
|
expect(isValid).toBe(true);
|
|
107
103
|
});
|
|
108
104
|
|
|
@@ -452,227 +448,3 @@ test("validates a valid contract", () => {
|
|
|
452
448
|
const isValid = validator(data);
|
|
453
449
|
expect(isValid).toBe(true);
|
|
454
450
|
});
|
|
455
|
-
|
|
456
|
-
test("overlay as key and as object produce identical results", () => {
|
|
457
|
-
// Get the NTS overlay object directly from the overlays map
|
|
458
|
-
const ntsOverlayObject = { ...overlaysMap[schemaId]["nts2023"] };
|
|
459
|
-
// Remove the $id to prevent schema registration conflicts
|
|
460
|
-
ntsOverlayObject.$id =
|
|
461
|
-
"https://trust.propdata.org.uk/schemas/v3/overlays/nts2024.json";
|
|
462
|
-
|
|
463
|
-
// Get schema using the key
|
|
464
|
-
const schemaWithKey = getTransactionSchema(schemaId, ["nts2023"]);
|
|
465
|
-
|
|
466
|
-
// Get schema using the object
|
|
467
|
-
const schemaWithObject = getTransactionSchema(schemaId, [ntsOverlayObject]);
|
|
468
|
-
|
|
469
|
-
// Create copies without $id for comparison
|
|
470
|
-
const schemaWithKeyNoId = { ...schemaWithKey };
|
|
471
|
-
const schemaWithObjectNoId = { ...schemaWithObject };
|
|
472
|
-
delete schemaWithKeyNoId.$id;
|
|
473
|
-
delete schemaWithObjectNoId.$id;
|
|
474
|
-
|
|
475
|
-
// Deep compare the results (excluding $id)
|
|
476
|
-
expect(schemaWithObjectNoId).toEqual(schemaWithKeyNoId);
|
|
477
|
-
|
|
478
|
-
// Test that validation produces the same errors
|
|
479
|
-
const validatorWithKey = getValidator(schemaId, ["nts2023"]);
|
|
480
|
-
const validatorWithObject = getValidator(schemaId, [ntsOverlayObject]);
|
|
481
|
-
|
|
482
|
-
validatorWithKey(exampleTransaction);
|
|
483
|
-
validatorWithObject(exampleTransaction);
|
|
484
|
-
|
|
485
|
-
// Compare validation errors
|
|
486
|
-
expect(validatorWithObject.errors).toEqual(validatorWithKey.errors);
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
test("correctly merges custom Japanese Knotweed overlay with base schema", () => {
|
|
490
|
-
// Load the custom overlay
|
|
491
|
-
const customOverlay = require("../../examples/v3/exampleCustomOverlay.json");
|
|
492
|
-
|
|
493
|
-
// Get base schema and schema with overlay
|
|
494
|
-
const baseSchema = getTransactionSchema(schemaId);
|
|
495
|
-
const schemaWithOverlay = getTransactionSchema(schemaId, [customOverlay]);
|
|
496
|
-
|
|
497
|
-
// Verify that other properties in propertyPack are preserved
|
|
498
|
-
expect(
|
|
499
|
-
Object.keys(schemaWithOverlay.properties.propertyPack.properties)
|
|
500
|
-
).toEqual(Object.keys(baseSchema.properties.propertyPack.properties));
|
|
501
|
-
|
|
502
|
-
// Verify that other properties in specialistIssues are preserved
|
|
503
|
-
const baseSpecialistIssues =
|
|
504
|
-
baseSchema.properties.propertyPack.properties.specialistIssues;
|
|
505
|
-
const overlaidSpecialistIssues =
|
|
506
|
-
schemaWithOverlay.properties.propertyPack.properties.specialistIssues;
|
|
507
|
-
expect(Object.keys(overlaidSpecialistIssues.properties)).toEqual(
|
|
508
|
-
Object.keys(baseSpecialistIssues.properties)
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
// Verify that Japanese Knotweed section is updated
|
|
512
|
-
const japaneseKnotweed = overlaidSpecialistIssues.properties.japaneseKnotweed;
|
|
513
|
-
expect(japaneseKnotweed.title).toBe(
|
|
514
|
-
"Is the property affected by Japanese knotweed?"
|
|
515
|
-
);
|
|
516
|
-
expect(japaneseKnotweed.required).toEqual(["yesNo"]);
|
|
517
|
-
|
|
518
|
-
// Verify the oneOf conditions are updated
|
|
519
|
-
expect(japaneseKnotweed.oneOf).toHaveLength(2);
|
|
520
|
-
expect(japaneseKnotweed.oneOf[0].properties.yesNo.enum).toEqual([
|
|
521
|
-
"No",
|
|
522
|
-
"Not known",
|
|
523
|
-
]);
|
|
524
|
-
expect(japaneseKnotweed.oneOf[1].properties.yesNo.enum).toEqual(["Yes"]);
|
|
525
|
-
expect(japaneseKnotweed.oneOf[1].required).toEqual([
|
|
526
|
-
"managementPlanInPlace",
|
|
527
|
-
"attachments",
|
|
528
|
-
]);
|
|
529
|
-
});
|
|
530
|
-
|
|
531
|
-
test("correctly merges multiple overlays using both key and object references", () => {
|
|
532
|
-
// Load the custom overlay
|
|
533
|
-
const customOverlay = require("../../examples/v3/exampleCustomOverlay.json");
|
|
534
|
-
|
|
535
|
-
// Get schema with both overlays
|
|
536
|
-
const schemaWithOverlays = getTransactionSchema(schemaId, [
|
|
537
|
-
"nts2023",
|
|
538
|
-
customOverlay,
|
|
539
|
-
]);
|
|
540
|
-
|
|
541
|
-
// Get individual overlay schemas for comparison
|
|
542
|
-
const ntsSchema = getTransactionSchema(schemaId, ["nts2023"]);
|
|
543
|
-
const customSchema = getTransactionSchema(schemaId, [customOverlay]);
|
|
544
|
-
|
|
545
|
-
// Verify propertyPack structure is preserved
|
|
546
|
-
const propertyPack = schemaWithOverlays.properties.propertyPack;
|
|
547
|
-
expect(Object.keys(propertyPack.properties)).toEqual(
|
|
548
|
-
expect.arrayContaining(
|
|
549
|
-
Object.keys(ntsSchema.properties.propertyPack.properties)
|
|
550
|
-
)
|
|
551
|
-
);
|
|
552
|
-
|
|
553
|
-
// Verify Japanese Knotweed changes from custom overlay are present
|
|
554
|
-
const japaneseKnotweed =
|
|
555
|
-
propertyPack.properties.specialistIssues.properties.japaneseKnotweed;
|
|
556
|
-
expect(japaneseKnotweed.title).toBe(
|
|
557
|
-
"Is the property affected by Japanese knotweed?"
|
|
558
|
-
);
|
|
559
|
-
expect(japaneseKnotweed.oneOf[1].required).toEqual([
|
|
560
|
-
"managementPlanInPlace",
|
|
561
|
-
"attachments",
|
|
562
|
-
]);
|
|
563
|
-
|
|
564
|
-
// Verify customRef properties are correctly merged
|
|
565
|
-
expect(propertyPack.customRef).toBe("JKW");
|
|
566
|
-
expect(propertyPack.properties.specialistIssues.customRef).toBe("JKW.7");
|
|
567
|
-
expect(japaneseKnotweed.customRef).toBe("JKW.7.8");
|
|
568
|
-
expect(
|
|
569
|
-
japaneseKnotweed.oneOf[1].properties.managementPlanInPlace.customRef
|
|
570
|
-
).toBe("JKW.7.8.2");
|
|
571
|
-
expect(japaneseKnotweed.oneOf[1].properties.attachments.customRef).toBe(
|
|
572
|
-
"JKW.7.8.3"
|
|
573
|
-
);
|
|
574
|
-
expect(japaneseKnotweed.properties.yesNo.customRef).toBe("JKW.7.8.1");
|
|
575
|
-
|
|
576
|
-
// Verify NTS specific changes are present (e.g., simplified enums)
|
|
577
|
-
const mainsElectricity =
|
|
578
|
-
propertyPack.properties.electricity.properties.mainsElectricity;
|
|
579
|
-
expect(mainsElectricity.properties.yesNo.enum).toEqual(["Yes", "No"]);
|
|
580
|
-
|
|
581
|
-
// Verify required fields from both overlays are merged
|
|
582
|
-
expect(propertyPack.required).toEqual(
|
|
583
|
-
expect.arrayContaining([
|
|
584
|
-
...ntsSchema.properties.propertyPack.required,
|
|
585
|
-
...customSchema.properties.propertyPack.required,
|
|
586
|
-
])
|
|
587
|
-
);
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
test("correctly merges NTS and both custom overlays with proper required fields", () => {
|
|
591
|
-
// Load both custom overlays
|
|
592
|
-
const japaneseKnoweedOverlay = require("../../examples/v3/exampleCustomOverlay.json");
|
|
593
|
-
const managingAgentOverlay = require("../../examples/v3/exampleCustomOverlay2.json");
|
|
594
|
-
|
|
595
|
-
// Get schema with all overlays
|
|
596
|
-
const schemaWithOverlays = getTransactionSchema(schemaId, [
|
|
597
|
-
"nts2023",
|
|
598
|
-
japaneseKnoweedOverlay,
|
|
599
|
-
managingAgentOverlay,
|
|
600
|
-
]);
|
|
601
|
-
|
|
602
|
-
// Navigate to the leasehold section in the oneOf array
|
|
603
|
-
const leaseholdSchema =
|
|
604
|
-
schemaWithOverlays.properties.propertyPack.properties.ownership.properties.ownershipsToBeTransferred.items.oneOf.find(
|
|
605
|
-
(schema) => schema.properties.ownershipType.enum[0] === "Leasehold"
|
|
606
|
-
);
|
|
607
|
-
|
|
608
|
-
// Verify the leasehold information structure
|
|
609
|
-
const leaseholdInfo = leaseholdSchema.properties.leaseholdInformation;
|
|
610
|
-
|
|
611
|
-
// Check required fields - should include both NTS and custom overlay requirements
|
|
612
|
-
expect(leaseholdInfo.required).toContain("contactDetails");
|
|
613
|
-
expect(leaseholdInfo.required).toContain("leaseTerm"); // from NTS
|
|
614
|
-
expect(leaseholdInfo.required).toContain("groundRent"); // from NTS
|
|
615
|
-
|
|
616
|
-
// Verify managing agent contact structure and refs
|
|
617
|
-
const managingAgent =
|
|
618
|
-
leaseholdInfo.properties.contactDetails.properties.contacts.properties
|
|
619
|
-
.managingAgent;
|
|
620
|
-
expect(managingAgent.macRef).toBe("MAC.4.1c");
|
|
621
|
-
|
|
622
|
-
// Verify contact details structure
|
|
623
|
-
const contact = managingAgent.properties.contact;
|
|
624
|
-
expect(contact.properties.nameOrOrganisation.macRef).toBe("MAC.4.1c.1");
|
|
625
|
-
expect(contact.properties.address.macRef).toBe("MAC.4.1c.0");
|
|
626
|
-
expect(contact.properties.telephone.macRef).toBe("MAC.4.1c.7");
|
|
627
|
-
expect(contact.properties.emailAddress.macRef).toBe("MAC.4.1c.8");
|
|
628
|
-
|
|
629
|
-
// Verify Japanese Knotweed section is still present and correct
|
|
630
|
-
const japaneseKnotweed =
|
|
631
|
-
schemaWithOverlays.properties.propertyPack.properties.specialistIssues
|
|
632
|
-
.properties.japaneseKnotweed;
|
|
633
|
-
expect(japaneseKnotweed.customRef).toBe("JKW.7.8");
|
|
634
|
-
|
|
635
|
-
// Verify NTS changes are still present
|
|
636
|
-
const mainsElectricity =
|
|
637
|
-
schemaWithOverlays.properties.propertyPack.properties.electricity.properties
|
|
638
|
-
.mainsElectricity;
|
|
639
|
-
expect(mainsElectricity.properties.yesNo.enum).toEqual(["Yes", "No"]);
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
test("ta7 overlay as key and as object produce identical results at leasehold info level", () => {
|
|
643
|
-
// Get the TA7 overlay object directly from the overlays map
|
|
644
|
-
const ta7OverlayObject = { ...overlaysMap[schemaId]["ta7ed3"] };
|
|
645
|
-
// Remove the $id to prevent schema registration conflicts
|
|
646
|
-
ta7OverlayObject.$id =
|
|
647
|
-
"https://trust.propdata.org.uk/schemas/v3/overlays/ta7ed3-copy.json";
|
|
648
|
-
|
|
649
|
-
// Get schemas using both methods
|
|
650
|
-
const schemaWithKey = getTransactionSchema(schemaId, ["ta7ed3"]);
|
|
651
|
-
const schemaWithObject = getTransactionSchema(schemaId, [ta7OverlayObject]);
|
|
652
|
-
|
|
653
|
-
// Navigate to the leasehold information section in both schemas
|
|
654
|
-
const getLeaseholdInfo = (schema) => {
|
|
655
|
-
const ownershipsToBeTransferred =
|
|
656
|
-
schema.properties.propertyPack.properties.ownership.properties
|
|
657
|
-
.ownershipsToBeTransferred;
|
|
658
|
-
const leaseholdSchema = ownershipsToBeTransferred.items.oneOf.find(
|
|
659
|
-
(schema) => schema.properties.ownershipType.enum[0] === "Leasehold"
|
|
660
|
-
);
|
|
661
|
-
return leaseholdSchema.properties.leaseholdInformation;
|
|
662
|
-
};
|
|
663
|
-
|
|
664
|
-
const leaseholdInfoWithKey = getLeaseholdInfo(schemaWithKey);
|
|
665
|
-
const leaseholdInfoWithObject = getLeaseholdInfo(schemaWithObject);
|
|
666
|
-
|
|
667
|
-
// Deep compare the leasehold information sections
|
|
668
|
-
expect(leaseholdInfoWithObject).toEqual(leaseholdInfoWithKey);
|
|
669
|
-
|
|
670
|
-
// Verify specific TA7 requirements are present in both
|
|
671
|
-
expect(leaseholdInfoWithKey.required).toContain("contactDetails");
|
|
672
|
-
expect(leaseholdInfoWithObject.required).toContain("contactDetails");
|
|
673
|
-
|
|
674
|
-
// Verify TA7 refs are present and identical
|
|
675
|
-
const contactDetails = leaseholdInfoWithKey.properties.contactDetails;
|
|
676
|
-
expect(contactDetails.ta7Ref).toBe("4.1");
|
|
677
|
-
expect(leaseholdInfoWithObject.properties.contactDetails.ta7Ref).toBe("4.1");
|
|
678
|
-
});
|
|
@@ -184,7 +184,7 @@ function hasNts2Ref(obj) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// Extract only NTS2-specific properties (with nts2Ref)
|
|
187
|
-
function extractNts2Properties(obj) {
|
|
187
|
+
function extractNts2Properties(obj, parentKey) {
|
|
188
188
|
const result = {};
|
|
189
189
|
|
|
190
190
|
// Copy nts2-specific metadata at current level
|
|
@@ -205,7 +205,7 @@ function extractNts2Properties(obj) {
|
|
|
205
205
|
Object.keys(obj.properties).forEach((key) => {
|
|
206
206
|
const prop = obj.properties[key];
|
|
207
207
|
if (hasNts2Ref(prop)) {
|
|
208
|
-
result.properties[key] = extractNts2Properties(prop);
|
|
208
|
+
result.properties[key] = extractNts2Properties(prop, key);
|
|
209
209
|
}
|
|
210
210
|
});
|
|
211
211
|
// Only keep properties if we found some with nts2Ref
|
|
@@ -223,8 +223,8 @@ function extractNts2Properties(obj) {
|
|
|
223
223
|
|
|
224
224
|
// Handle oneOf
|
|
225
225
|
if (obj.oneOf) {
|
|
226
|
-
|
|
227
|
-
obj.oneOf.
|
|
226
|
+
// Always preserve the full array structure and order
|
|
227
|
+
const processedOneOf = obj.oneOf.map((schema, index) => {
|
|
228
228
|
if (hasNts2Ref(schema)) {
|
|
229
229
|
const extracted = extractNts2Properties(schema);
|
|
230
230
|
// For oneOf schemas, we need to preserve discriminator enum values
|
|
@@ -239,11 +239,55 @@ function extractNts2Properties(obj) {
|
|
|
239
239
|
};
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
|
-
|
|
242
|
+
// Also preserve base enum values for all other properties in the extension
|
|
243
|
+
if (schema.properties) {
|
|
244
|
+
if (!extracted.properties) extracted.properties = {};
|
|
245
|
+
Object.keys(schema.properties).forEach((propName) => {
|
|
246
|
+
const prop = schema.properties[propName];
|
|
247
|
+
if (prop.enum) {
|
|
248
|
+
if (!extracted.properties[propName]) {
|
|
249
|
+
extracted.properties[propName] = {
|
|
250
|
+
enum: prop.enum,
|
|
251
|
+
};
|
|
252
|
+
} else if (!extracted.properties[propName].enum) {
|
|
253
|
+
// If the property exists but doesn't have enum, add it
|
|
254
|
+
extracted.properties[propName].enum = prop.enum;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
return extracted;
|
|
260
|
+
} else {
|
|
261
|
+
// If this branch doesn't have nts2Ref, preserve the base discriminator enum
|
|
262
|
+
if (obj.discriminator && schema.properties) {
|
|
263
|
+
const propName = obj.discriminator.propertyName;
|
|
264
|
+
if (schema.properties[propName]) {
|
|
265
|
+
return {
|
|
266
|
+
properties: {
|
|
267
|
+
[propName]: {
|
|
268
|
+
enum: schema.properties[propName].enum,
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// If no discriminator, return empty object to preserve structure
|
|
275
|
+
return {};
|
|
243
276
|
}
|
|
244
277
|
});
|
|
245
|
-
|
|
246
|
-
|
|
278
|
+
result.oneOf = processedOneOf;
|
|
279
|
+
|
|
280
|
+
// --- NEW: If this is an extension property inside a oneOf, promote it to the parent overlay ---
|
|
281
|
+
// If parentKey is set, and this object has extension metadata, return a stub for the parent
|
|
282
|
+
if (
|
|
283
|
+
parentKey &&
|
|
284
|
+
(result.ntsRef || result.required || result.discriminator)
|
|
285
|
+
) {
|
|
286
|
+
// This is an extension property inside a oneOf branch
|
|
287
|
+
// Return a stub for the parent property with metadata and oneOf structure
|
|
288
|
+
const parentStub = {};
|
|
289
|
+
Object.assign(parentStub, result);
|
|
290
|
+
return parentStub;
|
|
247
291
|
}
|
|
248
292
|
}
|
|
249
293
|
|