@planning-inspectorate/data-model 1.11.0

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 (131) hide show
  1. package/.azure/ci.yml +60 -0
  2. package/.azure/release.yml +57 -0
  3. package/.github/dependabot.yml +11 -0
  4. package/.husky/install.mjs +7 -0
  5. package/.husky/pre-commit +1 -0
  6. package/.prettierignore +11 -0
  7. package/.prettierrc +19 -0
  8. package/LICENSE +21 -0
  9. package/README.md +237 -0
  10. package/commitlint.config.js +8 -0
  11. package/docs/README.md +85 -0
  12. package/docs/appeal-document.md +776 -0
  13. package/docs/appeal-event-estimate.md +108 -0
  14. package/docs/appeal-event.md +404 -0
  15. package/docs/appeal-has.md +1960 -0
  16. package/docs/appeal-representation.md +375 -0
  17. package/docs/appeal-s78.md +3607 -0
  18. package/docs/appeal.md +1879 -0
  19. package/docs/case-schedule.md +8 -0
  20. package/docs/dart-response.md +1413 -0
  21. package/docs/employee.md +70 -0
  22. package/docs/entraid.md +108 -0
  23. package/docs/folder.md +147 -0
  24. package/docs/gettimesheets-response.md +188 -0
  25. package/docs/interested-party.md +284 -0
  26. package/docs/listed-building.md +113 -0
  27. package/docs/listed-buildings.md +113 -0
  28. package/docs/nsip-document.md +966 -0
  29. package/docs/nsip-exam-timetable-defs-event.md +223 -0
  30. package/docs/nsip-exam-timetable-defs-line-item.md +51 -0
  31. package/docs/nsip-exam-timetable-defs.md +3 -0
  32. package/docs/nsip-exam-timetable-submission.md +113 -0
  33. package/docs/nsip-exam-timetable.md +352 -0
  34. package/docs/nsip-project-update.md +163 -0
  35. package/docs/nsip-project.md +2098 -0
  36. package/docs/nsip-representation-defs-attachment.md +133 -0
  37. package/docs/nsip-representation-defs.md +3 -0
  38. package/docs/nsip-representation.md +448 -0
  39. package/docs/nsip-subscription.md +193 -0
  40. package/docs/pins-inspector.md +184 -0
  41. package/docs/pins-lpa.md +260 -0
  42. package/docs/s51-advice.md +460 -0
  43. package/docs/service-user.md +575 -0
  44. package/eslint.config.js +21 -0
  45. package/jsconfig.json +12 -0
  46. package/package.json +55 -0
  47. package/pins_data_model/__init__.py +0 -0
  48. package/pins_data_model/gen_models.py +51 -0
  49. package/pins_data_model/load_schemas.py +51 -0
  50. package/pins_data_model/models/__init__.py +0 -0
  51. package/pins_data_model/models/model_appeal_document.py +294 -0
  52. package/pins_data_model/models/model_appeal_event.py +117 -0
  53. package/pins_data_model/models/model_appeal_event_estimate.py +37 -0
  54. package/pins_data_model/models/model_appeal_has.py +585 -0
  55. package/pins_data_model/models/model_appeal_representation.py +132 -0
  56. package/pins_data_model/models/model_appeal_s78.py +864 -0
  57. package/pins_data_model/models/model_case_schedule.py +11 -0
  58. package/pins_data_model/models/model_dart_response.py +292 -0
  59. package/pins_data_model/models/model_entraid.py +37 -0
  60. package/pins_data_model/models/model_folder.py +56 -0
  61. package/pins_data_model/models/model_gettimesheets_response.py +45 -0
  62. package/pins_data_model/models/model_listed_building.py +33 -0
  63. package/pins_data_model/models/model_listed_buildings.py +33 -0
  64. package/pins_data_model/models/model_nsip_document.py +199 -0
  65. package/pins_data_model/models/model_nsip_exam_timetable.py +90 -0
  66. package/pins_data_model/models/model_nsip_project.py +488 -0
  67. package/pins_data_model/models/model_nsip_project_update.py +60 -0
  68. package/pins_data_model/models/model_nsip_representation.py +83 -0
  69. package/pins_data_model/models/model_nsip_subscription.py +60 -0
  70. package/pins_data_model/models/model_pins_inspector.py +53 -0
  71. package/pins_data_model/models/model_pins_lpa.py +69 -0
  72. package/pins_data_model/models/model_s51_advice.py +109 -0
  73. package/pins_data_model/models/model_service_user.py +121 -0
  74. package/requirements.txt +2 -0
  75. package/schemas/__init__.py +0 -0
  76. package/schemas/appeal-document.schema.json +250 -0
  77. package/schemas/appeal-event-estimate.schema.json +31 -0
  78. package/schemas/appeal-event.schema.json +123 -0
  79. package/schemas/appeal-has.schema.json +576 -0
  80. package/schemas/appeal-representation.schema.json +131 -0
  81. package/schemas/appeal-s78.schema.json +1060 -0
  82. package/schemas/commands/__init__.py +0 -0
  83. package/schemas/commands/appeal-common-submission.schema.json +202 -0
  84. package/schemas/commands/appeal-has-submission.schema.json +82 -0
  85. package/schemas/commands/appeal-representation-submission.schema.json +192 -0
  86. package/schemas/commands/appeal-s78-submission.schema.json +100 -0
  87. package/schemas/commands/appellant-submission.schema.json +164 -0
  88. package/schemas/commands/interested-party.schema.json +80 -0
  89. package/schemas/commands/lpa-questionnaire-common.schema.json +110 -0
  90. package/schemas/commands/lpa-questionnaire-has.schema.json +69 -0
  91. package/schemas/commands/lpa-questionnaire-s78.schema.json +218 -0
  92. package/schemas/commands/lpa-questionnaire.schema.json +125 -0
  93. package/schemas/commands/new-deadline-submission.schema.json +59 -0
  94. package/schemas/commands/nsip-exam-timetable-submission.schema.json +27 -0
  95. package/schemas/commands/register-nsip-subscription.schema.json +32 -0
  96. package/schemas/commands/register-representation.schema.json +51 -0
  97. package/schemas/dart-response.schema.json +350 -0
  98. package/schemas/entraid.schema.json +31 -0
  99. package/schemas/folder.schema.json +49 -0
  100. package/schemas/gettimesheets-response.schema.json +46 -0
  101. package/schemas/listed-building.schema.json +31 -0
  102. package/schemas/listed-buildings.schema.json +31 -0
  103. package/schemas/nsip-document.schema.json +235 -0
  104. package/schemas/nsip-exam-timetable.schema.json +101 -0
  105. package/schemas/nsip-project-update.schema.json +49 -0
  106. package/schemas/nsip-project.schema.json +539 -0
  107. package/schemas/nsip-representation.schema.json +110 -0
  108. package/schemas/nsip-subscription.schema.json +52 -0
  109. package/schemas/pins-inspector.schema.json +47 -0
  110. package/schemas/pins-lpa.schema.json +63 -0
  111. package/schemas/s51-advice.schema.json +113 -0
  112. package/schemas/service-user.schema.json +152 -0
  113. package/setup.py +15 -0
  114. package/src/enums.cjs +632 -0
  115. package/src/enums.d.ts +573 -0
  116. package/src/enums.js +573 -0
  117. package/src/gen-docs.js +34 -0
  118. package/src/gen-enums.js +119 -0
  119. package/src/gen-types.js +70 -0
  120. package/src/index.cjs +746 -0
  121. package/src/index.d.ts +18 -0
  122. package/src/index.js +34 -0
  123. package/src/lib/load.js +78 -0
  124. package/src/schemas.d.ts +4570 -0
  125. package/tests/appeals/appeal-event.test.js +62 -0
  126. package/tests/appeals/commands/appeal-representation-submission.test.js +107 -0
  127. package/tests/appeals/commands/has-questionnaire.test.js +92 -0
  128. package/tests/appeals/commands/has-submission.test.js +113 -0
  129. package/tests/appeals/commands/s78-questionnaire.test.js +96 -0
  130. package/tests/appeals/commands/s78-submission.test.js +119 -0
  131. package/tests/schemas.test.js +97 -0
@@ -0,0 +1,62 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import Ajv from 'ajv';
4
+ import addFormats from 'ajv-formats';
5
+ import { loadAllSchemasSync } from '../../src/index.js';
6
+
7
+ const schema = 'appeal-event.schema.json';
8
+
9
+ const event = {
10
+ eventId: 'ipsum ut proident Ut',
11
+ caseReference: 'irure dolore in exercitation elit',
12
+ eventType: 'site_visit_accompanied',
13
+ eventName: 'minim anim',
14
+ eventStatus: 'confirmed',
15
+ isUrgent: true,
16
+ eventPublished: false,
17
+ eventStartDateTime: '1913-04-07T20:47:33.0Z',
18
+ eventEndDateTime: '1961-01-01T16:05:12.0Z',
19
+ notificationOfSiteVisit: '1957-10-24T08:38:08.0Z',
20
+ addressLine1: 'test line 1',
21
+ addressLine2: 'test line 2',
22
+ addressTown: 'test town',
23
+ addressCounty: 'test county',
24
+ addressPostcode: 'test postcode'
25
+ };
26
+
27
+ describe(schema, () => {
28
+ const schemas = loadAllSchemasSync();
29
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
30
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
31
+ addFormats(ajv);
32
+
33
+ it('should allow valid event schema', () => {
34
+ const test = structuredClone(event);
35
+
36
+ const validationResult = ajv.validate(schema, test);
37
+ assert.strictEqual(validationResult, true);
38
+ });
39
+
40
+ it('should allow valid event schema with null values', () => {
41
+ const test = structuredClone(event);
42
+ test.eventName = null;
43
+ test.eventPublished = null;
44
+ test.eventEndDateTime = null;
45
+ test.notificationOfSiteVisit = null;
46
+ test.addressLine1 = null;
47
+ test.addressLine2 = null;
48
+ test.addressTown = null;
49
+ test.addressCounty = null;
50
+ test.addressPostcode = null;
51
+
52
+ const validationResult = ajv.validate(schema, test);
53
+ assert.strictEqual(validationResult, true);
54
+ });
55
+
56
+ it('should allow additional props', () => {
57
+ const test = structuredClone(event);
58
+ test.test = 1; // additional unknown prop allowed
59
+ const validationResult = ajv.validate(schema, test);
60
+ assert.strictEqual(validationResult, true);
61
+ });
62
+ });
@@ -0,0 +1,107 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import Ajv from 'ajv';
4
+ import addFormats from 'ajv-formats';
5
+ import { loadAllSchemasSync } from '../../../src/index.js';
6
+
7
+ const schema = 'commands/appeal-representation-submission.schema.json';
8
+
9
+ const document = {
10
+ documentId: 'Lorem',
11
+ filename: 'sunt',
12
+ originalFilename: 'dolore fugiat',
13
+ size: 663667,
14
+ mime: 'consequat veniam ipsum ullamco sint',
15
+ documentURI: 'reprehenderit',
16
+ dateCreated: '1904-04-10T14:53:32.0Z',
17
+ documentType: 'lpaStatement'
18
+ };
19
+
20
+ const newUser = {
21
+ salutation: null,
22
+ firstName: null,
23
+ lastName: null,
24
+ emailAddress: null,
25
+ telephoneNumber: null,
26
+ organisation: null,
27
+ serviceUserType: 'InterestedParty'
28
+ };
29
+
30
+ const representation = {
31
+ caseReference: '123',
32
+ representationSubmittedDate: '1990-12-28T22:49:47.0Z',
33
+ representation: 'This is a text based representation',
34
+ representationType: 'comment'
35
+ };
36
+
37
+ describe('appeal-representation-submission.schema', () => {
38
+ const schemas = loadAllSchemasSync();
39
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
40
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
41
+ addFormats(ajv);
42
+
43
+ it('should allow valid representation schema with new user', () => {
44
+ const test = structuredClone(representation);
45
+ test.documents = [document];
46
+ test.newUser = newUser;
47
+
48
+ const validationResult = ajv.validate(schema, test);
49
+ assert.strictEqual(validationResult, true);
50
+ });
51
+
52
+ it('should allow valid representation schema with serviceUserId', () => {
53
+ const test = structuredClone(representation);
54
+ test.documents = [document];
55
+ test.serviceUserId = 'a';
56
+
57
+ const validationResult = ajv.validate(schema, test);
58
+ assert.strictEqual(validationResult, true);
59
+ });
60
+
61
+ it('should allow valid representation schema with lpa', () => {
62
+ const test = structuredClone(representation);
63
+ test.documents = [document];
64
+ test.lpaCode = 'a';
65
+
66
+ const validationResult = ajv.validate(schema, test);
67
+ assert.strictEqual(validationResult, true);
68
+ });
69
+
70
+ it('should reject missing user from one of', () => {
71
+ const test = structuredClone(representation);
72
+ test.documents = [document];
73
+ const validationResult = ajv.validate(schema, test);
74
+ assert.strictEqual(validationResult, false);
75
+ assert.strictEqual(
76
+ ajv.errors.some((x) => x.message === 'must match exactly one schema in oneOf'),
77
+ true
78
+ );
79
+ });
80
+
81
+ it('should reject missing root property', () => {
82
+ const test = structuredClone(representation);
83
+ test.documents = [document];
84
+ test.newUser = newUser;
85
+ delete test.representation;
86
+ const validationResult = ajv.validate(schema, test);
87
+ assert.strictEqual(validationResult, false);
88
+ assert.strictEqual(
89
+ ajv.errors.some((x) => x.message === "must have required property 'representation'"),
90
+ true
91
+ );
92
+ });
93
+
94
+ it('should allow additional props', () => {
95
+ const test = structuredClone(representation);
96
+ test.test = 1; // additional unknown prop allowed
97
+
98
+ test.documents = [document];
99
+ test.documents[0].test = 1; // additional unknown prop allowed
100
+
101
+ test.newUser = newUser;
102
+ test.newUser.test = 1; // additional unknown prop allowed
103
+
104
+ const validationResult = ajv.validate(schema, test);
105
+ assert.strictEqual(validationResult, true);
106
+ });
107
+ });
@@ -0,0 +1,92 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import Ajv from 'ajv';
4
+ import addFormats from 'ajv-formats';
5
+ import { loadAllSchemasSync } from '../../../src/index.js';
6
+
7
+ const schema = 'lpa-questionnaire.schema.json';
8
+
9
+ const document = {
10
+ documentId: 'deserunt sint aliqua adipisicing dolore',
11
+ filename: 'ut dolore',
12
+ originalFilename: 'Lorem amet fugiat adipisicing Excepteur',
13
+ size: -90476934,
14
+ mime: 'magna labore exercitation ut',
15
+ documentURI: 'ut reprehenderit',
16
+ dateCreated: '1933-04-03T03:24:38.0Z',
17
+ documentType: 'plansDrawings'
18
+ };
19
+
20
+ const exampleHasQuestionnaireSchema = {
21
+ casedata: {
22
+ caseType: 'D',
23
+ caseReference: 'nisi incididunt cillum',
24
+ lpaQuestionnaireSubmittedDate: null,
25
+ lpaStatement: 'consequat ex',
26
+ siteAccessDetails: null,
27
+ siteSafetyDetails: null,
28
+ isCorrectAppealType: null,
29
+ isGreenBelt: null,
30
+ inConservationArea: null,
31
+ newConditionDetails: 'anim sint ut aute',
32
+ notificationMethod: null,
33
+ nearbyCaseReferences: null,
34
+ neighbouringSiteAddresses: [
35
+ {
36
+ neighbouringSiteAddressLine1: 'commodo dolore incididunt',
37
+ neighbouringSiteAddressLine2: 'est voluptate irure aliqua elit',
38
+ neighbouringSiteAddressTown: 'labore aliquip ex incididunt nulla',
39
+ neighbouringSiteAddressCounty: 'enim ut Duis ut ex',
40
+ neighbouringSiteAddressPostcode: 'pariatur',
41
+ neighbouringSiteAccessDetails: 'aliqua irure mollit consequat nisi',
42
+ neighbouringSiteSafetyDetails: null
43
+ }
44
+ ],
45
+ affectedListedBuildingNumbers: [
46
+ 'occaecat id aliquip dolore',
47
+ 'magna',
48
+ 'minim laboris fugiat id aliqua',
49
+ 'ullamco quis',
50
+ 'voluptate quis'
51
+ ],
52
+ lpaCostsAppliedFor: null
53
+ },
54
+ documents: [document]
55
+ };
56
+
57
+ describe('HAS questionnaire command schema', () => {
58
+ const schemas = loadAllSchemasSync();
59
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
60
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
61
+ addFormats(ajv);
62
+
63
+ it('should allow valid HAS schema', () => {
64
+ const validationResult = ajv.validate(schema, exampleHasQuestionnaireSchema);
65
+ assert.strictEqual(validationResult, true);
66
+ });
67
+
68
+ it('should allow HAS case without caseType', () => {
69
+ const test = structuredClone(exampleHasQuestionnaireSchema);
70
+ delete test.casedata.caseType;
71
+ const validationResult = ajv.validate(schema, test);
72
+ assert.strictEqual(validationResult, true);
73
+ });
74
+
75
+ it('should reject missing caseReference', () => {
76
+ const test = structuredClone(exampleHasQuestionnaireSchema);
77
+ delete test.casedata.caseReference;
78
+ const validationResult = ajv.validate(schema, test);
79
+ assert.strictEqual(validationResult, false);
80
+ assert.strictEqual(
81
+ ajv.errors.some((x) => x.message === "must have required property 'caseReference'"),
82
+ true
83
+ );
84
+ });
85
+
86
+ it('should allow additional props', () => {
87
+ const test = structuredClone(exampleHasQuestionnaireSchema);
88
+ test.test = 1;
89
+ const validationResult = ajv.validate(schema, test);
90
+ assert.strictEqual(validationResult, true);
91
+ });
92
+ });
@@ -0,0 +1,113 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import Ajv from 'ajv';
4
+ import addFormats from 'ajv-formats';
5
+ import { loadAllSchemasSync } from '../../../src/index.js';
6
+
7
+ const schema = 'appellant-submission.schema.json';
8
+
9
+ const user = {
10
+ salutation: 'nisi aute tempor Ut',
11
+ firstName: null,
12
+ lastName: null,
13
+ emailAddress: null,
14
+ telephoneNumber: 'irure',
15
+ organisation: null,
16
+ serviceUserType: 'Appellant'
17
+ };
18
+
19
+ const document = {
20
+ documentId: 'Lorem',
21
+ filename: 'sunt',
22
+ originalFilename: 'dolore fugiat',
23
+ size: 663667,
24
+ mime: 'consequat veniam ipsum ullamco sint',
25
+ documentURI: 'reprehenderit',
26
+ dateCreated: '1904-04-10T14:53:32.0Z',
27
+ documentType: 'appellantCaseWithdrawalLetter'
28
+ };
29
+
30
+ const exampleHasSubmissionSchema = {
31
+ casedata: {
32
+ submissionId: '072a9b08-0266-a291-ecd0-bb0ded9d7350',
33
+ caseType: 'D',
34
+ caseProcedure: 'written',
35
+ lpaCode: 'in laborum pariatur cupidatat',
36
+ caseSubmittedDate: '1920-12-06T17:37:12.0Z',
37
+ enforcementNotice: false,
38
+ applicationReference: 'voluptate esse Excepteur veniam sunt',
39
+ applicationDate: '1921-01-26T02:05:56.0Z',
40
+ applicationDecision: 'not_received',
41
+ applicationDecisionDate: '1890-12-26T17:09:36.0Z',
42
+ caseSubmissionDueDate: null,
43
+ siteAddressLine1: 'exercitation incididunt laborum',
44
+ siteAddressLine2: 'in Ut voluptate ea',
45
+ siteAddressTown: 'Lorem amet consectetur',
46
+ siteAddressCounty: null,
47
+ siteAddressPostcode: 'sunt nulla aliquip labore',
48
+ siteAccessDetails: null,
49
+ siteSafetyDetails: ['Duis laboris ex', 'sint Lorem cupidatat commodo'],
50
+ isGreenBelt: null,
51
+ siteAreaSquareMetres: -11383801.18983768,
52
+ floorSpaceSquareMetres: -69227017.16474937,
53
+ ownsAllLand: null,
54
+ ownsSomeLand: null,
55
+ knowsOtherOwners: null,
56
+ knowsAllOwners: null,
57
+ advertisedAppeal: true,
58
+ ownersInformed: null,
59
+ originalDevelopmentDescription: null,
60
+ changedDevelopmentDescription: false,
61
+ nearbyCaseReferences: [
62
+ 'enim consectetur irure ad',
63
+ 'irure ut',
64
+ 'irure eiusmod minim Excepteur id',
65
+ 'aliqua pariatur consectetur eiusmod'
66
+ ],
67
+ neighbouringSiteAddresses: null,
68
+ appellantCostsAppliedFor: true
69
+ },
70
+ documents: [document],
71
+ users: [user]
72
+ };
73
+
74
+ describe('HAS submission command schema', () => {
75
+ const schemas = loadAllSchemasSync();
76
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
77
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
78
+ addFormats(ajv);
79
+
80
+ it('should allow valid HAS schema', () => {
81
+ const validationResult = ajv.validate(schema, exampleHasSubmissionSchema);
82
+ assert.strictEqual(validationResult, true);
83
+ });
84
+
85
+ it('should reject missing root property', () => {
86
+ const test = structuredClone(exampleHasSubmissionSchema);
87
+ delete test.casedata.caseType;
88
+ const validationResult = ajv.validate(schema, test);
89
+ assert.strictEqual(validationResult, false);
90
+ assert.strictEqual(
91
+ ajv.errors.some((x) => x.message === "must have required property 'caseType'"),
92
+ true
93
+ );
94
+ });
95
+
96
+ it('should reject missing nested property', () => {
97
+ const test = structuredClone(exampleHasSubmissionSchema);
98
+ delete test.casedata.caseProcedure;
99
+ const validationResult = ajv.validate(schema, test);
100
+ assert.strictEqual(validationResult, false);
101
+ assert.strictEqual(
102
+ ajv.errors.some((x) => x.message === "must have required property 'caseProcedure'"),
103
+ true
104
+ );
105
+ });
106
+
107
+ it('should allow additional props', () => {
108
+ const test = structuredClone(exampleHasSubmissionSchema);
109
+ test.test = 1;
110
+ const validationResult = ajv.validate(schema, test);
111
+ assert.strictEqual(validationResult, true);
112
+ });
113
+ });
@@ -0,0 +1,96 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import Ajv from 'ajv';
4
+ import addFormats from 'ajv-formats';
5
+ import { loadAllSchemasSync } from '../../../src/index.js';
6
+
7
+ const schema = 'lpa-questionnaire.schema.json';
8
+
9
+ const document = {
10
+ documentId: 'deserunt sint aliqua adipisicing dolore',
11
+ filename: 'ut dolore',
12
+ originalFilename: 'Lorem amet fugiat adipisicing Excepteur',
13
+ size: -90476934,
14
+ mime: 'magna labore exercitation ut',
15
+ documentURI: 'ut reprehenderit',
16
+ dateCreated: '1933-04-03T03:24:38.0Z',
17
+ documentType: 'plansDrawings'
18
+ };
19
+
20
+ const exampleS78QuestionnaireSchema = {
21
+ casedata: {
22
+ caseType: 'W',
23
+ caseReference: 'est ea elit in nulla',
24
+ lpaQuestionnaireSubmittedDate: null,
25
+ siteAccessDetails: ['velit adipisicing fugiat dolor ullamco'],
26
+ siteSafetyDetails: ['ut', 'dolore ut sint Duis', 'magna consequat quis laboris officia'],
27
+ nearbyCaseReferences: ['nulla', 'incididunt eu nostrud eiusmod', 'exercitation', 'Ut nostrud', 'eiusmod cillum eu'],
28
+ neighbouringSiteAddresses: null,
29
+ isCorrectAppealType: true,
30
+ affectedListedBuildingNumbers: null,
31
+ isGreenBelt: null,
32
+ inConservationArea: null,
33
+ newConditionDetails: null,
34
+ notificationMethod: ['irure', 'cillum tempor incididunt labore in'],
35
+ lpaCostsAppliedFor: true,
36
+ lpaStatement: 'ex laborum cupidatat',
37
+ changedListedBuildingNumbers: null,
38
+ affectsScheduledMonument: true,
39
+ hasProtectedSpecies: false,
40
+ isAonbNationalLandscape: true,
41
+ designatedSitesNames: ['quis nulla eu officia exercitation'],
42
+ hasTreePreservationOrder: null,
43
+ isGypsyOrTravellerSite: true,
44
+ isPublicRightOfWay: null,
45
+ eiaEnvironmentalImpactSchedule: 'schedule-2',
46
+ eiaDevelopmentDescription: 'production-processing-of-metals',
47
+ eiaSensitiveAreaDetails: null,
48
+ eiaColumnTwoThreshold: false,
49
+ eiaScreeningOpinion: false,
50
+ eiaRequiresEnvironmentalStatement: null,
51
+ eiaCompletedEnvironmentalStatement: true,
52
+ hasStatutoryConsultees: true,
53
+ consultedBodiesDetails: 'in in labore irure nostrud',
54
+ hasConsultationResponses: null,
55
+ hasEmergingPlan: null,
56
+ hasSupplementaryPlanningDocs: null,
57
+ hasInfrastructureLevy: true,
58
+ isInfrastructureLevyFormallyAdopted: false,
59
+ infrastructureLevyAdoptedDate: '1913-06-14T10:40:33.0Z',
60
+ infrastructureLevyExpectedDate: '1912-06-14T10:40:33.0Z',
61
+ lpaProcedurePreference: null,
62
+ lpaProcedurePreferenceDetails: 'in reprehenderit',
63
+ lpaProcedurePreferenceDuration: -34480053.16630782
64
+ },
65
+ documents: [document]
66
+ };
67
+
68
+ describe('S78 questionnaire command schema', () => {
69
+ const schemas = loadAllSchemasSync();
70
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
71
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
72
+ addFormats(ajv);
73
+
74
+ it('should allow valid HAS schema', () => {
75
+ const validationResult = ajv.validate(schema, exampleS78QuestionnaireSchema);
76
+ assert.strictEqual(validationResult, true);
77
+ });
78
+
79
+ it('should reject missing caseReference', () => {
80
+ const test = structuredClone(exampleS78QuestionnaireSchema);
81
+ delete test.casedata.caseReference;
82
+ const validationResult = ajv.validate(schema, test);
83
+ assert.strictEqual(validationResult, false);
84
+ assert.strictEqual(
85
+ ajv.errors.some((x) => x.message === "must have required property 'caseReference'"),
86
+ true
87
+ );
88
+ });
89
+
90
+ it('should allow additional props', () => {
91
+ const test = structuredClone(exampleS78QuestionnaireSchema);
92
+ test.test = 1;
93
+ const validationResult = ajv.validate(schema, test);
94
+ assert.strictEqual(validationResult, true);
95
+ });
96
+ });
@@ -0,0 +1,119 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import Ajv from 'ajv';
4
+ import addFormats from 'ajv-formats';
5
+ import { loadAllSchemasSync } from '../../../src/index.js';
6
+
7
+ const schema = 'appellant-submission.schema.json';
8
+
9
+ const user = {
10
+ salutation: 'nisi aute tempor Ut',
11
+ firstName: null,
12
+ lastName: null,
13
+ emailAddress: null,
14
+ telephoneNumber: 'irure',
15
+ organisation: null,
16
+ serviceUserType: 'Appellant'
17
+ };
18
+
19
+ const document = {
20
+ documentId: 'Lorem',
21
+ filename: 'sunt',
22
+ originalFilename: 'dolore fugiat',
23
+ size: 663667,
24
+ mime: 'consequat veniam ipsum ullamco sint',
25
+ documentURI: 'reprehenderit',
26
+ dateCreated: '1904-04-10T14:53:32.0Z',
27
+ documentType: 'appellantCaseWithdrawalLetter'
28
+ };
29
+
30
+ const exampleS78SubmissionSchema = {
31
+ casedata: {
32
+ caseType: 'W',
33
+ submissionId: '4b78fc58-fcb0-70d6-5dea-b9b774f802d6',
34
+ caseProcedure: 'written',
35
+ lpaCode: 'cupidatat',
36
+ caseSubmittedDate: '1890-12-28T22:49:47.0Z',
37
+ enforcementNotice: null,
38
+ applicationReference: 'consectetur enim et',
39
+ applicationDate: '1920-04-07T20:23:53.0Z',
40
+ applicationDecision: 'granted',
41
+ applicationDecisionDate: null,
42
+ caseSubmissionDueDate: null,
43
+ siteAddressLine1: 'occaecat aliquip non pariatur consequat',
44
+ siteAddressLine2: null,
45
+ siteAddressTown: 'et',
46
+ siteAddressCounty: 'velit',
47
+ siteAddressPostcode: 'nisi minim laboris sed',
48
+ siteAccessDetails: ['laborum', 'aute velit quis', 'dolor'],
49
+ siteSafetyDetails: ['velit aliqua', 'qui', 'tempor Duis ut dolore', 'anim Ut in ea', 'eiusmod laborum'],
50
+ neighbouringSiteAddresses: null,
51
+ nearbyCaseReferences: ['incididunt'],
52
+ isGreenBelt: false,
53
+ siteAreaSquareMetres: null,
54
+ floorSpaceSquareMetres: -6341893.2219680995,
55
+ ownsAllLand: null,
56
+ ownsSomeLand: true,
57
+ knowsOtherOwners: 'Some',
58
+ knowsAllOwners: 'Some',
59
+ advertisedAppeal: null,
60
+ ownersInformed: null,
61
+ originalDevelopmentDescription: 'in amet',
62
+ changedDevelopmentDescription: true,
63
+ appellantCostsAppliedFor: false,
64
+ agriculturalHolding: true,
65
+ tenantAgriculturalHolding: true,
66
+ otherTenantsAgriculturalHolding: null,
67
+ informedTenantsAgriculturalHolding: null,
68
+ appellantProcedurePreference: null,
69
+ appellantProcedurePreferenceDetails: 'eiusmod ex exercitation',
70
+ appellantProcedurePreferenceDuration: 10816414.941069126,
71
+ appellantProcedurePreferenceWitnessCount: -91300569.36444478,
72
+ planningObligation: false,
73
+ statusPlanningObligation: 'proident aute',
74
+ developmentType: 'change-of-use'
75
+ },
76
+ documents: [document],
77
+ users: [user]
78
+ };
79
+
80
+ describe('S78 submission command schema', () => {
81
+ const schemas = loadAllSchemasSync();
82
+ const flatSchemas = Object.values(schemas).reduce((a, c) => ({ ...a, ...c }), {});
83
+ const ajv = new Ajv({ schemas: flatSchemas, allErrors: true });
84
+ addFormats(ajv);
85
+
86
+ it('should allow valid S78 schema', () => {
87
+ const validationResult = ajv.validate(schema, exampleS78SubmissionSchema);
88
+ assert.strictEqual(validationResult, true);
89
+ });
90
+
91
+ it('should reject missing root property', () => {
92
+ const test = structuredClone(exampleS78SubmissionSchema);
93
+ delete test.casedata.caseType;
94
+ const validationResult = ajv.validate(schema, test);
95
+ assert.strictEqual(validationResult, false);
96
+ assert.strictEqual(
97
+ ajv.errors.some((x) => x.message === "must have required property 'caseType'"),
98
+ true
99
+ );
100
+ });
101
+
102
+ it('should reject missing nested property', () => {
103
+ const test = structuredClone(exampleS78SubmissionSchema);
104
+ delete test.casedata.caseProcedure;
105
+ const validationResult = ajv.validate(schema, test);
106
+ assert.strictEqual(validationResult, false);
107
+ assert.strictEqual(
108
+ ajv.errors.some((x) => x.message === "must have required property 'caseProcedure'"),
109
+ true
110
+ );
111
+ });
112
+
113
+ it('should allow additional props', () => {
114
+ const test = structuredClone(exampleS78SubmissionSchema);
115
+ test.test = 1;
116
+ const validationResult = ajv.validate(schema, test);
117
+ assert.strictEqual(validationResult, true);
118
+ });
119
+ });
@@ -0,0 +1,97 @@
1
+ import { describe, it } from 'node:test';
2
+ import Ajv from 'ajv';
3
+ import addFormats from 'ajv-formats';
4
+ import { loadAllSchemasSync } from '../src/index.js';
5
+
6
+ describe('schemas', () => {
7
+ it('should only include valid JSON schemas', () => {
8
+ // validate that all schemas are valid JSON schema documents
9
+ // by default AJV uses draft-07 which is the same as the schemas
10
+ // https://ajv.js.org/json-schema.html#json-schema-versions
11
+
12
+ const ajv = new Ajv({ allErrors: true, strict: true });
13
+ addFormats(ajv);
14
+
15
+ const allSchemas = loadAllSchemasSync();
16
+ const schemas = Object.values(allSchemas).reduce((a, c) => ({ ...a, ...c }), {});
17
+ const errorsBySchema = {};
18
+ for (const schema of Object.values(schemas)) {
19
+ ajv.validateSchema(schema);
20
+ if (ajv.errors) {
21
+ errorsBySchema[schema.$id] = ajv.errors.map((e) => {
22
+ let error = `${e.instancePath} ${e.message}`;
23
+ if (e.params) {
24
+ error += ` ${JSON.stringify(e.params)}`;
25
+ }
26
+ return error;
27
+ });
28
+ }
29
+ }
30
+ if (Object.keys(errorsBySchema).length) {
31
+ console.log('*'.repeat(50));
32
+ console.log('ERRORS:');
33
+ for (const [schema, errors] of Object.entries(errorsBySchema)) {
34
+ console.log(schema);
35
+ for (const error of errors) {
36
+ console.log(' ', error);
37
+ }
38
+ }
39
+ throw new Error(
40
+ `${Object.keys(errorsBySchema).join(', ')} schemas are invalid (count: ${Object.keys(errorsBySchema).length}), see console output`
41
+ );
42
+ }
43
+ });
44
+ it('should have matching $id and filenames', () => {
45
+ const allSchemas = loadAllSchemasSync();
46
+ const errorsBySchema = {};
47
+ for (let [name, schema] of Object.entries(allSchemas.schemas)) {
48
+ if (name.includes('/')) {
49
+ name = name.split('/').pop();
50
+ }
51
+ if (name !== schema.$id) {
52
+ errorsBySchema[schema.$id] = name;
53
+ }
54
+ }
55
+ for (let [name, schema] of Object.entries(allSchemas.commands)) {
56
+ // ID includes the commands/ prefix
57
+ if ('commands/' + name !== schema.$id) {
58
+ errorsBySchema[schema.$id] = name;
59
+ }
60
+ }
61
+ if (Object.keys(errorsBySchema).length) {
62
+ console.log('*'.repeat(50));
63
+ console.log('ERRORS:');
64
+ for (const [schema, filename] of Object.entries(errorsBySchema)) {
65
+ console.log(schema, filename);
66
+ }
67
+ throw new Error(
68
+ `${Object.keys(errorsBySchema).join(', ')} schemas have mismatched $id and filenames (count: ${Object.keys(errorsBySchema).length}), see console output`
69
+ );
70
+ }
71
+ });
72
+ it('should use the singular form for $id', () => {
73
+ // very crude test to ensure schemas don't end in 's'...
74
+ const suffixExceptions = [
75
+ 'has', // allow has suffix for appeals
76
+ 'listed-buildings' // allow this temporary schema, to remove when this schema copy is removed
77
+ ];
78
+
79
+ const allSchemas = loadAllSchemasSync();
80
+ const schemas = Object.values(allSchemas).reduce((a, c) => ({ ...a, ...c }), {});
81
+ const errors = [];
82
+ for (const schema of Object.values(schemas)) {
83
+ const name = schema.$id.replace('.schema.json', '');
84
+ if (name.endsWith('s') && !suffixExceptions.some((suffix) => name.endsWith(suffix))) {
85
+ errors.push(schema.$id);
86
+ }
87
+ }
88
+ if (errors.length > 0) {
89
+ console.log('*'.repeat(50));
90
+ console.log('ERRORS:');
91
+ for (const error of errors) {
92
+ console.log(' ' + error);
93
+ }
94
+ throw new Error(`${errors.join(', ')} schemas use plural names, see console output`);
95
+ }
96
+ });
97
+ });