@defra/grants-reporting-publisher 0.1.0 → 0.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @defra/grants-reporting-publisher
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 62e9659: Make some option fields optional
8
+
9
+ ## 0.1.1
10
+
11
+ ### Patch Changes
12
+
13
+ - f8be355: Update readme with examples
14
+
3
15
  ## 0.1.0
4
16
 
5
17
  ### Minor 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
@@ -4,6 +4,8 @@ NPM module for providing validation and publishing capability for grants reporti
4
4
 
5
5
  ## Usage
6
6
 
7
+ Refer to the [Usage Examples](EXAMPLES.md) for detailed examples of how to publish reporting events.
8
+
7
9
  ### Installation
8
10
 
9
11
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/grants-reporting-publisher",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "NPM module for publishing grants reporting events",
5
5
  "engines": {
6
6
  "node": ">=24",
@@ -1,5 +1,5 @@
1
1
  import { publishReportingEvent } from "./publish.js";
2
- import { AGREEMENT_STATUS_CHANGED } from "./constants.js";
2
+ import { AGREEMENT_CREATED, AGREEMENT_STATUS_CHANGED } from "./constants.js";
3
3
 
4
4
  const mockSend = vi.hoisted(() =>
5
5
  vi.fn().mockResolvedValue({ MessageId: "msg-001" }),
@@ -162,6 +162,38 @@ describe("publishAuditEvent", () => {
162
162
  await publishReportingEvent(validEvent, baseConfig);
163
163
  expect(mockSend).toHaveBeenCalledTimes(1);
164
164
  });
165
+
166
+ test("publishes AGREEMENT_CREATED event with minimal options", async () => {
167
+ const agreementCreatedEvent = {
168
+ correlationId: "abc-123",
169
+ datetime: "2025-12-01T12:51:41.381Z",
170
+ version: "1.0.0",
171
+ application: "FCP001",
172
+ service: "grants",
173
+ eventData: {
174
+ eventType: AGREEMENT_CREATED,
175
+ agreementId: "WMP123456789",
176
+ agreementType: "WOODLAND",
177
+ agreementStatus: "ACCEPTED",
178
+ sbi: "200000001",
179
+ options: [
180
+ {
181
+ parcelReference: "SD8545-9935",
182
+ optionCode: "WMP1",
183
+ optionQuantity: 15.75,
184
+ optionValue: 1575.0,
185
+ },
186
+ ],
187
+ },
188
+ };
189
+
190
+ const result = await publishReportingEvent(
191
+ agreementCreatedEvent,
192
+ baseConfig,
193
+ );
194
+ expect(result).toEqual({ messageId: "msg-001" });
195
+ expect(mockSend).toHaveBeenCalledTimes(1);
196
+ });
165
197
  });
166
198
 
167
199
  describe("event validation", () => {
package/src/schema.js CHANGED
@@ -35,8 +35,8 @@ export const eventSchema = Joi.object({
35
35
  parcelSizeUnderAgreement: Joi.number().optional(),
36
36
  optionCode: Joi.string().required(),
37
37
  optionYear: Joi.number().integer().optional(),
38
- optionStartDate: Joi.date().iso().required(),
39
- optionEndDate: Joi.date().iso().required(),
38
+ optionStartDate: Joi.date().iso().optional(),
39
+ optionEndDate: Joi.date().iso().optional(),
40
40
  optionQuantity: Joi.number().required(),
41
41
  optionValue: Joi.number().required(),
42
42
  }),
@@ -187,6 +187,28 @@ describe("validateReportingEvent", () => {
187
187
  });
188
188
 
189
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
+ optionQuantity: 15.75,
203
+ optionValue: 1575.0,
204
+ },
205
+ ],
206
+ },
207
+ });
208
+ expect(result.valid).toBe(true);
209
+ });
210
+
211
+ test("accepts Agreement created event with option containing optionStartDate only", () => {
190
212
  const result = validateReportingEvent({
191
213
  ...validBase,
192
214
  eventData: {
@@ -200,6 +222,28 @@ describe("validateReportingEvent", () => {
200
222
  parcelReference: "SD8545-9935",
201
223
  optionCode: "WMP1",
202
224
  optionStartDate: "2026-09-01",
225
+ optionQuantity: 15.75,
226
+ optionValue: 1575.0,
227
+ },
228
+ ],
229
+ },
230
+ });
231
+ expect(result.valid).toBe(true);
232
+ });
233
+
234
+ test("accepts Agreement created event with option containing optionEndDate only", () => {
235
+ const result = validateReportingEvent({
236
+ ...validBase,
237
+ eventData: {
238
+ eventType: AGREEMENT_CREATED,
239
+ agreementId: "WMP123456789",
240
+ agreementType: "WOODLAND",
241
+ agreementStatus: "ACCEPTED",
242
+ sbi: "200000001",
243
+ options: [
244
+ {
245
+ parcelReference: "SD8545-9935",
246
+ optionCode: "WMP1",
203
247
  optionEndDate: "2029-08-31",
204
248
  optionQuantity: 15.75,
205
249
  optionValue: 1575.0,
@@ -210,6 +254,58 @@ describe("validateReportingEvent", () => {
210
254
  expect(result.valid).toBe(true);
211
255
  });
212
256
 
257
+ test("rejects Agreement created event if optionStartDate is not a valid date", () => {
258
+ const result = validateReportingEvent({
259
+ ...validBase,
260
+ eventData: {
261
+ eventType: AGREEMENT_CREATED,
262
+ agreementId: "WMP123456789",
263
+ agreementType: "WOODLAND",
264
+ agreementStatus: "ACCEPTED",
265
+ sbi: "200000001",
266
+ options: [
267
+ {
268
+ parcelReference: "SD8545-9935",
269
+ optionCode: "WMP1",
270
+ optionStartDate: "invalid-date",
271
+ optionQuantity: 15.75,
272
+ optionValue: 1575.0,
273
+ },
274
+ ],
275
+ },
276
+ });
277
+ expect(result.valid).toBe(false);
278
+ expect(result.errors).toContain(
279
+ '"eventData.options[0].optionStartDate" must be in ISO 8601 date format',
280
+ );
281
+ });
282
+
283
+ test("rejects Agreement created event if optionEndDate is not a valid date", () => {
284
+ const result = validateReportingEvent({
285
+ ...validBase,
286
+ eventData: {
287
+ eventType: AGREEMENT_CREATED,
288
+ agreementId: "WMP123456789",
289
+ agreementType: "WOODLAND",
290
+ agreementStatus: "ACCEPTED",
291
+ sbi: "200000001",
292
+ options: [
293
+ {
294
+ parcelReference: "SD8545-9935",
295
+ optionCode: "WMP1",
296
+ optionEndDate: "invalid-date",
297
+ optionQuantity: 15.75,
298
+ optionValue: 1575.0,
299
+ },
300
+ ],
301
+ },
302
+ });
303
+ expect(result.valid).toBe(false);
304
+ expect(result.errors).toContain(
305
+ '"eventData.options[0].optionEndDate" must be in ISO 8601 date format',
306
+ );
307
+ });
308
+
213
309
  test("rejects Agreement created event if required fields are missing", () => {
214
310
  const result = validateReportingEvent({
215
311
  ...validBase,