vellum 0.2.10 → 0.2.12

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.
@@ -2,9 +2,8 @@
2
2
  * Integration tests for Twilio webhook route handlers.
3
3
  *
4
4
  * Tests:
5
- * - Signature valid/invalid/missing header
6
- * - Fail-closed behavior when auth token is not configured
7
- * - TWILIO_WEBHOOK_VALIDATION_DISABLED env flag bypass
5
+ * - Gateway-only blocking of direct webhook routes (signature validation
6
+ * is now handled at the gateway, not the runtime)
8
7
  * - Duplicate callback replay (idempotency)
9
8
  * - Unknown status and malformed payload handling
10
9
  * - Handler-level idempotency concurrency (concurrent duplicates, failure-retry)
@@ -122,11 +121,8 @@ import {
122
121
  getCallSession,
123
122
  updateCallSession,
124
123
  getCallEvents,
125
- buildCallbackDedupeKey,
126
- claimCallback,
127
- releaseCallbackClaim,
128
124
  } from '../calls/call-store.js';
129
- import { resolveRelayUrl, handleStatusCallback } from '../calls/twilio-routes.js';
125
+ import { resolveRelayUrl, handleStatusCallback, handleVoiceWebhook } from '../calls/twilio-routes.js';
130
126
  import { registerCallCompletionNotifier, unregisterCallCompletionNotifier } from '../calls/call-state.js';
131
127
 
132
128
  initializeDb();
@@ -187,6 +183,22 @@ function createTestSession(convId: string, callSid: string) {
187
183
  return session;
188
184
  }
189
185
 
186
+ function makeStatusRequest(params: Record<string, string>): Request {
187
+ return new Request('http://127.0.0.1/v1/calls/twilio/status', {
188
+ method: 'POST',
189
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
190
+ body: new URLSearchParams(params).toString(),
191
+ });
192
+ }
193
+
194
+ function makeVoiceRequest(sessionId: string, params: Record<string, string>): Request {
195
+ return new Request(`http://127.0.0.1/v1/calls/twilio/voice-webhook?callSessionId=${sessionId}`, {
196
+ method: 'POST',
197
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
198
+ body: new URLSearchParams(params).toString(),
199
+ });
200
+ }
201
+
190
202
  // ── Tests ──────────────────────────────────────────────────────────────
191
203
 
192
204
  describe('twilio webhook routes', () => {
@@ -239,23 +251,24 @@ describe('twilio webhook routes', () => {
239
251
  };
240
252
  }
241
253
 
242
- // ── Signature validation tests ─────────────────────────────────────
254
+ // ── Gateway-only blocking tests ───────────────────────────────────
255
+ // Direct Twilio webhook routes are blocked in gateway-only mode.
256
+ // Signature validation is now handled at the gateway level, not the runtime.
243
257
 
244
- describe('signature validation', () => {
245
- test('valid signature returns 200', async () => {
258
+ describe('gateway-only blocking of direct webhook routes', () => {
259
+ test('direct status callback returns 410', async () => {
246
260
  await startServer();
247
- createTestSession('conv-sig-1', 'CA_sig_valid');
248
261
  const url = statusUrl();
249
262
  const params = { CallSid: 'CA_sig_valid', CallStatus: 'completed' };
250
263
  const { body, headers } = signedRequest(url, params);
251
264
 
252
265
  const res = await fetch(url, { method: 'POST', headers, body });
253
- expect(res.status).toBe(200);
266
+ expect(res.status).toBe(410);
254
267
 
255
268
  await stopServer();
256
269
  });
257
270
 
258
- test('missing X-Twilio-Signature header returns 403', async () => {
271
+ test('direct status callback without signature returns 410', async () => {
259
272
  await startServer();
260
273
  const url = statusUrl();
261
274
  const params = { CallSid: 'CA_no_sig', CallStatus: 'completed' };
@@ -266,14 +279,12 @@ describe('twilio webhook routes', () => {
266
279
  body: buildFormBody(params),
267
280
  });
268
281
 
269
- expect(res.status).toBe(403);
270
- const body = await res.json() as { error: string };
271
- expect(body.error).toBe('Forbidden');
282
+ expect(res.status).toBe(410);
272
283
 
273
284
  await stopServer();
274
285
  });
275
286
 
276
- test('invalid signature returns 403', async () => {
287
+ test('direct status callback with invalid signature returns 410', async () => {
277
288
  await startServer();
278
289
  const url = statusUrl();
279
290
  const params = { CallSid: 'CA_bad_sig', CallStatus: 'completed' };
@@ -287,27 +298,27 @@ describe('twilio webhook routes', () => {
287
298
  body: buildFormBody(params),
288
299
  });
289
300
 
290
- expect(res.status).toBe(403);
301
+ expect(res.status).toBe(410);
291
302
 
292
303
  await stopServer();
293
304
  });
294
305
 
295
- test('signature computed with wrong token returns 403', async () => {
306
+ test('direct status callback with wrong token signature returns 410', async () => {
296
307
  await startServer();
297
308
  const url = statusUrl();
298
309
  const params = { CallSid: 'CA_wrong_token', CallStatus: 'completed' };
299
- const wrongSig = computeSignature(url, params, 'wrong-auth-token');
310
+ computeSignature(url, params, 'wrong-auth-token');
300
311
 
301
312
  const res = await fetch(url, {
302
313
  method: 'POST',
303
314
  headers: {
304
315
  'Content-Type': 'application/x-www-form-urlencoded',
305
- 'X-Twilio-Signature': wrongSig,
316
+ 'X-Twilio-Signature': computeSignature(url, params, 'wrong-auth-token'),
306
317
  },
307
318
  body: buildFormBody(params),
308
319
  });
309
320
 
310
- expect(res.status).toBe(403);
321
+ expect(res.status).toBe(410);
311
322
 
312
323
  await stopServer();
313
324
  });
@@ -316,7 +327,7 @@ describe('twilio webhook routes', () => {
316
327
  // ── Fail-closed behavior ──────────────────────────────────────────
317
328
 
318
329
  describe('fail-closed when auth token missing', () => {
319
- test('returns 403 when auth token is not configured', async () => {
330
+ test('direct route returns 410 regardless of auth token config', async () => {
320
331
  mockAuthToken = undefined;
321
332
  await startServer();
322
333
 
@@ -329,7 +340,7 @@ describe('twilio webhook routes', () => {
329
340
  body: buildFormBody(params),
330
341
  });
331
342
 
332
- expect(res.status).toBe(403);
343
+ expect(res.status).toBe(410);
333
344
 
334
345
  await stopServer();
335
346
  });
@@ -338,12 +349,11 @@ describe('twilio webhook routes', () => {
338
349
  // ── TWILIO_WEBHOOK_VALIDATION_DISABLED bypass ─────────────────────
339
350
 
340
351
  describe('validation disabled env flag', () => {
341
- test('skips validation when TWILIO_WEBHOOK_VALIDATION_DISABLED=true', async () => {
352
+ test('direct route returns 410 even when validation disabled', async () => {
342
353
  process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED = 'true';
343
- mockAuthToken = undefined; // Token not configured, but bypass should work
354
+ mockAuthToken = undefined;
344
355
  await startServer();
345
356
 
346
- createTestSession('conv-bypass-1', 'CA_bypass');
347
357
  const url = statusUrl();
348
358
  const params = { CallSid: 'CA_bypass', CallStatus: 'completed' };
349
359
 
@@ -353,12 +363,12 @@ describe('twilio webhook routes', () => {
353
363
  body: buildFormBody(params),
354
364
  });
355
365
 
356
- expect(res.status).toBe(200);
366
+ expect(res.status).toBe(410);
357
367
 
358
368
  await stopServer();
359
369
  });
360
370
 
361
- test('does NOT skip validation when TWILIO_WEBHOOK_VALIDATION_DISABLED is set but not "true"', async () => {
371
+ test('direct route returns 410 when env var is non-true value', async () => {
362
372
  process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED = '1';
363
373
  mockAuthToken = undefined;
364
374
  await startServer();
@@ -372,13 +382,12 @@ describe('twilio webhook routes', () => {
372
382
  body: buildFormBody(params),
373
383
  });
374
384
 
375
- // Should fail-closed: token missing and bypass not activated
376
- expect(res.status).toBe(403);
385
+ expect(res.status).toBe(410);
377
386
 
378
387
  await stopServer();
379
388
  });
380
389
 
381
- test('does NOT skip validation when env var is empty string', async () => {
390
+ test('direct route returns 410 when env var is empty string', async () => {
382
391
  process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED = '';
383
392
  mockAuthToken = undefined;
384
393
  await startServer();
@@ -392,148 +401,113 @@ describe('twilio webhook routes', () => {
392
401
  body: buildFormBody(params),
393
402
  });
394
403
 
395
- expect(res.status).toBe(403);
404
+ expect(res.status).toBe(410);
396
405
 
397
406
  await stopServer();
398
407
  });
399
408
  });
400
409
 
401
410
  // ── Callback idempotency / replay tests ───────────────────────────
411
+ // These call handleStatusCallback directly (bypassing the HTTP server)
412
+ // since direct routes are blocked by gateway-only mode.
402
413
 
403
414
  describe('callback idempotency', () => {
404
415
  test('replaying the same status callback does not create duplicate events', async () => {
405
- await startServer();
406
416
  const session = createTestSession('conv-idem-1', 'CA_idem_1');
407
- const url = statusUrl();
408
417
  const params = {
409
418
  CallSid: 'CA_idem_1',
410
419
  CallStatus: 'in-progress',
411
420
  Timestamp: '2025-01-15T10:00:00Z',
412
421
  };
413
- const { body, headers } = signedRequest(url, params);
414
422
 
415
423
  // First callback — should process
416
- const res1 = await fetch(url, { method: 'POST', headers, body });
424
+ const res1 = await handleStatusCallback(makeStatusRequest(params));
417
425
  expect(res1.status).toBe(200);
418
426
 
419
427
  // Second callback (replay) — should return 200 but not create new events
420
- const res2 = await fetch(url, { method: 'POST', headers, body });
428
+ const res2 = await handleStatusCallback(makeStatusRequest(params));
421
429
  expect(res2.status).toBe(200);
422
430
 
423
431
  // Verify only one event was recorded
424
432
  const events = getCallEvents(session.id);
425
433
  const connectedEvents = events.filter(e => e.eventType === 'call_connected');
426
434
  expect(connectedEvents.length).toBe(1);
427
-
428
- await stopServer();
429
435
  });
430
436
 
431
437
  test('different statuses for the same call create separate events', async () => {
432
- await startServer();
433
438
  const session = createTestSession('conv-idem-2', 'CA_idem_2');
434
- const url = statusUrl();
435
439
 
436
440
  // First: ringing
437
- const params1 = { CallSid: 'CA_idem_2', CallStatus: 'ringing', Timestamp: 'T1' };
438
- const req1 = signedRequest(url, params1);
439
- await fetch(url, { method: 'POST', headers: req1.headers, body: req1.body });
441
+ await handleStatusCallback(makeStatusRequest({
442
+ CallSid: 'CA_idem_2', CallStatus: 'ringing', Timestamp: 'T1',
443
+ }));
440
444
 
441
445
  // Second: in-progress (different status)
442
- const params2 = { CallSid: 'CA_idem_2', CallStatus: 'in-progress', Timestamp: 'T2' };
443
- const req2 = signedRequest(url, params2);
444
- await fetch(url, { method: 'POST', headers: req2.headers, body: req2.body });
446
+ await handleStatusCallback(makeStatusRequest({
447
+ CallSid: 'CA_idem_2', CallStatus: 'in-progress', Timestamp: 'T2',
448
+ }));
445
449
 
446
450
  const events = getCallEvents(session.id);
447
451
  expect(events.length).toBe(2);
448
-
449
- await stopServer();
450
452
  });
451
453
 
452
454
  test('third replay of same callback is still no-op', async () => {
453
- await startServer();
454
455
  const session = createTestSession('conv-idem-3', 'CA_idem_3');
455
- const url = statusUrl();
456
456
  const params = {
457
457
  CallSid: 'CA_idem_3',
458
458
  CallStatus: 'completed',
459
459
  Timestamp: '2025-01-15T11:00:00Z',
460
460
  };
461
- const { body, headers } = signedRequest(url, params);
462
461
 
463
462
  // Process three times
464
- await fetch(url, { method: 'POST', headers, body });
465
- await fetch(url, { method: 'POST', headers, body });
466
- await fetch(url, { method: 'POST', headers, body });
463
+ await handleStatusCallback(makeStatusRequest(params));
464
+ await handleStatusCallback(makeStatusRequest(params));
465
+ await handleStatusCallback(makeStatusRequest(params));
467
466
 
468
467
  const events = getCallEvents(session.id);
469
468
  const endedEvents = events.filter(e => e.eventType === 'call_ended');
470
469
  expect(endedEvents.length).toBe(1);
471
-
472
- await stopServer();
473
470
  });
474
471
  });
475
472
 
476
473
  // ── Unknown status + malformed payload tests ──────────────────────
474
+ // Call handleStatusCallback directly since direct routes are blocked.
477
475
 
478
476
  describe('unknown status and malformed payloads', () => {
479
477
  test('unknown Twilio status returns 200 but does not record event', async () => {
480
- await startServer();
481
478
  const session = createTestSession('conv-unknown-1', 'CA_unknown_1');
482
- const url = statusUrl();
483
479
  const params = {
484
480
  CallSid: 'CA_unknown_1',
485
481
  CallStatus: 'some-future-status',
486
482
  Timestamp: 'T1',
487
483
  };
488
- const { body, headers } = signedRequest(url, params);
489
484
 
490
- const res = await fetch(url, { method: 'POST', headers, body });
485
+ const res = await handleStatusCallback(makeStatusRequest(params));
491
486
  expect(res.status).toBe(200);
492
487
 
493
488
  const events = getCallEvents(session.id);
494
489
  expect(events.length).toBe(0);
495
-
496
- await stopServer();
497
490
  });
498
491
 
499
492
  test('missing CallSid returns 200 (graceful handling)', async () => {
500
- await startServer();
501
- const url = statusUrl();
502
- const params = { CallStatus: 'completed' };
503
- const { body, headers } = signedRequest(url, params);
504
-
505
- const res = await fetch(url, { method: 'POST', headers, body });
493
+ const res = await handleStatusCallback(makeStatusRequest({ CallStatus: 'completed' }));
506
494
  expect(res.status).toBe(200);
507
-
508
- await stopServer();
509
495
  });
510
496
 
511
497
  test('missing CallStatus returns 200 (graceful handling)', async () => {
512
- await startServer();
513
- const url = statusUrl();
514
- const params = { CallSid: 'CA_no_status' };
515
- const { body, headers } = signedRequest(url, params);
516
-
517
- const res = await fetch(url, { method: 'POST', headers, body });
498
+ const res = await handleStatusCallback(makeStatusRequest({ CallSid: 'CA_no_status' }));
518
499
  expect(res.status).toBe(200);
519
-
520
- await stopServer();
521
500
  });
522
501
 
523
502
  test('CallSid not matching any session returns 200 without error', async () => {
524
- await startServer();
525
- const url = statusUrl();
526
503
  const params = {
527
504
  CallSid: 'CA_nonexistent_session',
528
505
  CallStatus: 'completed',
529
506
  Timestamp: 'T1',
530
507
  };
531
- const { body, headers } = signedRequest(url, params);
532
508
 
533
- const res = await fetch(url, { method: 'POST', headers, body });
509
+ const res = await handleStatusCallback(makeStatusRequest(params));
534
510
  expect(res.status).toBe(200);
535
-
536
- await stopServer();
537
511
  });
538
512
  });
539
513
 
@@ -688,78 +662,53 @@ describe('twilio webhook routes', () => {
688
662
  });
689
663
 
690
664
  // ── TwiML relay URL generation ──────────────────────────────────────
665
+ // Call handleVoiceWebhook directly since direct routes are blocked.
691
666
 
692
667
  describe('voice webhook TwiML relay URL', () => {
693
- function voiceUrl(sessionId: string): string {
694
- return `http://127.0.0.1:${port}/v1/calls/twilio/voice-webhook?callSessionId=${sessionId}`;
695
- }
696
-
697
668
  test('TwiML uses explicit wssBaseUrl when set', async () => {
698
669
  mockWssBaseUrl = 'wss://explicit-ws.example.com';
699
- process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED = 'true';
700
- await startServer();
701
670
 
702
671
  const session = createTestSession('conv-twiml-1', 'CA_twiml_1');
703
- const url = voiceUrl(session.id);
704
- const params = { CallSid: 'CA_twiml_1' };
705
- const body = buildFormBody(params);
672
+ const req = makeVoiceRequest(session.id, { CallSid: 'CA_twiml_1' });
706
673
 
707
- const res = await fetch(url, {
708
- method: 'POST',
709
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
710
- body,
711
- });
674
+ const res = await handleVoiceWebhook(req);
712
675
 
713
676
  expect(res.status).toBe(200);
714
677
  const twiml = await res.text();
715
678
  expect(twiml).toContain('wss://explicit-ws.example.com/v1/calls/relay');
716
-
717
- await stopServer();
718
679
  });
719
680
 
720
681
  test('TwiML falls back to webhookBaseUrl when wssBaseUrl is empty', async () => {
721
682
  mockWssBaseUrl = '';
722
683
  mockWebhookBaseUrl = 'https://gateway.example.com';
723
- process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED = 'true';
724
- await startServer();
725
684
 
726
685
  const session = createTestSession('conv-twiml-2', 'CA_twiml_2');
727
- const url = voiceUrl(session.id);
728
- const params = { CallSid: 'CA_twiml_2' };
729
- const body = buildFormBody(params);
686
+ const req = makeVoiceRequest(session.id, { CallSid: 'CA_twiml_2' });
730
687
 
731
- const res = await fetch(url, {
732
- method: 'POST',
733
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
734
- body,
735
- });
688
+ const res = await handleVoiceWebhook(req);
736
689
 
737
690
  expect(res.status).toBe(200);
738
691
  const twiml = await res.text();
739
692
  expect(twiml).toContain('wss://gateway.example.com/v1/calls/relay');
740
-
741
- await stopServer();
742
693
  });
743
694
  });
744
695
 
745
696
  // ── Handler-level idempotency concurrency tests ─────────────────
697
+ // Call handleStatusCallback directly since direct routes are blocked.
746
698
 
747
699
  describe('handler-level idempotency concurrency', () => {
748
700
  test('two concurrent identical status callbacks produce exactly one event', async () => {
749
- await startServer();
750
701
  const session = createTestSession('conv-conc-1', 'CA_conc_1');
751
- const url = statusUrl();
752
702
  const params = {
753
703
  CallSid: 'CA_conc_1',
754
704
  CallStatus: 'in-progress',
755
705
  Timestamp: '2025-01-20T10:00:00Z',
756
706
  };
757
- const { body, headers } = signedRequest(url, params);
758
707
 
759
708
  // Fire two identical callbacks concurrently
760
709
  const [res1, res2] = await Promise.all([
761
- fetch(url, { method: 'POST', headers, body }),
762
- fetch(url, { method: 'POST', headers, body }),
710
+ handleStatusCallback(makeStatusRequest(params)),
711
+ handleStatusCallback(makeStatusRequest(params)),
763
712
  ]);
764
713
 
765
714
  // Both should return 200 (one processes, one is deduplicated)
@@ -770,26 +719,21 @@ describe('twilio webhook routes', () => {
770
719
  const events = getCallEvents(session.id);
771
720
  const connectedEvents = events.filter(e => e.eventType === 'call_connected');
772
721
  expect(connectedEvents.length).toBe(1);
773
-
774
- await stopServer();
775
722
  });
776
723
 
777
724
  test('three concurrent identical status callbacks still produce exactly one event', async () => {
778
- await startServer();
779
725
  const session = createTestSession('conv-conc-2', 'CA_conc_2');
780
- const url = statusUrl();
781
726
  const params = {
782
727
  CallSid: 'CA_conc_2',
783
728
  CallStatus: 'completed',
784
729
  Timestamp: '2025-01-20T11:00:00Z',
785
730
  };
786
- const { body, headers } = signedRequest(url, params);
787
731
 
788
732
  // Fire three identical callbacks concurrently
789
733
  const [res1, res2, res3] = await Promise.all([
790
- fetch(url, { method: 'POST', headers, body }),
791
- fetch(url, { method: 'POST', headers, body }),
792
- fetch(url, { method: 'POST', headers, body }),
734
+ handleStatusCallback(makeStatusRequest(params)),
735
+ handleStatusCallback(makeStatusRequest(params)),
736
+ handleStatusCallback(makeStatusRequest(params)),
793
737
  ]);
794
738
 
795
739
  expect(res1.status).toBe(200);
@@ -799,14 +743,10 @@ describe('twilio webhook routes', () => {
799
743
  const events = getCallEvents(session.id);
800
744
  const endedEvents = events.filter(e => e.eventType === 'call_ended');
801
745
  expect(endedEvents.length).toBe(1);
802
-
803
- await stopServer();
804
746
  });
805
747
 
806
748
  test('processing failure releases claim and allows successful retry', async () => {
807
- await startServer();
808
749
  const session = createTestSession('conv-conc-3', 'CA_conc_3');
809
- const url = statusUrl();
810
750
  const params = {
811
751
  CallSid: 'CA_conc_3',
812
752
  CallStatus: 'in-progress',
@@ -829,14 +769,8 @@ describe('twilio webhook routes', () => {
829
769
  return originalRecordCallEvent(...args);
830
770
  });
831
771
 
832
- // Call handleStatusCallback directly (not through Bun.serve) so we can
833
- // catch the re-thrown error without Bun's HTTP server swallowing it.
834
- const formBody = new URLSearchParams(params).toString();
835
- const directReq = new Request(url, {
836
- method: 'POST',
837
- body: formBody,
838
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
839
- });
772
+ // Call handleStatusCallback directly so we can catch the re-thrown error
773
+ const directReq = makeStatusRequest(params);
840
774
 
841
775
  // The handler should claim → throw in recordCallEvent → catch releases claim → re-throw
842
776
  let handlerThrew = false;
@@ -852,46 +786,37 @@ describe('twilio webhook routes', () => {
852
786
  const eventsAfterFailure = getCallEvents(session.id);
853
787
  expect(eventsAfterFailure.length).toBe(0);
854
788
 
855
- // Retry via the real HTTP handler — should succeed because the catch block
856
- // released the claim, allowing a fresh claim on retry.
857
- const { body, headers } = signedRequest(url, params);
858
- const res = await fetch(url, { method: 'POST', headers, body });
859
- expect(res.status).toBe(200);
789
+ // Retry — should succeed because the catch block released the claim
790
+ const retryRes = await handleStatusCallback(makeStatusRequest(params));
791
+ expect(retryRes.status).toBe(200);
860
792
 
861
793
  // Now exactly one event should exist from the successful retry
862
794
  const eventsAfterRetry = getCallEvents(session.id);
863
795
  const connectedEvents = eventsAfterRetry.filter(e => e.eventType === 'call_connected');
864
796
  expect(connectedEvents.length).toBe(1);
865
-
866
- await stopServer();
867
797
  });
868
798
 
869
799
  test('permanently claimed callback cannot be retried', async () => {
870
- await startServer();
871
800
  const session = createTestSession('conv-conc-4', 'CA_conc_4');
872
- const url = statusUrl();
873
801
  const params = {
874
802
  CallSid: 'CA_conc_4',
875
803
  CallStatus: 'completed',
876
804
  Timestamp: '2025-01-20T13:00:00Z',
877
805
  };
878
- const { body, headers } = signedRequest(url, params);
879
806
 
880
807
  // First request processes successfully and finalizes the claim
881
- const res1 = await fetch(url, { method: 'POST', headers, body });
808
+ const res1 = await handleStatusCallback(makeStatusRequest(params));
882
809
  expect(res1.status).toBe(200);
883
810
 
884
811
  const events1 = getCallEvents(session.id);
885
812
  expect(events1.filter(e => e.eventType === 'call_ended').length).toBe(1);
886
813
 
887
814
  // Second request (retry) — should be deduplicated, no new events
888
- const res2 = await fetch(url, { method: 'POST', headers, body });
815
+ const res2 = await handleStatusCallback(makeStatusRequest(params));
889
816
  expect(res2.status).toBe(200);
890
817
 
891
818
  const events2 = getCallEvents(session.id);
892
819
  expect(events2.filter(e => e.eventType === 'call_ended').length).toBe(1);
893
-
894
- await stopServer();
895
820
  });
896
821
  });
897
822
  });
@@ -306,7 +306,7 @@ describe('Twitter auth handler', () => {
306
306
  };
307
307
 
308
308
  const msg: TwitterAuthStartRequest = { type: 'twitter_auth_start' };
309
- const { ctx, sent } = createTestContext();
309
+ const { ctx } = createTestContext();
310
310
  await handleMessage(msg, {} as net.Socket, ctx);
311
311
 
312
312
  await new Promise((r) => setTimeout(r, 50));