@defra/grants-reporting-publisher 0.0.3 → 0.1.1
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/CHANGELOG.md +12 -0
- package/EXAMPLES.md +127 -0
- package/README.md +2 -0
- package/package.json +3 -2
- package/src/constants.js +2 -0
- package/src/index.js +1 -0
- package/src/publish.test.js +6 -1
- package/src/schema.js +43 -9
- package/src/validate.test.js +215 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @defra/grants-reporting-publisher
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f8be355: Update readme with examples
|
|
8
|
+
|
|
9
|
+
## 0.1.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 94ffa84: Add the schema definition for input events
|
|
14
|
+
|
|
3
15
|
## 0.0.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Usage Examples
|
|
2
|
+
|
|
3
|
+
This page provides examples of how to use the `@defra/grants-reporting-publisher` library to publish reporting events.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
To publish events, you need to provide a configuration object that includes an AWS SNS client and the target topic ARN.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
import { SNSClient } from "@aws-sdk/client-sns";
|
|
11
|
+
import {
|
|
12
|
+
publishReportingEvent,
|
|
13
|
+
AGREEMENT_CREATED,
|
|
14
|
+
AGREEMENT_STATUS_CHANGED,
|
|
15
|
+
} from "@defra/grants-reporting-publisher";
|
|
16
|
+
|
|
17
|
+
const snsClient = new SNSClient({ region: "eu-west-2" });
|
|
18
|
+
|
|
19
|
+
const config = {
|
|
20
|
+
snsClient,
|
|
21
|
+
sns: {
|
|
22
|
+
topicArn: "arn:aws:sns:eu-west-2:123456789012:your-topic-name",
|
|
23
|
+
},
|
|
24
|
+
application: "YOUR_APPLICATION_CODE", // e.g., "GAS or grants-payments-service"
|
|
25
|
+
service: "your-service-name", // optional, defaults to "grants", just omit this for now
|
|
26
|
+
generateCorrelationId: true, // automatically generate a UUID for each event
|
|
27
|
+
};
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Event Types
|
|
31
|
+
|
|
32
|
+
### Agreement Created (`AGREEMENT_CREATED`)
|
|
33
|
+
|
|
34
|
+
Use this event type when a new agreement is created.
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
const agreementCreatedEvent = {
|
|
38
|
+
eventData: {
|
|
39
|
+
eventType: AGREEMENT_CREATED,
|
|
40
|
+
agreementId: "AG-123456",
|
|
41
|
+
agreementType: "WOODLAND",
|
|
42
|
+
agreementStatus: "ACCEPTED",
|
|
43
|
+
agreementStartDate: "2026-01-01",
|
|
44
|
+
agreementEndDate: "2031-12-31",
|
|
45
|
+
agreementValue: 50000.0,
|
|
46
|
+
sbi: "200000001",
|
|
47
|
+
options: [
|
|
48
|
+
{
|
|
49
|
+
parcelReference: "SD8545-9935",
|
|
50
|
+
parcelSizeUnderAgreement: 10.5,
|
|
51
|
+
optionCode: "WD1",
|
|
52
|
+
optionYear: 1,
|
|
53
|
+
optionStartDate: "2026-01-01",
|
|
54
|
+
optionEndDate: "2026-12-31",
|
|
55
|
+
optionQuantity: 10.5,
|
|
56
|
+
optionValue: 10500.0,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const { messageId } = await publishReportingEvent(
|
|
64
|
+
agreementCreatedEvent,
|
|
65
|
+
config,
|
|
66
|
+
);
|
|
67
|
+
console.log(`Event published with Message ID: ${messageId}`);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error("Failed to publish event:", error.message);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Agreement Status Changed (`AGREEMENT_STATUS_CHANGED`)
|
|
74
|
+
|
|
75
|
+
Use this event type when the status of an existing agreement changes.
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
const statusChangedEvent = {
|
|
79
|
+
eventData: {
|
|
80
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
81
|
+
agreementId: "AG-123456",
|
|
82
|
+
agreementStatus: "COMPLETED",
|
|
83
|
+
statusDate: new Date().toISOString(),
|
|
84
|
+
agreementStartDate: "2026-01-01",
|
|
85
|
+
agreementEndDate: "2031-12-31",
|
|
86
|
+
agreementValue: 50000.0,
|
|
87
|
+
userId: "user-uuid-123", // optional
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const { messageId } = await publishReportingEvent(statusChangedEvent, config);
|
|
93
|
+
console.log(`Event published with Message ID: ${messageId}`);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error("Failed to publish event:", error.message);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Validation Only
|
|
100
|
+
|
|
101
|
+
If you only want to validate an event without publishing it:
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
import { validateReportingEvent } from "@defra/grants-reporting-publisher";
|
|
105
|
+
|
|
106
|
+
const event = {
|
|
107
|
+
correlationId: "your-correlation-id",
|
|
108
|
+
datetime: new Date().toISOString(),
|
|
109
|
+
version: "1.0.0",
|
|
110
|
+
application: "FCP",
|
|
111
|
+
service: "grants",
|
|
112
|
+
eventData: {
|
|
113
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
114
|
+
agreementId: "AG-123",
|
|
115
|
+
agreementStatus: "ACTIVE",
|
|
116
|
+
statusDate: new Date().toISOString(),
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const { valid, errors, value } = validateReportingEvent(event);
|
|
121
|
+
|
|
122
|
+
if (!valid) {
|
|
123
|
+
console.error("Validation errors:", errors);
|
|
124
|
+
} else {
|
|
125
|
+
console.log("Event is valid:", value);
|
|
126
|
+
}
|
|
127
|
+
```
|
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/grants-reporting-publisher",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "NPM module for publishing grants reporting events",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
".": "./src/index.js",
|
|
33
33
|
"./publish": "./src/publish.js",
|
|
34
34
|
"./validate": "./src/validate.js",
|
|
35
|
-
"./schema": "./src/schema.js"
|
|
35
|
+
"./schema": "./src/schema.js",
|
|
36
|
+
"./constants": "./src/constants.js"
|
|
36
37
|
},
|
|
37
38
|
"license": "OGL-UK-3.0",
|
|
38
39
|
"bugs": {
|
package/src/constants.js
ADDED
package/src/index.js
CHANGED
package/src/publish.test.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { publishReportingEvent } from "./publish.js";
|
|
2
|
+
import { AGREEMENT_STATUS_CHANGED } from "./constants.js";
|
|
2
3
|
|
|
3
4
|
const mockSend = vi.hoisted(() =>
|
|
4
5
|
vi.fn().mockResolvedValue({ MessageId: "msg-001" }),
|
|
@@ -22,8 +23,12 @@ const validEvent = {
|
|
|
22
23
|
datetime: "2025-12-01T12:51:41.381Z",
|
|
23
24
|
version: "1.0.0",
|
|
24
25
|
application: "FCP001",
|
|
26
|
+
service: "grants",
|
|
25
27
|
eventData: {
|
|
26
|
-
|
|
28
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
29
|
+
agreementId: "WMP123456789",
|
|
30
|
+
agreementStatus: "created",
|
|
31
|
+
statusDate: "2025-12-01T12:51:41.381Z",
|
|
27
32
|
},
|
|
28
33
|
};
|
|
29
34
|
|
package/src/schema.js
CHANGED
|
@@ -1,28 +1,62 @@
|
|
|
1
1
|
import Joi from "joi";
|
|
2
|
+
import { AGREEMENT_CREATED, AGREEMENT_STATUS_CHANGED } from "./constants.js";
|
|
2
3
|
|
|
3
4
|
const MAX_30 = 30;
|
|
4
5
|
const MAX_50 = 50;
|
|
5
|
-
const MAX_120 = 120;
|
|
6
6
|
|
|
7
7
|
const accountId = Joi.alternatives()
|
|
8
8
|
.try(Joi.string().max(MAX_50), Joi.number())
|
|
9
9
|
.custom(String);
|
|
10
10
|
|
|
11
11
|
export const eventSchema = Joi.object({
|
|
12
|
-
user: Joi.string().max(MAX_50).allow(""),
|
|
13
|
-
sessionId: Joi.string().max(MAX_50).allow(""),
|
|
14
12
|
correlationId: Joi.string().max(MAX_50).required(),
|
|
15
13
|
datetime: Joi.date().iso().required(),
|
|
16
14
|
version: Joi.string().max(10).required(),
|
|
17
15
|
application: Joi.string().max(MAX_30).required(),
|
|
18
16
|
service: Joi.string().max(MAX_30).required(),
|
|
19
17
|
eventData: Joi.object({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
18
|
+
eventType: Joi.string()
|
|
19
|
+
.valid(AGREEMENT_CREATED, AGREEMENT_STATUS_CHANGED)
|
|
20
|
+
.required(),
|
|
21
|
+
})
|
|
22
|
+
.when(Joi.object({ eventType: AGREEMENT_CREATED }).unknown(), {
|
|
23
|
+
then: Joi.object({
|
|
24
|
+
agreementId: Joi.string().required(),
|
|
25
|
+
agreementType: Joi.string().required(),
|
|
26
|
+
agreementStatus: Joi.string().required(),
|
|
27
|
+
agreementStartDate: Joi.date().iso().optional(),
|
|
28
|
+
agreementEndDate: Joi.date().iso().optional(),
|
|
29
|
+
agreementValue: Joi.number().optional(),
|
|
30
|
+
sbi: accountId.required(),
|
|
31
|
+
options: Joi.array()
|
|
32
|
+
.items(
|
|
33
|
+
Joi.object({
|
|
34
|
+
parcelReference: Joi.string().allow("").required(),
|
|
35
|
+
parcelSizeUnderAgreement: Joi.number().optional(),
|
|
36
|
+
optionCode: Joi.string().required(),
|
|
37
|
+
optionYear: Joi.number().integer().optional(),
|
|
38
|
+
optionStartDate: Joi.date().iso().required(),
|
|
39
|
+
optionEndDate: Joi.date().iso().required(),
|
|
40
|
+
optionQuantity: Joi.number().required(),
|
|
41
|
+
optionValue: Joi.number().required(),
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
44
|
+
.required(),
|
|
45
|
+
}),
|
|
46
|
+
})
|
|
47
|
+
.when(Joi.object({ eventType: AGREEMENT_STATUS_CHANGED }).unknown(), {
|
|
48
|
+
then: Joi.object({
|
|
49
|
+
agreementId: Joi.string().required(),
|
|
50
|
+
agreementStatus: Joi.string().required(),
|
|
51
|
+
statusDate: Joi.date().iso().required(),
|
|
52
|
+
agreementStartDate: Joi.date().iso().optional(),
|
|
53
|
+
agreementEndDate: Joi.date().iso().optional(),
|
|
54
|
+
agreementValue: Joi.number().optional(),
|
|
55
|
+
userId: Joi.string().optional(),
|
|
56
|
+
}),
|
|
57
|
+
})
|
|
58
|
+
.unknown()
|
|
59
|
+
.required(),
|
|
26
60
|
}).required();
|
|
27
61
|
|
|
28
62
|
export const configSchema = Joi.object({
|
package/src/validate.test.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { validateReportingEvent } from "./validate.js";
|
|
2
|
+
import { AGREEMENT_CREATED, AGREEMENT_STATUS_CHANGED } from "./constants.js";
|
|
2
3
|
|
|
3
4
|
const validBase = {
|
|
4
5
|
correlationId: "abc-123",
|
|
@@ -9,9 +10,11 @@ const validBase = {
|
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
const eventDataPayload = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
14
|
+
agreementId: "WMP123456789",
|
|
15
|
+
agreementStatus: "ACCEPTED",
|
|
16
|
+
statusDate: "2025-12-01T12:51:41.381Z",
|
|
17
|
+
sbi: "abc123",
|
|
15
18
|
};
|
|
16
19
|
|
|
17
20
|
describe("validateReportingEvent", () => {
|
|
@@ -25,24 +28,27 @@ describe("validateReportingEvent", () => {
|
|
|
25
28
|
expect(result.value).toBeDefined();
|
|
26
29
|
});
|
|
27
30
|
|
|
28
|
-
test("accepts optional
|
|
31
|
+
test("accepts optional agreementStatus and details fields", () => {
|
|
29
32
|
const result = validateReportingEvent({
|
|
30
33
|
...validBase,
|
|
31
34
|
eventData: {
|
|
32
|
-
|
|
35
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
36
|
+
agreementId: "WMP123456789",
|
|
37
|
+
agreementStatus: "created",
|
|
38
|
+
statusDate: "2025-12-01T12:51:41.381Z",
|
|
33
39
|
details: { agreementId: "agreement_456" },
|
|
34
40
|
},
|
|
35
41
|
});
|
|
36
42
|
expect(result.valid).toBe(true);
|
|
37
43
|
});
|
|
38
44
|
|
|
39
|
-
test("normalises the validated value (
|
|
45
|
+
test("normalises the validated value (converts datetime to Date object)", () => {
|
|
40
46
|
const result = validateReportingEvent({
|
|
41
47
|
...validBase,
|
|
42
48
|
eventData: eventDataPayload,
|
|
43
49
|
});
|
|
44
50
|
expect(result.valid).toBe(true);
|
|
45
|
-
expect(result.value.
|
|
51
|
+
expect(result.value.datetime).toBeInstanceOf(Date);
|
|
46
52
|
});
|
|
47
53
|
});
|
|
48
54
|
|
|
@@ -99,10 +105,212 @@ describe("validateReportingEvent", () => {
|
|
|
99
105
|
expect(result.errors.some((e) => e.includes("version"))).toBe(true);
|
|
100
106
|
});
|
|
101
107
|
|
|
108
|
+
test("returns valid:false when eventType is invalid", () => {
|
|
109
|
+
const result = validateReportingEvent({
|
|
110
|
+
...validBase,
|
|
111
|
+
eventData: {
|
|
112
|
+
...eventDataPayload,
|
|
113
|
+
eventType: "INVALID_EVENT_TYPE",
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
expect(result.valid).toBe(false);
|
|
117
|
+
expect(result.errors).toContain(
|
|
118
|
+
'"eventData.eventType" must be one of [AGREEMENT_CREATED, AGREEMENT_STATUS_CHANGED]',
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
102
122
|
test("returns multiple errors with abortEarly:false", () => {
|
|
103
123
|
const result = validateReportingEvent({ eventData: eventDataPayload });
|
|
104
124
|
expect(result.valid).toBe(false);
|
|
105
125
|
expect(result.errors.length).toBeGreaterThan(1);
|
|
106
126
|
});
|
|
107
127
|
});
|
|
128
|
+
|
|
129
|
+
describe("Agreement created events", () => {
|
|
130
|
+
test("accepts a valid Agreement created event", () => {
|
|
131
|
+
const result = validateReportingEvent({
|
|
132
|
+
...validBase,
|
|
133
|
+
eventData: {
|
|
134
|
+
eventType: AGREEMENT_CREATED,
|
|
135
|
+
agreementId: "WMP123456789",
|
|
136
|
+
agreementType: "WOODLAND",
|
|
137
|
+
agreementStatus: "ACCEPTED",
|
|
138
|
+
agreementStartDate: "2026-09-01",
|
|
139
|
+
agreementEndDate: "2029-08-31",
|
|
140
|
+
agreementValue: 1575.0,
|
|
141
|
+
sbi: "200000001",
|
|
142
|
+
options: [],
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
expect(result.valid).toBe(true);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("accepts Agreement created event with options", () => {
|
|
149
|
+
const result = validateReportingEvent({
|
|
150
|
+
...validBase,
|
|
151
|
+
eventData: {
|
|
152
|
+
eventType: AGREEMENT_CREATED,
|
|
153
|
+
agreementId: "WMP123456789",
|
|
154
|
+
agreementType: "WOODLAND",
|
|
155
|
+
agreementStatus: "ACCEPTED",
|
|
156
|
+
sbi: "200000001",
|
|
157
|
+
options: [
|
|
158
|
+
{
|
|
159
|
+
parcelReference: "SD8545-9935",
|
|
160
|
+
parcelSizeUnderAgreement: 15.75,
|
|
161
|
+
optionCode: "WMP1",
|
|
162
|
+
optionYear: 1,
|
|
163
|
+
optionStartDate: "2026-09-01",
|
|
164
|
+
optionEndDate: "2029-08-31",
|
|
165
|
+
optionQuantity: 15.75,
|
|
166
|
+
optionValue: 1575.0,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
expect(result.valid).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("accepts Agreement created event without optional fields", () => {
|
|
175
|
+
const result = validateReportingEvent({
|
|
176
|
+
...validBase,
|
|
177
|
+
eventData: {
|
|
178
|
+
eventType: AGREEMENT_CREATED,
|
|
179
|
+
agreementId: "WMP123456789",
|
|
180
|
+
agreementType: "WOODLAND",
|
|
181
|
+
agreementStatus: "ACCEPTED",
|
|
182
|
+
sbi: "200000001",
|
|
183
|
+
options: [],
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
expect(result.valid).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("accepts Agreement created event with minimal options", () => {
|
|
190
|
+
const result = validateReportingEvent({
|
|
191
|
+
...validBase,
|
|
192
|
+
eventData: {
|
|
193
|
+
eventType: AGREEMENT_CREATED,
|
|
194
|
+
agreementId: "WMP123456789",
|
|
195
|
+
agreementType: "WOODLAND",
|
|
196
|
+
agreementStatus: "ACCEPTED",
|
|
197
|
+
sbi: "200000001",
|
|
198
|
+
options: [
|
|
199
|
+
{
|
|
200
|
+
parcelReference: "SD8545-9935",
|
|
201
|
+
optionCode: "WMP1",
|
|
202
|
+
optionStartDate: "2026-09-01",
|
|
203
|
+
optionEndDate: "2029-08-31",
|
|
204
|
+
optionQuantity: 15.75,
|
|
205
|
+
optionValue: 1575.0,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
expect(result.valid).toBe(true);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
test("rejects Agreement created event if required fields are missing", () => {
|
|
214
|
+
const result = validateReportingEvent({
|
|
215
|
+
...validBase,
|
|
216
|
+
eventData: {
|
|
217
|
+
eventType: AGREEMENT_CREATED,
|
|
218
|
+
agreementId: "WMP123456789",
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
expect(result.valid).toBe(false);
|
|
222
|
+
expect(result.errors).toContain('"eventData.agreementType" is required');
|
|
223
|
+
expect(result.errors).toContain(
|
|
224
|
+
'"eventData.agreementStatus" is required',
|
|
225
|
+
);
|
|
226
|
+
expect(result.errors).toContain('"eventData.sbi" is required');
|
|
227
|
+
expect(result.errors).toContain('"eventData.options" is required');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("rejects Agreement created event if options contain invalid data", () => {
|
|
231
|
+
const result = validateReportingEvent({
|
|
232
|
+
...validBase,
|
|
233
|
+
eventData: {
|
|
234
|
+
eventType: AGREEMENT_CREATED,
|
|
235
|
+
agreementId: "WMP123456789",
|
|
236
|
+
agreementType: "WOODLAND",
|
|
237
|
+
agreementStatus: "ACCEPTED",
|
|
238
|
+
sbi: "200000001",
|
|
239
|
+
options: [
|
|
240
|
+
{
|
|
241
|
+
agreementId: "WMP123456789",
|
|
242
|
+
// missing mandatory option fields
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
expect(result.valid).toBe(false);
|
|
248
|
+
expect(result.errors).toContain(
|
|
249
|
+
'"eventData.options[0].parcelReference" is required',
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
describe("Agreement status changed events", () => {
|
|
255
|
+
test("accepts a valid Agreement status changed event with all fields", () => {
|
|
256
|
+
const result = validateReportingEvent({
|
|
257
|
+
...validBase,
|
|
258
|
+
eventData: {
|
|
259
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
260
|
+
agreementId: "WMP123456789",
|
|
261
|
+
agreementStatus: "ACCEPTED",
|
|
262
|
+
agreementStartDate: "2026-09-01",
|
|
263
|
+
agreementEndDate: "2029-08-31",
|
|
264
|
+
statusDate: "2026-09-02T10:00:00Z",
|
|
265
|
+
agreementValue: 1575.0,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
expect(result.valid).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("accepts a valid Agreement status changed event with all fields including userId", () => {
|
|
272
|
+
const result = validateReportingEvent({
|
|
273
|
+
...validBase,
|
|
274
|
+
eventData: {
|
|
275
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
276
|
+
agreementId: "WMP123456789",
|
|
277
|
+
agreementStatus: "ACCEPTED",
|
|
278
|
+
agreementStartDate: "2026-09-01",
|
|
279
|
+
agreementEndDate: "2029-08-31",
|
|
280
|
+
statusDate: "2026-09-02T10:00:00Z",
|
|
281
|
+
agreementValue: 1575.0,
|
|
282
|
+
userId: "user-123",
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
expect(result.valid).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("accepts a valid Agreement status changed event with only mandatory fields", () => {
|
|
289
|
+
const result = validateReportingEvent({
|
|
290
|
+
...validBase,
|
|
291
|
+
eventData: {
|
|
292
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
293
|
+
agreementId: "WMP123456789",
|
|
294
|
+
agreementStatus: "ACCEPTED",
|
|
295
|
+
statusDate: "2026-09-02T10:00:00Z",
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
expect(result.valid).toBe(true);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
test("rejects Agreement status changed event if required fields are missing", () => {
|
|
302
|
+
const result = validateReportingEvent({
|
|
303
|
+
...validBase,
|
|
304
|
+
eventData: {
|
|
305
|
+
eventType: AGREEMENT_STATUS_CHANGED,
|
|
306
|
+
agreementId: "WMP123456789",
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
expect(result.valid).toBe(false);
|
|
310
|
+
expect(result.errors).toContain(
|
|
311
|
+
'"eventData.agreementStatus" is required',
|
|
312
|
+
);
|
|
313
|
+
expect(result.errors).toContain('"eventData.statusDate" is required');
|
|
314
|
+
});
|
|
315
|
+
});
|
|
108
316
|
});
|