@open-mercato/webhooks 0.6.5-develop.4790.1.bffb4fd44b → 0.6.5-develop.4861.1.59f6de1891

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- [build:webhooks] found 66 entry points
1
+ [build:webhooks] found 67 entry points
2
2
  [build:webhooks] built successfully
@@ -0,0 +1,95 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { apiRequest, getAuthToken } from "@open-mercato/core/helpers/integration/api";
3
+ import { readJsonSafe } from "@open-mercato/core/helpers/integration/generalFixtures";
4
+ import {
5
+ runCrudFormRoundTrip,
6
+ skipIfCrudFormExtensionTestsDisabled
7
+ } from "@open-mercato/core/helpers/integration/crudFormPersistence";
8
+ const WEBHOOKS_PATH = "/api/webhooks";
9
+ async function readWebhookById(request, token, id) {
10
+ const response = await apiRequest(request, "GET", `${WEBHOOKS_PATH}/${encodeURIComponent(id)}`, { token });
11
+ if (response.status() === 404) return null;
12
+ expect(response.status(), `read-back webhook failed: ${response.status()}`).toBe(200);
13
+ const body = await readJsonSafe(response);
14
+ return body && body.id === id ? body : null;
15
+ }
16
+ test.describe("TC-WH-CRUDFORM-001: Webhook CrudForm persists scalars, events multiselect + headers JSON", () => {
17
+ test.beforeAll(() => {
18
+ skipIfCrudFormExtensionTestsDisabled();
19
+ });
20
+ test("round-trips scalars, subscribedEvents array, and customHeaders JSON on create and update", async ({ request }) => {
21
+ const token = await getAuthToken(request, "admin");
22
+ const stamp = Date.now();
23
+ const createEvents = ["catalog.product.created", "catalog.product.updated"];
24
+ const updateEvents = ["sales.quote.created", "catalog.product.deleted", "sales.order.placed"];
25
+ const createHeaders = { "X-Source": "om-qa", "X-Trace": `crudform-${stamp}` };
26
+ const updateHeaders = { "X-Env": "integration", "X-Rotated": "true" };
27
+ await runCrudFormRoundTrip({
28
+ request,
29
+ token,
30
+ collectionPath: WEBHOOKS_PATH,
31
+ recordPath: (id) => `${WEBHOOKS_PATH}/${encodeURIComponent(id)}`,
32
+ readById: (id) => readWebhookById(request, token, id),
33
+ create: {
34
+ payload: {
35
+ name: `QA CRUDFORM Webhook ${stamp}`,
36
+ description: "Original webhook description",
37
+ url: `https://example.com/hooks/crudform-${stamp}`,
38
+ subscribedEvents: createEvents,
39
+ httpMethod: "POST",
40
+ maxRetries: 3,
41
+ timeoutMs: 5e3,
42
+ rateLimitPerMinute: 0,
43
+ autoDisableThreshold: 5,
44
+ customHeaders: createHeaders
45
+ }
46
+ },
47
+ expectAfterCreate: {
48
+ scalars: {
49
+ name: `QA CRUDFORM Webhook ${stamp}`,
50
+ description: "Original webhook description",
51
+ url: `https://example.com/hooks/crudform-${stamp}`,
52
+ subscribedEvents: createEvents,
53
+ httpMethod: "POST",
54
+ isActive: true,
55
+ maxRetries: 3,
56
+ timeoutMs: 5e3,
57
+ rateLimitPerMinute: 0,
58
+ autoDisableThreshold: 5,
59
+ customHeaders: createHeaders
60
+ }
61
+ },
62
+ update: {
63
+ payload: () => ({
64
+ name: `QA CRUDFORM Webhook ${stamp} EDITED`,
65
+ description: "Updated webhook description",
66
+ url: `https://example.com/hooks/crudform-${stamp}-edited`,
67
+ subscribedEvents: updateEvents,
68
+ httpMethod: "PUT",
69
+ isActive: false,
70
+ maxRetries: 7,
71
+ timeoutMs: 2e4,
72
+ rateLimitPerMinute: 120,
73
+ autoDisableThreshold: 25,
74
+ customHeaders: updateHeaders
75
+ })
76
+ },
77
+ expectAfterUpdate: {
78
+ scalars: {
79
+ name: `QA CRUDFORM Webhook ${stamp} EDITED`,
80
+ description: "Updated webhook description",
81
+ url: `https://example.com/hooks/crudform-${stamp}-edited`,
82
+ subscribedEvents: updateEvents,
83
+ httpMethod: "PUT",
84
+ isActive: false,
85
+ maxRetries: 7,
86
+ timeoutMs: 2e4,
87
+ rateLimitPerMinute: 120,
88
+ autoDisableThreshold: 25,
89
+ customHeaders: updateHeaders
90
+ }
91
+ }
92
+ });
93
+ });
94
+ });
95
+ //# sourceMappingURL=TC-WH-CRUDFORM-001.spec.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/webhooks/__integration__/TC-WH-CRUDFORM-001.spec.ts"],
4
+ "sourcesContent": ["import { expect, test, type APIRequestContext } from '@playwright/test';\nimport { apiRequest, getAuthToken } from '@open-mercato/core/helpers/integration/api';\nimport { readJsonSafe } from '@open-mercato/core/helpers/integration/generalFixtures';\nimport {\n runCrudFormRoundTrip,\n skipIfCrudFormExtensionTestsDisabled,\n type CrudRecord,\n} from '@open-mercato/core/helpers/integration/crudFormPersistence';\n\n/**\n * TC-WH-CRUDFORM-001: Webhook CrudForm persists scalars, the events multiselect, and the\n * custom-headers JSON object (#2466, Tier B \u2014 hand-written / non-makeCrud saves).\n *\n * Webhooks is the first hand-written surface in the sweep. The backend CrudForm\n * (`backend/webhooks`) submits through the canonical, hand-written routes \u2014 `POST /api/webhooks`,\n * `PUT /api/webhooks/:id`, `DELETE /api/webhooks/:id` \u2014 NOT the makeCrud `/api/webhooks/webhooks`\n * alias, so this spec drives those exact endpoints via the harness `recordPath` seam to prove the\n * real form-submit path persists every field.\n *\n * Verified contract:\n * - Responses are camelCase (hand-written serializer), so scalar assertions use camelCase keys.\n * - `subscribedEvents` is a multiselect string[] (the form `tags` field); `customHeaders` is a JSON\n * object (the form `JsonBuilder`). Both round-trip through deep equality.\n * - `PUT /api/webhooks/:id` is a partial patch; the spec sends the full field set on update.\n * - Read-back uses the detail GET `/api/webhooks/:id` \u2014 the list GET omits `customHeaders` and has no\n * `id` filter. Cleanup soft-deletes via `DELETE /api/webhooks/:id` in the harness `finally`.\n * - The webhook entity has no custom fields, so the rich coverage here is the multiselect + JSON.\n * - Self-contained: a single webhook fixture, created and deleted within the round-trip.\n *\n * Gated by `OM_INTEGRATION_CRUDFORM_EXTENSION_TESTS_DISABLED` (default off \u2192 runs).\n */\nconst WEBHOOKS_PATH = '/api/webhooks';\n\nasync function readWebhookById(\n request: APIRequestContext,\n token: string,\n id: string,\n): Promise<CrudRecord | null> {\n const response = await apiRequest(request, 'GET', `${WEBHOOKS_PATH}/${encodeURIComponent(id)}`, { token });\n if (response.status() === 404) return null;\n expect(response.status(), `read-back webhook failed: ${response.status()}`).toBe(200);\n const body = await readJsonSafe<CrudRecord>(response);\n return body && body.id === id ? body : null;\n}\n\ntest.describe('TC-WH-CRUDFORM-001: Webhook CrudForm persists scalars, events multiselect + headers JSON', () => {\n test.beforeAll(() => {\n skipIfCrudFormExtensionTestsDisabled();\n });\n\n test('round-trips scalars, subscribedEvents array, and customHeaders JSON on create and update', async ({ request }) => {\n const token = await getAuthToken(request, 'admin');\n const stamp = Date.now();\n const createEvents = ['catalog.product.created', 'catalog.product.updated'];\n const updateEvents = ['sales.quote.created', 'catalog.product.deleted', 'sales.order.placed'];\n const createHeaders = { 'X-Source': 'om-qa', 'X-Trace': `crudform-${stamp}` };\n const updateHeaders = { 'X-Env': 'integration', 'X-Rotated': 'true' };\n\n await runCrudFormRoundTrip({\n request,\n token,\n collectionPath: WEBHOOKS_PATH,\n recordPath: (id) => `${WEBHOOKS_PATH}/${encodeURIComponent(id)}`,\n readById: (id) => readWebhookById(request, token, id),\n create: {\n payload: {\n name: `QA CRUDFORM Webhook ${stamp}`,\n description: 'Original webhook description',\n url: `https://example.com/hooks/crudform-${stamp}`,\n subscribedEvents: createEvents,\n httpMethod: 'POST',\n maxRetries: 3,\n timeoutMs: 5000,\n rateLimitPerMinute: 0,\n autoDisableThreshold: 5,\n customHeaders: createHeaders,\n },\n },\n expectAfterCreate: {\n scalars: {\n name: `QA CRUDFORM Webhook ${stamp}`,\n description: 'Original webhook description',\n url: `https://example.com/hooks/crudform-${stamp}`,\n subscribedEvents: createEvents,\n httpMethod: 'POST',\n isActive: true,\n maxRetries: 3,\n timeoutMs: 5000,\n rateLimitPerMinute: 0,\n autoDisableThreshold: 5,\n customHeaders: createHeaders,\n },\n },\n update: {\n payload: () => ({\n name: `QA CRUDFORM Webhook ${stamp} EDITED`,\n description: 'Updated webhook description',\n url: `https://example.com/hooks/crudform-${stamp}-edited`,\n subscribedEvents: updateEvents,\n httpMethod: 'PUT',\n isActive: false,\n maxRetries: 7,\n timeoutMs: 20000,\n rateLimitPerMinute: 120,\n autoDisableThreshold: 25,\n customHeaders: updateHeaders,\n }),\n },\n expectAfterUpdate: {\n scalars: {\n name: `QA CRUDFORM Webhook ${stamp} EDITED`,\n description: 'Updated webhook description',\n url: `https://example.com/hooks/crudform-${stamp}-edited`,\n subscribedEvents: updateEvents,\n httpMethod: 'PUT',\n isActive: false,\n maxRetries: 7,\n timeoutMs: 20000,\n rateLimitPerMinute: 120,\n autoDisableThreshold: 25,\n customHeaders: updateHeaders,\n },\n },\n });\n });\n});\n"],
5
+ "mappings": "AAAA,SAAS,QAAQ,YAAoC;AACrD,SAAS,YAAY,oBAAoB;AACzC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAwBP,MAAM,gBAAgB;AAEtB,eAAe,gBACb,SACA,OACA,IAC4B;AAC5B,QAAM,WAAW,MAAM,WAAW,SAAS,OAAO,GAAG,aAAa,IAAI,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;AACzG,MAAI,SAAS,OAAO,MAAM,IAAK,QAAO;AACtC,SAAO,SAAS,OAAO,GAAG,6BAA6B,SAAS,OAAO,CAAC,EAAE,EAAE,KAAK,GAAG;AACpF,QAAM,OAAO,MAAM,aAAyB,QAAQ;AACpD,SAAO,QAAQ,KAAK,OAAO,KAAK,OAAO;AACzC;AAEA,KAAK,SAAS,4FAA4F,MAAM;AAC9G,OAAK,UAAU,MAAM;AACnB,yCAAqC;AAAA,EACvC,CAAC;AAED,OAAK,4FAA4F,OAAO,EAAE,QAAQ,MAAM;AACtH,UAAM,QAAQ,MAAM,aAAa,SAAS,OAAO;AACjD,UAAM,QAAQ,KAAK,IAAI;AACvB,UAAM,eAAe,CAAC,2BAA2B,yBAAyB;AAC1E,UAAM,eAAe,CAAC,uBAAuB,2BAA2B,oBAAoB;AAC5F,UAAM,gBAAgB,EAAE,YAAY,SAAS,WAAW,YAAY,KAAK,GAAG;AAC5E,UAAM,gBAAgB,EAAE,SAAS,eAAe,aAAa,OAAO;AAEpE,UAAM,qBAAqB;AAAA,MACzB;AAAA,MACA;AAAA,MACA,gBAAgB;AAAA,MAChB,YAAY,CAAC,OAAO,GAAG,aAAa,IAAI,mBAAmB,EAAE,CAAC;AAAA,MAC9D,UAAU,CAAC,OAAO,gBAAgB,SAAS,OAAO,EAAE;AAAA,MACpD,QAAQ;AAAA,QACN,SAAS;AAAA,UACP,MAAM,uBAAuB,KAAK;AAAA,UAClC,aAAa;AAAA,UACb,KAAK,sCAAsC,KAAK;AAAA,UAChD,kBAAkB;AAAA,UAClB,YAAY;AAAA,UACZ,YAAY;AAAA,UACZ,WAAW;AAAA,UACX,oBAAoB;AAAA,UACpB,sBAAsB;AAAA,UACtB,eAAe;AAAA,QACjB;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,SAAS;AAAA,UACP,MAAM,uBAAuB,KAAK;AAAA,UAClC,aAAa;AAAA,UACb,KAAK,sCAAsC,KAAK;AAAA,UAChD,kBAAkB;AAAA,UAClB,YAAY;AAAA,UACZ,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,WAAW;AAAA,UACX,oBAAoB;AAAA,UACpB,sBAAsB;AAAA,UACtB,eAAe;AAAA,QACjB;AAAA,MACF;AAAA,MACA,QAAQ;AAAA,QACN,SAAS,OAAO;AAAA,UACd,MAAM,uBAAuB,KAAK;AAAA,UAClC,aAAa;AAAA,UACb,KAAK,sCAAsC,KAAK;AAAA,UAChD,kBAAkB;AAAA,UAClB,YAAY;AAAA,UACZ,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,WAAW;AAAA,UACX,oBAAoB;AAAA,UACpB,sBAAsB;AAAA,UACtB,eAAe;AAAA,QACjB;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB,SAAS;AAAA,UACP,MAAM,uBAAuB,KAAK;AAAA,UAClC,aAAa;AAAA,UACb,KAAK,sCAAsC,KAAK;AAAA,UAChD,kBAAkB;AAAA,UAClB,YAAY;AAAA,UACZ,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,WAAW;AAAA,UACX,oBAAoB;AAAA,UACpB,sBAAsB;AAAA,UACtB,eAAe;AAAA,QACjB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACH,CAAC;",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/webhooks",
3
- "version": "0.6.5-develop.4790.1.bffb4fd44b",
3
+ "version": "0.6.5-develop.4861.1.59f6de1891",
4
4
  "description": "Webhooks module for Open Mercato — Standard Webhooks compliant outbound/inbound delivery",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -69,19 +69,19 @@
69
69
  }
70
70
  },
71
71
  "dependencies": {
72
- "@open-mercato/core": "0.6.5-develop.4790.1.bffb4fd44b",
73
- "@open-mercato/queue": "0.6.5-develop.4790.1.bffb4fd44b",
74
- "@open-mercato/ui": "0.6.5-develop.4790.1.bffb4fd44b",
72
+ "@open-mercato/core": "0.6.5-develop.4861.1.59f6de1891",
73
+ "@open-mercato/queue": "0.6.5-develop.4861.1.59f6de1891",
74
+ "@open-mercato/ui": "0.6.5-develop.4861.1.59f6de1891",
75
75
  "svix": "^1.95.1"
76
76
  },
77
77
  "peerDependencies": {
78
78
  "@mikro-orm/postgresql": "^7.0.14",
79
- "@open-mercato/shared": "0.6.5-develop.4790.1.bffb4fd44b",
79
+ "@open-mercato/shared": "0.6.5-develop.4861.1.59f6de1891",
80
80
  "react": "^19.0.0",
81
81
  "react-dom": "^19.0.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@open-mercato/shared": "0.6.5-develop.4790.1.bffb4fd44b",
84
+ "@open-mercato/shared": "0.6.5-develop.4861.1.59f6de1891",
85
85
  "@types/jest": "^30.0.0",
86
86
  "@types/react": "^19.2.16",
87
87
  "@types/react-dom": "^19.2.3",
@@ -0,0 +1,126 @@
1
+ import { expect, test, type APIRequestContext } from '@playwright/test';
2
+ import { apiRequest, getAuthToken } from '@open-mercato/core/helpers/integration/api';
3
+ import { readJsonSafe } from '@open-mercato/core/helpers/integration/generalFixtures';
4
+ import {
5
+ runCrudFormRoundTrip,
6
+ skipIfCrudFormExtensionTestsDisabled,
7
+ type CrudRecord,
8
+ } from '@open-mercato/core/helpers/integration/crudFormPersistence';
9
+
10
+ /**
11
+ * TC-WH-CRUDFORM-001: Webhook CrudForm persists scalars, the events multiselect, and the
12
+ * custom-headers JSON object (#2466, Tier B — hand-written / non-makeCrud saves).
13
+ *
14
+ * Webhooks is the first hand-written surface in the sweep. The backend CrudForm
15
+ * (`backend/webhooks`) submits through the canonical, hand-written routes — `POST /api/webhooks`,
16
+ * `PUT /api/webhooks/:id`, `DELETE /api/webhooks/:id` — NOT the makeCrud `/api/webhooks/webhooks`
17
+ * alias, so this spec drives those exact endpoints via the harness `recordPath` seam to prove the
18
+ * real form-submit path persists every field.
19
+ *
20
+ * Verified contract:
21
+ * - Responses are camelCase (hand-written serializer), so scalar assertions use camelCase keys.
22
+ * - `subscribedEvents` is a multiselect string[] (the form `tags` field); `customHeaders` is a JSON
23
+ * object (the form `JsonBuilder`). Both round-trip through deep equality.
24
+ * - `PUT /api/webhooks/:id` is a partial patch; the spec sends the full field set on update.
25
+ * - Read-back uses the detail GET `/api/webhooks/:id` — the list GET omits `customHeaders` and has no
26
+ * `id` filter. Cleanup soft-deletes via `DELETE /api/webhooks/:id` in the harness `finally`.
27
+ * - The webhook entity has no custom fields, so the rich coverage here is the multiselect + JSON.
28
+ * - Self-contained: a single webhook fixture, created and deleted within the round-trip.
29
+ *
30
+ * Gated by `OM_INTEGRATION_CRUDFORM_EXTENSION_TESTS_DISABLED` (default off → runs).
31
+ */
32
+ const WEBHOOKS_PATH = '/api/webhooks';
33
+
34
+ async function readWebhookById(
35
+ request: APIRequestContext,
36
+ token: string,
37
+ id: string,
38
+ ): Promise<CrudRecord | null> {
39
+ const response = await apiRequest(request, 'GET', `${WEBHOOKS_PATH}/${encodeURIComponent(id)}`, { token });
40
+ if (response.status() === 404) return null;
41
+ expect(response.status(), `read-back webhook failed: ${response.status()}`).toBe(200);
42
+ const body = await readJsonSafe<CrudRecord>(response);
43
+ return body && body.id === id ? body : null;
44
+ }
45
+
46
+ test.describe('TC-WH-CRUDFORM-001: Webhook CrudForm persists scalars, events multiselect + headers JSON', () => {
47
+ test.beforeAll(() => {
48
+ skipIfCrudFormExtensionTestsDisabled();
49
+ });
50
+
51
+ test('round-trips scalars, subscribedEvents array, and customHeaders JSON on create and update', async ({ request }) => {
52
+ const token = await getAuthToken(request, 'admin');
53
+ const stamp = Date.now();
54
+ const createEvents = ['catalog.product.created', 'catalog.product.updated'];
55
+ const updateEvents = ['sales.quote.created', 'catalog.product.deleted', 'sales.order.placed'];
56
+ const createHeaders = { 'X-Source': 'om-qa', 'X-Trace': `crudform-${stamp}` };
57
+ const updateHeaders = { 'X-Env': 'integration', 'X-Rotated': 'true' };
58
+
59
+ await runCrudFormRoundTrip({
60
+ request,
61
+ token,
62
+ collectionPath: WEBHOOKS_PATH,
63
+ recordPath: (id) => `${WEBHOOKS_PATH}/${encodeURIComponent(id)}`,
64
+ readById: (id) => readWebhookById(request, token, id),
65
+ create: {
66
+ payload: {
67
+ name: `QA CRUDFORM Webhook ${stamp}`,
68
+ description: 'Original webhook description',
69
+ url: `https://example.com/hooks/crudform-${stamp}`,
70
+ subscribedEvents: createEvents,
71
+ httpMethod: 'POST',
72
+ maxRetries: 3,
73
+ timeoutMs: 5000,
74
+ rateLimitPerMinute: 0,
75
+ autoDisableThreshold: 5,
76
+ customHeaders: createHeaders,
77
+ },
78
+ },
79
+ expectAfterCreate: {
80
+ scalars: {
81
+ name: `QA CRUDFORM Webhook ${stamp}`,
82
+ description: 'Original webhook description',
83
+ url: `https://example.com/hooks/crudform-${stamp}`,
84
+ subscribedEvents: createEvents,
85
+ httpMethod: 'POST',
86
+ isActive: true,
87
+ maxRetries: 3,
88
+ timeoutMs: 5000,
89
+ rateLimitPerMinute: 0,
90
+ autoDisableThreshold: 5,
91
+ customHeaders: createHeaders,
92
+ },
93
+ },
94
+ update: {
95
+ payload: () => ({
96
+ name: `QA CRUDFORM Webhook ${stamp} EDITED`,
97
+ description: 'Updated webhook description',
98
+ url: `https://example.com/hooks/crudform-${stamp}-edited`,
99
+ subscribedEvents: updateEvents,
100
+ httpMethod: 'PUT',
101
+ isActive: false,
102
+ maxRetries: 7,
103
+ timeoutMs: 20000,
104
+ rateLimitPerMinute: 120,
105
+ autoDisableThreshold: 25,
106
+ customHeaders: updateHeaders,
107
+ }),
108
+ },
109
+ expectAfterUpdate: {
110
+ scalars: {
111
+ name: `QA CRUDFORM Webhook ${stamp} EDITED`,
112
+ description: 'Updated webhook description',
113
+ url: `https://example.com/hooks/crudform-${stamp}-edited`,
114
+ subscribedEvents: updateEvents,
115
+ httpMethod: 'PUT',
116
+ isActive: false,
117
+ maxRetries: 7,
118
+ timeoutMs: 20000,
119
+ rateLimitPerMinute: 120,
120
+ autoDisableThreshold: 25,
121
+ customHeaders: updateHeaders,
122
+ },
123
+ },
124
+ });
125
+ });
126
+ });