@dev.smartpricing/message-composer-utils 4.3.2-fix.1 → 4.4.1-next.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.
- package/dist/schemas/dynamic-values.js +4 -1
- package/dist/schemas/dynamic-values.js.map +1 -1
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/index.js.map +1 -1
- package/dist/schemas/message-group.js +27 -6
- package/dist/schemas/message-group.js.map +1 -1
- package/dist/schemas/onboarding.js +3 -1
- package/dist/schemas/onboarding.js.map +1 -1
- package/dist/schemas/pagination.js +40 -0
- package/dist/schemas/pagination.js.map +1 -0
- package/dist/schemas/pagination.test.js +82 -0
- package/dist/schemas/pagination.test.js.map +1 -0
- package/dist/types/app.js.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/message.js +22 -0
- package/dist/types/message.js.map +1 -1
- package/dist/types/pagination.js +2 -0
- package/dist/types/pagination.js.map +1 -0
- package/dist/utils/templates/whatsapp-compiler.test.js +241 -0
- package/dist/utils/templates/whatsapp-compiler.test.js.map +1 -0
- package/package.json +1 -1
- package/src/schemas/dynamic-values.ts +7 -2
- package/src/schemas/index.ts +1 -0
- package/src/schemas/message-group.ts +36 -6
- package/src/schemas/onboarding.ts +3 -0
- package/src/schemas/pagination.test.ts +101 -0
- package/src/schemas/pagination.ts +49 -0
- package/src/types/api/apiMessages.ts +3 -0
- package/src/types/app.ts +7 -2
- package/src/types/index.ts +1 -0
- package/src/types/kafka-events.ts +7 -1
- package/src/types/message.ts +84 -0
- package/src/types/pagination.ts +36 -0
- package/src/utils/templates/whatsapp-compiler.test.ts +282 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import type { JSONContent } from "@tiptap/core";
|
|
3
|
+
import type { WhatsappMessage } from "../../types/index.js";
|
|
4
|
+
import {
|
|
5
|
+
compileWhatsappTemplate,
|
|
6
|
+
extractRequiredParameters,
|
|
7
|
+
validateParameters,
|
|
8
|
+
} from "./whatsapp-compiler.js";
|
|
9
|
+
import {
|
|
10
|
+
mergeBrandDataParams,
|
|
11
|
+
type BrandCascadeData,
|
|
12
|
+
} from "./brand-cascade.js";
|
|
13
|
+
|
|
14
|
+
const mention = (label: string): JSONContent => ({
|
|
15
|
+
type: "mention",
|
|
16
|
+
attrs: { id: label, label },
|
|
17
|
+
});
|
|
18
|
+
const text = (value: string): JSONContent => ({ type: "text", text: value });
|
|
19
|
+
const doc = (...content: JSONContent[]): JSONContent => ({
|
|
20
|
+
type: "doc",
|
|
21
|
+
content: [{ type: "paragraph", content }],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
/** An approved, submitted WhatsApp Message with every block empty by default. */
|
|
25
|
+
function whatsappMessage(
|
|
26
|
+
body: Partial<WhatsappMessage["body"]> = {},
|
|
27
|
+
): WhatsappMessage {
|
|
28
|
+
return {
|
|
29
|
+
id: "msg-1",
|
|
30
|
+
created_at: new Date("2026-09-01T00:00:00.000Z"),
|
|
31
|
+
updated_at: new Date("2026-09-01T00:00:00.000Z"),
|
|
32
|
+
message_group_id: "group-1",
|
|
33
|
+
language_id: "en",
|
|
34
|
+
status: "approved",
|
|
35
|
+
render_type: "whatsapp_v1",
|
|
36
|
+
user_id: "user-1",
|
|
37
|
+
body: {
|
|
38
|
+
header: { type: "none" },
|
|
39
|
+
body: { enabled: true, content: undefined },
|
|
40
|
+
footer: { enabled: false, content: "" },
|
|
41
|
+
buttons: { enabled: false, buttons: [] },
|
|
42
|
+
...body,
|
|
43
|
+
},
|
|
44
|
+
metadata: {
|
|
45
|
+
meta: {
|
|
46
|
+
template_id: "1234567890",
|
|
47
|
+
template_name: "welcome_en",
|
|
48
|
+
submission_date: "2026-09-01T00:00:00.000Z",
|
|
49
|
+
translated_template: {
|
|
50
|
+
name: "welcome_en",
|
|
51
|
+
language: "en",
|
|
52
|
+
category: "UTILITY",
|
|
53
|
+
parameter_format: "NAMED",
|
|
54
|
+
allow_category_change: false,
|
|
55
|
+
components: [],
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("extractRequiredParameters", () => {
|
|
63
|
+
it("lists header, body and URL button keys once each, in template order", () => {
|
|
64
|
+
const message = whatsappMessage({
|
|
65
|
+
header: {
|
|
66
|
+
type: "text",
|
|
67
|
+
content: doc(text("Hi "), mention("guest_name")),
|
|
68
|
+
},
|
|
69
|
+
body: {
|
|
70
|
+
enabled: true,
|
|
71
|
+
content: doc(
|
|
72
|
+
mention("guest_name"),
|
|
73
|
+
text(" your stay at "),
|
|
74
|
+
mention("brand_name"),
|
|
75
|
+
text(" starts "),
|
|
76
|
+
mention("check_in"),
|
|
77
|
+
),
|
|
78
|
+
},
|
|
79
|
+
buttons: {
|
|
80
|
+
enabled: true,
|
|
81
|
+
buttons: [
|
|
82
|
+
{
|
|
83
|
+
id: "b1",
|
|
84
|
+
type: "link",
|
|
85
|
+
text: "Open",
|
|
86
|
+
url: "https://example.com/{{booking_id}}?guest={{guest_name}}",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(extractRequiredParameters(message)).toEqual([
|
|
93
|
+
"guest_name",
|
|
94
|
+
"brand_name",
|
|
95
|
+
"check_in",
|
|
96
|
+
"booking_id",
|
|
97
|
+
]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns no keys for a template without variables", () => {
|
|
101
|
+
const message = whatsappMessage({
|
|
102
|
+
body: { enabled: true, content: doc(text("Plain text")) },
|
|
103
|
+
});
|
|
104
|
+
expect(extractRequiredParameters(message)).toEqual([]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("ignores URL buttons when the button block is disabled", () => {
|
|
108
|
+
const message = whatsappMessage({
|
|
109
|
+
buttons: {
|
|
110
|
+
enabled: false,
|
|
111
|
+
buttons: [
|
|
112
|
+
{ id: "b1", type: "link", text: "Open", url: "https://x/{{id}}" },
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
expect(extractRequiredParameters(message)).toEqual([]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("validateParameters with brand_* keys", () => {
|
|
121
|
+
const message = whatsappMessage({
|
|
122
|
+
body: {
|
|
123
|
+
enabled: true,
|
|
124
|
+
content: doc(mention("guest_name"), text(" @ "), mention("brand_name")),
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
const brand: BrandCascadeData = {
|
|
128
|
+
default_email_settings: {},
|
|
129
|
+
default_button_settings: {},
|
|
130
|
+
default_socials: null,
|
|
131
|
+
logo_url: null,
|
|
132
|
+
data: { name: "Hotel Sole" },
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
it("reports brand_name missing when validated on the raw parameters", () => {
|
|
136
|
+
expect(validateParameters(message, { guest_name: "Mario" })).toEqual({
|
|
137
|
+
valid: false,
|
|
138
|
+
required: ["guest_name", "brand_name"],
|
|
139
|
+
missing: ["brand_name"],
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("counts brand_name as present once the Brand data is merged in", () => {
|
|
144
|
+
const merged = mergeBrandDataParams({ guest_name: "Mario" }, brand);
|
|
145
|
+
expect(validateParameters(message, merged)).toEqual({
|
|
146
|
+
valid: true,
|
|
147
|
+
required: ["guest_name", "brand_name"],
|
|
148
|
+
missing: [],
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("still reports a brand_* key the Brand cannot supply", () => {
|
|
153
|
+
const withSlogan = whatsappMessage({
|
|
154
|
+
body: { enabled: true, content: doc(mention("brand_slogan")) },
|
|
155
|
+
});
|
|
156
|
+
const merged = mergeBrandDataParams({}, brand);
|
|
157
|
+
expect(validateParameters(withSlogan, merged).missing).toEqual([
|
|
158
|
+
"brand_slogan",
|
|
159
|
+
]);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe("compileWhatsappTemplate", () => {
|
|
164
|
+
it("builds the template envelope from the Meta metadata and language", () => {
|
|
165
|
+
const payload = compileWhatsappTemplate(whatsappMessage(), {});
|
|
166
|
+
expect(payload).toEqual({
|
|
167
|
+
messaging_product: "whatsapp",
|
|
168
|
+
type: "template",
|
|
169
|
+
template: {
|
|
170
|
+
name: "welcome_en",
|
|
171
|
+
language: { code: "en" },
|
|
172
|
+
components: undefined,
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("throws when the Message has no Meta template name", () => {
|
|
178
|
+
const message = whatsappMessage();
|
|
179
|
+
message.metadata = {};
|
|
180
|
+
expect(() => compileWhatsappTemplate(message, {})).toThrow(
|
|
181
|
+
"Message does not have Meta template name in metadata",
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("emits a HEADER component with the header text variable", () => {
|
|
186
|
+
const message = whatsappMessage({
|
|
187
|
+
header: {
|
|
188
|
+
type: "text",
|
|
189
|
+
content: doc(text("Hi "), mention("guest_name")),
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
const payload = compileWhatsappTemplate(message, { guest_name: "Mario" });
|
|
193
|
+
expect(payload.template.components).toEqual([
|
|
194
|
+
{
|
|
195
|
+
type: "HEADER",
|
|
196
|
+
parameters: [
|
|
197
|
+
{ type: "text", text: "Mario", parameter_name: "guest_name" },
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("emits BODY named parameters in order, coercing values to strings", () => {
|
|
204
|
+
const message = whatsappMessage({
|
|
205
|
+
body: {
|
|
206
|
+
enabled: true,
|
|
207
|
+
content: doc(
|
|
208
|
+
mention("guest_name"),
|
|
209
|
+
text(", "),
|
|
210
|
+
mention("nights"),
|
|
211
|
+
text(" nights, "),
|
|
212
|
+
mention("missing_key"),
|
|
213
|
+
),
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
const payload = compileWhatsappTemplate(message, {
|
|
217
|
+
guest_name: "Mario",
|
|
218
|
+
nights: 3,
|
|
219
|
+
});
|
|
220
|
+
expect(payload.template.components).toEqual([
|
|
221
|
+
{
|
|
222
|
+
type: "BODY",
|
|
223
|
+
parameters: [
|
|
224
|
+
{ type: "text", text: "Mario", parameter_name: "guest_name" },
|
|
225
|
+
{ type: "text", text: "3", parameter_name: "nights" },
|
|
226
|
+
{ type: "text", text: "", parameter_name: "missing_key" },
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("emits a media HEADER by smartchat media id", () => {
|
|
233
|
+
const message = whatsappMessage({
|
|
234
|
+
header: {
|
|
235
|
+
type: "image",
|
|
236
|
+
url: "https://cdn.example.com/hero.png",
|
|
237
|
+
smartchatMediaId: "media-42",
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
const payload = compileWhatsappTemplate(message, {});
|
|
241
|
+
expect(payload.template.components).toEqual([
|
|
242
|
+
{
|
|
243
|
+
type: "HEADER",
|
|
244
|
+
parameters: [{ type: "image", image: { id: "media-42" } }],
|
|
245
|
+
},
|
|
246
|
+
]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("omits the media HEADER when the media id is not resolved", () => {
|
|
250
|
+
const message = whatsappMessage({
|
|
251
|
+
header: { type: "image", url: "https://cdn.example.com/hero.png" },
|
|
252
|
+
});
|
|
253
|
+
const payload = compileWhatsappTemplate(message, {});
|
|
254
|
+
expect(payload.template.components).toBeUndefined();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("emits a URL BUTTON component named by button position, as submitted to Meta", () => {
|
|
258
|
+
const message = whatsappMessage({
|
|
259
|
+
buttons: {
|
|
260
|
+
enabled: true,
|
|
261
|
+
buttons: [
|
|
262
|
+
{ id: "b0", type: "quick_reply", text: "Yes" },
|
|
263
|
+
{
|
|
264
|
+
id: "b1",
|
|
265
|
+
type: "link",
|
|
266
|
+
text: "Open",
|
|
267
|
+
url: "https://example.com/{{booking_id}}",
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
const payload = compileWhatsappTemplate(message, { booking_id: "abc" });
|
|
273
|
+
expect(payload.template.components).toEqual([
|
|
274
|
+
{
|
|
275
|
+
type: "BUTTON",
|
|
276
|
+
sub_type: "url",
|
|
277
|
+
index: "1",
|
|
278
|
+
parameters: [{ type: "text", text: "abc", parameter_name: "{{2}}" }],
|
|
279
|
+
},
|
|
280
|
+
]);
|
|
281
|
+
});
|
|
282
|
+
});
|