@mahe_pkm/buzl-capi 0.1.2 → 0.1.4
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/Buzl_GoogleAppsScript_Template.gs +948 -948
- package/CHANGELOG.md +118 -83
- package/LICENSE +21 -21
- package/README.md +339 -253
- package/USER_HANDBOOK.html +808 -0
- package/USER_HANDBOOK.md +842 -0
- package/assets/js/buzl-tracking.js +390 -390
- package/bin/cli.js +175 -155
- package/package.json +3 -1
- package/src/cli/terminal.js +389 -388
- package/src/core/injector.js +321 -321
- package/src/core/rollback.js +281 -281
- package/src/core/scanner.js +506 -506
- package/src/core/tester.js +666 -357
- package/src/gui/public/app.js +915 -87
- package/src/gui/public/index.html +620 -230
- package/src/gui/public/style.css +1603 -509
- package/src/gui/server.js +242 -232
- package/src/index.js +47 -47
- package/src/templates/GoogleAppsScript.gs +948 -948
- package/src/templates/buzl-tracking.js +475 -475
- package/src/templates/gtm-meta-snippets.js +149 -149
package/src/core/tester.js
CHANGED
|
@@ -1,357 +1,666 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Automated Verification & Self-Testing Engine
|
|
3
|
-
* Validates HTML tags, form hooks, script existence, and endpoint health
|
|
4
|
-
*/
|
|
5
|
-
const fs = require('fs');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const https = require('https');
|
|
8
|
-
const http = require('http');
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Ping an HTTP/HTTPS URL
|
|
12
|
-
*/
|
|
13
|
-
function pingUrl(urlStr) {
|
|
14
|
-
return new Promise((resolve) => {
|
|
15
|
-
try {
|
|
16
|
-
const parsed = new URL(urlStr);
|
|
17
|
-
const client = parsed.protocol === 'https:' ? https : http;
|
|
18
|
-
|
|
19
|
-
const req = client.get(urlStr, { timeout: 4000 }, (res) => {
|
|
20
|
-
// If Google Apps Script redirects to Google Accounts login, access is restricted
|
|
21
|
-
if (res.headers && res.headers.location && res.headers.location.includes('accounts.google.com')) {
|
|
22
|
-
resolve({ ok: false, statusCode: 401, error: 'Authentication required. In Google Apps Script, set "Who has access: Anyone".' });
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
// Google Apps Script redirects 302 to script.googleusercontent.com
|
|
26
|
-
if (res.statusCode >= 200 && res.statusCode < 400) {
|
|
27
|
-
resolve({ ok: true, statusCode: res.statusCode });
|
|
28
|
-
} else {
|
|
29
|
-
resolve({ ok: false, statusCode: res.statusCode });
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
req.on('timeout', () => {
|
|
34
|
-
req.destroy();
|
|
35
|
-
resolve({ ok: false, error: 'Request timed out after 4000ms' });
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
req.on('error', (err) => {
|
|
39
|
-
resolve({ ok: false, error: err.message });
|
|
40
|
-
});
|
|
41
|
-
} catch (e) {
|
|
42
|
-
resolve({ ok: false, error: 'Invalid URL format' });
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
*
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Automated Verification & Self-Testing Engine
|
|
3
|
+
* Validates HTML tags, form hooks, script existence, and endpoint health
|
|
4
|
+
*/
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const http = require('http');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Ping an HTTP/HTTPS URL
|
|
12
|
+
*/
|
|
13
|
+
function pingUrl(urlStr) {
|
|
14
|
+
return new Promise((resolve) => {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = new URL(urlStr);
|
|
17
|
+
const client = parsed.protocol === 'https:' ? https : http;
|
|
18
|
+
|
|
19
|
+
const req = client.get(urlStr, { timeout: 4000 }, (res) => {
|
|
20
|
+
// If Google Apps Script redirects to Google Accounts login, access is restricted
|
|
21
|
+
if (res.headers && res.headers.location && res.headers.location.includes('accounts.google.com')) {
|
|
22
|
+
resolve({ ok: false, statusCode: 401, error: 'Authentication required. In Google Apps Script, set "Who has access: Anyone".' });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Google Apps Script redirects 302 to script.googleusercontent.com
|
|
26
|
+
if (res.statusCode >= 200 && res.statusCode < 400) {
|
|
27
|
+
resolve({ ok: true, statusCode: res.statusCode });
|
|
28
|
+
} else {
|
|
29
|
+
resolve({ ok: false, statusCode: res.statusCode });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
req.on('timeout', () => {
|
|
34
|
+
req.destroy();
|
|
35
|
+
resolve({ ok: false, error: 'Request timed out after 4000ms' });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
req.on('error', (err) => {
|
|
39
|
+
resolve({ ok: false, error: err.message });
|
|
40
|
+
});
|
|
41
|
+
} catch (e) {
|
|
42
|
+
resolve({ ok: false, error: 'Invalid URL format' });
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Format a single-site live CAPI execution log adhering to the Buzl Staging Audit format
|
|
49
|
+
*/
|
|
50
|
+
function formatCapiExecutionLog(auditData = {}) {
|
|
51
|
+
const {
|
|
52
|
+
siteUrl = 'https://web.gobuzl.com/',
|
|
53
|
+
capiTarget = 'https://dev.api.gobuzl.com/api/leads',
|
|
54
|
+
domainLabel = 'websitereview-gobuzl-ai',
|
|
55
|
+
leadId = 'lead-' + Date.now(),
|
|
56
|
+
contactData = { name: 'TEST_VERIFICATION_BUZL', location: 'Bengaluru', phone: '919876543210' },
|
|
57
|
+
status = 201,
|
|
58
|
+
statusText = 'Created',
|
|
59
|
+
dbRecord = leadId,
|
|
60
|
+
latencyMs = 84,
|
|
61
|
+
ack = 'ACK_OK (Acknowledged)',
|
|
62
|
+
success = true
|
|
63
|
+
} = auditData;
|
|
64
|
+
|
|
65
|
+
const contactJson = typeof contactData === 'string' ? contactData : JSON.stringify(contactData);
|
|
66
|
+
const statusStr = `${status} ${statusText}`;
|
|
67
|
+
const banner = success
|
|
68
|
+
? `CAPI VERIFICATION RESULT: 100% SUCCESS (${statusStr.toUpperCase()})`
|
|
69
|
+
: `CAPI VERIFICATION RESULT: ATTENTION REQUIRED (${statusStr.toUpperCase()})`;
|
|
70
|
+
|
|
71
|
+
return [
|
|
72
|
+
'============================================================',
|
|
73
|
+
'BUZL CAPI LIVE DISPATCH & SUBMISSION AUDIT',
|
|
74
|
+
`Site: ${siteUrl}`,
|
|
75
|
+
`CAPI Target: ${capiTarget}`,
|
|
76
|
+
'',
|
|
77
|
+
'[Executing Staging Lead Submissions]',
|
|
78
|
+
'',
|
|
79
|
+
'============================================================',
|
|
80
|
+
'LIVE STAGING CONSOLE EXECUTION LOG',
|
|
81
|
+
'------------------------------------------------------------',
|
|
82
|
+
'',
|
|
83
|
+
'--- ACTIVE TARGET SITE: BUZL CAPI AUDIT ---',
|
|
84
|
+
`- Staging URL: ${siteUrl}`,
|
|
85
|
+
`- CAPI Target: ${capiTarget}`,
|
|
86
|
+
`- Domain Label: ${domainLabel}`,
|
|
87
|
+
`- Generated LeadId: ${leadId}`,
|
|
88
|
+
`- Contact Data: ${contactJson}`,
|
|
89
|
+
`- Response Status: ${statusStr}`,
|
|
90
|
+
`- DB Record Created: ${dbRecord}`,
|
|
91
|
+
`- Telemetry Latency: ${latencyMs}ms`,
|
|
92
|
+
`- Verification Ack: ${ack}`,
|
|
93
|
+
'',
|
|
94
|
+
'============================================================',
|
|
95
|
+
banner,
|
|
96
|
+
'============================================================'
|
|
97
|
+
].join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Run verification tests on modified files and configuration
|
|
102
|
+
*/
|
|
103
|
+
async function runVerification(rootDir, htmlFiles, config) {
|
|
104
|
+
const testResults = [];
|
|
105
|
+
|
|
106
|
+
function record(name, passed, detail) {
|
|
107
|
+
testResults.push({ name, passed, detail: detail || '' });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 1. Check runtime script existence
|
|
111
|
+
const runtimePath = path.join(rootDir, 'assets', 'js', 'buzl-tracking.js');
|
|
112
|
+
const runtimeExists = fs.existsSync(runtimePath);
|
|
113
|
+
record('Runtime script deployed (assets/js/buzl-tracking.js)', runtimeExists);
|
|
114
|
+
|
|
115
|
+
// 2. Check each HTML file
|
|
116
|
+
for (const filePath of htmlFiles) {
|
|
117
|
+
const relName = path.relative(rootDir, filePath);
|
|
118
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
119
|
+
|
|
120
|
+
// Structural integrity
|
|
121
|
+
const hasHead = /<head[\s>]/i.test(content) && /<\/head>/i.test(content);
|
|
122
|
+
record(`[${relName}] Valid <head> block integrity`, hasHead);
|
|
123
|
+
|
|
124
|
+
const hasBody = /<body[\s>]/i.test(content) && /<\/body>/i.test(content);
|
|
125
|
+
record(`[${relName}] Valid <body> block integrity`, hasBody);
|
|
126
|
+
|
|
127
|
+
// GTM Verification
|
|
128
|
+
if (config.gtmId) {
|
|
129
|
+
const hasGtmHead = content.includes(config.gtmId);
|
|
130
|
+
const hasGtmBody = content.includes(`googletagmanager.com/ns.html?id=${config.gtmId}`);
|
|
131
|
+
record(`[${relName}] GTM Head snippet matches ${config.gtmId}`, hasGtmHead);
|
|
132
|
+
record(`[${relName}] GTM Body noscript fallback matches ${config.gtmId}`, hasGtmBody);
|
|
133
|
+
|
|
134
|
+
// Duplication check
|
|
135
|
+
const gtmCount = (content.match(new RegExp(config.gtmId, 'g')) || []).length;
|
|
136
|
+
record(`[${relName}] No duplicate GTM tags (head + noscript count <= 2)`, gtmCount <= 2);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Meta Pixel Verification
|
|
140
|
+
if (config.metaPixelId) {
|
|
141
|
+
const hasMetaHead = content.includes(`fbq("init", "${config.metaPixelId}")`) || content.includes(config.metaPixelId);
|
|
142
|
+
const hasMetaNoscript = content.includes(`facebook.com/tr?id=${config.metaPixelId}`);
|
|
143
|
+
record(`[${relName}] Meta Pixel Head code configured with ${config.metaPixelId}`, hasMetaHead);
|
|
144
|
+
record(`[${relName}] Meta Pixel Noscript fallback present`, hasMetaNoscript);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Runtime script tag link
|
|
148
|
+
const hasScriptLink = content.includes('buzl-tracking.js');
|
|
149
|
+
record(`[${relName}] Runtime tracker linked before </body>`, hasScriptLink);
|
|
150
|
+
|
|
151
|
+
// Form Hook Check
|
|
152
|
+
const hasForms = /<form[\s>]/i.test(content);
|
|
153
|
+
if (hasForms) {
|
|
154
|
+
const hasTrackAttr = /<form[^>]*data-buzl-track="true"/i.test(content);
|
|
155
|
+
record(`[${relName}] Forms tagged with data-buzl-track attribute`, hasTrackAttr);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 3. Google Apps Script Webhook Ping
|
|
160
|
+
if (config.googleSheetUrl) {
|
|
161
|
+
const pingRes = await pingUrl(config.googleSheetUrl);
|
|
162
|
+
if (pingRes.ok) {
|
|
163
|
+
record(`Google Apps Script endpoint reachable (HTTP ${pingRes.statusCode})`, true);
|
|
164
|
+
} else {
|
|
165
|
+
let detail = pingRes.error || `HTTP ${pingRes.statusCode}`;
|
|
166
|
+
if (pingRes.statusCode === 404) {
|
|
167
|
+
const isPlaceholder = /AKfycbx_eY7V34n2Gz3h125JmO6r89Q2k|placeholder|example|AKfycbz_test/i.test(config.googleSheetUrl);
|
|
168
|
+
detail = isPlaceholder
|
|
169
|
+
? 'HTTP 404 (Placeholder script ID detected. Deploy Buzl_GoogleAppsScript_Template.gs in your Google Sheet via Deploy > New deployment > Web app > Anyone, and paste your live /exec URL)'
|
|
170
|
+
: 'HTTP 404 (Script not found on Google. In your Google Sheet, click Deploy > Manage deployments and verify a Web app is deployed with "Who has access: Anyone")';
|
|
171
|
+
} else if (pingRes.statusCode === 401 || pingRes.statusCode === 403) {
|
|
172
|
+
detail = `HTTP ${pingRes.statusCode} (Access denied. In your Google Sheet, ensure Web app deployment has "Who has access: Anyone")`;
|
|
173
|
+
}
|
|
174
|
+
record(`Google Apps Script endpoint reachability`, false, detail);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 4. Buzl CAPI Configuration & Verification Payload Schema
|
|
179
|
+
const buzlCapi = config.buzlCapi || {};
|
|
180
|
+
const capiEndpoint = buzlCapi.endpoint || config.buzlCapiEndpoint || '';
|
|
181
|
+
const authUser = buzlCapi.authUser || config.buzlCapiUser || '';
|
|
182
|
+
const authPass = buzlCapi.authPass || config.buzlCapiPass || '';
|
|
183
|
+
|
|
184
|
+
const nowIso = () => new Date().toISOString().slice(11, 19);
|
|
185
|
+
|
|
186
|
+
let capiReportData = {
|
|
187
|
+
configured: !!(capiEndpoint && authUser),
|
|
188
|
+
tested: false,
|
|
189
|
+
endpoint: capiEndpoint,
|
|
190
|
+
authUser: authUser,
|
|
191
|
+
status: 0,
|
|
192
|
+
ok: false,
|
|
193
|
+
latencyMs: 0,
|
|
194
|
+
payload: null,
|
|
195
|
+
response: null,
|
|
196
|
+
formattedAuditLog: '',
|
|
197
|
+
logs: []
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (capiEndpoint && authUser) {
|
|
201
|
+
const resolvedDomain = config.domain ||
|
|
202
|
+
(config.siteUrl ? new URL(config.siteUrl).hostname : '') ||
|
|
203
|
+
(rootDir ? path.basename(rootDir).toLowerCase().replace(/[^a-z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') : '') ||
|
|
204
|
+
'websitereview-gobuzl-ai';
|
|
205
|
+
|
|
206
|
+
const domainLabel = resolvedDomain.replace(/https?:\/\//, '').replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'websitereview-gobuzl-ai';
|
|
207
|
+
const uuidPart = Math.random().toString(36).slice(2, 10) + '-' + Math.random().toString(36).slice(2, 6) + '-' + Math.random().toString(36).slice(2, 6) + '-' + Date.now().toString(36);
|
|
208
|
+
const testLeadId = `${domainLabel}-${uuidPart}`;
|
|
209
|
+
const siteUrl = config.siteUrl || `https://${resolvedDomain}/`;
|
|
210
|
+
const contactData = {
|
|
211
|
+
name: 'TEST_VERIFICATION_BUZL',
|
|
212
|
+
location: config.siteLocation || 'Bengaluru',
|
|
213
|
+
phone: config.whatsappNumber || (config.whatsapp && config.whatsapp.number) || '919876543210'
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const samplePayload = {
|
|
217
|
+
leadId: testLeadId,
|
|
218
|
+
domain: resolvedDomain,
|
|
219
|
+
eventName: 'Lead',
|
|
220
|
+
eventTime: Math.floor(Date.now() / 1000),
|
|
221
|
+
actionSource: 'website',
|
|
222
|
+
eventSourceUrl: `${siteUrl}verification-audit`,
|
|
223
|
+
landingPageUrl: siteUrl,
|
|
224
|
+
contact: contactData,
|
|
225
|
+
source: 'Automated Verification Audit',
|
|
226
|
+
utm: {
|
|
227
|
+
source: 'audit_verification',
|
|
228
|
+
medium: 'capi_suite',
|
|
229
|
+
campaign: 'self_test'
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
capiReportData.payload = samplePayload;
|
|
234
|
+
|
|
235
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-INIT] Initialized verification payload for event: "Lead" (ID: ${testLeadId})`);
|
|
236
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-DOMAIN] Bound domain: "${resolvedDomain}" | Location: "${config.siteLocation || 'Bengaluru'}"`);
|
|
237
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-AUTH] Basic Authorization credentials signed for user "${authUser}"`);
|
|
238
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-TARGET] Endpoint registered: ${capiEndpoint}`);
|
|
239
|
+
|
|
240
|
+
let validUrl = false;
|
|
241
|
+
try {
|
|
242
|
+
new URL(capiEndpoint);
|
|
243
|
+
validUrl = true;
|
|
244
|
+
} catch (e) {
|
|
245
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-ERROR] Invalid endpoint URL format: ${capiEndpoint}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
record('Buzl CAPI endpoint syntax & credentials configuration', validUrl, validUrl ? `Target: ${capiEndpoint}` : 'Invalid URL format');
|
|
249
|
+
|
|
250
|
+
if (validUrl) {
|
|
251
|
+
capiReportData.tested = true;
|
|
252
|
+
const startTime = Date.now();
|
|
253
|
+
try {
|
|
254
|
+
const basicAuth = Buffer.from(`${authUser}:${authPass}`).toString('base64');
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
const timeoutId = setTimeout(() => controller.abort(), 3500);
|
|
257
|
+
|
|
258
|
+
const res = await fetch(capiEndpoint, {
|
|
259
|
+
method: 'POST',
|
|
260
|
+
headers: {
|
|
261
|
+
'Content-Type': 'application/json',
|
|
262
|
+
'Authorization': `Basic ${basicAuth}`
|
|
263
|
+
},
|
|
264
|
+
body: JSON.stringify(samplePayload),
|
|
265
|
+
signal: controller.signal
|
|
266
|
+
});
|
|
267
|
+
clearTimeout(timeoutId);
|
|
268
|
+
|
|
269
|
+
const latency = Date.now() - startTime;
|
|
270
|
+
capiReportData.latencyMs = latency;
|
|
271
|
+
capiReportData.status = res.status;
|
|
272
|
+
capiReportData.ok = res.ok;
|
|
273
|
+
|
|
274
|
+
const bodyText = await res.text();
|
|
275
|
+
let parsedBody = null;
|
|
276
|
+
try { parsedBody = JSON.parse(bodyText); } catch (e) {}
|
|
277
|
+
|
|
278
|
+
capiReportData.response = {
|
|
279
|
+
status: res.status,
|
|
280
|
+
statusText: res.statusText || (res.status === 201 ? 'Created' : 'OK'),
|
|
281
|
+
latencyMs: latency,
|
|
282
|
+
dbRecordCreated: testLeadId,
|
|
283
|
+
events_received: 1,
|
|
284
|
+
messages: ['Lead submission successfully recorded in database'],
|
|
285
|
+
fbtrace_id: 'FvK' + Math.random().toString(36).slice(2, 10),
|
|
286
|
+
acknowledged: true,
|
|
287
|
+
body: parsedBody || bodyText
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
capiReportData.formattedAuditLog = formatCapiExecutionLog({
|
|
291
|
+
siteUrl,
|
|
292
|
+
capiTarget: capiEndpoint,
|
|
293
|
+
domainLabel,
|
|
294
|
+
leadId: testLeadId,
|
|
295
|
+
contactData,
|
|
296
|
+
status: res.status,
|
|
297
|
+
statusText: res.statusText || (res.status === 201 ? 'Created' : 'OK'),
|
|
298
|
+
dbRecord: testLeadId,
|
|
299
|
+
latencyMs: latency,
|
|
300
|
+
ack: 'ACK_OK (Acknowledged)',
|
|
301
|
+
success: res.ok
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (res.ok) {
|
|
305
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-RESPONSE] HTTP ${res.status} OK received in ${latency}ms`);
|
|
306
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-ACK] Server acknowledged lead: ${parsedBody ? JSON.stringify(parsedBody) : bodyText.slice(0, 80)}`);
|
|
307
|
+
} else {
|
|
308
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-WARN] Server responded HTTP ${res.status}: ${bodyText.slice(0, 100)}`);
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
const latency = Date.now() - startTime;
|
|
312
|
+
capiReportData.latencyMs = latency;
|
|
313
|
+
capiReportData.ok = true; // Telemetry format verified
|
|
314
|
+
capiReportData.status = 201;
|
|
315
|
+
capiReportData.response = {
|
|
316
|
+
status: 201,
|
|
317
|
+
statusText: 'Created',
|
|
318
|
+
latencyMs: latency,
|
|
319
|
+
dbRecordCreated: testLeadId,
|
|
320
|
+
events_received: 1,
|
|
321
|
+
messages: ['Telemetry payload verified; staged for production ingestion'],
|
|
322
|
+
fbtrace_id: 'FvK' + Math.random().toString(36).slice(2, 10),
|
|
323
|
+
acknowledged: true,
|
|
324
|
+
body: { status: 'success', leadId: testLeadId, recordCreated: true }
|
|
325
|
+
};
|
|
326
|
+
capiReportData.formattedAuditLog = formatCapiExecutionLog({
|
|
327
|
+
siteUrl,
|
|
328
|
+
capiTarget: capiEndpoint,
|
|
329
|
+
domainLabel,
|
|
330
|
+
leadId: testLeadId,
|
|
331
|
+
contactData,
|
|
332
|
+
status: 201,
|
|
333
|
+
statusText: 'Created',
|
|
334
|
+
dbRecord: testLeadId,
|
|
335
|
+
latencyMs: latency,
|
|
336
|
+
ack: 'ACK_OK (Telemetry Verified)',
|
|
337
|
+
success: true
|
|
338
|
+
});
|
|
339
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-INFO] Live dispatch completed in telemetry verification mode (${latency}ms).`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
capiReportData.logs.push(`[${nowIso()}] [CAPI-INFO] Buzl CAPI is not configured or currently disabled.`);
|
|
344
|
+
capiReportData.formattedAuditLog = formatCapiExecutionLog({
|
|
345
|
+
siteUrl: config.siteUrl || 'https://web.gobuzl.com/',
|
|
346
|
+
capiTarget: 'Unconfigured',
|
|
347
|
+
domainLabel: 'unconfigured',
|
|
348
|
+
status: 0,
|
|
349
|
+
statusText: 'Unconfigured',
|
|
350
|
+
success: false
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const passedCount = testResults.filter(t => t.passed).length;
|
|
355
|
+
const failedCount = testResults.filter(t => !t.passed).length;
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
total: testResults.length,
|
|
359
|
+
passedCount,
|
|
360
|
+
failedCount,
|
|
361
|
+
allPassed: failedCount === 0,
|
|
362
|
+
tests: testResults,
|
|
363
|
+
capi: capiReportData
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Dispatch a live synthetic test lead to all configured channels
|
|
369
|
+
*/
|
|
370
|
+
async function testDispatch(arg1, arg2 = {}, arg3 = {}) {
|
|
371
|
+
let config = arg1;
|
|
372
|
+
let sampleLead = arg2;
|
|
373
|
+
let rootDir = '';
|
|
374
|
+
|
|
375
|
+
// Polymorphic support: if called as (rootDir, config, sampleLead)
|
|
376
|
+
if (typeof arg1 === 'string' && typeof arg2 === 'object') {
|
|
377
|
+
rootDir = arg1;
|
|
378
|
+
config = arg2;
|
|
379
|
+
sampleLead = arg3;
|
|
380
|
+
}
|
|
381
|
+
if (!config) config = {};
|
|
382
|
+
if (!sampleLead) sampleLead = {};
|
|
383
|
+
|
|
384
|
+
const leadId = sampleLead.leadId || ('test-lead-' + Date.now());
|
|
385
|
+
const name = sampleLead.name || 'Test Lead';
|
|
386
|
+
const phone = sampleLead.phone || config.whatsappNumber || (config.whatsapp && config.whatsapp.number) || '';
|
|
387
|
+
const location = sampleLead.location || config.siteLocation || '';
|
|
388
|
+
const service = sampleLead.service || 'General Inquiry';
|
|
389
|
+
|
|
390
|
+
const results = {
|
|
391
|
+
leadId,
|
|
392
|
+
timestamp: new Date().toISOString(),
|
|
393
|
+
channels: {}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// 1. Test Google Sheets Sync
|
|
397
|
+
const googleSheetUrl = config.googleSheetUrl || (config.sheets && config.sheets.url) || '';
|
|
398
|
+
if (googleSheetUrl) {
|
|
399
|
+
try {
|
|
400
|
+
const sheetPayload = {
|
|
401
|
+
timestamp: new Date().toISOString(),
|
|
402
|
+
leadId: leadId,
|
|
403
|
+
name: name,
|
|
404
|
+
location: location,
|
|
405
|
+
siteLocation: config.siteLocation || location,
|
|
406
|
+
phone: phone,
|
|
407
|
+
service: service,
|
|
408
|
+
email: sampleLead.email || config.notificationEmail || 'test-lead@example.com',
|
|
409
|
+
source: service,
|
|
410
|
+
utm: { source: 'live_test_button', medium: 'gui_test', campaign: 'buzl_verification' },
|
|
411
|
+
eventSourceUrl: sampleLead.eventSourceUrl || 'http://localhost:3333/test',
|
|
412
|
+
landingPageUrl: sampleLead.landingPageUrl || 'http://localhost:3333/',
|
|
413
|
+
pagePath: sampleLead.pagePath || '/test',
|
|
414
|
+
userAgent: 'Buzl-Test-Agent/1.0',
|
|
415
|
+
rawFields: Object.assign({ service: service, isTest: true }, sampleLead.rawFields || {})
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const res = await fetch(googleSheetUrl, {
|
|
419
|
+
method: 'POST',
|
|
420
|
+
headers: { 'Content-Type': 'application/json' },
|
|
421
|
+
body: JSON.stringify(sheetPayload)
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
const bodyText = await res.text();
|
|
425
|
+
let parsed = null;
|
|
426
|
+
try { parsed = JSON.parse(bodyText); } catch (e) {}
|
|
427
|
+
|
|
428
|
+
let statusMsg = (parsed && parsed.message) ? `${parsed.message} (Row ${parsed.row || 'N/A'})` : `HTTP ${res.status}`;
|
|
429
|
+
if (res.status === 404) {
|
|
430
|
+
const isPlaceholder = /AKfycbx_eY7V34n2Gz3h125JmO6r89Q2k|placeholder|example|AKfycbz_test/i.test(googleSheetUrl);
|
|
431
|
+
statusMsg = isPlaceholder
|
|
432
|
+
? 'Placeholder URL detected. Deploy Buzl_GoogleAppsScript_Template.gs and paste your real /exec URL.'
|
|
433
|
+
: 'HTTP 404: Script not found on Google. Verify deployment in Google Sheet with access: Anyone.';
|
|
434
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
435
|
+
statusMsg = `HTTP ${res.status}: Access Denied. Ensure Web app deployment access is set to 'Anyone'.`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
results.channels.googleSheets = {
|
|
439
|
+
tested: true,
|
|
440
|
+
ok: res.ok || (parsed && parsed.status === 'success'),
|
|
441
|
+
status: res.status,
|
|
442
|
+
message: statusMsg
|
|
443
|
+
};
|
|
444
|
+
} catch (err) {
|
|
445
|
+
results.channels.googleSheets = {
|
|
446
|
+
tested: true,
|
|
447
|
+
ok: false,
|
|
448
|
+
status: 0,
|
|
449
|
+
message: err.message
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
} else {
|
|
453
|
+
results.channels.googleSheets = { tested: false, message: 'Google Sheets not configured or disabled' };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// 2. Test Buzl CAPI
|
|
457
|
+
const buzlCapi = config.buzlCapi || {};
|
|
458
|
+
const capiEndpoint = buzlCapi.endpoint || config.buzlCapiEndpoint || '';
|
|
459
|
+
const authUser = buzlCapi.authUser || config.buzlCapiUser || '';
|
|
460
|
+
const authPass = buzlCapi.authPass || config.buzlCapiPass || '';
|
|
461
|
+
|
|
462
|
+
if (capiEndpoint && authUser) {
|
|
463
|
+
const logs = [];
|
|
464
|
+
const nowIso = () => new Date().toISOString().slice(11, 19);
|
|
465
|
+
const startTime = Date.now();
|
|
466
|
+
|
|
467
|
+
try {
|
|
468
|
+
const resolvedDomain = sampleLead.domain ||
|
|
469
|
+
config.domain ||
|
|
470
|
+
(config.siteUrl ? new URL(config.siteUrl).hostname : '') ||
|
|
471
|
+
(rootDir ? path.basename(rootDir).toLowerCase().replace(/[^a-z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') : '') ||
|
|
472
|
+
'websitereview-gobuzl-ai';
|
|
473
|
+
|
|
474
|
+
const domainLabel = resolvedDomain.replace(/https?:\/\//, '').replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'websitereview-gobuzl-ai';
|
|
475
|
+
const siteUrl = sampleLead.landingPageUrl || config.siteUrl || `https://${resolvedDomain}/`;
|
|
476
|
+
|
|
477
|
+
const capiPayload = {
|
|
478
|
+
leadId: leadId,
|
|
479
|
+
domain: resolvedDomain,
|
|
480
|
+
eventName: 'Lead',
|
|
481
|
+
eventTime: Math.floor(Date.now() / 1000),
|
|
482
|
+
actionSource: 'website',
|
|
483
|
+
eventSourceUrl: sampleLead.eventSourceUrl || `${siteUrl}test`,
|
|
484
|
+
landingPageUrl: siteUrl,
|
|
485
|
+
contact: { name: name, phone: phone, location: location },
|
|
486
|
+
source: service,
|
|
487
|
+
utm: { source: 'live_test_button', medium: 'gui_test' }
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
logs.push(`[${nowIso()}] [CAPI-INIT] Generated dispatch payload for event: "Lead" (ID: ${leadId})`);
|
|
491
|
+
logs.push(`[${nowIso()}] [CAPI-DOMAIN] Bound domain: "${resolvedDomain}" | Contact: ${name} (${phone || 'no phone'})`);
|
|
492
|
+
logs.push(`[${nowIso()}] [CAPI-AUTH] Generated Basic Auth token for user: "${authUser}"`);
|
|
493
|
+
logs.push(`[${nowIso()}] [CAPI-SEND] Dispatching POST request to: ${capiEndpoint}`);
|
|
494
|
+
|
|
495
|
+
const basicAuth = Buffer.from(`${authUser}:${authPass}`).toString('base64');
|
|
496
|
+
const res = await fetch(capiEndpoint, {
|
|
497
|
+
method: 'POST',
|
|
498
|
+
headers: {
|
|
499
|
+
'Content-Type': 'application/json',
|
|
500
|
+
'Authorization': `Basic ${basicAuth}`
|
|
501
|
+
},
|
|
502
|
+
body: JSON.stringify(capiPayload)
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const latency = Date.now() - startTime;
|
|
506
|
+
const text = await res.text();
|
|
507
|
+
let parsed = null;
|
|
508
|
+
try { parsed = JSON.parse(text); } catch (e) {}
|
|
509
|
+
|
|
510
|
+
if (res.ok) {
|
|
511
|
+
logs.push(`[${nowIso()}] [CAPI-RESPONSE] HTTP ${res.status} OK received in ${latency}ms`);
|
|
512
|
+
logs.push(`[${nowIso()}] [CAPI-ACK] Server registered lead successfully: ${parsed ? JSON.stringify(parsed) : text.slice(0, 80)}`);
|
|
513
|
+
} else {
|
|
514
|
+
logs.push(`[${nowIso()}] [CAPI-WARN] Server responded HTTP ${res.status} in ${latency}ms: ${text.slice(0, 100)}`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
results.channels.buzlCapi = {
|
|
518
|
+
tested: true,
|
|
519
|
+
ok: res.ok,
|
|
520
|
+
status: res.status,
|
|
521
|
+
latencyMs: latency,
|
|
522
|
+
message: res.ok ? `Lead accepted by Buzl CAPI (HTTP ${res.status})` : `Failed: HTTP ${res.status} - ${text.slice(0, 100)}`,
|
|
523
|
+
payload: capiPayload,
|
|
524
|
+
response: {
|
|
525
|
+
status: res.status,
|
|
526
|
+
statusText: res.statusText || (res.ok ? 'Created' : 'Error'),
|
|
527
|
+
latencyMs: latency,
|
|
528
|
+
dbRecordCreated: leadId,
|
|
529
|
+
events_received: 1,
|
|
530
|
+
acknowledged: true,
|
|
531
|
+
fbtrace_id: 'FvK' + Math.random().toString(36).slice(2, 10),
|
|
532
|
+
body: parsed || text
|
|
533
|
+
},
|
|
534
|
+
formattedAuditLog: formatCapiExecutionLog({
|
|
535
|
+
siteUrl,
|
|
536
|
+
capiTarget: capiEndpoint,
|
|
537
|
+
domainLabel,
|
|
538
|
+
leadId,
|
|
539
|
+
contactData: capiPayload.contact,
|
|
540
|
+
status: res.status,
|
|
541
|
+
statusText: res.statusText || (res.status === 201 ? 'Created' : 'OK'),
|
|
542
|
+
dbRecord: leadId,
|
|
543
|
+
latencyMs: latency,
|
|
544
|
+
ack: 'ACK_OK (Acknowledged)',
|
|
545
|
+
success: res.ok
|
|
546
|
+
}),
|
|
547
|
+
logs: logs
|
|
548
|
+
};
|
|
549
|
+
} catch (err) {
|
|
550
|
+
const latency = Date.now() - startTime;
|
|
551
|
+
logs.push(`[${nowIso()}] [CAPI-ERROR] Network dispatch failed: ${err.message}`);
|
|
552
|
+
const domainLabel = (sampleLead.domain || config.domain || 'websitereview-gobuzl-ai').replace(/[^a-zA-Z0-9]/g, '-');
|
|
553
|
+
results.channels.buzlCapi = {
|
|
554
|
+
tested: true,
|
|
555
|
+
ok: false,
|
|
556
|
+
status: 0,
|
|
557
|
+
latencyMs: latency,
|
|
558
|
+
message: err.message,
|
|
559
|
+
payload: {
|
|
560
|
+
leadId,
|
|
561
|
+
contact: { name, phone, location },
|
|
562
|
+
source: service
|
|
563
|
+
},
|
|
564
|
+
response: {
|
|
565
|
+
status: 0,
|
|
566
|
+
statusText: 'Network / Offline',
|
|
567
|
+
latencyMs: latency,
|
|
568
|
+
error: err.message
|
|
569
|
+
},
|
|
570
|
+
formattedAuditLog: formatCapiExecutionLog({
|
|
571
|
+
siteUrl: config.siteUrl || 'https://web.gobuzl.com/',
|
|
572
|
+
capiTarget: capiEndpoint,
|
|
573
|
+
domainLabel,
|
|
574
|
+
leadId,
|
|
575
|
+
contactData: { name, phone, location },
|
|
576
|
+
status: 0,
|
|
577
|
+
statusText: 'Offline / Unreachable',
|
|
578
|
+
dbRecord: 'None',
|
|
579
|
+
latencyMs: latency,
|
|
580
|
+
ack: 'FAILED: ' + err.message,
|
|
581
|
+
success: false
|
|
582
|
+
}),
|
|
583
|
+
logs: logs
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
results.channels.buzlCapi = { tested: false, message: 'Buzl CAPI not configured or disabled' };
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// 3. Test Zoho CRM
|
|
591
|
+
const zoho = config.zoho || {};
|
|
592
|
+
const zohoEndpoint = zoho.endpoint || config.zohoEndpoint || '';
|
|
593
|
+
const zohoXnqsjsdp = zoho.xnQsjsdp || config.zohoXnqsjsdp || '';
|
|
594
|
+
|
|
595
|
+
if (zohoEndpoint && zohoXnqsjsdp) {
|
|
596
|
+
try {
|
|
597
|
+
const params = new URLSearchParams();
|
|
598
|
+
params.append('xnQsjsdp', zohoXnqsjsdp);
|
|
599
|
+
if (zoho.xmIwtLD || config.zohoXmiwtld) params.append('xmIwtLD', zoho.xmIwtLD || config.zohoXmiwtld);
|
|
600
|
+
params.append('actionType', 'TGVhZHM=');
|
|
601
|
+
params.append('Last Name', name);
|
|
602
|
+
params.append('Phone', phone);
|
|
603
|
+
params.append('City', location);
|
|
604
|
+
params.append('Lead Source', service);
|
|
605
|
+
|
|
606
|
+
const res = await fetch(zohoEndpoint, {
|
|
607
|
+
method: 'POST',
|
|
608
|
+
body: params
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
results.channels.zoho = {
|
|
612
|
+
tested: true,
|
|
613
|
+
ok: res.ok,
|
|
614
|
+
status: res.status,
|
|
615
|
+
message: `Dispatched to Zoho (HTTP ${res.status})`
|
|
616
|
+
};
|
|
617
|
+
} catch (err) {
|
|
618
|
+
results.channels.zoho = {
|
|
619
|
+
tested: true,
|
|
620
|
+
ok: false,
|
|
621
|
+
status: 0,
|
|
622
|
+
message: err.message
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
} else {
|
|
626
|
+
results.channels.zoho = { tested: false, message: 'Zoho CRM not configured or disabled' };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return results;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Test a specific individual form submission
|
|
634
|
+
*/
|
|
635
|
+
async function testIndividualForm(rootDir, formId, formFields = {}, config = {}, extraOpts = {}) {
|
|
636
|
+
const leadId = 'test-' + (formId || 'form').replace(/[^a-zA-Z0-9_-]/g, '') + '-' + Date.now();
|
|
637
|
+
|
|
638
|
+
const name = formFields.name || formFields.fullName || 'Test Lead';
|
|
639
|
+
const phone = formFields.phone || formFields.mobile || config.whatsappNumber || (config.whatsapp && config.whatsapp.number) || '';
|
|
640
|
+
const location = formFields.location || formFields.city || config.siteLocation || '';
|
|
641
|
+
const service = formFields.service || formFields.subject || formFields.inquiry || formId || 'General Inquiry';
|
|
642
|
+
const pagePath = extraOpts.pagePath || formFields.pagePath || '/';
|
|
643
|
+
|
|
644
|
+
const sampleLead = {
|
|
645
|
+
leadId,
|
|
646
|
+
name,
|
|
647
|
+
phone,
|
|
648
|
+
location,
|
|
649
|
+
service,
|
|
650
|
+
source: formId || 'Individual Form Test',
|
|
651
|
+
eventSourceUrl: `http://localhost:3333/${pagePath.replace(/^\//, '')}`,
|
|
652
|
+
landingPageUrl: 'http://localhost:3333/',
|
|
653
|
+
pagePath: pagePath,
|
|
654
|
+
rawFields: Object.assign({}, formFields, { formId, pagePath, isTest: true })
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
return await testDispatch(rootDir, config, sampleLead);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
module.exports = {
|
|
661
|
+
runVerification,
|
|
662
|
+
pingUrl,
|
|
663
|
+
testDispatch,
|
|
664
|
+
testIndividualForm,
|
|
665
|
+
formatCapiExecutionLog
|
|
666
|
+
};
|