Skip to content

Commit 60f5c56

Browse files
committed
Fix unit tests
1 parent 1477de1 commit 60f5c56

File tree

2 files changed

+27
-119
lines changed

2 files changed

+27
-119
lines changed

packages/ai/src/requests/request.test.ts

Lines changed: 25 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@ import Sinon, { match, restore, stub, useFakeTimers } from 'sinon';
2020
import sinonChai from 'sinon-chai';
2121
import chaiAsPromised from 'chai-as-promised';
2222
import {
23+
ABORT_ERROR_NAME,
2324
RequestURL,
2425
ServerPromptTemplateTask,
26+
TIMEOUT_EXPIRED_MESSAGE,
2527
Task,
2628
getHeaders,
2729
makeRequest
@@ -306,7 +308,7 @@ describe('request methods', () => {
306308
const signal = options!.signal;
307309
return new Promise((_resolve, reject): void => {
308310
const abortListener = (): void => {
309-
reject(new DOMException(signal?.reason || 'Aborted', 'AbortError'));
311+
reject(new DOMException(signal?.reason || 'Aborted', ABORT_ERROR_NAME));
310312
};
311313

312314
signal?.addEventListener('abort', abortListener, { once: true });
@@ -343,7 +345,7 @@ describe('request methods', () => {
343345
fetchStub.resolves({
344346
ok: false,
345347
status: 500,
346-
statusText: 'AbortError'
348+
statusText: ABORT_ERROR_NAME
347349
} as Response);
348350

349351
try {
@@ -363,7 +365,7 @@ describe('request methods', () => {
363365
expect((e as AIError).code).to.equal(AIErrorCode.FETCH_ERROR);
364366
expect((e as AIError).customErrorData?.status).to.equal(500);
365367
expect((e as AIError).customErrorData?.statusText).to.equal(
366-
'AbortError'
368+
ABORT_ERROR_NAME
367369
);
368370
expect((e as AIError).message).to.include('500 AbortError');
369371
}
@@ -512,7 +514,7 @@ describe('request methods', () => {
512514

513515
expect(fetchStub).not.to.have.been.called;
514516
});
515-
it('should abort fetch if external signal aborts during request', async () => {
517+
it('should throw DOMException if external signal aborts during request', async () => {
516518
fetchStub.callsFake(fetchAborter);
517519
const controller = new AbortController();
518520
const abortReason = 'Aborted during request';
@@ -531,7 +533,10 @@ describe('request methods', () => {
531533
await clock.tickAsync(0);
532534
controller.abort(abortReason);
533535

534-
await expect(requestPromise).to.be.rejectedWith('Aborted during request');
536+
await expect(requestPromise).to.be.rejectedWith(
537+
DOMException,
538+
abortReason
539+
);
535540
});
536541

537542
it('should abort fetch if timeout expires during request', async () => {
@@ -552,16 +557,16 @@ describe('request methods', () => {
552557
await clock.tickAsync(timeoutDuration + 100);
553558

554559
await expect(requestPromise).to.be.rejectedWith(
555-
'AbortError',
556-
'Timeout has expired'
560+
DOMException,
561+
TIMEOUT_EXPIRED_MESSAGE
557562
);
558563

559564
expect(fetchStub).to.have.been.calledOnce;
560565
const fetchOptions = fetchStub.firstCall.args[1] as RequestInit;
561566
const internalSignal = fetchOptions.signal;
562567

563568
expect(internalSignal?.aborted).to.be.true;
564-
expect((internalSignal?.reason as Error).name).to.equal('AbortError');
569+
expect((internalSignal?.reason as Error).name).to.equal(ABORT_ERROR_NAME);
565570
expect((internalSignal?.reason as Error).message).to.equal(
566571
'Timeout has expired.'
567572
);
@@ -596,34 +601,6 @@ describe('request methods', () => {
596601
expect(fetchStub).to.have.been.calledOnce;
597602
});
598603

599-
it('should succeed and clear timeout/listener if fetch completes with signal provided but not aborted', async () => {
600-
const controller = new AbortController();
601-
const mockResponse = new Response('{}', {
602-
status: 200,
603-
statusText: 'OK'
604-
});
605-
const fetchPromise = Promise.resolve(mockResponse);
606-
fetchStub.resolves(fetchPromise);
607-
608-
const requestPromise = makeRequest(
609-
{
610-
model: 'models/model-name',
611-
task: Task.GENERATE_CONTENT,
612-
apiSettings: fakeApiSettings,
613-
stream: false,
614-
singleRequestOptions: { signal: controller.signal } // Generous timeout
615-
},
616-
'{}'
617-
);
618-
619-
// Advance time slightly
620-
await clock.tickAsync(10);
621-
622-
const response = await requestPromise;
623-
expect(response.ok).to.be.true;
624-
expect(fetchStub).to.have.been.calledOnce;
625-
});
626-
627604
it('should use external signal abort reason if it occurs before timeout', async () => {
628605
const controller = new AbortController();
629606
const abortReason = 'External Abort Wins';
@@ -648,7 +625,10 @@ describe('request methods', () => {
648625
await clock.tickAsync(timeoutDuration / 2);
649626
controller.abort(abortReason);
650627

651-
await expect(requestPromise).to.be.rejectedWith(abortReason);
628+
await expect(requestPromise).to.be.rejectedWith(
629+
DOMException,
630+
abortReason
631+
);
652632
});
653633

654634
it('should use timeout reason if it occurs before external signal abort', async () => {
@@ -678,8 +658,8 @@ describe('request methods', () => {
678658
await clock.tickAsync(timeoutDuration + 1);
679659

680660
await expect(requestPromise).to.be.rejectedWith(
681-
'AbortError',
682-
'Timeout has expired'
661+
DOMException,
662+
TIMEOUT_EXPIRED_MESSAGE
683663
);
684664
});
685665

@@ -707,80 +687,6 @@ describe('request methods', () => {
707687
expect(fetchOptions.signal?.aborted).to.be.false;
708688
});
709689

710-
it('should remove abort listener on successful completion to prevent memory leaks', async () => {
711-
const controller = new AbortController();
712-
const addSpy = Sinon.spy(controller.signal, 'addEventListener');
713-
const removeSpy = Sinon.spy(controller.signal, 'removeEventListener');
714-
715-
const mockResponse = new Response('{}', {
716-
status: 200,
717-
statusText: 'OK'
718-
});
719-
fetchStub.resolves(mockResponse);
720-
721-
await makeRequest(
722-
{
723-
model: 'models/model-name',
724-
task: Task.GENERATE_CONTENT,
725-
apiSettings: fakeApiSettings,
726-
stream: false,
727-
singleRequestOptions: { signal: controller.signal }
728-
},
729-
'{}'
730-
);
731-
732-
expect(addSpy).to.have.been.calledOnceWith('abort');
733-
expect(removeSpy).to.have.been.calledOnceWith('abort');
734-
});
735-
736-
it('should remove listener if fetch itself rejects', async () => {
737-
const controller = new AbortController();
738-
const removeSpy = Sinon.spy(controller.signal, 'removeEventListener');
739-
const error = new Error('Network failure');
740-
fetchStub.rejects(error);
741-
742-
const requestPromise = makeRequest(
743-
{
744-
model: 'models/model-name',
745-
task: Task.GENERATE_CONTENT,
746-
apiSettings: fakeApiSettings,
747-
stream: false,
748-
singleRequestOptions: { signal: controller.signal }
749-
},
750-
'{}'
751-
);
752-
753-
await expect(requestPromise).to.be.rejectedWith(
754-
AIError,
755-
/Network failure/
756-
);
757-
expect(removeSpy).to.have.been.calledOnce;
758-
});
759-
760-
it('should remove listener if response is not ok', async () => {
761-
const controller = new AbortController();
762-
const removeSpy = Sinon.spy(controller.signal, 'removeEventListener');
763-
const mockResponse = new Response('{}', {
764-
status: 500,
765-
statusText: 'Internal Server Error'
766-
});
767-
fetchStub.resolves(mockResponse);
768-
769-
const requestPromise = makeRequest(
770-
{
771-
model: 'models/model-name',
772-
task: Task.GENERATE_CONTENT,
773-
apiSettings: fakeApiSettings,
774-
stream: false,
775-
singleRequestOptions: { signal: controller.signal }
776-
},
777-
'{}'
778-
);
779-
780-
await expect(requestPromise).to.be.rejectedWith(AIError, /500/);
781-
expect(removeSpy).to.have.been.calledOnce;
782-
});
783-
784690
it('should abort immediately if timeout is 0', async () => {
785691
fetchStub.callsFake(fetchAborter);
786692
const requestPromise = makeRequest(
@@ -797,19 +703,21 @@ describe('request methods', () => {
797703
// Tick the clock just enough to trigger a timeout(0)
798704
await clock.tickAsync(1);
799705

800-
await expect(requestPromise).to.be.rejectedWith('AbortError');
706+
await expect(requestPromise).to.be.rejectedWith(
707+
DOMException,
708+
TIMEOUT_EXPIRED_MESSAGE
709+
);
801710
});
802711

803712
it('should not error if signal is aborted after completion', async () => {
804713
const controller = new AbortController();
805-
const removeSpy = Sinon.spy(controller.signal, 'removeEventListener');
806714
const mockResponse = new Response('{}', {
807715
status: 200,
808716
statusText: 'OK'
809717
});
810718
fetchStub.resolves(mockResponse);
811719

812-
await makeRequest(
720+
const response = await makeRequest(
813721
{
814722
model: 'models/model-name',
815723
task: Task.GENERATE_CONTENT,
@@ -823,7 +731,7 @@ describe('request methods', () => {
823731
// Listener should be removed, so this abort should do nothing.
824732
controller.abort('Too late');
825733

826-
expect(removeSpy).to.have.been.calledOnce;
734+
expect(response.ok).to.be.true;
827735
});
828736
});
829737
});

packages/ai/src/requests/request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ import {
2727
import { logger } from '../logger';
2828
import { BackendType } from '../public-types';
2929

30-
const TIMEOUT_EXPIRED_MESSAGE = 'Timeout has expired.';
31-
const ABORT_ERROR_NAME = 'AbortError';
30+
export const TIMEOUT_EXPIRED_MESSAGE = 'Timeout has expired.';
31+
export const ABORT_ERROR_NAME = 'AbortError';
3232

3333
export const enum Task {
3434
GENERATE_CONTENT = 'generateContent',

0 commit comments

Comments
 (0)