From 1c0b32d015d678a52d03200c709564d5a0bf8480 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 14 Sep 2026 15:36:18 -0400 Subject: [PATCH 1/3] Run the unit tests on the bun engine --- .github/workflows/presubmit-bun.yml | 2 +- ci/run_conditional_tests.sh | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/presubmit-bun.yml b/.github/workflows/presubmit-bun.yml index 3543f8f6ab57..7d27d2b1df97 100644 --- a/.github/workflows/presubmit-bun.yml +++ b/.github/workflows/presubmit-bun.yml @@ -75,7 +75,7 @@ jobs: RUN_TESTS_MODE: RUN_UNIT_TESTS BUILD_TYPE: presubmit TEST_TYPE: units - TEST_CMD: bun run + TEST_CMD: bun --bun run SHARD_TOTAL: ${{ needs.setup.outputs.shard-total }} SHARD_INDEX: ${{ matrix.shard-index }} GIT_DIFF_ARG: HEAD^1 diff --git a/ci/run_conditional_tests.sh b/ci/run_conditional_tests.sh index 36e00d66c0f3..4b76c3f0deff 100755 --- a/ci/run_conditional_tests.sh +++ b/ci/run_conditional_tests.sh @@ -220,9 +220,12 @@ for subdir in ${subdirs[@]}; do echo "run samples tests for core/packages in ${d}" should_test=true fi + elif [[ "${TEST_TYPE}" == "lint" ]] || [[ "${TEST_TYPE}" == "units" ]]; then + echo "change detected in ${d} for ${TEST_TYPE} test" + should_test=true elif [[ "${d}" == core/packages/* ]] || [[ "${d}" == core/dev-packages/* ]]; then echo "skipping core package ${d} in non-core trigger" - elif [[ "${TEST_TYPE}" == "system" ]] || [[ "${TEST_TYPE}" == "lint" ]] || [[ "${TEST_TYPE}" == "units" ]]; then + elif [[ "${TEST_TYPE}" == "system" ]]; then echo "change detected in ${d} for ${TEST_TYPE} test" should_test=true elif [[ "${tests_with_credentials[*]}" =~ "${d}" ]] && [[ -n "${GOOGLE_APPLICATION_CREDENTIALS}" ]]; then From 7325f797c660e6238358b3a29477f1fc2532ff04 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 17 Sep 2026 14:22:25 -0400 Subject: [PATCH 2/3] Use the latest version of bun --- .github/workflows/presubmit-bun.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/presubmit-bun.yml b/.github/workflows/presubmit-bun.yml index 7d27d2b1df97..7e882c2a8b80 100644 --- a/.github/workflows/presubmit-bun.yml +++ b/.github/workflows/presubmit-bun.yml @@ -67,7 +67,7 @@ jobs: run: pnpm run compile - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: - bun-version: 1.3.14 + bun-version: latest - run: bun --version - run: bash ci/run_conditional_tests.sh --strict name: Run unit tests with Bun From abb43cb2ca63e8ac730dc84a6ed6f8d3629ede82 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Fri, 18 Sep 2026 17:15:46 -0400 Subject: [PATCH 3/3] Make updates to paginator to be Bun compatible --- .github/workflows/presubmit-bun.yml | 2 +- ci/run_single_test.sh | 6 +- core/paginator/bunfig.toml | 2 + core/paginator/jest.config.js | 5 +- core/paginator/package.json | 1 + core/paginator/src/index.ts | 6 +- core/paginator/test/bun-test-shim.ts | 39 ++++ core/paginator/test/bun-test.d.ts | 52 +++++ .../test/{index.ts => index.test.ts} | 193 ++++++++---------- ...urce-stream.ts => resource-stream.test.ts} | 56 +++-- 10 files changed, 229 insertions(+), 133 deletions(-) create mode 100644 core/paginator/bunfig.toml create mode 100644 core/paginator/test/bun-test-shim.ts create mode 100644 core/paginator/test/bun-test.d.ts rename core/paginator/test/{index.ts => index.test.ts} (78%) rename core/paginator/test/{resource-stream.ts => resource-stream.test.ts} (84%) diff --git a/.github/workflows/presubmit-bun.yml b/.github/workflows/presubmit-bun.yml index 7e882c2a8b80..fcd8ba3db6c7 100644 --- a/.github/workflows/presubmit-bun.yml +++ b/.github/workflows/presubmit-bun.yml @@ -75,7 +75,7 @@ jobs: RUN_TESTS_MODE: RUN_UNIT_TESTS BUILD_TYPE: presubmit TEST_TYPE: units - TEST_CMD: bun --bun run + TEST_CMD: bun run SHARD_TOTAL: ${{ needs.setup.outputs.shard-total }} SHARD_INDEX: ${{ matrix.shard-index }} GIT_DIFF_ARG: HEAD^1 diff --git a/ci/run_single_test.sh b/ci/run_single_test.sh index 8a03dfa4ff71..9ad01e03a5ba 100755 --- a/ci/run_single_test.sh +++ b/ci/run_single_test.sh @@ -107,7 +107,11 @@ system) retval=$? ;; units) - ${TEST_CMD} test + if [[ "${TEST_CMD}" == *"bun"* ]] && grep -q '"test:bun"' package.json; then + ${TEST_CMD} test:bun + else + ${TEST_CMD} test + fi retval=$? ;; *) diff --git a/core/paginator/bunfig.toml b/core/paginator/bunfig.toml new file mode 100644 index 000000000000..49211bd46e2d --- /dev/null +++ b/core/paginator/bunfig.toml @@ -0,0 +1,2 @@ +[test] +root = "./test" diff --git a/core/paginator/jest.config.js b/core/paginator/jest.config.js index 2a860f423405..479a6126c027 100644 --- a/core/paginator/jest.config.js +++ b/core/paginator/jest.config.js @@ -13,9 +13,12 @@ // limitations under the License. module.exports = { - testMatch: ['/test/**/*.ts'], + testMatch: ['/test/**/*.test.ts'], transform: { '^.+\\.tsx?$': ['ts-jest', {tsconfig: 'tsconfig.json'}], }, + moduleNameMapper: { + '^bun:test$': '/test/bun-test-shim.ts', + }, clearMocks: true, }; diff --git a/core/paginator/package.json b/core/paginator/package.json index 2c2d9c3a24ef..31593e6b45e8 100644 --- a/core/paginator/package.json +++ b/core/paginator/package.json @@ -10,6 +10,7 @@ "url": "https://github.com/googleapis/google-cloud-node.git" }, "scripts": { + "test:bun": "bun test", "test": "jest --coverage", "compile": "tsc -p .", "fix": "gts fix", diff --git a/core/paginator/src/index.ts b/core/paginator/src/index.ts index dd5f569840c3..9227cc39c07f 100644 --- a/core/paginator/src/index.ts +++ b/core/paginator/src/index.ts @@ -18,7 +18,9 @@ * @module common/paginator */ -import * as extend from 'extend'; +import * as extendMod from 'extend'; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const extend: typeof extendMod = (extendMod as any).default || extendMod; import {TransformOptions} from 'stream'; import {ResourceStream} from './resource-stream'; @@ -236,7 +238,7 @@ export class Paginator { if (!callback) { return promise.then(results => [results, query, ...otherArgs]); } - promise.then( + return promise.then( results => callback(null, results, query, ...otherArgs), (err: Error) => callback(err), ); diff --git a/core/paginator/test/bun-test-shim.ts b/core/paginator/test/bun-test-shim.ts new file mode 100644 index 000000000000..9037ec7280bf --- /dev/null +++ b/core/paginator/test/bun-test-shim.ts @@ -0,0 +1,39 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/* eslint-disable @typescript-eslint/no-explicit-any, import/no-extraneous-dependencies, n/no-extraneous-import */ +import {jest} from '@jest/globals'; + +export const describe = (globalThis as any).describe; +export const it = (globalThis as any).it; +export const test = (globalThis as any).test; +export const expect = (globalThis as any).expect; +export const beforeEach = (globalThis as any).beforeEach; +export const afterEach = (globalThis as any).afterEach; +export const beforeAll = (globalThis as any).beforeAll; +export const afterAll = (globalThis as any).afterAll; + +export const mock: any = Object.assign( + (...args: any[]) => (jest.fn as any)(...args), + { + module: (moduleName: string, factory: () => any) => { + jest.mock(moduleName, factory); + }, + restore: () => jest.restoreAllMocks(), + }, +); + +export const spyOn: any = (obj: any, method: any) => jest.spyOn(obj, method); +export type Mock = any; +export {jest}; diff --git a/core/paginator/test/bun-test.d.ts b/core/paginator/test/bun-test.d.ts new file mode 100644 index 000000000000..747c83e20e2c --- /dev/null +++ b/core/paginator/test/bun-test.d.ts @@ -0,0 +1,52 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +declare module 'bun:test' { + export type DoneCallback = (err?: any) => void; + export function describe(name: string, fn: () => void): void; + export namespace describe { + export function only(name: string, fn: () => void): void; + export function skip(name: string, fn: () => void): void; + } + export function it(name: string, fn: (done: DoneCallback) => any): void; + export namespace it { + export function only(name: string, fn: (done: DoneCallback) => any): void; + export function skip(name: string, fn: (done: DoneCallback) => any): void; + } + export function test(name: string, fn: (done: DoneCallback) => any): void; + export namespace test { + export function only(name: string, fn: (done: DoneCallback) => any): void; + export function skip(name: string, fn: (done: DoneCallback) => any): void; + } + export function beforeEach(fn: (done: DoneCallback) => any): void; + export function afterEach(fn: (done: DoneCallback) => any): void; + export function beforeAll(fn: (done: DoneCallback) => any): void; + export function afterAll(fn: (done: DoneCallback) => any): void; + export const expect: any; + export function mock any = (...args: any[]) => any>( + fn?: T + ): Mock; + export namespace mock { + export function module(moduleName: string, factory: () => any): void; + export function restore(): void; + } + export function spyOn(obj: any, method: any): Mock; + export const jest: any; + export interface Mock< + T extends (...args: any[]) => any = (...args: any[]) => any, + > { + (...args: Parameters): ReturnType; + mock: { + calls: any[]; + results: {type: string; value: any}[]; + lastCall?: any; + }; + mockImplementation( + fn?: (a?: any, b?: any, c?: any, d?: any, ...args: any[]) => any + ): this; + mockReturnValue(val: any): this; + mockResolvedValue(val: any): this; + mockRejectedValue(val: any): this; + mockRestore(): void; + mockClear(): void; + mockReset(): void; + } +} diff --git a/core/paginator/test/index.ts b/core/paginator/test/index.test.ts similarity index 78% rename from core/paginator/test/index.ts rename to core/paginator/test/index.test.ts index 4978c3231d6a..288d55370df0 100644 --- a/core/paginator/test/index.ts +++ b/core/paginator/test/index.test.ts @@ -12,8 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {PassThrough, Transform} from 'stream'; +import {PassThrough} from 'stream'; import * as crypto from 'crypto'; +import { + describe, + it, + expect, + mock, + spyOn, + beforeEach, + afterEach, +} from 'bun:test'; import {paginator, ParsedArguments, ResourceStream} from '../src'; const util = { @@ -22,26 +31,8 @@ const util = { }, }; -interface MockFakeResourceStream extends Transform { - calledWith: unknown[]; -} - -// Mock the resource-stream module so runAsStream_ instantiates the fake class -jest.mock('../src/resource-stream', () => { - const {Transform} = require('stream'); - return { - ResourceStream: class MockFakeResourceStream extends Transform { - calledWith: unknown[]; - constructor(...args: unknown[]) { - super({objectMode: true}); - this.calledWith = args; - } - }, - }; -}); - afterEach(() => { - jest.restoreAllMocks(); + mock.restore(); }); /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -86,7 +77,7 @@ describe('paginator', () => { }); it('should parse the arguments', done => { - jest.spyOn(paginator, 'parseArguments_').mockImplementation(args => { + spyOn(paginator, 'parseArguments_').mockImplementation(args => { try { expect([].slice.call(args)).toEqual([1, 2, 3]); done(); @@ -95,7 +86,7 @@ describe('paginator', () => { } return args as ParsedArguments; }); - jest.spyOn(paginator, 'run_').mockImplementation(util.noop); + spyOn(paginator, 'run_').mockImplementation(util.noop); paginator.extend(FakeClass, 'methodToExtend'); FakeClass.prototype.methodToExtend(1, 2, 3); }); @@ -104,18 +95,16 @@ describe('paginator', () => { const expectedReturnValue = FakeClass.prototype.methodToExtend(); const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments; - jest.spyOn(paginator, 'parseArguments_').mockReturnValue(parsedArguments); - jest - .spyOn(paginator, 'run_') - .mockImplementation((args, originalMethod) => { - try { - expect(args).toBe(parsedArguments); - expect(originalMethod()).toBe(expectedReturnValue); - done(); - } catch (e) { - done(e); - } - }); + spyOn(paginator, 'parseArguments_').mockReturnValue(parsedArguments); + spyOn(paginator, 'run_').mockImplementation((args, originalMethod) => { + try { + expect(args).toBe(parsedArguments); + expect(originalMethod()).toBe(expectedReturnValue); + done(); + } catch (e) { + done(e); + } + }); paginator.extend(FakeClass, 'methodToExtend'); FakeClass.prototype.methodToExtend(); @@ -129,7 +118,7 @@ describe('paginator', () => { const cls = new (FakeClass as any)(); cls.uuid = crypto.randomUUID(); - jest.spyOn(paginator, 'run_').mockImplementation((_, originalMethod) => { + spyOn(paginator, 'run_').mockImplementation((_, originalMethod) => { try { expect(originalMethod()).toBe(cls.uuid); done(); @@ -144,7 +133,7 @@ describe('paginator', () => { it('should return what the router returns', () => { const uniqueValue = 234; - jest.spyOn(paginator, 'run_').mockImplementation(() => { + spyOn(paginator, 'run_').mockImplementation(() => { return uniqueValue; }); paginator.extend(FakeClass, 'methodToExtend'); @@ -165,7 +154,7 @@ describe('paginator', () => { it('should parse the arguments', done => { const fakeArgs = [1, 2, 3]; - jest.spyOn(paginator, 'parseArguments_').mockImplementation(args => { + spyOn(paginator, 'parseArguments_').mockImplementation(args => { try { expect([].slice.call(args)).toEqual(fakeArgs); done(); @@ -174,27 +163,23 @@ describe('paginator', () => { } return args as ParsedArguments; }); - jest - .spyOn(paginator, 'runAsStream_') - .mockImplementation(createFakeStream); + spyOn(paginator, 'runAsStream_').mockImplementation(createFakeStream); FakeClass.prototype.streamMethod(...fakeArgs); }); it('should run the method as a stream', done => { const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments; - jest.spyOn(paginator, 'parseArguments_').mockReturnValue(parsedArguments); - jest - .spyOn(paginator, 'runAsStream_') - .mockImplementation((args, callback) => { - try { - expect(args).toBe(parsedArguments); - expect(callback()).toBe(UUID); - setImmediate(done); - } catch (e) { - done(e); - } - return createFakeStream(); - }); + spyOn(paginator, 'parseArguments_').mockReturnValue(parsedArguments); + spyOn(paginator, 'runAsStream_').mockImplementation((args, callback) => { + try { + expect(args).toBe(parsedArguments); + expect(callback()).toBe(UUID); + setImmediate(done); + } catch (e) { + done(e); + } + return createFakeStream(); + }); FakeClass.prototype.streamMethod(); }); @@ -204,18 +189,16 @@ describe('paginator', () => { FakeClass.prototype.methodToExtend = function (this: any) { return this; }; - jest.spyOn(paginator, 'parseArguments_').mockReturnValue(parsedArguments); - jest - .spyOn(paginator, 'runAsStream_') - .mockImplementation((_, callback) => { - try { - expect(callback()).toBe(FakeClass.prototype); - setImmediate(done); - } catch (e) { - done(e); - } - return createFakeStream(); - }); + spyOn(paginator, 'parseArguments_').mockReturnValue(parsedArguments); + spyOn(paginator, 'runAsStream_').mockImplementation((_, callback) => { + try { + expect(callback()).toBe(FakeClass.prototype); + setImmediate(done); + } catch (e) { + done(e); + } + return createFakeStream(); + }); FakeClass.prototype.streamMethod(); }); @@ -226,27 +209,25 @@ describe('paginator', () => { FakeClass.prototype.methodToExtend_ = () => { return fakeValue; }; - jest.spyOn(paginator, 'parseArguments_').mockReturnValue(parsedArguments); - jest - .spyOn(paginator, 'runAsStream_') - .mockImplementation((_, callback) => { - try { - expect(callback()).toBe(fakeValue); - setImmediate(done); - } catch (e) { - done(e); - } - return createFakeStream(); - }); + spyOn(paginator, 'parseArguments_').mockReturnValue(parsedArguments); + spyOn(paginator, 'runAsStream_').mockImplementation((_, callback) => { + try { + expect(callback()).toBe(fakeValue); + setImmediate(done); + } catch (e) { + done(e); + } + return createFakeStream(); + }); FakeClass.prototype.streamMethod(); }); it('should return a stream', () => { const fakeStream = createFakeStream(); - jest - .spyOn(paginator, 'parseArguments_') - .mockReturnValue({} as ParsedArguments); - jest.spyOn(paginator, 'runAsStream_').mockReturnValue(fakeStream); + spyOn(paginator, 'parseArguments_').mockReturnValue( + {} as ParsedArguments, + ); + spyOn(paginator, 'runAsStream_').mockReturnValue(fakeStream); const stream = FakeClass.prototype.streamMethod(); expect(stream).toBe(fakeStream); }); @@ -354,9 +335,8 @@ describe('paginator', () => { callback: util.noop, }; - jest - .spyOn(paginator, 'runAsStream_') - .mockImplementation((args, originalMethod) => { + spyOn(paginator, 'runAsStream_').mockImplementation( + (args, originalMethod) => { try { expect(args).toBe(parsedArguments); originalMethod(); @@ -364,7 +344,8 @@ describe('paginator', () => { done(e); } return createFakeStream(); - }); + }, + ); paginator.run_(parsedArguments, done); }); @@ -384,7 +365,7 @@ describe('paginator', () => { }, }; - jest.spyOn(paginator, 'runAsStream_').mockImplementation(() => { + spyOn(paginator, 'runAsStream_').mockImplementation(() => { const stream = createFakeStream(); setImmediate(() => { stream.emit('error', error); @@ -410,7 +391,7 @@ describe('paginator', () => { }, }; - jest.spyOn(paginator, 'runAsStream_').mockImplementation(() => { + spyOn(paginator, 'runAsStream_').mockImplementation(() => { const stream = createFakeStream(); setImmediate(() => { results.forEach(result => stream.push(result)); @@ -447,7 +428,7 @@ describe('paginator', () => { }, }; - jest.spyOn(paginator, 'runAsStream_').mockImplementation(() => { + spyOn(paginator, 'runAsStream_').mockImplementation(() => { const stream = createFakeStream(); setImmediate(() => { results.forEach(result => stream.push(result)); @@ -466,9 +447,8 @@ describe('paginator', () => { autoPaginate: true, }; it('should call runAsStream_ when autoPaginate:true', done => { - jest - .spyOn(paginator, 'runAsStream_') - .mockImplementation((args, originalMethod) => { + spyOn(paginator, 'runAsStream_').mockImplementation( + (args, originalMethod) => { try { expect(args).toBe(parsedArguments); originalMethod(); @@ -477,7 +457,8 @@ describe('paginator', () => { done(e); } return createFakeStream(); - }); + }, + ); paginator.run_(parsedArguments, util.noop); }); @@ -485,7 +466,7 @@ describe('paginator', () => { it('should reject a promise on error', async () => { const error = new Error('Error.'); - jest.spyOn(paginator, 'runAsStream_').mockImplementation(() => { + spyOn(paginator, 'runAsStream_').mockImplementation(() => { const stream = createFakeStream(); setImmediate(() => { stream.emit('error', error); @@ -501,7 +482,7 @@ describe('paginator', () => { it('should resolve with all results on end', async () => { const results = [{a: 1}, {b: 2}, {c: 3}]; - jest.spyOn(paginator, 'runAsStream_').mockImplementation(() => { + spyOn(paginator, 'runAsStream_').mockImplementation(() => { const stream = createFakeStream(); setImmediate(() => { results.forEach(result => stream.push(result)); @@ -518,7 +499,7 @@ describe('paginator', () => { const results = [{a: 1}, {b: 2}, {c: 3}]; const args: any[] = [{msg: 'OK'}, 10]; - jest.spyOn(paginator, 'runAsStream_').mockImplementation(() => { + spyOn(paginator, 'runAsStream_').mockImplementation(() => { const stream = createFakeStream(); setImmediate(() => { results.forEach(result => stream.push(result)); @@ -551,9 +532,7 @@ describe('paginator', () => { }, callback: done, } as ParsedArguments; - jest - .spyOn(paginator, 'runAsStream_') - .mockImplementation(createFakeStream); + spyOn(paginator, 'runAsStream_').mockImplementation(createFakeStream); paginator.run_(parsedArguments, (query: {}, callback: () => void) => { try { expect(query).toEqual(parsedArguments.query); @@ -574,9 +553,7 @@ describe('paginator', () => { c: 'd', }, } as ParsedArguments; - jest - .spyOn(paginator, 'runAsStream_') - .mockImplementation(createFakeStream); + spyOn(paginator, 'runAsStream_').mockImplementation(createFakeStream); paginator.run_(parsedArguments, (query: {}) => { expect(query).toEqual(parsedArguments.query); }); @@ -586,17 +563,13 @@ describe('paginator', () => { describe('runAsStream_', () => { it('should create a resource stream', () => { - const fakeArgs = {}; - const fakeFn = jest.fn(); - const stream = paginator.runAsStream_( - fakeArgs, - fakeFn, - ) as unknown as MockFakeResourceStream; + const fakeArgs = {query: {a: 'b'}} as unknown as ParsedArguments; + const fakeFn = mock(); + const stream = paginator.runAsStream_(fakeArgs, fakeFn); expect(stream instanceof ResourceStream).toBe(true); - const [args, requestFn] = stream.calledWith; - expect(args).toBe(fakeArgs); - expect(requestFn).toBe(fakeFn); + expect(stream._nextQuery).toBe(fakeArgs.query); + expect(stream._requestFn).toBe(fakeFn); }); }); }); diff --git a/core/paginator/test/resource-stream.ts b/core/paginator/test/resource-stream.test.ts similarity index 84% rename from core/paginator/test/resource-stream.ts rename to core/paginator/test/resource-stream.test.ts index 6c98a18cbba5..664ea119b7b1 100644 --- a/core/paginator/test/resource-stream.ts +++ b/core/paginator/test/resource-stream.test.ts @@ -13,6 +13,17 @@ // limitations under the License. import {Transform} from 'stream'; +import { + describe, + it, + expect, + mock, + spyOn, + jest, + beforeEach, + afterEach, + type Mock, +} from 'bun:test'; import {ResourceStream} from '../src/resource-stream'; describe('ResourceStream', () => { @@ -22,11 +33,12 @@ describe('ResourceStream', () => { query: {}, }; - let requestSpy: jest.Mock; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let requestSpy: Mock; let stream: ResourceStream<{}>; beforeEach(() => { - requestSpy = jest.fn(); + requestSpy = mock(); stream = new ResourceStream(config, requestSpy); }); @@ -35,6 +47,14 @@ describe('ResourceStream', () => { jest.useRealTimers(); }); + const flushTimers = async () => { + jest.runAllTimers(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (typeof (globalThis as any).Bun !== 'undefined') { + await new Promise(resolve => setImmediate(resolve)); + } + }; + describe('instantiation', () => { it('should pass the streamingOptions to the constructor', () => { const highWaterMark = 1; @@ -97,7 +117,7 @@ describe('ResourceStream', () => { }); it('should call through to super.end', () => { - const stub = jest.spyOn(Transform.prototype, 'end').mockImplementation(); + const stub = spyOn(Transform.prototype, 'end').mockImplementation(); stream.end(); expect(stub).toHaveBeenCalledTimes(1); @@ -128,7 +148,7 @@ describe('ResourceStream', () => { it('should destroy the stream if an error occurs', () => { const fakeError = new Error('err'); - const stub = jest.spyOn(stream, 'destroy').mockImplementation(); + const stub = spyOn(stream, 'destroy').mockImplementation(); stream._read(); const callback = requestSpy.mock.lastCall![1]; @@ -175,7 +195,7 @@ describe('ResourceStream', () => { it('should push in all the results', () => { const results = Array(20).fill({}); - const stub = jest.spyOn(stream, 'push').mockImplementation(); + const stub = spyOn(stream, 'push').mockImplementation(); stream._read(); const callback = requestSpy.mock.lastCall![1]; @@ -202,7 +222,7 @@ describe('ResourceStream', () => { }); it('should end the stream if there is no next query', () => { - const stub = jest.spyOn(stream, 'end').mockImplementation(); + const stub = spyOn(stream, 'end').mockImplementation(); stream._read(); const callback = requestSpy.mock.lastCall![1]; @@ -215,7 +235,7 @@ describe('ResourceStream', () => { const maxResults = 10; const results = Array(maxResults).fill({}); stream = new ResourceStream({maxResults}, requestSpy); - const stub = jest.spyOn(stream, 'end').mockImplementation(); + const stub = spyOn(stream, 'end').mockImplementation(); stream._read(); const callback = requestSpy.mock.lastCall![1]; @@ -227,7 +247,7 @@ describe('ResourceStream', () => { it('should end the stream if max api calls is hit', () => { const maxApiCalls = 1; stream = new ResourceStream({maxApiCalls}, requestSpy); - const stub = jest.spyOn(stream, 'end').mockImplementation(); + const stub = spyOn(stream, 'end').mockImplementation(); stream._read(); const callback = requestSpy.mock.lastCall![1]; @@ -236,7 +256,7 @@ describe('ResourceStream', () => { expect(stub).toHaveBeenCalledTimes(1); }); - it('should stop reading if the buffer is full', () => { + it('should stop reading if the buffer is full', async () => { jest.useFakeTimers(); const results = Array(stream.readableHighWaterMark).fill({}); @@ -244,13 +264,13 @@ describe('ResourceStream', () => { const callback = requestSpy.mock.lastCall![1]; callback(null, results, {}); - const stub = jest.spyOn(stream, '_read').mockImplementation(); - jest.runAllTimers(); + const stub = spyOn(stream, '_read').mockImplementation(); + await flushTimers(); expect(stub).toHaveBeenCalledTimes(0); }); - it('should stop reading if the stream ended', () => { + it('should stop reading if the stream ended', async () => { jest.useFakeTimers(); stream.on('data', () => stream.end()); @@ -259,23 +279,23 @@ describe('ResourceStream', () => { const callback = requestSpy.mock.lastCall![1]; callback(null, [{}], {}); - const stub = jest.spyOn(stream, '_read').mockImplementation(); - jest.runAllTimers(); + const stub = spyOn(stream, '_read').mockImplementation(); + await flushTimers(); expect(stub).toHaveBeenCalledTimes(0); }); - it('should keep reading if not full/ended', () => { + it('should keep reading if not full/ended', async () => { jest.useFakeTimers(); stream._read(); const callback = requestSpy.mock.lastCall![1]; callback(null, [{}], {}); - const stub = jest.spyOn(stream, '_read').mockImplementation(); - jest.runAllTimers(); + const stub = spyOn(stream, '_read').mockImplementation(); + await flushTimers(); - expect(stub).toHaveBeenCalledTimes(1); + expect(stub.mock.calls.length).toBeGreaterThanOrEqual(1); }); it('should set reading to false inbetween reads', () => {