@kodelyth/googlechat 2026.5.42 → 2026.6.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.
Files changed (61) hide show
  1. package/klaw.plugin.json +967 -2
  2. package/package.json +18 -6
  3. package/api.ts +0 -3
  4. package/channel-config-api.ts +0 -1
  5. package/channel-plugin-api.ts +0 -1
  6. package/config-api.ts +0 -2
  7. package/contract-api.ts +0 -5
  8. package/doctor-contract-api.ts +0 -1
  9. package/index.ts +0 -20
  10. package/runtime-api.ts +0 -55
  11. package/secret-contract-api.ts +0 -5
  12. package/setup-entry.ts +0 -13
  13. package/setup-plugin-api.ts +0 -3
  14. package/src/accounts.ts +0 -181
  15. package/src/actions.test.ts +0 -289
  16. package/src/actions.ts +0 -227
  17. package/src/api.ts +0 -316
  18. package/src/approval-auth.test.ts +0 -24
  19. package/src/approval-auth.ts +0 -32
  20. package/src/auth.ts +0 -218
  21. package/src/channel-config.test.ts +0 -39
  22. package/src/channel.adapters.ts +0 -340
  23. package/src/channel.deps.runtime.ts +0 -29
  24. package/src/channel.runtime.ts +0 -17
  25. package/src/channel.setup.ts +0 -98
  26. package/src/channel.test.ts +0 -784
  27. package/src/channel.ts +0 -277
  28. package/src/config-schema.test.ts +0 -31
  29. package/src/config-schema.ts +0 -3
  30. package/src/doctor-contract.test.ts +0 -75
  31. package/src/doctor-contract.ts +0 -182
  32. package/src/doctor.ts +0 -57
  33. package/src/gateway.ts +0 -63
  34. package/src/google-auth.runtime.test.ts +0 -543
  35. package/src/google-auth.runtime.ts +0 -568
  36. package/src/group-policy.ts +0 -17
  37. package/src/monitor-access.test.ts +0 -491
  38. package/src/monitor-access.ts +0 -465
  39. package/src/monitor-durable.test.ts +0 -39
  40. package/src/monitor-durable.ts +0 -23
  41. package/src/monitor-reply-delivery.ts +0 -156
  42. package/src/monitor-routing.ts +0 -65
  43. package/src/monitor-types.ts +0 -33
  44. package/src/monitor-webhook.test.ts +0 -587
  45. package/src/monitor-webhook.ts +0 -303
  46. package/src/monitor.reply-delivery.test.ts +0 -144
  47. package/src/monitor.test.ts +0 -159
  48. package/src/monitor.ts +0 -527
  49. package/src/monitor.webhook-routing.test.ts +0 -257
  50. package/src/runtime.ts +0 -9
  51. package/src/secret-contract.test.ts +0 -60
  52. package/src/secret-contract.ts +0 -161
  53. package/src/setup-core.ts +0 -40
  54. package/src/setup-surface.ts +0 -243
  55. package/src/setup.test.ts +0 -619
  56. package/src/targets.test.ts +0 -453
  57. package/src/targets.ts +0 -66
  58. package/src/types.config.ts +0 -3
  59. package/src/types.ts +0 -73
  60. package/test-api.ts +0 -2
  61. package/tsconfig.json +0 -16
package/src/setup.test.ts DELETED
@@ -1,619 +0,0 @@
1
- import {
2
- expectLifecyclePatch,
3
- expectPendingUntilAbort,
4
- startAccountAndTrackLifecycle,
5
- } from "klaw/plugin-sdk/channel-test-helpers";
6
- import {
7
- createPluginSetupWizardConfigure,
8
- createPluginSetupWizardStatus,
9
- createTestWizardPrompter,
10
- runSetupWizardConfigure,
11
- } from "klaw/plugin-sdk/plugin-test-runtime";
12
- import type { WizardPrompter } from "klaw/plugin-sdk/plugin-test-runtime";
13
- import { DEFAULT_ACCOUNT_ID } from "klaw/plugin-sdk/setup";
14
- import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
15
- import type { KlawConfig } from "../runtime-api.js";
16
- import {
17
- listGoogleChatAccountIds,
18
- resolveGoogleChatAccount,
19
- resolveDefaultGoogleChatAccountId,
20
- type ResolvedGoogleChatAccount,
21
- } from "./accounts.js";
22
- import { startGoogleChatGatewayAccount } from "./gateway.js";
23
- import { googlechatSetupAdapter } from "./setup-core.js";
24
- import { googlechatSetupWizard } from "./setup-surface.js";
25
-
26
- const hoisted = vi.hoisted(() => ({
27
- startGoogleChatMonitor: vi.fn(),
28
- }));
29
-
30
- vi.mock("./channel.runtime.js", () => ({
31
- googleChatChannelRuntime: {
32
- resolveGoogleChatWebhookPath: ({
33
- account,
34
- }: {
35
- account: { config: { webhookPath?: string } };
36
- }) => account.config.webhookPath ?? "/googlechat",
37
- startGoogleChatMonitor: hoisted.startGoogleChatMonitor,
38
- },
39
- }));
40
-
41
- const googlechatSetupPlugin = {
42
- id: "googlechat",
43
- meta: {
44
- label: "Google Chat",
45
- },
46
- config: {
47
- defaultAccountId: resolveDefaultGoogleChatAccountId,
48
- listAccountIds: listGoogleChatAccountIds,
49
- },
50
- setupWizard: googlechatSetupWizard,
51
- } as never;
52
-
53
- const googlechatConfigure = createPluginSetupWizardConfigure(googlechatSetupPlugin);
54
- const googlechatStatus = createPluginSetupWizardStatus(googlechatSetupPlugin);
55
-
56
- function buildAccount(): ResolvedGoogleChatAccount {
57
- return {
58
- accountId: "default",
59
- enabled: true,
60
- credentialSource: "inline",
61
- credentials: {},
62
- config: {
63
- webhookPath: "/googlechat",
64
- webhookUrl: "https://example.com/googlechat",
65
- audienceType: "app-url",
66
- audience: "https://example.com/googlechat",
67
- },
68
- };
69
- }
70
-
71
- async function waitForGoogleChatMonitorStarted() {
72
- await vi.waitFor(() => expect(hoisted.startGoogleChatMonitor).toHaveBeenCalledOnce());
73
- }
74
-
75
- describe("googlechat setup", () => {
76
- afterEach(() => {
77
- vi.clearAllMocks();
78
- vi.unstubAllEnvs();
79
- });
80
-
81
- afterAll(() => {
82
- vi.doUnmock("./channel.runtime.js");
83
- vi.resetModules();
84
- });
85
-
86
- it("rejects env auth for non-default accounts", () => {
87
- if (!googlechatSetupAdapter.validateInput) {
88
- throw new Error("Expected googlechatSetupAdapter.validateInput to be defined");
89
- }
90
- expect(
91
- googlechatSetupAdapter.validateInput({
92
- accountId: "secondary",
93
- input: { useEnv: true },
94
- } as never),
95
- ).toBe("GOOGLE_CHAT_SERVICE_ACCOUNT env vars can only be used for the default account.");
96
- });
97
-
98
- it("requires inline or file credentials when env auth is not used", () => {
99
- if (!googlechatSetupAdapter.validateInput) {
100
- throw new Error("Expected googlechatSetupAdapter.validateInput to be defined");
101
- }
102
- expect(
103
- googlechatSetupAdapter.validateInput({
104
- accountId: DEFAULT_ACCOUNT_ID,
105
- input: { useEnv: false, token: "", tokenFile: "" },
106
- } as never),
107
- ).toBe("Google Chat requires --token (service account JSON) or --token-file.");
108
- });
109
-
110
- it("builds a patch from token-file and trims optional webhook fields", () => {
111
- if (!googlechatSetupAdapter.applyAccountConfig) {
112
- throw new Error("Expected googlechatSetupAdapter.applyAccountConfig to be defined");
113
- }
114
- expect(
115
- googlechatSetupAdapter.applyAccountConfig({
116
- cfg: { channels: { googlechat: {} } },
117
- accountId: DEFAULT_ACCOUNT_ID,
118
- input: {
119
- name: "Default",
120
- tokenFile: "/tmp/googlechat.json",
121
- audienceType: " app-url ",
122
- audience: " https://example.com/googlechat ",
123
- webhookPath: " /googlechat ",
124
- webhookUrl: " https://example.com/googlechat/hook ",
125
- },
126
- } as never),
127
- ).toEqual({
128
- channels: {
129
- googlechat: {
130
- enabled: true,
131
- name: "Default",
132
- serviceAccountFile: "/tmp/googlechat.json",
133
- audienceType: "app-url",
134
- audience: "https://example.com/googlechat",
135
- webhookPath: "/googlechat",
136
- webhookUrl: "https://example.com/googlechat/hook",
137
- },
138
- },
139
- });
140
- });
141
-
142
- it("prefers inline token patch when token-file is absent", () => {
143
- if (!googlechatSetupAdapter.applyAccountConfig) {
144
- throw new Error("Expected googlechatSetupAdapter.applyAccountConfig to be defined");
145
- }
146
- expect(
147
- googlechatSetupAdapter.applyAccountConfig({
148
- cfg: { channels: { googlechat: {} } },
149
- accountId: DEFAULT_ACCOUNT_ID,
150
- input: {
151
- name: "Default",
152
- token: { client_email: "bot@example.com" },
153
- },
154
- } as never),
155
- ).toEqual({
156
- channels: {
157
- googlechat: {
158
- enabled: true,
159
- name: "Default",
160
- serviceAccount: { client_email: "bot@example.com" },
161
- },
162
- },
163
- });
164
- });
165
-
166
- it("configures service-account auth and webhook audience", async () => {
167
- const prompter = createTestWizardPrompter({
168
- text: vi.fn(async ({ message }: { message: string }) => {
169
- if (message === "Service account JSON path") {
170
- return "/tmp/googlechat-service-account.json";
171
- }
172
- if (message === "App URL") {
173
- return "https://example.com/googlechat";
174
- }
175
- throw new Error(`Unexpected prompt: ${message}`);
176
- }) as WizardPrompter["text"],
177
- });
178
-
179
- const result = await runSetupWizardConfigure({
180
- configure: googlechatConfigure,
181
- cfg: {} as KlawConfig,
182
- prompter,
183
- options: {},
184
- });
185
-
186
- expect(result.accountId).toBe("default");
187
- expect(result.cfg.channels?.googlechat?.enabled).toBe(true);
188
- expect(result.cfg.channels?.googlechat?.serviceAccountFile).toBe(
189
- "/tmp/googlechat-service-account.json",
190
- );
191
- expect(result.cfg.channels?.googlechat?.audienceType).toBe("app-url");
192
- expect(result.cfg.channels?.googlechat?.audience).toBe("https://example.com/googlechat");
193
- });
194
-
195
- it("reads the named-account DM policy instead of the channel root", () => {
196
- expect(
197
- googlechatSetupWizard.dmPolicy?.getCurrent(
198
- {
199
- channels: {
200
- googlechat: {
201
- dm: {
202
- policy: "disabled",
203
- },
204
- accounts: {
205
- alerts: {
206
- serviceAccount: { client_email: "bot@example.com" },
207
- dm: {
208
- policy: "allowlist",
209
- },
210
- },
211
- },
212
- },
213
- },
214
- } as KlawConfig,
215
- "alerts",
216
- ),
217
- ).toBe("allowlist");
218
- });
219
-
220
- it("reports configured state for the selected account instead of any account", async () => {
221
- const status = await googlechatStatus({
222
- cfg: {
223
- channels: {
224
- googlechat: {
225
- accounts: {
226
- default: {
227
- serviceAccount: { client_email: "default@example.com" },
228
- },
229
- alerts: {},
230
- },
231
- },
232
- },
233
- } as KlawConfig,
234
- accountOverrides: {
235
- googlechat: "alerts",
236
- },
237
- options: {},
238
- });
239
-
240
- expect(status.configured).toBe(false);
241
- });
242
-
243
- it("reports configured state for the configured defaultAccount instead of any account", async () => {
244
- const status = await googlechatStatus({
245
- cfg: {
246
- channels: {
247
- googlechat: {
248
- defaultAccount: "alerts",
249
- accounts: {
250
- default: {
251
- serviceAccount: { client_email: "default@example.com" },
252
- },
253
- alerts: {},
254
- },
255
- },
256
- },
257
- } as KlawConfig,
258
- accountOverrides: {},
259
- options: {},
260
- });
261
-
262
- expect(status.configured).toBe(false);
263
- });
264
-
265
- it("reports account-scoped config keys for named accounts", () => {
266
- expect(googlechatSetupWizard.dmPolicy?.resolveConfigKeys?.({}, "alerts")).toEqual({
267
- policyKey: "channels.googlechat.accounts.alerts.dm.policy",
268
- allowFromKey: "channels.googlechat.accounts.alerts.dm.allowFrom",
269
- });
270
- });
271
-
272
- it("uses configured defaultAccount for omitted DM policy account context", () => {
273
- const cfg = {
274
- channels: {
275
- googlechat: {
276
- defaultAccount: "alerts",
277
- dm: {
278
- policy: "disabled",
279
- },
280
- accounts: {
281
- alerts: {
282
- serviceAccount: { client_email: "bot@example.com" },
283
- dm: {
284
- policy: "allowlist",
285
- },
286
- },
287
- },
288
- },
289
- },
290
- } as KlawConfig;
291
-
292
- expect(googlechatSetupWizard.dmPolicy?.getCurrent(cfg)).toBe("allowlist");
293
- expect(googlechatSetupWizard.dmPolicy?.resolveConfigKeys?.(cfg)).toEqual({
294
- policyKey: "channels.googlechat.accounts.alerts.dm.policy",
295
- allowFromKey: "channels.googlechat.accounts.alerts.dm.allowFrom",
296
- });
297
-
298
- const next = googlechatSetupWizard.dmPolicy?.setPolicy(cfg, "open");
299
- expect(next?.channels?.googlechat?.dm?.policy).toBe("disabled");
300
- expect(next?.channels?.googlechat?.accounts?.alerts?.dm?.policy).toBe("open");
301
- });
302
-
303
- it("uses configured defaultAccount for omitted allowFrom prompt context", async () => {
304
- const prompter = {
305
- note: vi.fn(async () => {}),
306
- text: vi.fn(async () => "users/123456789"),
307
- };
308
-
309
- const next = await googlechatSetupWizard.dmPolicy?.promptAllowFrom?.({
310
- cfg: {
311
- channels: {
312
- googlechat: {
313
- defaultAccount: "alerts",
314
- dm: {
315
- allowFrom: ["users/root"],
316
- },
317
- accounts: {
318
- alerts: {
319
- serviceAccount: { client_email: "bot@example.com" },
320
- dm: {
321
- allowFrom: ["users/alerts"],
322
- },
323
- },
324
- },
325
- },
326
- },
327
- } as KlawConfig,
328
- prompter: prompter as any,
329
- });
330
-
331
- expect(next?.channels?.googlechat?.dm?.allowFrom).toEqual(["users/root"]);
332
- expect(next?.channels?.googlechat?.accounts?.alerts?.dm?.allowFrom).toEqual([
333
- "users/123456789",
334
- ]);
335
- });
336
-
337
- it('writes open DM policy to the named account and preserves inherited allowFrom with "*"', () => {
338
- const next = googlechatSetupWizard.dmPolicy?.setPolicy(
339
- {
340
- channels: {
341
- googlechat: {
342
- dm: {
343
- allowFrom: ["users/123"],
344
- },
345
- accounts: {
346
- alerts: {
347
- serviceAccount: { client_email: "bot@example.com" },
348
- },
349
- },
350
- },
351
- },
352
- } as KlawConfig,
353
- "open",
354
- "alerts",
355
- );
356
-
357
- expect(next?.channels?.googlechat?.dm?.policy).toBeUndefined();
358
- expect(next?.channels?.googlechat?.accounts?.alerts?.dm?.policy).toBe("open");
359
- expect(next?.channels?.googlechat?.accounts?.alerts?.dm?.allowFrom).toEqual(["users/123", "*"]);
360
- });
361
-
362
- it("keeps startAccount pending until abort, then unregisters", async () => {
363
- const unregister = vi.fn();
364
- hoisted.startGoogleChatMonitor.mockResolvedValue(unregister);
365
-
366
- const { abort, patches, task, isSettled } = startAccountAndTrackLifecycle({
367
- startAccount: startGoogleChatGatewayAccount,
368
- account: buildAccount(),
369
- });
370
- await expectPendingUntilAbort({
371
- waitForStarted: waitForGoogleChatMonitorStarted,
372
- isSettled,
373
- abort,
374
- task,
375
- assertBeforeAbort: () => {
376
- expect(unregister).not.toHaveBeenCalled();
377
- },
378
- assertAfterAbort: () => {
379
- expect(unregister).toHaveBeenCalledOnce();
380
- },
381
- });
382
- expectLifecyclePatch(patches, { running: true });
383
- expectLifecyclePatch(patches, { running: false });
384
- });
385
- });
386
-
387
- describe("resolveGoogleChatAccount", () => {
388
- it("parses default-account env JSON credentials only when they decode to an object", () => {
389
- vi.stubEnv("GOOGLE_CHAT_SERVICE_ACCOUNT", '{"client_email":"bot@example.com"}');
390
-
391
- const resolved = resolveGoogleChatAccount({
392
- cfg: { channels: { googlechat: {} } },
393
- accountId: "default",
394
- });
395
-
396
- expect(resolved.credentialSource).toBe("env");
397
- expect(resolved.credentials).toEqual({ client_email: "bot@example.com" });
398
- });
399
-
400
- it("ignores env JSON credentials when they decode to a non-object value", () => {
401
- vi.stubEnv("GOOGLE_CHAT_SERVICE_ACCOUNT", '["not","an","object"]');
402
- vi.stubEnv("GOOGLE_CHAT_SERVICE_ACCOUNT_FILE", "/tmp/googlechat.json");
403
-
404
- const resolved = resolveGoogleChatAccount({
405
- cfg: { channels: { googlechat: {} } },
406
- accountId: "default",
407
- });
408
-
409
- expect(resolved.credentialSource).toBe("env");
410
- expect(resolved.credentials).toBeUndefined();
411
- expect(resolved.credentialsFile).toBe("/tmp/googlechat.json");
412
- });
413
-
414
- it("inherits shared defaults from accounts.default for named accounts", () => {
415
- const cfg: KlawConfig = {
416
- channels: {
417
- googlechat: {
418
- accounts: {
419
- default: {
420
- audienceType: "app-url",
421
- audience: "https://example.com/googlechat",
422
- webhookPath: "/googlechat",
423
- },
424
- andy: {
425
- serviceAccountFile: "/tmp/andy-sa.json",
426
- },
427
- },
428
- },
429
- },
430
- };
431
-
432
- const resolved = resolveGoogleChatAccount({ cfg, accountId: "andy" });
433
- expect(resolved.config.audienceType).toBe("app-url");
434
- expect(resolved.config.audience).toBe("https://example.com/googlechat");
435
- expect(resolved.config.webhookPath).toBe("/googlechat");
436
- expect(resolved.config.serviceAccountFile).toBe("/tmp/andy-sa.json");
437
- });
438
-
439
- it("prefers top-level and account overrides over accounts.default", () => {
440
- const cfg: KlawConfig = {
441
- channels: {
442
- googlechat: {
443
- audienceType: "project-number",
444
- audience: "1234567890",
445
- accounts: {
446
- default: {
447
- audienceType: "app-url",
448
- audience: "https://default.example.com/googlechat",
449
- webhookPath: "/googlechat-default",
450
- },
451
- april: {
452
- webhookPath: "/googlechat-april",
453
- },
454
- },
455
- },
456
- },
457
- };
458
-
459
- const resolved = resolveGoogleChatAccount({ cfg, accountId: "april" });
460
- expect(resolved.config.audienceType).toBe("project-number");
461
- expect(resolved.config.audience).toBe("1234567890");
462
- expect(resolved.config.webhookPath).toBe("/googlechat-april");
463
- });
464
-
465
- it("merges account bot loop protection over top-level defaults field-by-field", () => {
466
- const cfg: KlawConfig = {
467
- channels: {
468
- googlechat: {
469
- botLoopProtection: {
470
- maxEventsPerWindow: 8,
471
- windowSeconds: 120,
472
- cooldownSeconds: 240,
473
- },
474
- accounts: {
475
- april: {
476
- webhookPath: "/googlechat-april",
477
- botLoopProtection: {
478
- maxEventsPerWindow: 3,
479
- },
480
- },
481
- },
482
- },
483
- },
484
- };
485
-
486
- const resolved = resolveGoogleChatAccount({ cfg, accountId: "april" });
487
- expect(resolved.config.botLoopProtection).toEqual({
488
- maxEventsPerWindow: 3,
489
- windowSeconds: 120,
490
- cooldownSeconds: 240,
491
- });
492
- });
493
-
494
- it("merges account bot loop protection over accounts.default field-by-field", () => {
495
- const cfg: KlawConfig = {
496
- channels: {
497
- googlechat: {
498
- accounts: {
499
- default: {
500
- webhookPath: "/googlechat",
501
- botLoopProtection: {
502
- windowSeconds: 120,
503
- cooldownSeconds: 240,
504
- },
505
- },
506
- april: {
507
- webhookPath: "/googlechat-april",
508
- botLoopProtection: {
509
- maxEventsPerWindow: 3,
510
- },
511
- },
512
- },
513
- },
514
- },
515
- };
516
-
517
- const resolved = resolveGoogleChatAccount({ cfg, accountId: "april" });
518
- expect(resolved.config.botLoopProtection).toEqual({
519
- maxEventsPerWindow: 3,
520
- windowSeconds: 120,
521
- cooldownSeconds: 240,
522
- });
523
- });
524
-
525
- it("does not inherit disabled state from accounts.default for named accounts", () => {
526
- const cfg: KlawConfig = {
527
- channels: {
528
- googlechat: {
529
- accounts: {
530
- default: {
531
- enabled: false,
532
- audienceType: "app-url",
533
- audience: "https://example.com/googlechat",
534
- },
535
- andy: {
536
- serviceAccountFile: "/tmp/andy-sa.json",
537
- },
538
- },
539
- },
540
- },
541
- };
542
-
543
- const resolved = resolveGoogleChatAccount({ cfg, accountId: "andy" });
544
- expect(resolved.enabled).toBe(true);
545
- expect(resolved.config.enabled).toBeUndefined();
546
- expect(resolved.config.audienceType).toBe("app-url");
547
- });
548
-
549
- it("does not inherit default-account credentials into named accounts", () => {
550
- const cfg: KlawConfig = {
551
- channels: {
552
- googlechat: {
553
- accounts: {
554
- default: {
555
- serviceAccountRef: {
556
- source: "env",
557
- provider: "test",
558
- id: "default-sa",
559
- },
560
- audienceType: "app-url",
561
- audience: "https://example.com/googlechat",
562
- },
563
- andy: {
564
- serviceAccountFile: "/tmp/andy-sa.json",
565
- },
566
- },
567
- },
568
- },
569
- };
570
-
571
- const resolved = resolveGoogleChatAccount({ cfg, accountId: "andy" });
572
- expect(resolved.credentialSource).toBe("file");
573
- expect(resolved.credentialsFile).toBe("/tmp/andy-sa.json");
574
- expect(resolved.config.audienceType).toBe("app-url");
575
- });
576
-
577
- it("does not inherit dangerous name matching from accounts.default", () => {
578
- const cfg: KlawConfig = {
579
- channels: {
580
- googlechat: {
581
- accounts: {
582
- default: {
583
- dangerouslyAllowNameMatching: true,
584
- audienceType: "app-url",
585
- audience: "https://example.com/googlechat",
586
- },
587
- andy: {
588
- serviceAccountFile: "/tmp/andy-sa.json",
589
- },
590
- },
591
- },
592
- },
593
- };
594
-
595
- const resolved = resolveGoogleChatAccount({ cfg, accountId: "andy" });
596
- expect(resolved.config.dangerouslyAllowNameMatching).toBeUndefined();
597
- expect(resolved.config.audienceType).toBe("app-url");
598
- });
599
-
600
- it("uses configured defaultAccount when accountId is omitted", () => {
601
- const cfg: KlawConfig = {
602
- channels: {
603
- googlechat: {
604
- defaultAccount: "alerts",
605
- accounts: {
606
- alerts: {
607
- serviceAccountFile: "/tmp/alerts-sa.json",
608
- },
609
- },
610
- },
611
- },
612
- };
613
-
614
- const resolved = resolveGoogleChatAccount({ cfg });
615
- expect(resolved.accountId).toBe("alerts");
616
- expect(resolved.credentialSource).toBe("file");
617
- expect(resolved.credentialsFile).toBe("/tmp/alerts-sa.json");
618
- });
619
- });