@pdtf/schemas 3.5.0 → 3.5.1-2

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.
@@ -1,12 +1,21 @@
1
1
  ## README
2
2
 
3
3
  ### About verified claims
4
- `pdtf-verified-claims.json` describes the schema container in which we store property `claims` and their corresponding `verification`.
4
+ `pdtf-verified-claims.json` describes the schema container in which we store property `claims` and their corresponding `verification`.
5
5
 
6
- This schema structure is strongly influenced by OpenID Connect for Identity Assurance 1.0's way of storing "verified claims".
6
+ This schema structure is strongly influenced by OpenID Connect for Identity Assurance 1.0's way of storing "verified claims".
7
7
 
8
8
  More Details from the OIDC page which describes the details the nature of `verification` and `claims` objects: https://openid.net/specs/openid-connect-4-identity-assurance-1_0.html#name-representing-verified-claim
9
9
 
10
- Notes:
10
+ ### Terms of Use
11
+ The schema supports an optional `terms_of_use` object to control access to verified claims based on confidentiality levels:
12
+
13
+ * **Public**: No access restrictions (`allowed_roles: []`)
14
+ * **Restricted**: Requires some participant role but not specifically defined (`allowed_roles: []`)
15
+ * **Confidential**: Specific roles defined for access control (e.g., `allowed_roles: ["Seller's Conveyancer", "Buyer's Conveyancer"]`)
16
+
17
+ The `allowed_roles` array accepts any of the participant roles defined in [pdtf-transaction.json](../v3/pdtf-transaction.json).
18
+
19
+ Notes:
11
20
  * For PDTF Schema, all data included in the `claims` object must adhere to [pdtf-transaction.json (v3)](../v3/pdtf-transaction.json)
12
21
  * For verification, there is more freedom in terms of the type of data that can currently be provided, see the documentation in the OIDC link above for details and examples.
@@ -411,6 +411,22 @@
411
411
  },
412
412
  "minProperties": 1,
413
413
  "additionalProperties": false
414
+ },
415
+ "terms_of_use": {
416
+ "type": "object",
417
+ "properties": {
418
+ "confidentiality_level": {
419
+ "type": "string",
420
+ "enum": ["public", "restricted", "confidential"]
421
+ },
422
+ "allowed_roles": {
423
+ "type": "array",
424
+ "items": {
425
+ "type": "string"
426
+ }
427
+ }
428
+ },
429
+ "additionalProperties": false
414
430
  }
415
431
  },
416
432
  "required": ["verification", "claims"],
@@ -314,9 +314,11 @@ test("correctly gets a subschema validator for a TA6 overlay which validates wit
314
314
  );
315
315
  const data = jp.get(exampleTransaction, path);
316
316
  data.uprn = undefined;
317
- const validator = getSubschemaValidator(path, exampleTransaction.$schema, [
318
- "ta6ed4",
319
- ]);
317
+ const validator = getSubschemaValidator(
318
+ path,
319
+ clonedExampleTransaction.$schema,
320
+ ["ta6ed4"]
321
+ );
320
322
  let isValid = validator(data);
321
323
  expect(isValid).toBe(true);
322
324
  });
@@ -458,7 +460,7 @@ test("ntsl2025 requires parking and listingAndConservation fields", () => {
458
460
  const clonedExampleTransaction = JSON.parse(
459
461
  JSON.stringify(exampleTransaction)
460
462
  );
461
-
463
+
462
464
  // Set up lettings-specific fields
463
465
  clonedExampleTransaction.propertyPack.lettingInformation = {
464
466
  rent: 3500,
@@ -467,32 +469,47 @@ test("ntsl2025 requires parking and listingAndConservation fields", () => {
467
469
  };
468
470
  delete clonedExampleTransaction.propertyPack.priceInformation;
469
471
  delete clonedExampleTransaction.propertyPack.ownership;
470
-
472
+
471
473
  // Remove ALL parking fields - this should make validation fail
472
474
  delete clonedExampleTransaction.propertyPack.parking.parkingArrangements;
473
475
  delete clonedExampleTransaction.propertyPack.parking.disabledParking;
474
476
  delete clonedExampleTransaction.propertyPack.parking.controlledParking;
475
- delete clonedExampleTransaction.propertyPack.parking.electricVehicleChargingPoint;
476
-
477
+ delete clonedExampleTransaction.propertyPack.parking
478
+ .electricVehicleChargingPoint;
479
+
477
480
  // Remove ALL listingAndConservation fields - this should make validation fail
478
481
  delete clonedExampleTransaction.propertyPack.listingAndConservation.isListed;
479
- delete clonedExampleTransaction.propertyPack.listingAndConservation.isConservationArea;
480
- delete clonedExampleTransaction.propertyPack.listingAndConservation.hasTreePreservationOrder;
481
-
482
+ delete clonedExampleTransaction.propertyPack.listingAndConservation
483
+ .isConservationArea;
484
+ delete clonedExampleTransaction.propertyPack.listingAndConservation
485
+ .hasTreePreservationOrder;
486
+
482
487
  const isValid = validator(clonedExampleTransaction);
483
-
488
+
484
489
  // The data should be INVALID because required fields are missing
485
490
  expect(isValid).toBe(false);
486
-
491
+
487
492
  // 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
+ const errorMessages = validator.errors.map((e) => e.message);
494
+ expect(errorMessages).toContain(
495
+ "must have required property 'parkingArrangements'"
496
+ );
497
+ expect(errorMessages).toContain(
498
+ "must have required property 'disabledParking'"
499
+ );
500
+ expect(errorMessages).toContain(
501
+ "must have required property 'controlledParking'"
502
+ );
503
+ expect(errorMessages).toContain(
504
+ "must have required property 'electricVehicleChargingPoint'"
505
+ );
493
506
  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'");
507
+ expect(errorMessages).toContain(
508
+ "must have required property 'isConservationArea'"
509
+ );
510
+ expect(errorMessages).toContain(
511
+ "must have required property 'hasTreePreservationOrder'"
512
+ );
496
513
  });
497
514
 
498
515
  test("waterAndDrainage state is invalid under nts2023 overlay when mainsFoulDrainage yesNo is Not known", () => {
@@ -500,36 +517,37 @@ test("waterAndDrainage state is invalid under nts2023 overlay when mainsFoulDrai
500
517
  const clonedExampleTransaction = JSON.parse(
501
518
  JSON.stringify(exampleTransaction)
502
519
  );
503
-
520
+
504
521
  // Set the waterAndDrainage to an actually invalid state for nts2023
505
- // The nts overlay restricts mainsFoulDrainage.yesNo to only "Yes" or "No"
522
+ // The nts overlay restricts mainsFoulDrainage.yesNo to only "Yes" or "No"
506
523
  // (not "Not known" like the base schema allows)
507
524
  clonedExampleTransaction.propertyPack.waterAndDrainage = {
508
- "water": {
509
- "mainsWater": {
510
- "yesNo": "Yes",
511
- "waterMeter": {
512
- "isSupplyMetered": "No"
513
- }
514
- }
525
+ water: {
526
+ mainsWater: {
527
+ yesNo: "Yes",
528
+ waterMeter: {
529
+ isSupplyMetered: "No",
530
+ },
531
+ },
515
532
  },
516
- "drainage": {
517
- "mainsSurfaceWaterDrainage": {
518
- "yesNo": "Yes"
533
+ drainage: {
534
+ mainsSurfaceWaterDrainage: {
535
+ yesNo: "Yes",
519
536
  },
520
- "mainsFoulDrainage": {
521
- "yesNo": "Not known" // This is invalid under nts2023 overlay
522
- }
523
- }
537
+ mainsFoulDrainage: {
538
+ yesNo: "Not known", // This is invalid under nts2023 overlay
539
+ },
540
+ },
524
541
  };
525
-
542
+
526
543
  const isValid = validator(clonedExampleTransaction);
527
544
  expect(isValid).toBe(false);
528
-
545
+
529
546
  // Check that the validation error is about the invalid enum value
530
- const relevantError = validator.errors.find(error =>
531
- error.instancePath.includes('mainsFoulDrainage') &&
532
- error.message.includes('must be equal to one of the allowed values')
547
+ const relevantError = validator.errors.find(
548
+ (error) =>
549
+ error.instancePath.includes("mainsFoulDrainage") &&
550
+ error.message.includes("must be equal to one of the allowed values")
533
551
  );
534
552
  expect(relevantError).toBeDefined();
535
553
  });
@@ -539,36 +557,37 @@ test("waterAndDrainage state with mainsFoulDrainage No missing offMainsDrainageS
539
557
  const clonedExampleTransaction = JSON.parse(
540
558
  JSON.stringify(exampleTransaction)
541
559
  );
542
-
560
+
543
561
  // Set the waterAndDrainage to the state from the user request
544
562
  // This SHOULD be invalid because offMainsDrainageSystem is required when mainsFoulDrainage.yesNo is "No"
545
563
  clonedExampleTransaction.propertyPack.waterAndDrainage = {
546
- "water": {
547
- "mainsWater": {
548
- "yesNo": "Yes",
549
- "waterMeter": {
550
- "isSupplyMetered": "No"
551
- }
552
- }
564
+ water: {
565
+ mainsWater: {
566
+ yesNo: "Yes",
567
+ waterMeter: {
568
+ isSupplyMetered: "No",
569
+ },
570
+ },
553
571
  },
554
- "drainage": {
555
- "mainsSurfaceWaterDrainage": {
556
- "yesNo": "Yes"
572
+ drainage: {
573
+ mainsSurfaceWaterDrainage: {
574
+ yesNo: "Yes",
557
575
  },
558
- "mainsFoulDrainage": {
559
- "yesNo": "No"
576
+ mainsFoulDrainage: {
577
+ yesNo: "No",
560
578
  // Missing offMainsDrainageSystem - this should make validation fail
561
- }
562
- }
579
+ },
580
+ },
563
581
  };
564
-
582
+
565
583
  const isValid = validator(clonedExampleTransaction);
566
584
  expect(isValid).toBe(false);
567
-
585
+
568
586
  // Check that the validation error is about missing offMainsDrainageSystem
569
- const relevantError = validator.errors.find(error =>
570
- error.instancePath.includes('mainsFoulDrainage') &&
571
- error.message.includes('offMainsDrainageSystem')
587
+ const relevantError = validator.errors.find(
588
+ (error) =>
589
+ error.instancePath.includes("mainsFoulDrainage") &&
590
+ error.message.includes("offMainsDrainageSystem")
572
591
  );
573
592
  expect(relevantError).toBeDefined();
574
593
  });
@@ -126,3 +126,70 @@ test("returns an empty array of errors for verified claim with multiple valid pa
126
126
  };
127
127
  expect(validateVerifiedClaims([clonedVouch], v3SchemaId, null)).toEqual([]);
128
128
  });
129
+
130
+ test("returns an empty array for valid terms_of_use object with confidential level", () => {
131
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
132
+ clonedVouch.terms_of_use = {
133
+ confidentiality_level: "confidential",
134
+ allowed_roles: ["Seller's Conveyancer", "Buyer's Conveyancer"]
135
+ };
136
+ expect(validateVerifiedClaims([clonedVouch])).toEqual([]);
137
+ });
138
+
139
+ test("returns an empty array for valid terms_of_use with public confidentiality", () => {
140
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
141
+ clonedVouch.terms_of_use = {
142
+ confidentiality_level: "public",
143
+ allowed_roles: []
144
+ };
145
+ expect(validateVerifiedClaims([clonedVouch])).toEqual([]);
146
+ });
147
+
148
+ test("returns an empty array for valid terms_of_use with restricted level", () => {
149
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
150
+ clonedVouch.terms_of_use = {
151
+ confidentiality_level: "restricted",
152
+ allowed_roles: []
153
+ };
154
+ expect(validateVerifiedClaims([clonedVouch])).toEqual([]);
155
+ });
156
+
157
+ test("returns errors for invalid confidentiality_level in terms_of_use", () => {
158
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
159
+ clonedVouch.terms_of_use = {
160
+ confidentiality_level: "invalid_level",
161
+ allowed_roles: ["Seller's Conveyancer"]
162
+ };
163
+ const errors = validateVerifiedClaims([clonedVouch]);
164
+ expect(errors).toHaveLength(1);
165
+ expect(errors[0]).toHaveLength(1);
166
+ expect(errors[0][0].instancePath).toBe("/verified_claims/0/terms_of_use/confidentiality_level");
167
+ expect(errors[0][0].keyword).toBe("enum");
168
+ });
169
+
170
+ test("returns errors for invalid allowed_roles type in terms_of_use", () => {
171
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
172
+ clonedVouch.terms_of_use = {
173
+ confidentiality_level: "confidential",
174
+ allowed_roles: "not_an_array"
175
+ };
176
+ const errors = validateVerifiedClaims([clonedVouch]);
177
+ expect(errors).toHaveLength(1);
178
+ expect(errors[0]).toHaveLength(1);
179
+ expect(errors[0][0].instancePath).toBe("/verified_claims/0/terms_of_use/allowed_roles");
180
+ expect(errors[0][0].keyword).toBe("type");
181
+ });
182
+
183
+ test("returns errors for additional properties in terms_of_use", () => {
184
+ const clonedVouch = JSON.parse(JSON.stringify(exampleVouch));
185
+ clonedVouch.terms_of_use = {
186
+ confidentiality_level: "confidential",
187
+ allowed_roles: ["Estate Agent"],
188
+ extra_property: "should_not_be_allowed"
189
+ };
190
+ const errors = validateVerifiedClaims([clonedVouch]);
191
+ expect(errors).toHaveLength(1);
192
+ expect(errors[0]).toHaveLength(1);
193
+ expect(errors[0][0].instancePath).toBe("/verified_claims/0/terms_of_use");
194
+ expect(errors[0][0].keyword).toBe("additionalProperties");
195
+ });