@pdtf/schemas 3.5.1-2 → 3.5.1-3
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/package.json +1 -1
- package/src/schemas/v3/combined.json +42 -1
- package/src/schemas/v3/compactSkeleton.txt +3 -1
- package/src/schemas/v3/overlays/baspi4.json +9 -1
- package/src/schemas/v3/overlays/baspi5.json +9 -1
- package/src/schemas/v3/overlays/piq.json +9 -1
- package/src/schemas/v3/overlays/rds.json +1 -0
- package/src/schemas/v3/pdtf-transaction.json +69 -1
- package/src/schemas/v3/skeleton.json +3 -1
- package/src/tests/v3/offers.test.js +826 -0
package/package.json
CHANGED
|
@@ -259,7 +259,6 @@
|
|
|
259
259
|
"properties": {
|
|
260
260
|
"role": {
|
|
261
261
|
"enum": [
|
|
262
|
-
"Buyer",
|
|
263
262
|
"Seller's Conveyancer",
|
|
264
263
|
"Prospective Buyer",
|
|
265
264
|
"Buyer's Conveyancer",
|
|
@@ -274,6 +273,18 @@
|
|
|
274
273
|
}
|
|
275
274
|
}
|
|
276
275
|
},
|
|
276
|
+
{
|
|
277
|
+
"properties": {
|
|
278
|
+
"role": {
|
|
279
|
+
"enum": ["Buyer"]
|
|
280
|
+
},
|
|
281
|
+
"offerId": {
|
|
282
|
+
"title": "Reference to an offer in the offers object",
|
|
283
|
+
"type": "string",
|
|
284
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:+-]*$"
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
},
|
|
277
288
|
{
|
|
278
289
|
"properties": {
|
|
279
290
|
"role": {
|
|
@@ -41777,6 +41788,36 @@
|
|
|
41777
41788
|
}
|
|
41778
41789
|
}
|
|
41779
41790
|
}
|
|
41791
|
+
},
|
|
41792
|
+
"offers": {
|
|
41793
|
+
"title": "Offers for purchase of the property",
|
|
41794
|
+
"type": "object",
|
|
41795
|
+
"propertyNames": {
|
|
41796
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:+-]*$"
|
|
41797
|
+
},
|
|
41798
|
+
"patternProperties": {
|
|
41799
|
+
".*": {
|
|
41800
|
+
"type": "object",
|
|
41801
|
+
"properties": {
|
|
41802
|
+
"amount": { "type": "number", "minimum": 0 },
|
|
41803
|
+
"currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
|
|
41804
|
+
"status": {
|
|
41805
|
+
"type": "string",
|
|
41806
|
+
"enum": [
|
|
41807
|
+
"pending",
|
|
41808
|
+
"withdrawn",
|
|
41809
|
+
"rejected",
|
|
41810
|
+
"accepted",
|
|
41811
|
+
"noteOfInterest"
|
|
41812
|
+
]
|
|
41813
|
+
},
|
|
41814
|
+
"inclusions": { "type": "array", "items": { "type": "string" } },
|
|
41815
|
+
"exclusions": { "type": "array", "items": { "type": "string" } },
|
|
41816
|
+
"conditions": { "type": "array", "items": { "type": "string" } }
|
|
41817
|
+
},
|
|
41818
|
+
"required": ["amount", "currency", "status"]
|
|
41819
|
+
}
|
|
41820
|
+
}
|
|
41780
41821
|
}
|
|
41781
41822
|
}
|
|
41782
41823
|
}
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"properties": {
|
|
18
18
|
"role": {
|
|
19
19
|
"enum": [
|
|
20
|
-
"Buyer",
|
|
21
20
|
"Seller's Conveyancer",
|
|
22
21
|
"Prospective Buyer",
|
|
23
22
|
"Buyer's Conveyancer",
|
|
@@ -32,6 +31,15 @@
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
},
|
|
34
|
+
{
|
|
35
|
+
"properties": {
|
|
36
|
+
"role": {
|
|
37
|
+
"enum": [
|
|
38
|
+
"Buyer"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
35
43
|
{
|
|
36
44
|
"properties": {
|
|
37
45
|
"role": {
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
"properties": {
|
|
18
18
|
"role": {
|
|
19
19
|
"enum": [
|
|
20
|
-
"Buyer",
|
|
21
20
|
"Seller's Conveyancer",
|
|
22
21
|
"Prospective Buyer",
|
|
23
22
|
"Buyer's Conveyancer",
|
|
@@ -32,6 +31,15 @@
|
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
33
|
},
|
|
34
|
+
{
|
|
35
|
+
"properties": {
|
|
36
|
+
"role": {
|
|
37
|
+
"enum": [
|
|
38
|
+
"Buyer"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
35
43
|
{
|
|
36
44
|
"properties": {
|
|
37
45
|
"role": {
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"properties": {
|
|
14
14
|
"role": {
|
|
15
15
|
"enum": [
|
|
16
|
-
"Buyer",
|
|
17
16
|
"Seller's Conveyancer",
|
|
18
17
|
"Prospective Buyer",
|
|
19
18
|
"Buyer's Conveyancer",
|
|
@@ -28,6 +27,15 @@
|
|
|
28
27
|
}
|
|
29
28
|
}
|
|
30
29
|
},
|
|
30
|
+
{
|
|
31
|
+
"properties": {
|
|
32
|
+
"role": {
|
|
33
|
+
"enum": [
|
|
34
|
+
"Buyer"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
31
39
|
{
|
|
32
40
|
"properties": {
|
|
33
41
|
"role": {
|
|
@@ -273,7 +273,6 @@
|
|
|
273
273
|
"properties": {
|
|
274
274
|
"role": {
|
|
275
275
|
"enum": [
|
|
276
|
-
"Buyer",
|
|
277
276
|
"Seller's Conveyancer",
|
|
278
277
|
"Prospective Buyer",
|
|
279
278
|
"Buyer's Conveyancer",
|
|
@@ -288,6 +287,20 @@
|
|
|
288
287
|
}
|
|
289
288
|
}
|
|
290
289
|
},
|
|
290
|
+
{
|
|
291
|
+
"properties": {
|
|
292
|
+
"role": {
|
|
293
|
+
"enum": [
|
|
294
|
+
"Buyer"
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
"offerId": {
|
|
298
|
+
"title": "Reference to an offer in the offers object",
|
|
299
|
+
"type": "string",
|
|
300
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:+-]*$"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
},
|
|
291
304
|
{
|
|
292
305
|
"properties": {
|
|
293
306
|
"role": {
|
|
@@ -37292,6 +37305,61 @@
|
|
|
37292
37305
|
}
|
|
37293
37306
|
}
|
|
37294
37307
|
}
|
|
37308
|
+
},
|
|
37309
|
+
"offers": {
|
|
37310
|
+
"title": "Offers for purchase of the property",
|
|
37311
|
+
"type": "object",
|
|
37312
|
+
"propertyNames": {
|
|
37313
|
+
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:+-]*$"
|
|
37314
|
+
},
|
|
37315
|
+
"patternProperties": {
|
|
37316
|
+
".*": {
|
|
37317
|
+
"type": "object",
|
|
37318
|
+
"properties": {
|
|
37319
|
+
"amount": {
|
|
37320
|
+
"type": "number",
|
|
37321
|
+
"minimum": 0
|
|
37322
|
+
},
|
|
37323
|
+
"currency": {
|
|
37324
|
+
"type": "string",
|
|
37325
|
+
"pattern": "^[A-Z]{3}$"
|
|
37326
|
+
},
|
|
37327
|
+
"status": {
|
|
37328
|
+
"type": "string",
|
|
37329
|
+
"enum": [
|
|
37330
|
+
"pending",
|
|
37331
|
+
"withdrawn",
|
|
37332
|
+
"rejected",
|
|
37333
|
+
"accepted",
|
|
37334
|
+
"noteOfInterest"
|
|
37335
|
+
]
|
|
37336
|
+
},
|
|
37337
|
+
"inclusions": {
|
|
37338
|
+
"type": "array",
|
|
37339
|
+
"items": {
|
|
37340
|
+
"type": "string"
|
|
37341
|
+
}
|
|
37342
|
+
},
|
|
37343
|
+
"exclusions": {
|
|
37344
|
+
"type": "array",
|
|
37345
|
+
"items": {
|
|
37346
|
+
"type": "string"
|
|
37347
|
+
}
|
|
37348
|
+
},
|
|
37349
|
+
"conditions": {
|
|
37350
|
+
"type": "array",
|
|
37351
|
+
"items": {
|
|
37352
|
+
"type": "string"
|
|
37353
|
+
}
|
|
37354
|
+
}
|
|
37355
|
+
},
|
|
37356
|
+
"required": [
|
|
37357
|
+
"amount",
|
|
37358
|
+
"currency",
|
|
37359
|
+
"status"
|
|
37360
|
+
]
|
|
37361
|
+
}
|
|
37362
|
+
}
|
|
37295
37363
|
}
|
|
37296
37364
|
}
|
|
37297
37365
|
}
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
]
|
|
55
55
|
}
|
|
56
56
|
},
|
|
57
|
+
"offerId": "string",
|
|
57
58
|
"sellersCapacity": {
|
|
58
59
|
"capacity": "variant",
|
|
59
60
|
"sellersCapacityDetails": "string",
|
|
@@ -5178,5 +5179,6 @@
|
|
|
5178
5179
|
"expected": "string",
|
|
5179
5180
|
"completed": "string"
|
|
5180
5181
|
}
|
|
5181
|
-
}
|
|
5182
|
+
},
|
|
5183
|
+
"offers": {}
|
|
5182
5184
|
}
|
|
@@ -0,0 +1,826 @@
|
|
|
1
|
+
const { getValidator } = require("../../../index.js");
|
|
2
|
+
|
|
3
|
+
const schemaId =
|
|
4
|
+
"https://trust.propdata.org.uk/schemas/v3/pdtf-transaction.json";
|
|
5
|
+
|
|
6
|
+
describe("Offers object validation", () => {
|
|
7
|
+
describe("Valid offer keys", () => {
|
|
8
|
+
test("accepts valid UUID as offer key", () => {
|
|
9
|
+
const validator = getValidator(schemaId);
|
|
10
|
+
const transaction = {
|
|
11
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
12
|
+
status: "active",
|
|
13
|
+
participants: [
|
|
14
|
+
{
|
|
15
|
+
role: "Buyer",
|
|
16
|
+
participantStatus: "Active",
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
offers: {
|
|
20
|
+
"550e8400-e29b-41d4-a716-446655440000": {
|
|
21
|
+
amount: 250000,
|
|
22
|
+
currency: "GBP",
|
|
23
|
+
status: "pending",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const isValid = validator(transaction);
|
|
29
|
+
expect(isValid).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("accepts valid DID as offer key", () => {
|
|
33
|
+
const validator = getValidator(schemaId);
|
|
34
|
+
const transaction = {
|
|
35
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
36
|
+
status: "active",
|
|
37
|
+
participants: [
|
|
38
|
+
{
|
|
39
|
+
role: "Buyer",
|
|
40
|
+
participantStatus: "Active",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
offers: {
|
|
44
|
+
"did:example:123456789abcdefghi": {
|
|
45
|
+
amount: 300000,
|
|
46
|
+
currency: "GBP",
|
|
47
|
+
status: "accepted",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isValid = validator(transaction);
|
|
53
|
+
expect(isValid).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("accepts DID with various allowed characters", () => {
|
|
57
|
+
const validator = getValidator(schemaId);
|
|
58
|
+
const transaction = {
|
|
59
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
60
|
+
status: "active",
|
|
61
|
+
participants: [
|
|
62
|
+
{
|
|
63
|
+
role: "Buyer",
|
|
64
|
+
participantStatus: "Active",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
offers: {
|
|
68
|
+
"did:web:example.com:users:alice": {
|
|
69
|
+
amount: 275000,
|
|
70
|
+
currency: "GBP",
|
|
71
|
+
status: "pending",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const isValid = validator(transaction);
|
|
77
|
+
expect(isValid).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("accepts multiple offers with different key types", () => {
|
|
81
|
+
const validator = getValidator(schemaId);
|
|
82
|
+
const transaction = {
|
|
83
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
84
|
+
status: "active",
|
|
85
|
+
participants: [
|
|
86
|
+
{
|
|
87
|
+
role: "Buyer",
|
|
88
|
+
participantStatus: "Active",
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
offers: {
|
|
92
|
+
"550e8400-e29b-41d4-a716-446655440000": {
|
|
93
|
+
amount: 250000,
|
|
94
|
+
currency: "GBP",
|
|
95
|
+
status: "pending",
|
|
96
|
+
},
|
|
97
|
+
"did:example:buyer123": {
|
|
98
|
+
amount: 260000,
|
|
99
|
+
currency: "GBP",
|
|
100
|
+
status: "accepted",
|
|
101
|
+
},
|
|
102
|
+
"offer-abc123": {
|
|
103
|
+
amount: 240000,
|
|
104
|
+
currency: "GBP",
|
|
105
|
+
status: "rejected",
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const isValid = validator(transaction);
|
|
111
|
+
expect(isValid).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("Invalid offer keys", () => {
|
|
116
|
+
test("rejects offer key starting with invalid character", () => {
|
|
117
|
+
const validator = getValidator(schemaId);
|
|
118
|
+
const transaction = {
|
|
119
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
120
|
+
status: "active",
|
|
121
|
+
participants: [
|
|
122
|
+
{
|
|
123
|
+
role: "Buyer",
|
|
124
|
+
participantStatus: "Active",
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
offers: {
|
|
128
|
+
"-invalid-key": {
|
|
129
|
+
amount: 250000,
|
|
130
|
+
currency: "GBP",
|
|
131
|
+
status: "pending",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const isValid = validator(transaction);
|
|
137
|
+
expect(isValid).toBe(false);
|
|
138
|
+
expect(validator.errors.length).toBeGreaterThan(0);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("rejects offer key with spaces", () => {
|
|
142
|
+
const validator = getValidator(schemaId);
|
|
143
|
+
const transaction = {
|
|
144
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
145
|
+
status: "active",
|
|
146
|
+
participants: [
|
|
147
|
+
{
|
|
148
|
+
role: "Buyer",
|
|
149
|
+
participantStatus: "Active",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
offers: {
|
|
153
|
+
"invalid key with spaces": {
|
|
154
|
+
amount: 250000,
|
|
155
|
+
currency: "GBP",
|
|
156
|
+
status: "pending",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const isValid = validator(transaction);
|
|
162
|
+
expect(isValid).toBe(false);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("rejects offer key with special characters", () => {
|
|
166
|
+
const validator = getValidator(schemaId);
|
|
167
|
+
const transaction = {
|
|
168
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
169
|
+
status: "active",
|
|
170
|
+
participants: [
|
|
171
|
+
{
|
|
172
|
+
role: "Buyer",
|
|
173
|
+
participantStatus: "Active",
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
offers: {
|
|
177
|
+
"invalid@key#here": {
|
|
178
|
+
amount: 250000,
|
|
179
|
+
currency: "GBP",
|
|
180
|
+
status: "pending",
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const isValid = validator(transaction);
|
|
186
|
+
expect(isValid).toBe(false);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe("Valid offer contents", () => {
|
|
191
|
+
test("accepts offer with all required fields", () => {
|
|
192
|
+
const validator = getValidator(schemaId);
|
|
193
|
+
const transaction = {
|
|
194
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
195
|
+
status: "active",
|
|
196
|
+
participants: [
|
|
197
|
+
{
|
|
198
|
+
role: "Buyer",
|
|
199
|
+
participantStatus: "Active",
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
offers: {
|
|
203
|
+
"offer-1": {
|
|
204
|
+
amount: 250000,
|
|
205
|
+
currency: "GBP",
|
|
206
|
+
status: "pending",
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const isValid = validator(transaction);
|
|
212
|
+
expect(isValid).toBe(true);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("accepts offer with all fields including optional ones", () => {
|
|
216
|
+
const validator = getValidator(schemaId);
|
|
217
|
+
const transaction = {
|
|
218
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
219
|
+
status: "active",
|
|
220
|
+
participants: [
|
|
221
|
+
{
|
|
222
|
+
role: "Buyer",
|
|
223
|
+
participantStatus: "Active",
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
offers: {
|
|
227
|
+
"offer-1": {
|
|
228
|
+
amount: 250000,
|
|
229
|
+
currency: "GBP",
|
|
230
|
+
status: "accepted",
|
|
231
|
+
inclusions: ["curtains", "carpets", "light fixtures"],
|
|
232
|
+
exclusions: ["garden furniture", "shed"],
|
|
233
|
+
conditions: [
|
|
234
|
+
"Subject to survey",
|
|
235
|
+
"Subject to mortgage approval",
|
|
236
|
+
],
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const isValid = validator(transaction);
|
|
242
|
+
expect(isValid).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("accepts all valid status values", () => {
|
|
246
|
+
const validator = getValidator(schemaId);
|
|
247
|
+
const validStatuses = [
|
|
248
|
+
"pending",
|
|
249
|
+
"withdrawn",
|
|
250
|
+
"rejected",
|
|
251
|
+
"accepted",
|
|
252
|
+
"noteOfInterest",
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
validStatuses.forEach((status) => {
|
|
256
|
+
const transaction = {
|
|
257
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
258
|
+
status: "active",
|
|
259
|
+
participants: [
|
|
260
|
+
{
|
|
261
|
+
role: "Buyer",
|
|
262
|
+
participantStatus: "Active",
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
offers: {
|
|
266
|
+
"offer-1": {
|
|
267
|
+
amount: 250000,
|
|
268
|
+
currency: "GBP",
|
|
269
|
+
status: status,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const isValid = validator(transaction);
|
|
275
|
+
expect(isValid).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("accepts valid currency codes", () => {
|
|
280
|
+
const validator = getValidator(schemaId);
|
|
281
|
+
const validCurrencies = ["GBP", "USD", "EUR"];
|
|
282
|
+
|
|
283
|
+
validCurrencies.forEach((currency) => {
|
|
284
|
+
const transaction = {
|
|
285
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
286
|
+
status: "active",
|
|
287
|
+
participants: [
|
|
288
|
+
{
|
|
289
|
+
role: "Buyer",
|
|
290
|
+
participantStatus: "Active",
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
offers: {
|
|
294
|
+
"offer-1": {
|
|
295
|
+
amount: 250000,
|
|
296
|
+
currency: currency,
|
|
297
|
+
status: "pending",
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const isValid = validator(transaction);
|
|
303
|
+
expect(isValid).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("accepts zero amount", () => {
|
|
308
|
+
const validator = getValidator(schemaId);
|
|
309
|
+
const transaction = {
|
|
310
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
311
|
+
status: "active",
|
|
312
|
+
participants: [
|
|
313
|
+
{
|
|
314
|
+
role: "Buyer",
|
|
315
|
+
participantStatus: "Active",
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
offers: {
|
|
319
|
+
"offer-1": {
|
|
320
|
+
amount: 0,
|
|
321
|
+
currency: "GBP",
|
|
322
|
+
status: "noteOfInterest",
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const isValid = validator(transaction);
|
|
328
|
+
expect(isValid).toBe(true);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
describe("Invalid offer contents", () => {
|
|
333
|
+
test("rejects offer missing required amount field", () => {
|
|
334
|
+
const validator = getValidator(schemaId);
|
|
335
|
+
const transaction = {
|
|
336
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
337
|
+
status: "active",
|
|
338
|
+
participants: [
|
|
339
|
+
{
|
|
340
|
+
role: "Buyer",
|
|
341
|
+
participantStatus: "Active",
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
offers: {
|
|
345
|
+
"offer-1": {
|
|
346
|
+
currency: "GBP",
|
|
347
|
+
status: "pending",
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const isValid = validator(transaction);
|
|
353
|
+
expect(isValid).toBe(false);
|
|
354
|
+
expect(
|
|
355
|
+
validator.errors.some((e) => e.message.includes("required property"))
|
|
356
|
+
).toBe(true);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("rejects offer missing required currency field", () => {
|
|
360
|
+
const validator = getValidator(schemaId);
|
|
361
|
+
const transaction = {
|
|
362
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
363
|
+
status: "active",
|
|
364
|
+
participants: [
|
|
365
|
+
{
|
|
366
|
+
role: "Buyer",
|
|
367
|
+
participantStatus: "Active",
|
|
368
|
+
},
|
|
369
|
+
],
|
|
370
|
+
offers: {
|
|
371
|
+
"offer-1": {
|
|
372
|
+
amount: 250000,
|
|
373
|
+
status: "pending",
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const isValid = validator(transaction);
|
|
379
|
+
expect(isValid).toBe(false);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("rejects offer missing required status field", () => {
|
|
383
|
+
const validator = getValidator(schemaId);
|
|
384
|
+
const transaction = {
|
|
385
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
386
|
+
status: "active",
|
|
387
|
+
participants: [
|
|
388
|
+
{
|
|
389
|
+
role: "Buyer",
|
|
390
|
+
participantStatus: "Active",
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
offers: {
|
|
394
|
+
"offer-1": {
|
|
395
|
+
amount: 250000,
|
|
396
|
+
currency: "GBP",
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const isValid = validator(transaction);
|
|
402
|
+
expect(isValid).toBe(false);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("rejects offer with invalid status value", () => {
|
|
406
|
+
const validator = getValidator(schemaId);
|
|
407
|
+
const transaction = {
|
|
408
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
409
|
+
status: "active",
|
|
410
|
+
participants: [
|
|
411
|
+
{
|
|
412
|
+
role: "Buyer",
|
|
413
|
+
participantStatus: "Active",
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
offers: {
|
|
417
|
+
"offer-1": {
|
|
418
|
+
amount: 250000,
|
|
419
|
+
currency: "GBP",
|
|
420
|
+
status: "invalid-status",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const isValid = validator(transaction);
|
|
426
|
+
expect(isValid).toBe(false);
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
test("rejects offer with negative amount", () => {
|
|
430
|
+
const validator = getValidator(schemaId);
|
|
431
|
+
const transaction = {
|
|
432
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
433
|
+
status: "active",
|
|
434
|
+
participants: [
|
|
435
|
+
{
|
|
436
|
+
role: "Buyer",
|
|
437
|
+
participantStatus: "Active",
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
offers: {
|
|
441
|
+
"offer-1": {
|
|
442
|
+
amount: -100000,
|
|
443
|
+
currency: "GBP",
|
|
444
|
+
status: "pending",
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const isValid = validator(transaction);
|
|
450
|
+
expect(isValid).toBe(false);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
test("rejects offer with invalid currency code format", () => {
|
|
454
|
+
const validator = getValidator(schemaId);
|
|
455
|
+
const transaction = {
|
|
456
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
457
|
+
status: "active",
|
|
458
|
+
participants: [
|
|
459
|
+
{
|
|
460
|
+
role: "Buyer",
|
|
461
|
+
participantStatus: "Active",
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
offers: {
|
|
465
|
+
"offer-1": {
|
|
466
|
+
amount: 250000,
|
|
467
|
+
currency: "gb",
|
|
468
|
+
status: "pending",
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const isValid = validator(transaction);
|
|
474
|
+
expect(isValid).toBe(false);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
test("rejects offer with lowercase currency code", () => {
|
|
478
|
+
const validator = getValidator(schemaId);
|
|
479
|
+
const transaction = {
|
|
480
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
481
|
+
status: "active",
|
|
482
|
+
participants: [
|
|
483
|
+
{
|
|
484
|
+
role: "Buyer",
|
|
485
|
+
participantStatus: "Active",
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
offers: {
|
|
489
|
+
"offer-1": {
|
|
490
|
+
amount: 250000,
|
|
491
|
+
currency: "gbp",
|
|
492
|
+
status: "pending",
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const isValid = validator(transaction);
|
|
498
|
+
expect(isValid).toBe(false);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
test("rejects offer with non-array inclusions", () => {
|
|
502
|
+
const validator = getValidator(schemaId);
|
|
503
|
+
const transaction = {
|
|
504
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
505
|
+
status: "active",
|
|
506
|
+
participants: [
|
|
507
|
+
{
|
|
508
|
+
role: "Buyer",
|
|
509
|
+
participantStatus: "Active",
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
offers: {
|
|
513
|
+
"offer-1": {
|
|
514
|
+
amount: 250000,
|
|
515
|
+
currency: "GBP",
|
|
516
|
+
status: "pending",
|
|
517
|
+
inclusions: "curtains",
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
const isValid = validator(transaction);
|
|
523
|
+
expect(isValid).toBe(false);
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
test("rejects offer with non-string items in inclusions array", () => {
|
|
527
|
+
const validator = getValidator(schemaId);
|
|
528
|
+
const transaction = {
|
|
529
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
530
|
+
status: "active",
|
|
531
|
+
participants: [
|
|
532
|
+
{
|
|
533
|
+
role: "Buyer",
|
|
534
|
+
participantStatus: "Active",
|
|
535
|
+
},
|
|
536
|
+
],
|
|
537
|
+
offers: {
|
|
538
|
+
"offer-1": {
|
|
539
|
+
amount: 250000,
|
|
540
|
+
currency: "GBP",
|
|
541
|
+
status: "pending",
|
|
542
|
+
inclusions: ["curtains", 123, "carpets"],
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
const isValid = validator(transaction);
|
|
548
|
+
expect(isValid).toBe(false);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
test("rejects offer with non-array exclusions", () => {
|
|
552
|
+
const validator = getValidator(schemaId);
|
|
553
|
+
const transaction = {
|
|
554
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
555
|
+
status: "active",
|
|
556
|
+
participants: [
|
|
557
|
+
{
|
|
558
|
+
role: "Buyer",
|
|
559
|
+
participantStatus: "Active",
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
offers: {
|
|
563
|
+
"offer-1": {
|
|
564
|
+
amount: 250000,
|
|
565
|
+
currency: "GBP",
|
|
566
|
+
status: "pending",
|
|
567
|
+
exclusions: { item: "garden furniture" },
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const isValid = validator(transaction);
|
|
573
|
+
expect(isValid).toBe(false);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test("rejects offer with non-array conditions", () => {
|
|
577
|
+
const validator = getValidator(schemaId);
|
|
578
|
+
const transaction = {
|
|
579
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
580
|
+
status: "active",
|
|
581
|
+
participants: [
|
|
582
|
+
{
|
|
583
|
+
role: "Buyer",
|
|
584
|
+
participantStatus: "Active",
|
|
585
|
+
},
|
|
586
|
+
],
|
|
587
|
+
offers: {
|
|
588
|
+
"offer-1": {
|
|
589
|
+
amount: 250000,
|
|
590
|
+
currency: "GBP",
|
|
591
|
+
status: "pending",
|
|
592
|
+
conditions: "Subject to survey",
|
|
593
|
+
},
|
|
594
|
+
},
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
const isValid = validator(transaction);
|
|
598
|
+
expect(isValid).toBe(false);
|
|
599
|
+
});
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
describe("Empty offers object", () => {
|
|
603
|
+
test("accepts empty offers object", () => {
|
|
604
|
+
const validator = getValidator(schemaId);
|
|
605
|
+
const transaction = {
|
|
606
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
607
|
+
status: "active",
|
|
608
|
+
participants: [
|
|
609
|
+
{
|
|
610
|
+
role: "Buyer",
|
|
611
|
+
participantStatus: "Active",
|
|
612
|
+
},
|
|
613
|
+
],
|
|
614
|
+
offers: {},
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
const isValid = validator(transaction);
|
|
618
|
+
expect(isValid).toBe(true);
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
test("accepts transaction without offers property", () => {
|
|
622
|
+
const validator = getValidator(schemaId);
|
|
623
|
+
const transaction = {
|
|
624
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
625
|
+
status: "active",
|
|
626
|
+
participants: [
|
|
627
|
+
{
|
|
628
|
+
role: "Buyer",
|
|
629
|
+
participantStatus: "Active",
|
|
630
|
+
},
|
|
631
|
+
],
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
const isValid = validator(transaction);
|
|
635
|
+
expect(isValid).toBe(true);
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
describe("Buyer participant with offerId", () => {
|
|
640
|
+
test("accepts Buyer with valid offerId", () => {
|
|
641
|
+
const validator = getValidator(schemaId);
|
|
642
|
+
const transaction = {
|
|
643
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
644
|
+
status: "active",
|
|
645
|
+
participants: [
|
|
646
|
+
{
|
|
647
|
+
role: "Buyer",
|
|
648
|
+
participantStatus: "Active",
|
|
649
|
+
offerId: "550e8400-e29b-41d4-a716-446655440000",
|
|
650
|
+
},
|
|
651
|
+
],
|
|
652
|
+
offers: {
|
|
653
|
+
"550e8400-e29b-41d4-a716-446655440000": {
|
|
654
|
+
amount: 250000,
|
|
655
|
+
currency: "GBP",
|
|
656
|
+
status: "accepted",
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
const isValid = validator(transaction);
|
|
662
|
+
expect(isValid).toBe(true);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
test("accepts Buyer with offerId as DID", () => {
|
|
666
|
+
const validator = getValidator(schemaId);
|
|
667
|
+
const transaction = {
|
|
668
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
669
|
+
status: "active",
|
|
670
|
+
participants: [
|
|
671
|
+
{
|
|
672
|
+
role: "Buyer",
|
|
673
|
+
participantStatus: "Active",
|
|
674
|
+
offerId: "did:example:buyer123",
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
offers: {
|
|
678
|
+
"did:example:buyer123": {
|
|
679
|
+
amount: 260000,
|
|
680
|
+
currency: "GBP",
|
|
681
|
+
status: "accepted",
|
|
682
|
+
},
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const isValid = validator(transaction);
|
|
687
|
+
expect(isValid).toBe(true);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
test("accepts Buyer without offerId", () => {
|
|
691
|
+
const validator = getValidator(schemaId);
|
|
692
|
+
const transaction = {
|
|
693
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
694
|
+
status: "active",
|
|
695
|
+
participants: [
|
|
696
|
+
{
|
|
697
|
+
role: "Buyer",
|
|
698
|
+
participantStatus: "Active",
|
|
699
|
+
},
|
|
700
|
+
],
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const isValid = validator(transaction);
|
|
704
|
+
expect(isValid).toBe(true);
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
test("rejects Buyer with invalid offerId format starting with dash", () => {
|
|
708
|
+
const validator = getValidator(schemaId);
|
|
709
|
+
const transaction = {
|
|
710
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
711
|
+
status: "active",
|
|
712
|
+
participants: [
|
|
713
|
+
{
|
|
714
|
+
role: "Buyer",
|
|
715
|
+
participantStatus: "Active",
|
|
716
|
+
offerId: "-invalid-offer-id",
|
|
717
|
+
},
|
|
718
|
+
],
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
const isValid = validator(transaction);
|
|
722
|
+
expect(isValid).toBe(false);
|
|
723
|
+
// Should fail because offerId pattern requires starting with alphanumeric
|
|
724
|
+
const offerIdError = validator.errors.find(
|
|
725
|
+
(e) => e.instancePath.includes("offerId")
|
|
726
|
+
);
|
|
727
|
+
expect(offerIdError).toBeDefined();
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
test("rejects Buyer with offerId containing spaces", () => {
|
|
731
|
+
const validator = getValidator(schemaId);
|
|
732
|
+
const transaction = {
|
|
733
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
734
|
+
status: "active",
|
|
735
|
+
participants: [
|
|
736
|
+
{
|
|
737
|
+
role: "Buyer",
|
|
738
|
+
participantStatus: "Active",
|
|
739
|
+
offerId: "invalid offer id",
|
|
740
|
+
},
|
|
741
|
+
],
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
const isValid = validator(transaction);
|
|
745
|
+
expect(isValid).toBe(false);
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
test("accepts multiple Buyers with different offerIds", () => {
|
|
749
|
+
const validator = getValidator(schemaId);
|
|
750
|
+
const transaction = {
|
|
751
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
752
|
+
status: "active",
|
|
753
|
+
participants: [
|
|
754
|
+
{
|
|
755
|
+
role: "Buyer",
|
|
756
|
+
participantStatus: "Active",
|
|
757
|
+
offerId: "offer-1",
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
role: "Buyer",
|
|
761
|
+
participantStatus: "Active",
|
|
762
|
+
offerId: "offer-2",
|
|
763
|
+
},
|
|
764
|
+
],
|
|
765
|
+
offers: {
|
|
766
|
+
"offer-1": {
|
|
767
|
+
amount: 250000,
|
|
768
|
+
currency: "GBP",
|
|
769
|
+
status: "pending",
|
|
770
|
+
},
|
|
771
|
+
"offer-2": {
|
|
772
|
+
amount: 260000,
|
|
773
|
+
currency: "GBP",
|
|
774
|
+
status: "accepted",
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
const isValid = validator(transaction);
|
|
780
|
+
expect(isValid).toBe(true);
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
test("accepts Buyer with offerId referencing non-existent offer (schema allows this)", () => {
|
|
784
|
+
// Note: The schema doesn't enforce referential integrity between offerId and offers keys
|
|
785
|
+
// This would need to be enforced at the application level
|
|
786
|
+
const validator = getValidator(schemaId);
|
|
787
|
+
const transaction = {
|
|
788
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
789
|
+
status: "active",
|
|
790
|
+
participants: [
|
|
791
|
+
{
|
|
792
|
+
role: "Buyer",
|
|
793
|
+
participantStatus: "Active",
|
|
794
|
+
offerId: "non-existent-offer",
|
|
795
|
+
},
|
|
796
|
+
],
|
|
797
|
+
offers: {},
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
const isValid = validator(transaction);
|
|
801
|
+
expect(isValid).toBe(true);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
test("allows offerId on non-Buyer participants (schema doesn't restrict additionalProperties)", () => {
|
|
805
|
+
// Note: The schema uses oneOf with discriminator on role, but doesn't use
|
|
806
|
+
// additionalProperties: false, so extra properties are allowed on all participants
|
|
807
|
+
// This is intentional to maintain flexibility
|
|
808
|
+
const validator = getValidator(schemaId);
|
|
809
|
+
const transaction = {
|
|
810
|
+
transactionId: "123e4567-e89b-12d3-a456-426614174000",
|
|
811
|
+
status: "active",
|
|
812
|
+
participants: [
|
|
813
|
+
{
|
|
814
|
+
role: "Seller",
|
|
815
|
+
participantStatus: "Active",
|
|
816
|
+
offerId: "offer-1",
|
|
817
|
+
},
|
|
818
|
+
],
|
|
819
|
+
};
|
|
820
|
+
|
|
821
|
+
const isValid = validator(transaction);
|
|
822
|
+
// This will actually pass because additionalProperties is not restricted
|
|
823
|
+
expect(isValid).toBe(true);
|
|
824
|
+
});
|
|
825
|
+
});
|
|
826
|
+
});
|