@pdtf/schemas 3.6.0-2 → 3.6.0-21

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.
Files changed (35) hide show
  1. package/.claude/settings.local.json +17 -2
  2. package/docs/sef25-extensions-ui-spec.md +146 -0
  3. package/index.js +97 -58
  4. package/package.json +4 -2
  5. package/src/schemas/v3/combined.json +7013 -1313
  6. package/src/schemas/v3/compactSkeleton.txt +353 -11
  7. package/src/schemas/v3/overlays/baspi4.json +161 -17
  8. package/src/schemas/v3/overlays/baspi5.json +166 -19
  9. package/src/schemas/v3/overlays/extensions/jk.json +24 -6
  10. package/src/schemas/v3/overlays/extensions/pc.json +36 -0
  11. package/src/schemas/v3/overlays/extensions/ph.json +197 -0
  12. package/src/schemas/v3/overlays/extensions/sc.json +83 -0
  13. package/src/schemas/v3/overlays/fme1.json +16 -14
  14. package/src/schemas/v3/overlays/lpe1.json +60 -30
  15. package/src/schemas/v3/overlays/nts.json +68 -1
  16. package/src/schemas/v3/overlays/nts2.json +68 -1
  17. package/src/schemas/v3/overlays/ntsl.json +68 -1
  18. package/src/schemas/v3/overlays/ntsl2.json +68 -1
  19. package/src/schemas/v3/overlays/piq.json +38 -1
  20. package/src/schemas/v3/overlays/rds.json +118 -6
  21. package/src/schemas/v3/overlays/ta6.json +40 -14
  22. package/src/schemas/v3/overlays/ta6ed6.json +6436 -0
  23. package/src/schemas/v3/overlays/ta7.json +70 -6
  24. package/src/schemas/v3/overlays/ta7ed5.json +1539 -0
  25. package/src/schemas/v3/pdtf-transaction.json +4098 -615
  26. package/src/schemas/v3/skeleton.json +491 -34
  27. package/src/tests/v3/buyerCircumstances.test.js +543 -0
  28. package/src/tests/v3/extensionOverlays.test.js +436 -0
  29. package/src/tests/v3/offers.test.js +37 -40
  30. package/src/tests/v3/patternProperties.test.js +149 -30
  31. package/src/tests/v3/ta6ed6.test.js +2356 -0
  32. package/src/tests/v3/ta7ed5.test.js +567 -0
  33. package/src/utils/extractExtensionOverlays.js +104 -31
  34. package/src/utils/extractOverlay.js +87 -23
  35. package/src/utils/pathSkeleton.js +1 -1
@@ -0,0 +1,543 @@
1
+ const { isPathValid, getSubschema, getValidator } = require("../../../index.js");
2
+
3
+ const schemaId =
4
+ "https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
5
+
6
+ describe("buyerCircumstances path validation", () => {
7
+ describe("isPathValid for buyerCircumstances fields", () => {
8
+ test("validates path /offers/*/buyerCircumstances", () => {
9
+ const path = "/offers/offer-123/buyerCircumstances";
10
+ expect(isPathValid(path, schemaId)).toBe(true);
11
+ });
12
+
13
+ test("validates path /offers/*/buyerCircumstances/intendedUse", () => {
14
+ const path = "/offers/offer-123/buyerCircumstances/intendedUse";
15
+ expect(isPathValid(path, schemaId)).toBe(true);
16
+ });
17
+
18
+ test("validates path /offers/*/buyerCircumstances/firstTimeBuyer", () => {
19
+ const path = "/offers/offer-123/buyerCircumstances/firstTimeBuyer";
20
+ expect(isPathValid(path, schemaId)).toBe(true);
21
+ });
22
+
23
+ test("validates path /offers/*/buyerCircumstances/rightToBuy", () => {
24
+ const path = "/offers/offer-123/buyerCircumstances/rightToBuy";
25
+ expect(isPathValid(path, schemaId)).toBe(true);
26
+ });
27
+
28
+ test("validates path /offers/*/buyerCircumstances/ltdCompanyPurchase", () => {
29
+ const path = "/offers/offer-123/buyerCircumstances/ltdCompanyPurchase";
30
+ expect(isPathValid(path, schemaId)).toBe(true);
31
+ });
32
+
33
+ test("validates path /offers/*/buyerCircumstances/numberOfOwners", () => {
34
+ const path = "/offers/offer-123/buyerCircumstances/numberOfOwners";
35
+ expect(isPathValid(path, schemaId)).toBe(true);
36
+ });
37
+
38
+ test("validates path /offers/*/buyerCircumstances/numberOfExpats", () => {
39
+ const path = "/offers/offer-123/buyerCircumstances/numberOfExpats";
40
+ expect(isPathValid(path, schemaId)).toBe(true);
41
+ });
42
+
43
+ test("validates path /offers/*/buyerCircumstances/giftedEquity", () => {
44
+ const path = "/offers/offer-123/buyerCircumstances/giftedEquity";
45
+ expect(isPathValid(path, schemaId)).toBe(true);
46
+ });
47
+
48
+ test("validates path /offers/*/buyerCircumstances/numberOfGiftedDeposits", () => {
49
+ const path = "/offers/offer-123/buyerCircumstances/numberOfGiftedDeposits";
50
+ expect(isPathValid(path, schemaId)).toBe(true);
51
+ });
52
+
53
+ test("validates path /offers/*/buyerCircumstances/requiresMortgage", () => {
54
+ const path = "/offers/offer-123/buyerCircumstances/requiresMortgage";
55
+ expect(isPathValid(path, schemaId)).toBe(true);
56
+ });
57
+
58
+ test("validates path /offers/*/buyerCircumstances/helpToBuyEquityLoan", () => {
59
+ const path = "/offers/offer-123/buyerCircumstances/helpToBuyEquityLoan";
60
+ expect(isPathValid(path, schemaId)).toBe(true);
61
+ });
62
+
63
+ test("validates path /offers/*/buyerCircumstances/armedForcesHelpToBuy", () => {
64
+ const path = "/offers/offer-123/buyerCircumstances/armedForcesHelpToBuy";
65
+ expect(isPathValid(path, schemaId)).toBe(true);
66
+ });
67
+
68
+ test("validates path /offers/*/buyerCircumstances/numberOfHelpToBuyIsas", () => {
69
+ const path = "/offers/offer-123/buyerCircumstances/numberOfHelpToBuyIsas";
70
+ expect(isPathValid(path, schemaId)).toBe(true);
71
+ });
72
+
73
+ test("validates path /offers/*/buyerCircumstances/bridgingFinanceRepresentation", () => {
74
+ const path = "/offers/offer-123/buyerCircumstances/bridgingFinanceRepresentation";
75
+ expect(isPathValid(path, schemaId)).toBe(true);
76
+ });
77
+
78
+ test("validates path /offers/*/buyerCircumstances/depositAmount", () => {
79
+ const path = "/offers/offer-123/buyerCircumstances/depositAmount";
80
+ expect(isPathValid(path, schemaId)).toBe(true);
81
+ });
82
+
83
+ test("validates path /offers/*/buyerCircumstances/loanToValue", () => {
84
+ const path = "/offers/offer-123/buyerCircumstances/loanToValue";
85
+ expect(isPathValid(path, schemaId)).toBe(true);
86
+ });
87
+
88
+ test("validates path /offers/*/buyerCircumstances/numberOfBorrowers", () => {
89
+ const path = "/offers/offer-123/buyerCircumstances/numberOfBorrowers";
90
+ expect(isPathValid(path, schemaId)).toBe(true);
91
+ });
92
+
93
+ test("validates path /offers/*/buyerCircumstances/jointProprietorSoleMortgagor", () => {
94
+ const path = "/offers/offer-123/buyerCircumstances/jointProprietorSoleMortgagor";
95
+ expect(isPathValid(path, schemaId)).toBe(true);
96
+ });
97
+
98
+ test("validates path /offers/*/buyerCircumstances/soleProprietorJointMortgagor", () => {
99
+ const path = "/offers/offer-123/buyerCircumstances/soleProprietorJointMortgagor";
100
+ expect(isPathValid(path, schemaId)).toBe(true);
101
+ });
102
+
103
+ test("validates path /offers/*/buyerCircumstances/deedOfPostponement", () => {
104
+ const path = "/offers/offer-123/buyerCircumstances/deedOfPostponement";
105
+ expect(isPathValid(path, schemaId)).toBe(true);
106
+ });
107
+
108
+ test("validates path /offers/*/buyerCircumstances/separateRepresentation", () => {
109
+ const path = "/offers/offer-123/buyerCircumstances/separateRepresentation";
110
+ expect(isPathValid(path, schemaId)).toBe(true);
111
+ });
112
+
113
+ test("validates path /offers/*/buyerCircumstances/oldestBorrowerAge", () => {
114
+ const path = "/offers/offer-123/buyerCircumstances/oldestBorrowerAge";
115
+ expect(isPathValid(path, schemaId)).toBe(true);
116
+ });
117
+
118
+ test("validates path /offers/*/buyerCircumstances/mortgageTerm", () => {
119
+ const path = "/offers/offer-123/buyerCircumstances/mortgageTerm";
120
+ expect(isPathValid(path, schemaId)).toBe(true);
121
+ });
122
+
123
+ test("validates path /offers/*/buyerCircumstances/adverseCredit", () => {
124
+ const path = "/offers/offer-123/buyerCircumstances/adverseCredit";
125
+ expect(isPathValid(path, schemaId)).toBe(true);
126
+ });
127
+
128
+ test("validates path /offers/*/buyerCircumstances/employmentType", () => {
129
+ const path = "/offers/offer-123/buyerCircumstances/employmentType";
130
+ expect(isPathValid(path, schemaId)).toBe(true);
131
+ });
132
+
133
+ test("validates path /offers/*/buyerCircumstances/existingMortgagedProperties", () => {
134
+ const path = "/offers/offer-123/buyerCircumstances/existingMortgagedProperties";
135
+ expect(isPathValid(path, schemaId)).toBe(true);
136
+ });
137
+
138
+ test("validates path /offers/*/buyerCircumstances/annualIncome", () => {
139
+ const path = "/offers/offer-123/buyerCircumstances/annualIncome";
140
+ expect(isPathValid(path, schemaId)).toBe(true);
141
+ });
142
+
143
+ test("validates path /offers/*/buyerCircumstances/residencyStatus", () => {
144
+ const path = "/offers/offer-123/buyerCircumstances/residencyStatus";
145
+ expect(isPathValid(path, schemaId)).toBe(true);
146
+ });
147
+
148
+ test("validates path /offers/*/buyerCircumstances/nominatedLender", () => {
149
+ const path = "/offers/offer-123/buyerCircumstances/nominatedLender";
150
+ expect(isPathValid(path, schemaId)).toBe(true);
151
+ });
152
+
153
+ test("rejects invalid path /offers/*/buyerCircumstances/nonExistentField", () => {
154
+ const path = "/offers/offer-123/buyerCircumstances/nonExistentField";
155
+ expect(isPathValid(path, schemaId)).toBe(false);
156
+ });
157
+ });
158
+
159
+ describe("getSubschema for buyerCircumstances", () => {
160
+ test("returns correct subschema for buyerCircumstances", () => {
161
+ const path = "/offers/offer-123/buyerCircumstances";
162
+ const subschema = getSubschema(path, schemaId);
163
+
164
+ expect(subschema).toBeDefined();
165
+ expect(subschema.type).toBe("object");
166
+ expect(subschema.properties).toBeDefined();
167
+ expect(subschema.properties.intendedUse).toBeDefined();
168
+ expect(subschema.properties.firstTimeBuyer).toBeDefined();
169
+ expect(subschema.properties.requiresMortgage).toBeDefined();
170
+ });
171
+
172
+ test("returns correct subschema for intendedUse", () => {
173
+ const path = "/offers/offer-123/buyerCircumstances/intendedUse";
174
+ const subschema = getSubschema(path, schemaId);
175
+
176
+ expect(subschema).toBeDefined();
177
+ expect(subschema.type).toBe("string");
178
+ });
179
+
180
+ test("returns correct subschema for firstTimeBuyer (boolean)", () => {
181
+ const path = "/offers/offer-123/buyerCircumstances/firstTimeBuyer";
182
+ const subschema = getSubschema(path, schemaId);
183
+
184
+ expect(subschema).toBeDefined();
185
+ expect(subschema.type).toBe("boolean");
186
+ });
187
+
188
+ test("returns correct subschema for numberOfOwners (integer)", () => {
189
+ const path = "/offers/offer-123/buyerCircumstances/numberOfOwners";
190
+ const subschema = getSubschema(path, schemaId);
191
+
192
+ expect(subschema).toBeDefined();
193
+ expect(subschema.type).toBe("integer");
194
+ });
195
+
196
+ test("returns correct subschema for depositAmount (number)", () => {
197
+ const path = "/offers/offer-123/buyerCircumstances/depositAmount";
198
+ const subschema = getSubschema(path, schemaId);
199
+
200
+ expect(subschema).toBeDefined();
201
+ expect(subschema.type).toBe("number");
202
+ });
203
+ });
204
+
205
+ describe("validation of buyerCircumstances data", () => {
206
+ test("accepts valid offer with buyerCircumstances (requiresMortgage: Yes)", () => {
207
+ const validator = getValidator(schemaId);
208
+ const transaction = {
209
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
210
+ status: "active",
211
+ participants: [
212
+ {
213
+ role: "Buyer",
214
+ participantStatus: "Active",
215
+ },
216
+ ],
217
+ offers: {
218
+ "offer-1": {
219
+ amount: 250000,
220
+ currency: "GBP",
221
+ status: "Pending",
222
+ buyerCircumstances: {
223
+ intendedUse: "Owner occupied",
224
+ firstTimeBuyer: true,
225
+ rightToBuy: false,
226
+ ltdCompanyPurchase: false,
227
+ numberOfOwners: 2,
228
+ numberOfExpats: 0,
229
+ giftedEquity: false,
230
+ numberOfGiftedDeposits: 1,
231
+ requiresMortgage: "Yes",
232
+ helpToBuyEquityLoan: false,
233
+ armedForcesHelpToBuy: false,
234
+ numberOfHelpToBuyIsas: 2,
235
+ depositAmount: 50000,
236
+ loanToValue: 80,
237
+ numberOfBorrowers: 2,
238
+ jointProprietorSoleMortgagor: false,
239
+ soleProprietorJointMortgagor: false,
240
+ deedOfPostponement: false,
241
+ separateRepresentation: false,
242
+ oldestBorrowerAge: 35,
243
+ mortgageTerm: 25,
244
+ existingMortgagedProperties: 0,
245
+ annualIncome: 75000,
246
+ },
247
+ },
248
+ },
249
+ };
250
+
251
+ const isValid = validator(transaction);
252
+ expect(isValid).toBe(true);
253
+ });
254
+
255
+ test("accepts valid offer with buyerCircumstances (requiresMortgage: No)", () => {
256
+ const validator = getValidator(schemaId);
257
+ const transaction = {
258
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
259
+ status: "active",
260
+ participants: [
261
+ {
262
+ role: "Buyer",
263
+ participantStatus: "Active",
264
+ },
265
+ ],
266
+ offers: {
267
+ "offer-1": {
268
+ amount: 250000,
269
+ currency: "GBP",
270
+ status: "Pending",
271
+ buyerCircumstances: {
272
+ intendedUse: "Buy-to-let",
273
+ firstTimeBuyer: false,
274
+ requiresMortgage: "No",
275
+ },
276
+ },
277
+ },
278
+ };
279
+
280
+ const isValid = validator(transaction);
281
+ expect(isValid).toBe(true);
282
+ });
283
+
284
+ test("accepts offer with partial buyerCircumstances (requiresMortgage required)", () => {
285
+ const validator = getValidator(schemaId);
286
+ const transaction = {
287
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
288
+ status: "active",
289
+ participants: [
290
+ {
291
+ role: "Buyer",
292
+ participantStatus: "Active",
293
+ },
294
+ ],
295
+ offers: {
296
+ "offer-1": {
297
+ amount: 250000,
298
+ currency: "GBP",
299
+ status: "Pending",
300
+ buyerCircumstances: {
301
+ firstTimeBuyer: true,
302
+ requiresMortgage: "Yes",
303
+ },
304
+ },
305
+ },
306
+ };
307
+
308
+ const isValid = validator(transaction);
309
+ expect(isValid).toBe(true);
310
+ });
311
+
312
+ test("rejects buyerCircumstances without required requiresMortgage field", () => {
313
+ const validator = getValidator(schemaId);
314
+ const transaction = {
315
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
316
+ status: "active",
317
+ participants: [
318
+ {
319
+ role: "Buyer",
320
+ participantStatus: "Active",
321
+ },
322
+ ],
323
+ offers: {
324
+ "offer-1": {
325
+ amount: 250000,
326
+ currency: "GBP",
327
+ status: "Pending",
328
+ buyerCircumstances: {
329
+ firstTimeBuyer: true,
330
+ },
331
+ },
332
+ },
333
+ };
334
+
335
+ const isValid = validator(transaction);
336
+ expect(isValid).toBe(false);
337
+ });
338
+
339
+ test("rejects buyerCircumstances with wrong type for firstTimeBuyer", () => {
340
+ const validator = getValidator(schemaId);
341
+ const transaction = {
342
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
343
+ status: "active",
344
+ participants: [
345
+ {
346
+ role: "Buyer",
347
+ participantStatus: "Active",
348
+ },
349
+ ],
350
+ offers: {
351
+ "offer-1": {
352
+ amount: 250000,
353
+ currency: "GBP",
354
+ status: "Pending",
355
+ buyerCircumstances: {
356
+ firstTimeBuyer: "yes", // should be boolean
357
+ },
358
+ },
359
+ },
360
+ };
361
+
362
+ const isValid = validator(transaction);
363
+ expect(isValid).toBe(false);
364
+ });
365
+
366
+ test("rejects buyerCircumstances with wrong type for numberOfOwners", () => {
367
+ const validator = getValidator(schemaId);
368
+ const transaction = {
369
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
370
+ status: "active",
371
+ participants: [
372
+ {
373
+ role: "Buyer",
374
+ participantStatus: "Active",
375
+ },
376
+ ],
377
+ offers: {
378
+ "offer-1": {
379
+ amount: 250000,
380
+ currency: "GBP",
381
+ status: "Pending",
382
+ buyerCircumstances: {
383
+ numberOfOwners: "two", // should be integer
384
+ },
385
+ },
386
+ },
387
+ };
388
+
389
+ const isValid = validator(transaction);
390
+ expect(isValid).toBe(false);
391
+ });
392
+
393
+ test("rejects buyerCircumstances with wrong type for depositAmount", () => {
394
+ const validator = getValidator(schemaId);
395
+ const transaction = {
396
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
397
+ status: "active",
398
+ participants: [
399
+ {
400
+ role: "Buyer",
401
+ participantStatus: "Active",
402
+ },
403
+ ],
404
+ offers: {
405
+ "offer-1": {
406
+ amount: 250000,
407
+ currency: "GBP",
408
+ status: "Pending",
409
+ buyerCircumstances: {
410
+ depositAmount: "50000", // should be number
411
+ },
412
+ },
413
+ },
414
+ };
415
+
416
+ const isValid = validator(transaction);
417
+ expect(isValid).toBe(false);
418
+ });
419
+ });
420
+ });
421
+
422
+ describe("ownership new fields path validation", () => {
423
+ describe("isPathValid for outstandingMortgage and existingLender", () => {
424
+ test("validates path /propertyPack/ownership/outstandingMortgage", () => {
425
+ const path = "/propertyPack/ownership/outstandingMortgage";
426
+ expect(isPathValid(path, schemaId)).toBe(true);
427
+ });
428
+
429
+ test("validates path /propertyPack/ownership/existingLender", () => {
430
+ const path = "/propertyPack/ownership/existingLender";
431
+ expect(isPathValid(path, schemaId)).toBe(true);
432
+ });
433
+ });
434
+
435
+ describe("getSubschema for ownership new fields", () => {
436
+ test("returns correct subschema for outstandingMortgage", () => {
437
+ const path = "/propertyPack/ownership/outstandingMortgage";
438
+ const subschema = getSubschema(path, schemaId);
439
+
440
+ expect(subschema).toBeDefined();
441
+ expect(subschema.type).toBe("string");
442
+ });
443
+
444
+ test("returns correct subschema for existingLender", () => {
445
+ const path = "/propertyPack/ownership/existingLender";
446
+ const subschema = getSubschema(path, schemaId);
447
+
448
+ expect(subschema).toBeDefined();
449
+ expect(subschema.type).toBe("string");
450
+ });
451
+ });
452
+
453
+ describe("validation of ownership new fields", () => {
454
+ test("accepts valid ownership with outstandingMortgage and existingLender", () => {
455
+ const validator = getValidator(schemaId);
456
+ const transaction = {
457
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
458
+ status: "active",
459
+ participants: [
460
+ {
461
+ role: "Seller",
462
+ participantStatus: "Active",
463
+ },
464
+ ],
465
+ propertyPack: {
466
+ ownership: {
467
+ outstandingMortgage: "Yes",
468
+ existingLender: "Nationwide Building Society",
469
+ },
470
+ },
471
+ };
472
+
473
+ const isValid = validator(transaction);
474
+ expect(isValid).toBe(true);
475
+ });
476
+
477
+ test("accepts ownership without outstandingMortgage and existingLender", () => {
478
+ const validator = getValidator(schemaId);
479
+ const transaction = {
480
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
481
+ status: "active",
482
+ participants: [
483
+ {
484
+ role: "Seller",
485
+ participantStatus: "Active",
486
+ },
487
+ ],
488
+ propertyPack: {
489
+ ownership: {
490
+ numberOfSellers: 2,
491
+ },
492
+ },
493
+ };
494
+
495
+ const isValid = validator(transaction);
496
+ expect(isValid).toBe(true);
497
+ });
498
+
499
+ test("rejects ownership with wrong type for outstandingMortgage", () => {
500
+ const validator = getValidator(schemaId);
501
+ const transaction = {
502
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
503
+ status: "active",
504
+ participants: [
505
+ {
506
+ role: "Seller",
507
+ participantStatus: "Active",
508
+ },
509
+ ],
510
+ propertyPack: {
511
+ ownership: {
512
+ outstandingMortgage: true, // should be string
513
+ },
514
+ },
515
+ };
516
+
517
+ const isValid = validator(transaction);
518
+ expect(isValid).toBe(false);
519
+ });
520
+
521
+ test("rejects ownership with wrong type for existingLender", () => {
522
+ const validator = getValidator(schemaId);
523
+ const transaction = {
524
+ transactionId: "123e4567-e89b-12d3-a456-426614174000",
525
+ status: "active",
526
+ participants: [
527
+ {
528
+ role: "Seller",
529
+ participantStatus: "Active",
530
+ },
531
+ ],
532
+ propertyPack: {
533
+ ownership: {
534
+ existingLender: 12345, // should be string
535
+ },
536
+ },
537
+ };
538
+
539
+ const isValid = validator(transaction);
540
+ expect(isValid).toBe(false);
541
+ });
542
+ });
543
+ });