From 401b5a062db3a2471e42a800c8a0bd6833883117 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 15 Oct 2026 20:54:45 +0000 Subject: [PATCH 1/4] test: remove --proxyquire-shim from bigtable, spanner, and storage --- handwritten/bigtable/package.json | 2 +- .../client-side-metrics-all-methods.ts | 2 +- .../system-test/client-side-metrics.ts | 2 +- .../system-test/gcp-metrics-handler.ts | 2 +- handwritten/bigtable/system-test/read-rows.ts | 7 +- handwritten/bigtable/test/app-profile.ts | 2 +- handwritten/bigtable/test/backup.ts | 2 +- handwritten/bigtable/test/base64keymap.ts | 1 - handwritten/bigtable/test/chunktransformer.ts | 2 +- handwritten/bigtable/test/cluster.ts | 2 +- handwritten/bigtable/test/executequery.ts | 2 +- handwritten/bigtable/test/family.ts | 2 +- handwritten/bigtable/test/filter.ts | 2 +- handwritten/bigtable/test/index.ts | 3 +- handwritten/bigtable/test/instance.ts | 2 +- .../test/metric-service-client-credentials.ts | 2 +- .../metrics-collector/gcp-metrics-handler.ts | 2 +- .../metrics-collector/metrics-collector.ts | 2 +- handwritten/bigtable/test/proxyquire.ts | 115 ++++++++++++++++++ handwritten/bigtable/test/row.ts | 2 +- handwritten/bigtable/test/table.ts | 2 +- .../observability-test/batch-transaction.ts | 2 +- .../spanner/observability-test/database.ts | 2 +- .../spanner/observability-test/helper.ts | 1 + .../spanner/observability-test/spanner.ts | 1 + .../spanner/observability-test/table.ts | 2 +- .../spanner/observability-test/transaction.ts | 2 +- handwritten/spanner/package.json | 4 +- handwritten/spanner/test/backup.ts | 2 +- handwritten/spanner/test/batch-transaction.ts | 2 +- .../spanner/test/common/service-object.ts | 2 +- handwritten/spanner/test/common/service.ts | 2 +- handwritten/spanner/test/database.ts | 2 +- handwritten/spanner/test/index.ts | 2 +- handwritten/spanner/test/instance-config.ts | 2 +- handwritten/spanner/test/instance.ts | 2 +- .../spanner/test/partial-result-stream.ts | 2 +- handwritten/spanner/test/proxyquire.ts | 115 ++++++++++++++++++ handwritten/spanner/test/session.ts | 2 +- handwritten/spanner/test/table.ts | 2 +- .../spanner/test/transaction-runner.ts | 3 +- handwritten/spanner/test/transaction.ts | 2 +- handwritten/storage/package.json | 2 +- handwritten/storage/test/acl.ts | 2 +- handwritten/storage/test/bucket.ts | 2 +- handwritten/storage/test/channel.ts | 2 +- handwritten/storage/test/file.ts | 2 +- handwritten/storage/test/headers.ts | 2 +- handwritten/storage/test/hmacKey.ts | 2 +- handwritten/storage/test/iam.ts | 2 +- handwritten/storage/test/index.ts | 2 +- .../test/nodejs-common/service-object.ts | 2 +- .../storage/test/nodejs-common/service.ts | 2 +- .../storage/test/nodejs-common/util.ts | 2 +- handwritten/storage/test/notification.ts | 2 +- handwritten/storage/test/proxyquire.ts | 115 ++++++++++++++++++ 56 files changed, 404 insertions(+), 53 deletions(-) create mode 100644 handwritten/bigtable/test/proxyquire.ts create mode 100644 handwritten/spanner/test/proxyquire.ts create mode 100644 handwritten/storage/test/proxyquire.ts diff --git a/handwritten/bigtable/package.json b/handwritten/bigtable/package.json index fab4e307fa9b..222d1421e862 100644 --- a/handwritten/bigtable/package.json +++ b/handwritten/bigtable/package.json @@ -42,7 +42,7 @@ "presystem-test": "pnpm run compile", "system-test": "node ../../bin/run-test.cjs build/system-test --timeout 600000 --retries 3", "pretest": "pnpm run compile", - "test": "node ../../bin/run-test.cjs --proxyquire-shim build/test", + "test": "node ../../bin/run-test.cjs build/test", "test:snap": "SNAPSHOT_UPDATE=1 pnpm test", "testproxy": "pnpm run compile && node build/testproxy/index.js", "clean": "gts clean", diff --git a/handwritten/bigtable/system-test/client-side-metrics-all-methods.ts b/handwritten/bigtable/system-test/client-side-metrics-all-methods.ts index d0d8d9ebe49b..42273eac501e 100644 --- a/handwritten/bigtable/system-test/client-side-metrics-all-methods.ts +++ b/handwritten/bigtable/system-test/client-side-metrics-all-methods.ts @@ -21,7 +21,7 @@ import { import {ResourceMetrics} from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import {GCPMetricsHandler} from '../src/client-side-metrics/gcp-metrics-handler'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../test/proxyquire'; import {Bigtable, RawFilter} from '../src'; import {Mutation} from '../src/mutation'; import {Row} from '../src/row'; diff --git a/handwritten/bigtable/system-test/client-side-metrics.ts b/handwritten/bigtable/system-test/client-side-metrics.ts index 7a029b37378e..4a1878131cc8 100644 --- a/handwritten/bigtable/system-test/client-side-metrics.ts +++ b/handwritten/bigtable/system-test/client-side-metrics.ts @@ -21,7 +21,7 @@ import { import {ResourceMetrics} from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import {GCPMetricsHandler} from '../src/client-side-metrics/gcp-metrics-handler'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../test/proxyquire'; import {Bigtable, BigtableOptions} from '../src'; import {Row} from '../src/row'; import { diff --git a/handwritten/bigtable/system-test/gcp-metrics-handler.ts b/handwritten/bigtable/system-test/gcp-metrics-handler.ts index 2608d6e191b1..bd847cbe2dfd 100644 --- a/handwritten/bigtable/system-test/gcp-metrics-handler.ts +++ b/handwritten/bigtable/system-test/gcp-metrics-handler.ts @@ -26,7 +26,7 @@ import {Bigtable} from '../src'; import {ResourceMetrics} from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import {ClientOptions} from 'google-gax'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../test/proxyquire'; function getHandler(Exporter: typeof CloudMonitoringExporter) { const FakeCGPMetricsHandler = proxyquire( diff --git a/handwritten/bigtable/system-test/read-rows.ts b/handwritten/bigtable/system-test/read-rows.ts index 15ac57a41298..3317432e6c5e 100644 --- a/handwritten/bigtable/system-test/read-rows.ts +++ b/handwritten/bigtable/system-test/read-rows.ts @@ -26,7 +26,7 @@ import {EventEmitter} from 'events'; import {Test} from './testTypes'; import {ServiceError, GrpcClient, GoogleError, CallOptions} from 'google-gax'; import {PassThrough} from 'stream'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../test/proxyquire'; import {TabularApiSurface} from '../src/tabular-api-surface'; import * as mocha from 'mocha'; import {generateId} from './common'; @@ -141,7 +141,10 @@ describe('Bigtable/Table', () => { 'An error should have been thrown because the client is closed', ); } catch (err: any) { - assert.strictEqual(err.message, 'The client has already been closed.'); + assert.strictEqual( + err.message, + 'The client has already been closed.', + ); } } finally { const bigtableSecondClient = new Bigtable(); diff --git a/handwritten/bigtable/test/app-profile.ts b/handwritten/bigtable/test/app-profile.ts index 581c46f98065..33b2d1b34c9a 100644 --- a/handwritten/bigtable/test/app-profile.ts +++ b/handwritten/bigtable/test/app-profile.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import {CallOptions} from 'google-gax'; let promisified = false; diff --git a/handwritten/bigtable/test/backup.ts b/handwritten/bigtable/test/backup.ts index 915bcde6bad2..fdf55e792e01 100644 --- a/handwritten/bigtable/test/backup.ts +++ b/handwritten/bigtable/test/backup.ts @@ -16,7 +16,7 @@ import {PreciseDate} from '@google-cloud/precise-date'; import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, describe, it, afterEach} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as pumpify from 'pumpify'; import {ServiceError} from 'google-gax'; diff --git a/handwritten/bigtable/test/base64keymap.ts b/handwritten/bigtable/test/base64keymap.ts index 7446126bff45..d9903a1afe75 100644 --- a/handwritten/bigtable/test/base64keymap.ts +++ b/handwritten/bigtable/test/base64keymap.ts @@ -15,7 +15,6 @@ import * as assert from 'assert'; import {describe, it, before, beforeEach, afterEach} from 'mocha'; import Long = require('long'); -import * as proxyquire from 'proxyquire'; import * as sn from 'sinon'; import {RowStateEnum} from '../src/chunktransformer.js'; diff --git a/handwritten/bigtable/test/chunktransformer.ts b/handwritten/bigtable/test/chunktransformer.ts index 88e0e313707a..fb7897914bc7 100644 --- a/handwritten/bigtable/test/chunktransformer.ts +++ b/handwritten/bigtable/test/chunktransformer.ts @@ -15,7 +15,7 @@ import * as assert from 'assert'; import {describe, it, before, beforeEach, afterEach} from 'mocha'; import Long = require('long'); -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sn from 'sinon'; import {RowStateEnum} from '../src/chunktransformer.js'; diff --git a/handwritten/bigtable/test/cluster.ts b/handwritten/bigtable/test/cluster.ts index a1c963cd22f9..b49ad5298e49 100644 --- a/handwritten/bigtable/test/cluster.ts +++ b/handwritten/bigtable/test/cluster.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import {PassThrough, Readable} from 'stream'; import {CallOptions} from 'google-gax'; import {PreciseDate} from '@google-cloud/precise-date'; diff --git a/handwritten/bigtable/test/executequery.ts b/handwritten/bigtable/test/executequery.ts index 4d0ea37f205b..fc1e2a469b0b 100644 --- a/handwritten/bigtable/test/executequery.ts +++ b/handwritten/bigtable/test/executequery.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import * as sinon from 'sinon'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import {grpc} from 'google-gax'; import * as inst from '../src/instance'; import {Bigtable} from '../src'; diff --git a/handwritten/bigtable/test/family.ts b/handwritten/bigtable/test/family.ts index 4d6b0babc4e2..9017558d2185 100644 --- a/handwritten/bigtable/test/family.ts +++ b/handwritten/bigtable/test/family.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {protos} from '@google-cloud/bigtable-api'; import google = protos.google; diff --git a/handwritten/bigtable/test/filter.ts b/handwritten/bigtable/test/filter.ts index d2dc9fbaed7d..cd9ae392ef74 100644 --- a/handwritten/bigtable/test/filter.ts +++ b/handwritten/bigtable/test/filter.ts @@ -14,7 +14,7 @@ import * as assert from 'assert'; import {afterEach, before, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import * as fr from '../src/filter'; import {Row} from '../src/row'; diff --git a/handwritten/bigtable/test/index.ts b/handwritten/bigtable/test/index.ts index 361d2527a300..b0995985e248 100644 --- a/handwritten/bigtable/test/index.ts +++ b/handwritten/bigtable/test/index.ts @@ -17,7 +17,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {afterEach, before, beforeEach, describe, it} from 'mocha'; import * as gax from 'google-gax'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sn from 'sinon'; import {Cluster} from '../src/cluster.js'; @@ -1216,6 +1216,7 @@ describe('Bigtable', () => { }); describe('close', () => { it('should have failed request after close is called', done => { + // eslint-disable-next-line promise/catch-or-return, promise/always-return bigtable.close().then(() => { bigtable.getInstances((err: Error) => { if (err) { diff --git a/handwritten/bigtable/test/instance.ts b/handwritten/bigtable/test/instance.ts index 47798885b905..876cb156f77d 100644 --- a/handwritten/bigtable/test/instance.ts +++ b/handwritten/bigtable/test/instance.ts @@ -16,7 +16,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import * as sinon from 'sinon'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import {ServiceError} from 'google-gax'; import * as snapshot from 'snap-shot-it'; import {Readable} from 'stream'; diff --git a/handwritten/bigtable/test/metric-service-client-credentials.ts b/handwritten/bigtable/test/metric-service-client-credentials.ts index b05caa470f61..bea333d2c105 100644 --- a/handwritten/bigtable/test/metric-service-client-credentials.ts +++ b/handwritten/bigtable/test/metric-service-client-credentials.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import {ClientOptions, grpc} from 'google-gax'; import * as assert from 'assert'; import {MetricServiceClient} from '@google-cloud/monitoring'; diff --git a/handwritten/bigtable/test/metrics-collector/gcp-metrics-handler.ts b/handwritten/bigtable/test/metrics-collector/gcp-metrics-handler.ts index 6f54951b5551..5deebaf2bed0 100644 --- a/handwritten/bigtable/test/metrics-collector/gcp-metrics-handler.ts +++ b/handwritten/bigtable/test/metrics-collector/gcp-metrics-handler.ts @@ -30,7 +30,7 @@ import { } from '../../test-common/expected-otel-export-input'; import * as assert from 'assert'; import {replaceTimestamps} from '../../test-common/replace-timestamps'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../proxyquire'; /** * Cleans a ResourceMetrics object by replacing client UUIDs with a placeholder. diff --git a/handwritten/bigtable/test/metrics-collector/metrics-collector.ts b/handwritten/bigtable/test/metrics-collector/metrics-collector.ts index b63bd2b0ba51..edaaa74bacd2 100644 --- a/handwritten/bigtable/test/metrics-collector/metrics-collector.ts +++ b/handwritten/bigtable/test/metrics-collector/metrics-collector.ts @@ -23,7 +23,7 @@ import { import {grpc} from 'google-gax'; import {expectedRequestsHandled} from '../../test-common/metrics-handler-fixture'; import * as gax from 'google-gax'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../proxyquire'; import {GCPMetricsHandler} from '../../src/client-side-metrics/gcp-metrics-handler'; import {getProtoPath} from '../../src/utils/protos'; const protoPath = getProtoPath('google/bigtable/v2/response_params.proto'); diff --git a/handwritten/bigtable/test/proxyquire.ts b/handwritten/bigtable/test/proxyquire.ts new file mode 100644 index 000000000000..44ff9f689c12 --- /dev/null +++ b/handwritten/bigtable/test/proxyquire.ts @@ -0,0 +1,115 @@ +// 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. + +import * as Module from 'module'; +import * as path from 'path'; + +delete require.cache[require.resolve(__filename)]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Stubs = Record; +const mod = Module as unknown as { + prototype: {require: NodeRequire}; + _resolveFilename: (request: string, parent: NodeModule) => string; +}; +const {hasOwnProperty} = Object.prototype; +const hasOwn = (o: unknown, k: string) => + Boolean(o && typeof o === 'object' && hasOwnProperty.call(o, k)); +const isGlobal = (s: unknown) => + hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); +const resolveFrom = (base: string, req: string) => { + try { + return require.resolve(req, {paths: [path.dirname(base)]}); + } catch { + return req[0] === '.' ? path.resolve(path.dirname(base), req) : req; + } +}; + +const parent = module.parent || module; +let skipCallThru = false; +let keepCache = true; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function proxyquire(request: string, stubs: Stubs = {}): T { + const sut = mod._resolveFilename(request, parent); + const resolved: Stubs = {}; + let global = false; + for (const [k, v] of Object.entries(stubs)) { + if (isGlobal(v)) global = true; + resolved[resolveFrom(sut, k)] = v; + } + const cache = require.cache; + const keys = global + ? Object.keys(cache).filter(id => !id.endsWith('.node')) + : [sut]; + const saved = keys.map(id => [id, cache[id]] as const); + for (const id of keys) delete cache[id]; + const prev = mod.prototype.require; + const req = function (this: NodeModule, id: string) { + const isSut = this?.filename === sut; + if (this?.filename && (isSut || global)) { + const hit = (isSut || !id.startsWith('.')) && hasOwn(stubs, id); + const rId = hit ? '' : resolveFrom(this.filename, id); + if (hit || hasOwn(resolved, rId)) { + const stub = hit ? stubs[id] : resolved[rId]; + if (isSut || isGlobal(stub)) { + if (stub === null) { + const e = new Error(`Cannot find module '${id}'`); + throw Object.assign(e, {code: 'MODULE_NOT_FOUND'}); + } + const skip = hasOwn(stub, '@noCallThru') + ? stub['@noCallThru'] + : skipCallThru; + if (!skip && stub && ['object', 'function'].includes(typeof stub)) { + try { + const real = prev.call(this, id); + if (real && ['object', 'function'].includes(typeof real)) { + for (const k of Object.keys(real)) { + if (!(k in stub)) stub[k] = real[k]; + } + } + } catch { + // ignore + } + } + return stub; + } + } + } + return prev.call(this, id); + }; + mod.prototype.require = Object.assign(req, prev); + try { + return prev.call(parent, request); + } finally { + mod.prototype.require = prev; + for (const id of global ? Object.keys(cache) : [sut]) { + if (!id.endsWith('.node')) delete cache[id]; + } + for (const [id, m] of saved) { + if (m && (keepCache || id !== sut)) cache[id] = m; + } + } +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +namespace proxyquire { + export const load = proxyquire; + export const noCallThru = () => ((skipCallThru = true), proxyquire); + export const callThru = () => ((skipCallThru = false), proxyquire); + export const noPreserveCache = () => ((keepCache = false), proxyquire); + export const preserveCache = () => ((keepCache = true), proxyquire); +} + +export = proxyquire; diff --git a/handwritten/bigtable/test/row.ts b/handwritten/bigtable/test/row.ts index ca1f444cfb65..ab2cba41c8a8 100644 --- a/handwritten/bigtable/test/row.ts +++ b/handwritten/bigtable/test/row.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {afterEach, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {Mutation} from '../src/mutation.js'; import * as rw from '../src/row'; diff --git a/handwritten/bigtable/test/table.ts b/handwritten/bigtable/test/table.ts index c10e89145d4e..5a7d54d67d07 100644 --- a/handwritten/bigtable/test/table.ts +++ b/handwritten/bigtable/test/table.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {afterEach, before, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as pumpify from 'pumpify'; import * as sinon from 'sinon'; import {PassThrough, Writable, Duplex} from 'stream'; diff --git a/handwritten/spanner/observability-test/batch-transaction.ts b/handwritten/spanner/observability-test/batch-transaction.ts index f3738c67ecef..4105c3d95993 100644 --- a/handwritten/spanner/observability-test/batch-transaction.ts +++ b/handwritten/spanner/observability-test/batch-transaction.ts @@ -21,7 +21,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, after, beforeEach, afterEach, describe, it} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../test/proxyquire'; import * as sinon from 'sinon'; const { AlwaysOnSampler, diff --git a/handwritten/spanner/observability-test/database.ts b/handwritten/spanner/observability-test/database.ts index 1c9410242d33..7d4ab0b796e6 100644 --- a/handwritten/spanner/observability-test/database.ts +++ b/handwritten/spanner/observability-test/database.ts @@ -30,7 +30,7 @@ import { } from '../src/transaction'; import {util} from '@google-cloud/common'; import {Transform} from 'stream'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../test/proxyquire'; import * as sinon from 'sinon'; const {SpanStatusCode} = require('@opentelemetry/api'); const { diff --git a/handwritten/spanner/observability-test/helper.ts b/handwritten/spanner/observability-test/helper.ts index fc610a4f912f..45be0d2c891a 100644 --- a/handwritten/spanner/observability-test/helper.ts +++ b/handwritten/spanner/observability-test/helper.ts @@ -43,6 +43,7 @@ export const cacheSessionEvents = ['Cache hit: has usable multiplexed session']; * and referring to context causes type/value collision errors. */ export function setGlobalContextManager(manager: ContextManager) { + context.disable(); if (typeof manager.enable === 'function') { manager.enable(); } diff --git a/handwritten/spanner/observability-test/spanner.ts b/handwritten/spanner/observability-test/spanner.ts index a262314b2f79..8d9854f8fb52 100644 --- a/handwritten/spanner/observability-test/spanner.ts +++ b/handwritten/spanner/observability-test/spanner.ts @@ -188,6 +188,7 @@ describe('EndToEnd', async () => { const database = instance.database('database'); beforeEach(async () => { + setGlobalContextManager(contextManager); // To deflake expectations of session creation, let's // issue out a warm-up request request that'll ensure // that the MultiplexedSession is created deterministically. diff --git a/handwritten/spanner/observability-test/table.ts b/handwritten/spanner/observability-test/table.ts index 36257b34e875..3506f10e4eeb 100644 --- a/handwritten/spanner/observability-test/table.ts +++ b/handwritten/spanner/observability-test/table.ts @@ -18,7 +18,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../test/proxyquire'; import * as sinon from 'sinon'; import * as through from 'through2'; diff --git a/handwritten/spanner/observability-test/transaction.ts b/handwritten/spanner/observability-test/transaction.ts index 126b52141b8c..351427dd0b9b 100644 --- a/handwritten/spanner/observability-test/transaction.ts +++ b/handwritten/spanner/observability-test/transaction.ts @@ -17,7 +17,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {EventEmitter} from 'events'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../test/proxyquire'; import * as sinon from 'sinon'; import {codec} from '../src/codec'; diff --git a/handwritten/spanner/package.json b/handwritten/spanner/package.json index e07b4ae6b71c..e26166d92aa7 100644 --- a/handwritten/spanner/package.json +++ b/handwritten/spanner/package.json @@ -35,9 +35,9 @@ "predocs": "pnpm run compile", "lint": "gts check", "system-test": "node ../../bin/run-test.cjs build/system-test --timeout 1600000", - "observability-test": "node ../../bin/run-test.cjs --proxyquire-shim build/observability-test --timeout 1600000", + "observability-test": "node ../../bin/run-test.cjs build/observability-test --timeout 1600000", "cleanup": "node ../../bin/run-test.cjs scripts/cleanup.js --timeout 30000", - "test": "node ../../bin/run-test.cjs --proxyquire-shim build/test build/test/common build/observability-test", + "test": "node ../../bin/run-test.cjs build/test build/test/common build/observability-test", "ycsb": "node ./benchmark/ycsb.js run -P ./benchmark/workloada -p table=usertable -p cloudspanner.instance=ycsb-instance -p operationcount=100 -p cloudspanner.database=ycsb", "fix": "gts fix", "clean": "gts clean", diff --git a/handwritten/spanner/test/backup.ts b/handwritten/spanner/test/backup.ts index f52acc976b1b..e8de46ecbed3 100644 --- a/handwritten/spanner/test/backup.ts +++ b/handwritten/spanner/test/backup.ts @@ -17,7 +17,7 @@ import * as assert from 'assert'; import {EventEmitter} from 'events'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {util} from '@google-cloud/common'; import * as pfy from '@google-cloud/promisify'; diff --git a/handwritten/spanner/test/batch-transaction.ts b/handwritten/spanner/test/batch-transaction.ts index 990bee6bb8bd..f8cd9043ae7a 100644 --- a/handwritten/spanner/test/batch-transaction.ts +++ b/handwritten/spanner/test/batch-transaction.ts @@ -20,7 +20,7 @@ import {util} from '@google-cloud/common'; import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {Session, Database, Spanner, protos} from '../src'; diff --git a/handwritten/spanner/test/common/service-object.ts b/handwritten/spanner/test/common/service-object.ts index 7e5544eb8e95..489863c87d5a 100644 --- a/handwritten/spanner/test/common/service-object.ts +++ b/handwritten/spanner/test/common/service-object.ts @@ -19,7 +19,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, describe, it} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../proxyquire'; let promisified = false; const fakePfy = Object.assign({}, pfy, { diff --git a/handwritten/spanner/test/common/service.ts b/handwritten/spanner/test/common/service.ts index 11ea01a9820a..c073c31cd095 100644 --- a/handwritten/spanner/test/common/service.ts +++ b/handwritten/spanner/test/common/service.ts @@ -20,7 +20,7 @@ import {before, beforeEach, after, afterEach, describe, it} from 'mocha'; import * as duplexify from 'duplexify'; import * as extend from 'extend'; import {grpc, GrpcClient} from 'google-gax'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from '../proxyquire'; import * as retryRequest from 'retry-request'; import * as sn from 'sinon'; import {PassThrough} from 'stream'; diff --git a/handwritten/spanner/test/database.ts b/handwritten/spanner/test/database.ts index ea94628136e6..eebe303775c2 100644 --- a/handwritten/spanner/test/database.ts +++ b/handwritten/spanner/test/database.ts @@ -21,7 +21,7 @@ import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {EventEmitter} from 'events'; import * as extend from 'extend'; import {ApiError, util} from '@google-cloud/common'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {Transform, Duplex} from 'stream'; import * as through from 'through2'; diff --git a/handwritten/spanner/test/index.ts b/handwritten/spanner/test/index.ts index 384eb8b5c440..3bbcbc34f054 100644 --- a/handwritten/spanner/test/index.ts +++ b/handwritten/spanner/test/index.ts @@ -20,7 +20,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import * as extend from 'extend'; import * as path from 'path'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as through from 'through2'; import {util} from '@google-cloud/common'; import {PreciseDate} from '@google-cloud/precise-date'; diff --git a/handwritten/spanner/test/instance-config.ts b/handwritten/spanner/test/instance-config.ts index dd050be447f0..528ca1e875e2 100644 --- a/handwritten/spanner/test/instance-config.ts +++ b/handwritten/spanner/test/instance-config.ts @@ -18,7 +18,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {grpc} from 'google-gax'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as pfy from '@google-cloud/promisify'; import * as sinon from 'sinon'; diff --git a/handwritten/spanner/test/instance.ts b/handwritten/spanner/test/instance.ts index f816e2fa0d15..ef6d21788c6c 100644 --- a/handwritten/spanner/test/instance.ts +++ b/handwritten/spanner/test/instance.ts @@ -20,7 +20,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {ApiError} from '@google-cloud/common'; import {grpc} from 'google-gax'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as pfy from '@google-cloud/promisify'; import * as sinon from 'sinon'; import snakeCase = require('lodash.snakecase'); diff --git a/handwritten/spanner/test/partial-result-stream.ts b/handwritten/spanner/test/partial-result-stream.ts index 4d8560be7593..beceec7e1521 100644 --- a/handwritten/spanner/test/partial-result-stream.ts +++ b/handwritten/spanner/test/partial-result-stream.ts @@ -19,7 +19,7 @@ import {before, beforeEach, afterEach, describe, it} from 'mocha'; // eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires const concat = require('concat-stream'); -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {Transform, finished} from 'stream'; import * as through from 'through2'; diff --git a/handwritten/spanner/test/proxyquire.ts b/handwritten/spanner/test/proxyquire.ts new file mode 100644 index 000000000000..44ff9f689c12 --- /dev/null +++ b/handwritten/spanner/test/proxyquire.ts @@ -0,0 +1,115 @@ +// 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. + +import * as Module from 'module'; +import * as path from 'path'; + +delete require.cache[require.resolve(__filename)]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Stubs = Record; +const mod = Module as unknown as { + prototype: {require: NodeRequire}; + _resolveFilename: (request: string, parent: NodeModule) => string; +}; +const {hasOwnProperty} = Object.prototype; +const hasOwn = (o: unknown, k: string) => + Boolean(o && typeof o === 'object' && hasOwnProperty.call(o, k)); +const isGlobal = (s: unknown) => + hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); +const resolveFrom = (base: string, req: string) => { + try { + return require.resolve(req, {paths: [path.dirname(base)]}); + } catch { + return req[0] === '.' ? path.resolve(path.dirname(base), req) : req; + } +}; + +const parent = module.parent || module; +let skipCallThru = false; +let keepCache = true; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function proxyquire(request: string, stubs: Stubs = {}): T { + const sut = mod._resolveFilename(request, parent); + const resolved: Stubs = {}; + let global = false; + for (const [k, v] of Object.entries(stubs)) { + if (isGlobal(v)) global = true; + resolved[resolveFrom(sut, k)] = v; + } + const cache = require.cache; + const keys = global + ? Object.keys(cache).filter(id => !id.endsWith('.node')) + : [sut]; + const saved = keys.map(id => [id, cache[id]] as const); + for (const id of keys) delete cache[id]; + const prev = mod.prototype.require; + const req = function (this: NodeModule, id: string) { + const isSut = this?.filename === sut; + if (this?.filename && (isSut || global)) { + const hit = (isSut || !id.startsWith('.')) && hasOwn(stubs, id); + const rId = hit ? '' : resolveFrom(this.filename, id); + if (hit || hasOwn(resolved, rId)) { + const stub = hit ? stubs[id] : resolved[rId]; + if (isSut || isGlobal(stub)) { + if (stub === null) { + const e = new Error(`Cannot find module '${id}'`); + throw Object.assign(e, {code: 'MODULE_NOT_FOUND'}); + } + const skip = hasOwn(stub, '@noCallThru') + ? stub['@noCallThru'] + : skipCallThru; + if (!skip && stub && ['object', 'function'].includes(typeof stub)) { + try { + const real = prev.call(this, id); + if (real && ['object', 'function'].includes(typeof real)) { + for (const k of Object.keys(real)) { + if (!(k in stub)) stub[k] = real[k]; + } + } + } catch { + // ignore + } + } + return stub; + } + } + } + return prev.call(this, id); + }; + mod.prototype.require = Object.assign(req, prev); + try { + return prev.call(parent, request); + } finally { + mod.prototype.require = prev; + for (const id of global ? Object.keys(cache) : [sut]) { + if (!id.endsWith('.node')) delete cache[id]; + } + for (const [id, m] of saved) { + if (m && (keepCache || id !== sut)) cache[id] = m; + } + } +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +namespace proxyquire { + export const load = proxyquire; + export const noCallThru = () => ((skipCallThru = true), proxyquire); + export const callThru = () => ((skipCallThru = false), proxyquire); + export const noPreserveCache = () => ((keepCache = false), proxyquire); + export const preserveCache = () => ((keepCache = true), proxyquire); +} + +export = proxyquire; diff --git a/handwritten/spanner/test/session.ts b/handwritten/spanner/test/session.ts index 60e2de6845a6..fb5cfb734054 100644 --- a/handwritten/spanner/test/session.ts +++ b/handwritten/spanner/test/session.ts @@ -19,7 +19,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import { CLOUD_RESOURCE_HEADER, LEADER_AWARE_ROUTING_HEADER, diff --git a/handwritten/spanner/test/table.ts b/handwritten/spanner/test/table.ts index 199d456215ba..cd4d2a744532 100644 --- a/handwritten/spanner/test/table.ts +++ b/handwritten/spanner/test/table.ts @@ -17,7 +17,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {Transform} from 'stream'; import * as through from 'through2'; diff --git a/handwritten/spanner/test/transaction-runner.ts b/handwritten/spanner/test/transaction-runner.ts index 9a2bd818b313..ee5c8764bcbc 100644 --- a/handwritten/spanner/test/transaction-runner.ts +++ b/handwritten/spanner/test/transaction-runner.ts @@ -18,7 +18,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {EventEmitter} from 'events'; import {grpc} from 'google-gax'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import * as through from 'through2'; import {RunTransactionOptions} from '../src/transaction-runner'; @@ -329,6 +329,7 @@ describe('TransactionRunner', () => { runner .run() + // eslint-disable-next-line promise/always-return .then(() => { done(new Error('missing expected DEADLINE_EXCEEDED error')); }) diff --git a/handwritten/spanner/test/transaction.ts b/handwritten/spanner/test/transaction.ts index 5e5e13ef7590..c3ae51eff5ac 100644 --- a/handwritten/spanner/test/transaction.ts +++ b/handwritten/spanner/test/transaction.ts @@ -19,7 +19,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {EventEmitter} from 'events'; import {common as p} from 'protobufjs'; -import * as proxyquire from 'proxyquire'; +import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {codec} from '../src/codec'; diff --git a/handwritten/storage/package.json b/handwritten/storage/package.json index 461391690a21..679530c84430 100644 --- a/handwritten/storage/package.json +++ b/handwritten/storage/package.json @@ -71,7 +71,7 @@ "pretest": "pnpm run compile --sourceMap", "system-test:esm": "mkdir -p $HOME/.config && node ../../bin/run-test.cjs --no-parallel build/esm/system-test --timeout 600000 --exit", "system-test": "mkdir -p $HOME/.config && node ../../bin/run-test.cjs --no-parallel build/cjs/system-test --timeout 600000 --exit", - "test": "cross-env NODE_OPTIONS=\"--require ./scripts/preload-yargs.cjs --no-deprecation\" node ../../bin/run-test.cjs --proxyquire-shim build/cjs/test" + "test": "cross-env NODE_OPTIONS=\"--require ./scripts/preload-yargs.cjs --no-deprecation\" node ../../bin/run-test.cjs build/cjs/test" }, "dependencies": { "@google-cloud/paginator": "^7.0.1", diff --git a/handwritten/storage/test/acl.ts b/handwritten/storage/test/acl.ts index 5c1d73e25ae0..6d35f9398488 100644 --- a/handwritten/storage/test/acl.ts +++ b/handwritten/storage/test/acl.ts @@ -15,7 +15,7 @@ import {DecorateRequestOptions, util} from '../src/nodejs-common/index.js'; import assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; import {Storage} from '../src/storage.js'; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/handwritten/storage/test/bucket.ts b/handwritten/storage/test/bucket.ts index 09928da7aab4..dc39164881c3 100644 --- a/handwritten/storage/test/bucket.ts +++ b/handwritten/storage/test/bucket.ts @@ -26,7 +26,7 @@ import {describe, it, before, beforeEach, after, afterEach} from 'mocha'; import mime from 'mime'; import pLimit from 'p-limit'; import * as path from 'path'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; import * as stream from 'stream'; import {Bucket, Channel, Notification, CRC32C} from '../src/index.js'; diff --git a/handwritten/storage/test/channel.ts b/handwritten/storage/test/channel.ts index e70272f20453..715f8adc1321 100644 --- a/handwritten/storage/test/channel.ts +++ b/handwritten/storage/test/channel.ts @@ -24,7 +24,7 @@ import { } from '../src/nodejs-common/index.js'; import assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; let promisified = false; const fakePromisify = { diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts index 938e8e317bba..04dd3bb069ef 100644 --- a/handwritten/storage/test/file.ts +++ b/handwritten/storage/test/file.ts @@ -36,7 +36,7 @@ import * as crypto from 'crypto'; import duplexify from 'duplexify'; import * as fs from 'fs'; import * as path from 'path'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; import * as resumableUpload from '../src/resumable-upload.js'; import * as sinon from 'sinon'; import * as tmp from 'tmp'; diff --git a/handwritten/storage/test/headers.ts b/handwritten/storage/test/headers.ts index b786ae54d4e0..4b3080293308 100644 --- a/handwritten/storage/test/headers.ts +++ b/handwritten/storage/test/headers.ts @@ -14,7 +14,7 @@ import * as assert from 'assert'; import {describe, it} from 'mocha'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; const error = Error('not implemented'); diff --git a/handwritten/storage/test/hmacKey.ts b/handwritten/storage/test/hmacKey.ts index b67da92d7233..01d8d73c4167 100644 --- a/handwritten/storage/test/hmacKey.ts +++ b/handwritten/storage/test/hmacKey.ts @@ -13,7 +13,7 @@ // limitations under the License. import * as sinon from 'sinon'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; import assert from 'assert'; import {describe, it, beforeEach, afterEach} from 'mocha'; import {util, ServiceObject} from '../src/nodejs-common/index.js'; diff --git a/handwritten/storage/test/iam.ts b/handwritten/storage/test/iam.ts index a037e77b0a46..9907c2981075 100644 --- a/handwritten/storage/test/iam.ts +++ b/handwritten/storage/test/iam.ts @@ -15,7 +15,7 @@ import {DecorateRequestOptions, util} from '../src/nodejs-common/index.js'; import assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; import {IAMExceptionMessages} from '../src/iam.js'; describe('storage/iam', () => { diff --git a/handwritten/storage/test/index.ts b/handwritten/storage/test/index.ts index f615cbeb1ffa..a3d3f9fc0b04 100644 --- a/handwritten/storage/test/index.ts +++ b/handwritten/storage/test/index.ts @@ -22,7 +22,7 @@ import { import {PromisifyAllOptions} from '@google-cloud/promisify'; import assert from 'assert'; import {describe, it, before, beforeEach, after, afterEach} from 'mocha'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import {Bucket, CRC32C_DEFAULT_VALIDATOR_GENERATOR} from '../src/index.js'; import {GetFilesOptions} from '../src/bucket.js'; diff --git a/handwritten/storage/test/nodejs-common/service-object.ts b/handwritten/storage/test/nodejs-common/service-object.ts index ac22a62dbdcf..58b7128964bb 100644 --- a/handwritten/storage/test/nodejs-common/service-object.ts +++ b/handwritten/storage/test/nodejs-common/service-object.ts @@ -21,7 +21,7 @@ import { } from '@google-cloud/promisify'; import assert from 'assert'; import {describe, it, beforeEach, afterEach} from 'mocha'; -import proxyquire from 'proxyquire'; +import proxyquire from '../proxyquire.js'; import type { OptionsWithUri, Request as TeenyRequest, diff --git a/handwritten/storage/test/nodejs-common/service.ts b/handwritten/storage/test/nodejs-common/service.ts index f785a1315793..e153b25a7de8 100644 --- a/handwritten/storage/test/nodejs-common/service.ts +++ b/handwritten/storage/test/nodejs-common/service.ts @@ -16,7 +16,7 @@ import assert from 'assert'; import {describe, it, before, beforeEach, after} from 'mocha'; -import proxyquire from 'proxyquire'; +import proxyquire from '../proxyquire.js'; import {Request} from 'teeny-request'; import {AuthClient, GoogleAuth, OAuth2Client} from 'google-auth-library'; diff --git a/handwritten/storage/test/nodejs-common/util.ts b/handwritten/storage/test/nodejs-common/util.ts index d7ebc515c3fb..4b43852fd4f7 100644 --- a/handwritten/storage/test/nodejs-common/util.ts +++ b/handwritten/storage/test/nodejs-common/util.ts @@ -27,7 +27,7 @@ import { OAuth2Client, } from 'google-auth-library'; import * as nock from 'nock'; -import proxyquire from 'proxyquire'; +import proxyquire from '../proxyquire.js'; import retryRequest from 'retry-request'; import * as sinon from 'sinon'; import * as stream from 'stream'; diff --git a/handwritten/storage/test/notification.ts b/handwritten/storage/test/notification.ts index fe396dcb512a..2635c34acf04 100644 --- a/handwritten/storage/test/notification.ts +++ b/handwritten/storage/test/notification.ts @@ -21,7 +21,7 @@ import { } from '../src/nodejs-common/index.js'; import assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import proxyquire from 'proxyquire'; +import proxyquire from './proxyquire.js'; import {Bucket} from '../src/index.js'; diff --git a/handwritten/storage/test/proxyquire.ts b/handwritten/storage/test/proxyquire.ts new file mode 100644 index 000000000000..42cdea201440 --- /dev/null +++ b/handwritten/storage/test/proxyquire.ts @@ -0,0 +1,115 @@ +// 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. + +import Module from 'module'; +import path from 'path'; + +delete require.cache[require.resolve(__filename)]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Stubs = Record; +const mod = Module as unknown as { + prototype: {require: NodeRequire}; + _resolveFilename: (request: string, parent: NodeModule) => string; +}; +const {hasOwnProperty} = Object.prototype; +const hasOwn = (o: unknown, k: string) => + Boolean(o && typeof o === 'object' && hasOwnProperty.call(o, k)); +const isGlobal = (s: unknown) => + hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); +const resolveFrom = (base: string, req: string) => { + try { + return require.resolve(req, {paths: [path.dirname(base)]}); + } catch { + return req[0] === '.' ? path.resolve(path.dirname(base), req) : req; + } +}; + +const parent = module.parent || module; +let skipCallThru = false; +let keepCache = true; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function proxyquire(request: string, stubs: Stubs = {}): T { + const sut = mod._resolveFilename(request, parent); + const resolved: Stubs = {}; + let global = false; + for (const [k, v] of Object.entries(stubs)) { + if (isGlobal(v)) global = true; + resolved[resolveFrom(sut, k)] = v; + } + const cache = require.cache; + const keys = global + ? Object.keys(cache).filter(id => !id.endsWith('.node')) + : [sut]; + const saved = keys.map(id => [id, cache[id]] as const); + for (const id of keys) delete cache[id]; + const prev = mod.prototype.require; + const req = function (this: NodeModule, id: string) { + const isSut = this?.filename === sut; + if (this?.filename && (isSut || global)) { + const hit = (isSut || !id.startsWith('.')) && hasOwn(stubs, id); + const rId = hit ? '' : resolveFrom(this.filename, id); + if (hit || hasOwn(resolved, rId)) { + const stub = hit ? stubs[id] : resolved[rId]; + if (isSut || isGlobal(stub)) { + if (stub === null) { + const e = new Error(`Cannot find module '${id}'`); + throw Object.assign(e, {code: 'MODULE_NOT_FOUND'}); + } + const skip = hasOwn(stub, '@noCallThru') + ? stub['@noCallThru'] + : skipCallThru; + if (!skip && stub && ['object', 'function'].includes(typeof stub)) { + try { + const real = prev.call(this, id); + if (real && ['object', 'function'].includes(typeof real)) { + for (const k of Object.keys(real)) { + if (!(k in stub)) stub[k] = real[k]; + } + } + } catch { + // ignore + } + } + return stub; + } + } + } + return prev.call(this, id); + }; + mod.prototype.require = Object.assign(req, prev); + try { + return prev.call(parent, request); + } finally { + mod.prototype.require = prev; + for (const id of global ? Object.keys(cache) : [sut]) { + if (!id.endsWith('.node')) delete cache[id]; + } + for (const [id, m] of saved) { + if (m && (keepCache || id !== sut)) cache[id] = m; + } + } +} + +// eslint-disable-next-line @typescript-eslint/no-namespace +namespace proxyquire { + export const load = proxyquire; + export const noCallThru = () => ((skipCallThru = true), proxyquire); + export const callThru = () => ((skipCallThru = false), proxyquire); + export const noPreserveCache = () => ((keepCache = false), proxyquire); + export const preserveCache = () => ((keepCache = true), proxyquire); +} + +export default proxyquire; From 97ffe67b05dde82441d779d402c2b5079ebf610c Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 19 Oct 2026 14:51:25 +0000 Subject: [PATCH 2/4] [Address CI errors] test: support function stubs and guard read-only properties in proxyquire helper --- handwritten/bigtable/test/proxyquire.ts | 13 ++++++++++--- handwritten/spanner/test/proxyquire.ts | 13 ++++++++++--- handwritten/storage/test/proxyquire.ts | 13 ++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/handwritten/bigtable/test/proxyquire.ts b/handwritten/bigtable/test/proxyquire.ts index 44ff9f689c12..5d344ba47dad 100644 --- a/handwritten/bigtable/test/proxyquire.ts +++ b/handwritten/bigtable/test/proxyquire.ts @@ -24,8 +24,9 @@ const mod = Module as unknown as { _resolveFilename: (request: string, parent: NodeModule) => string; }; const {hasOwnProperty} = Object.prototype; -const hasOwn = (o: unknown, k: string) => - Boolean(o && typeof o === 'object' && hasOwnProperty.call(o, k)); +const isObj = (o: unknown) => + Boolean(o && (typeof o === 'object' || typeof o === 'function')); +const hasOwn = (o: unknown, k: string) => isObj(o) && hasOwnProperty.call(o, k); const isGlobal = (s: unknown) => hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); const resolveFrom = (base: string, req: string) => { @@ -76,7 +77,13 @@ function proxyquire(request: string, stubs: Stubs = {}): T { const real = prev.call(this, id); if (real && ['object', 'function'].includes(typeof real)) { for (const k of Object.keys(real)) { - if (!(k in stub)) stub[k] = real[k]; + if (!(k in stub)) { + try { + stub[k] = real[k]; + } catch { + // ignore + } + } } } } catch { diff --git a/handwritten/spanner/test/proxyquire.ts b/handwritten/spanner/test/proxyquire.ts index 44ff9f689c12..5d344ba47dad 100644 --- a/handwritten/spanner/test/proxyquire.ts +++ b/handwritten/spanner/test/proxyquire.ts @@ -24,8 +24,9 @@ const mod = Module as unknown as { _resolveFilename: (request: string, parent: NodeModule) => string; }; const {hasOwnProperty} = Object.prototype; -const hasOwn = (o: unknown, k: string) => - Boolean(o && typeof o === 'object' && hasOwnProperty.call(o, k)); +const isObj = (o: unknown) => + Boolean(o && (typeof o === 'object' || typeof o === 'function')); +const hasOwn = (o: unknown, k: string) => isObj(o) && hasOwnProperty.call(o, k); const isGlobal = (s: unknown) => hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); const resolveFrom = (base: string, req: string) => { @@ -76,7 +77,13 @@ function proxyquire(request: string, stubs: Stubs = {}): T { const real = prev.call(this, id); if (real && ['object', 'function'].includes(typeof real)) { for (const k of Object.keys(real)) { - if (!(k in stub)) stub[k] = real[k]; + if (!(k in stub)) { + try { + stub[k] = real[k]; + } catch { + // ignore + } + } } } } catch { diff --git a/handwritten/storage/test/proxyquire.ts b/handwritten/storage/test/proxyquire.ts index 42cdea201440..b69639226354 100644 --- a/handwritten/storage/test/proxyquire.ts +++ b/handwritten/storage/test/proxyquire.ts @@ -24,8 +24,9 @@ const mod = Module as unknown as { _resolveFilename: (request: string, parent: NodeModule) => string; }; const {hasOwnProperty} = Object.prototype; -const hasOwn = (o: unknown, k: string) => - Boolean(o && typeof o === 'object' && hasOwnProperty.call(o, k)); +const isObj = (o: unknown) => + Boolean(o && (typeof o === 'object' || typeof o === 'function')); +const hasOwn = (o: unknown, k: string) => isObj(o) && hasOwnProperty.call(o, k); const isGlobal = (s: unknown) => hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); const resolveFrom = (base: string, req: string) => { @@ -76,7 +77,13 @@ function proxyquire(request: string, stubs: Stubs = {}): T { const real = prev.call(this, id); if (real && ['object', 'function'].includes(typeof real)) { for (const k of Object.keys(real)) { - if (!(k in stub)) stub[k] = real[k]; + if (!(k in stub)) { + try { + stub[k] = real[k]; + } catch { + // ignore + } + } } } } catch { From 01e56cdc60f4378961181436c88c38d7d0d0b3ea Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Mon, 19 Oct 2026 17:06:38 +0000 Subject: [PATCH 3/4] [Address review comments] test: use Proxy for call-through fallback in proxyquire helper --- handwritten/bigtable/test/proxyquire.ts | 32 ++++++++++++++++++------- handwritten/spanner/test/proxyquire.ts | 32 ++++++++++++++++++------- handwritten/storage/test/proxyquire.ts | 32 ++++++++++++++++++------- 3 files changed, 72 insertions(+), 24 deletions(-) diff --git a/handwritten/bigtable/test/proxyquire.ts b/handwritten/bigtable/test/proxyquire.ts index 5d344ba47dad..7db5ba67b401 100644 --- a/handwritten/bigtable/test/proxyquire.ts +++ b/handwritten/bigtable/test/proxyquire.ts @@ -76,15 +76,31 @@ function proxyquire(request: string, stubs: Stubs = {}): T { try { const real = prev.call(this, id); if (real && ['object', 'function'].includes(typeof real)) { - for (const k of Object.keys(real)) { - if (!(k in stub)) { - try { - stub[k] = real[k]; - } catch { - // ignore + return new Proxy(stub, { + get(target, prop) { + if (prop in target) { + return Reflect.get(target, prop); } - } - } + return Reflect.get(real, prop); + }, + has(target, prop) { + return prop in target || prop in real; + }, + ownKeys(target) { + return Array.from( + new Set([ + ...Reflect.ownKeys(target), + ...Reflect.ownKeys(real), + ]), + ); + }, + getOwnPropertyDescriptor(target, prop) { + const desc = + Reflect.getOwnPropertyDescriptor(target, prop) || + Reflect.getOwnPropertyDescriptor(real, prop); + return desc ? {...desc, configurable: true} : undefined; + }, + }); } } catch { // ignore diff --git a/handwritten/spanner/test/proxyquire.ts b/handwritten/spanner/test/proxyquire.ts index 5d344ba47dad..7db5ba67b401 100644 --- a/handwritten/spanner/test/proxyquire.ts +++ b/handwritten/spanner/test/proxyquire.ts @@ -76,15 +76,31 @@ function proxyquire(request: string, stubs: Stubs = {}): T { try { const real = prev.call(this, id); if (real && ['object', 'function'].includes(typeof real)) { - for (const k of Object.keys(real)) { - if (!(k in stub)) { - try { - stub[k] = real[k]; - } catch { - // ignore + return new Proxy(stub, { + get(target, prop) { + if (prop in target) { + return Reflect.get(target, prop); } - } - } + return Reflect.get(real, prop); + }, + has(target, prop) { + return prop in target || prop in real; + }, + ownKeys(target) { + return Array.from( + new Set([ + ...Reflect.ownKeys(target), + ...Reflect.ownKeys(real), + ]), + ); + }, + getOwnPropertyDescriptor(target, prop) { + const desc = + Reflect.getOwnPropertyDescriptor(target, prop) || + Reflect.getOwnPropertyDescriptor(real, prop); + return desc ? {...desc, configurable: true} : undefined; + }, + }); } } catch { // ignore diff --git a/handwritten/storage/test/proxyquire.ts b/handwritten/storage/test/proxyquire.ts index b69639226354..a9b403b01536 100644 --- a/handwritten/storage/test/proxyquire.ts +++ b/handwritten/storage/test/proxyquire.ts @@ -76,15 +76,31 @@ function proxyquire(request: string, stubs: Stubs = {}): T { try { const real = prev.call(this, id); if (real && ['object', 'function'].includes(typeof real)) { - for (const k of Object.keys(real)) { - if (!(k in stub)) { - try { - stub[k] = real[k]; - } catch { - // ignore + return new Proxy(stub, { + get(target, prop) { + if (prop in target) { + return Reflect.get(target, prop); } - } - } + return Reflect.get(real, prop); + }, + has(target, prop) { + return prop in target || prop in real; + }, + ownKeys(target) { + return Array.from( + new Set([ + ...Reflect.ownKeys(target), + ...Reflect.ownKeys(real), + ]) + ); + }, + getOwnPropertyDescriptor(target, prop) { + const desc = + Reflect.getOwnPropertyDescriptor(target, prop) || + Reflect.getOwnPropertyDescriptor(real, prop); + return desc ? {...desc, configurable: true} : undefined; + }, + }); } } catch { // ignore From 36000746a03925115553cc6af8f2bc850820e968 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Tue, 20 Oct 2026 19:10:58 +0000 Subject: [PATCH 4/4] refactor(bigtable,spanner,storage): use centralized proxyquire from google-test-utils (Path 1B) --- handwritten/bigtable/cloudbuild.yaml | 2 +- handwritten/bigtable/package.json | 1 + .../client-side-metrics-all-methods.ts | 2 +- .../system-test/client-side-metrics.ts | 2 +- .../system-test/gcp-metrics-handler.ts | 2 +- handwritten/bigtable/system-test/read-rows.ts | 2 +- handwritten/bigtable/test/app-profile.ts | 2 +- handwritten/bigtable/test/backup.ts | 2 +- handwritten/bigtable/test/chunktransformer.ts | 2 +- handwritten/bigtable/test/cluster.ts | 2 +- handwritten/bigtable/test/executequery.ts | 2 +- handwritten/bigtable/test/family.ts | 2 +- handwritten/bigtable/test/filter.ts | 2 +- handwritten/bigtable/test/index.ts | 2 +- handwritten/bigtable/test/instance.ts | 2 +- .../test/metric-service-client-credentials.ts | 2 +- .../metrics-collector/gcp-metrics-handler.ts | 2 +- .../metrics-collector/metrics-collector.ts | 2 +- handwritten/bigtable/test/proxyquire.ts | 138 ------------------ handwritten/bigtable/test/row.ts | 2 +- handwritten/bigtable/test/table.ts | 2 +- handwritten/spanner/cloudbuild.yaml | 2 +- .../observability-test/batch-transaction.ts | 2 +- .../spanner/observability-test/database.ts | 2 +- .../spanner/observability-test/table.ts | 2 +- .../spanner/observability-test/transaction.ts | 2 +- handwritten/spanner/package.json | 2 +- handwritten/spanner/test/backup.ts | 2 +- handwritten/spanner/test/batch-transaction.ts | 2 +- .../spanner/test/common/service-object.ts | 2 +- handwritten/spanner/test/common/service.ts | 2 +- handwritten/spanner/test/database.ts | 2 +- handwritten/spanner/test/index.ts | 2 +- handwritten/spanner/test/instance-config.ts | 2 +- handwritten/spanner/test/instance.ts | 2 +- .../spanner/test/partial-result-stream.ts | 3 +- handwritten/spanner/test/proxyquire.ts | 138 ------------------ handwritten/spanner/test/session.ts | 2 +- handwritten/spanner/test/table.ts | 2 +- .../spanner/test/transaction-runner.ts | 2 +- handwritten/spanner/test/transaction.ts | 2 +- handwritten/storage/cloudbuild.yaml | 2 +- handwritten/storage/package.json | 1 + handwritten/storage/test/acl.ts | 2 +- handwritten/storage/test/bucket.ts | 2 +- handwritten/storage/test/channel.ts | 2 +- handwritten/storage/test/file.ts | 2 +- handwritten/storage/test/headers.ts | 2 +- handwritten/storage/test/hmacKey.ts | 2 +- handwritten/storage/test/iam.ts | 2 +- handwritten/storage/test/index.ts | 2 +- .../test/nodejs-common/service-object.ts | 2 +- .../storage/test/nodejs-common/service.ts | 2 +- .../storage/test/nodejs-common/util.ts | 2 +- handwritten/storage/test/notification.ts | 2 +- handwritten/storage/test/proxyquire.ts | 138 ------------------ pnpm-lock.yaml | 16 +- 57 files changed, 61 insertions(+), 474 deletions(-) delete mode 100644 handwritten/bigtable/test/proxyquire.ts delete mode 100644 handwritten/spanner/test/proxyquire.ts delete mode 100644 handwritten/storage/test/proxyquire.ts diff --git a/handwritten/bigtable/cloudbuild.yaml b/handwritten/bigtable/cloudbuild.yaml index ef5dbbc30c9b..2fdae9239f34 100644 --- a/handwritten/bigtable/cloudbuild.yaml +++ b/handwritten/bigtable/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: - '-c' - | - cd ../.. && corepack enable pnpm && pnpm install --filter "...{./handwritten/bigtable}" + cd ../.. && corepack enable pnpm && pnpm install --filter "./core/test-utils" --filter "...{./handwritten/bigtable}" --ignore-scripts && pnpm --dir core/test-utils run compile && node -e "require('./bin/run-test.cjs').ensureWorkspacePackagesLinked()" && pnpm --filter "...{./handwritten/bigtable}" run prepare --if-present dir: 'handwritten/bigtable' id: 'install-dependencies' diff --git a/handwritten/bigtable/package.json b/handwritten/bigtable/package.json index 222d1421e862..e04716e47092 100644 --- a/handwritten/bigtable/package.json +++ b/handwritten/bigtable/package.json @@ -93,6 +93,7 @@ "c8": "^10.1.3", "codecov": "^3.8.3", "gapic-tools": "^2.0.0", + "google-test-utils": "^0.3.0", "gts": "^6.0.2", "jsdoc": "^4.0.4", "jsdoc-fresh": "^6.0.0", diff --git a/handwritten/bigtable/system-test/client-side-metrics-all-methods.ts b/handwritten/bigtable/system-test/client-side-metrics-all-methods.ts index 42273eac501e..d1458e07d1a9 100644 --- a/handwritten/bigtable/system-test/client-side-metrics-all-methods.ts +++ b/handwritten/bigtable/system-test/client-side-metrics-all-methods.ts @@ -21,7 +21,7 @@ import { import {ResourceMetrics} from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import {GCPMetricsHandler} from '../src/client-side-metrics/gcp-metrics-handler'; -import * as proxyquire from '../test/proxyquire'; +import {proxyquire} from 'google-test-utils'; import {Bigtable, RawFilter} from '../src'; import {Mutation} from '../src/mutation'; import {Row} from '../src/row'; diff --git a/handwritten/bigtable/system-test/client-side-metrics.ts b/handwritten/bigtable/system-test/client-side-metrics.ts index 4a1878131cc8..deb3254b6579 100644 --- a/handwritten/bigtable/system-test/client-side-metrics.ts +++ b/handwritten/bigtable/system-test/client-side-metrics.ts @@ -21,7 +21,7 @@ import { import {ResourceMetrics} from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import {GCPMetricsHandler} from '../src/client-side-metrics/gcp-metrics-handler'; -import * as proxyquire from '../test/proxyquire'; +import {proxyquire} from 'google-test-utils'; import {Bigtable, BigtableOptions} from '../src'; import {Row} from '../src/row'; import { diff --git a/handwritten/bigtable/system-test/gcp-metrics-handler.ts b/handwritten/bigtable/system-test/gcp-metrics-handler.ts index bd847cbe2dfd..a70b449a562c 100644 --- a/handwritten/bigtable/system-test/gcp-metrics-handler.ts +++ b/handwritten/bigtable/system-test/gcp-metrics-handler.ts @@ -26,7 +26,7 @@ import {Bigtable} from '../src'; import {ResourceMetrics} from '@opentelemetry/sdk-metrics'; import * as assert from 'assert'; import {ClientOptions} from 'google-gax'; -import * as proxyquire from '../test/proxyquire'; +import {proxyquire} from 'google-test-utils'; function getHandler(Exporter: typeof CloudMonitoringExporter) { const FakeCGPMetricsHandler = proxyquire( diff --git a/handwritten/bigtable/system-test/read-rows.ts b/handwritten/bigtable/system-test/read-rows.ts index 3317432e6c5e..454f743de83c 100644 --- a/handwritten/bigtable/system-test/read-rows.ts +++ b/handwritten/bigtable/system-test/read-rows.ts @@ -26,7 +26,7 @@ import {EventEmitter} from 'events'; import {Test} from './testTypes'; import {ServiceError, GrpcClient, GoogleError, CallOptions} from 'google-gax'; import {PassThrough} from 'stream'; -import * as proxyquire from '../test/proxyquire'; +import {proxyquire} from 'google-test-utils'; import {TabularApiSurface} from '../src/tabular-api-surface'; import * as mocha from 'mocha'; import {generateId} from './common'; diff --git a/handwritten/bigtable/test/app-profile.ts b/handwritten/bigtable/test/app-profile.ts index 33b2d1b34c9a..3ebd6ec679c7 100644 --- a/handwritten/bigtable/test/app-profile.ts +++ b/handwritten/bigtable/test/app-profile.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import {CallOptions} from 'google-gax'; let promisified = false; diff --git a/handwritten/bigtable/test/backup.ts b/handwritten/bigtable/test/backup.ts index fdf55e792e01..779747a32e2d 100644 --- a/handwritten/bigtable/test/backup.ts +++ b/handwritten/bigtable/test/backup.ts @@ -16,7 +16,7 @@ import {PreciseDate} from '@google-cloud/precise-date'; import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, describe, it, afterEach} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as pumpify from 'pumpify'; import {ServiceError} from 'google-gax'; diff --git a/handwritten/bigtable/test/chunktransformer.ts b/handwritten/bigtable/test/chunktransformer.ts index fb7897914bc7..cac31179cb03 100644 --- a/handwritten/bigtable/test/chunktransformer.ts +++ b/handwritten/bigtable/test/chunktransformer.ts @@ -15,7 +15,7 @@ import * as assert from 'assert'; import {describe, it, before, beforeEach, afterEach} from 'mocha'; import Long = require('long'); -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sn from 'sinon'; import {RowStateEnum} from '../src/chunktransformer.js'; diff --git a/handwritten/bigtable/test/cluster.ts b/handwritten/bigtable/test/cluster.ts index b49ad5298e49..a2144ef10318 100644 --- a/handwritten/bigtable/test/cluster.ts +++ b/handwritten/bigtable/test/cluster.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import {PassThrough, Readable} from 'stream'; import {CallOptions} from 'google-gax'; import {PreciseDate} from '@google-cloud/precise-date'; diff --git a/handwritten/bigtable/test/executequery.ts b/handwritten/bigtable/test/executequery.ts index fc1e2a469b0b..ea6168445aed 100644 --- a/handwritten/bigtable/test/executequery.ts +++ b/handwritten/bigtable/test/executequery.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import * as sinon from 'sinon'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import {grpc} from 'google-gax'; import * as inst from '../src/instance'; import {Bigtable} from '../src'; diff --git a/handwritten/bigtable/test/family.ts b/handwritten/bigtable/test/family.ts index 9017558d2185..a4661ca0a57a 100644 --- a/handwritten/bigtable/test/family.ts +++ b/handwritten/bigtable/test/family.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {protos} from '@google-cloud/bigtable-api'; import google = protos.google; diff --git a/handwritten/bigtable/test/filter.ts b/handwritten/bigtable/test/filter.ts index cd9ae392ef74..e90d445058d2 100644 --- a/handwritten/bigtable/test/filter.ts +++ b/handwritten/bigtable/test/filter.ts @@ -14,7 +14,7 @@ import * as assert from 'assert'; import {afterEach, before, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import * as fr from '../src/filter'; import {Row} from '../src/row'; diff --git a/handwritten/bigtable/test/index.ts b/handwritten/bigtable/test/index.ts index b0995985e248..5345ea6dfa0b 100644 --- a/handwritten/bigtable/test/index.ts +++ b/handwritten/bigtable/test/index.ts @@ -17,7 +17,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {afterEach, before, beforeEach, describe, it} from 'mocha'; import * as gax from 'google-gax'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sn from 'sinon'; import {Cluster} from '../src/cluster.js'; diff --git a/handwritten/bigtable/test/instance.ts b/handwritten/bigtable/test/instance.ts index 876cb156f77d..e0431a697ae2 100644 --- a/handwritten/bigtable/test/instance.ts +++ b/handwritten/bigtable/test/instance.ts @@ -16,7 +16,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import * as sinon from 'sinon'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import {ServiceError} from 'google-gax'; import * as snapshot from 'snap-shot-it'; import {Readable} from 'stream'; diff --git a/handwritten/bigtable/test/metric-service-client-credentials.ts b/handwritten/bigtable/test/metric-service-client-credentials.ts index bea333d2c105..ee563ea76e11 100644 --- a/handwritten/bigtable/test/metric-service-client-credentials.ts +++ b/handwritten/bigtable/test/metric-service-client-credentials.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import {ClientOptions, grpc} from 'google-gax'; import * as assert from 'assert'; import {MetricServiceClient} from '@google-cloud/monitoring'; diff --git a/handwritten/bigtable/test/metrics-collector/gcp-metrics-handler.ts b/handwritten/bigtable/test/metrics-collector/gcp-metrics-handler.ts index 5deebaf2bed0..8c726b111cfa 100644 --- a/handwritten/bigtable/test/metrics-collector/gcp-metrics-handler.ts +++ b/handwritten/bigtable/test/metrics-collector/gcp-metrics-handler.ts @@ -30,7 +30,7 @@ import { } from '../../test-common/expected-otel-export-input'; import * as assert from 'assert'; import {replaceTimestamps} from '../../test-common/replace-timestamps'; -import * as proxyquire from '../proxyquire'; +import {proxyquire} from 'google-test-utils'; /** * Cleans a ResourceMetrics object by replacing client UUIDs with a placeholder. diff --git a/handwritten/bigtable/test/metrics-collector/metrics-collector.ts b/handwritten/bigtable/test/metrics-collector/metrics-collector.ts index edaaa74bacd2..92dce755cbcb 100644 --- a/handwritten/bigtable/test/metrics-collector/metrics-collector.ts +++ b/handwritten/bigtable/test/metrics-collector/metrics-collector.ts @@ -23,7 +23,7 @@ import { import {grpc} from 'google-gax'; import {expectedRequestsHandled} from '../../test-common/metrics-handler-fixture'; import * as gax from 'google-gax'; -import * as proxyquire from '../proxyquire'; +import {proxyquire} from 'google-test-utils'; import {GCPMetricsHandler} from '../../src/client-side-metrics/gcp-metrics-handler'; import {getProtoPath} from '../../src/utils/protos'; const protoPath = getProtoPath('google/bigtable/v2/response_params.proto'); diff --git a/handwritten/bigtable/test/proxyquire.ts b/handwritten/bigtable/test/proxyquire.ts deleted file mode 100644 index 7db5ba67b401..000000000000 --- a/handwritten/bigtable/test/proxyquire.ts +++ /dev/null @@ -1,138 +0,0 @@ -// 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. - -import * as Module from 'module'; -import * as path from 'path'; - -delete require.cache[require.resolve(__filename)]; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type Stubs = Record; -const mod = Module as unknown as { - prototype: {require: NodeRequire}; - _resolveFilename: (request: string, parent: NodeModule) => string; -}; -const {hasOwnProperty} = Object.prototype; -const isObj = (o: unknown) => - Boolean(o && (typeof o === 'object' || typeof o === 'function')); -const hasOwn = (o: unknown, k: string) => isObj(o) && hasOwnProperty.call(o, k); -const isGlobal = (s: unknown) => - hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); -const resolveFrom = (base: string, req: string) => { - try { - return require.resolve(req, {paths: [path.dirname(base)]}); - } catch { - return req[0] === '.' ? path.resolve(path.dirname(base), req) : req; - } -}; - -const parent = module.parent || module; -let skipCallThru = false; -let keepCache = true; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function proxyquire(request: string, stubs: Stubs = {}): T { - const sut = mod._resolveFilename(request, parent); - const resolved: Stubs = {}; - let global = false; - for (const [k, v] of Object.entries(stubs)) { - if (isGlobal(v)) global = true; - resolved[resolveFrom(sut, k)] = v; - } - const cache = require.cache; - const keys = global - ? Object.keys(cache).filter(id => !id.endsWith('.node')) - : [sut]; - const saved = keys.map(id => [id, cache[id]] as const); - for (const id of keys) delete cache[id]; - const prev = mod.prototype.require; - const req = function (this: NodeModule, id: string) { - const isSut = this?.filename === sut; - if (this?.filename && (isSut || global)) { - const hit = (isSut || !id.startsWith('.')) && hasOwn(stubs, id); - const rId = hit ? '' : resolveFrom(this.filename, id); - if (hit || hasOwn(resolved, rId)) { - const stub = hit ? stubs[id] : resolved[rId]; - if (isSut || isGlobal(stub)) { - if (stub === null) { - const e = new Error(`Cannot find module '${id}'`); - throw Object.assign(e, {code: 'MODULE_NOT_FOUND'}); - } - const skip = hasOwn(stub, '@noCallThru') - ? stub['@noCallThru'] - : skipCallThru; - if (!skip && stub && ['object', 'function'].includes(typeof stub)) { - try { - const real = prev.call(this, id); - if (real && ['object', 'function'].includes(typeof real)) { - return new Proxy(stub, { - get(target, prop) { - if (prop in target) { - return Reflect.get(target, prop); - } - return Reflect.get(real, prop); - }, - has(target, prop) { - return prop in target || prop in real; - }, - ownKeys(target) { - return Array.from( - new Set([ - ...Reflect.ownKeys(target), - ...Reflect.ownKeys(real), - ]), - ); - }, - getOwnPropertyDescriptor(target, prop) { - const desc = - Reflect.getOwnPropertyDescriptor(target, prop) || - Reflect.getOwnPropertyDescriptor(real, prop); - return desc ? {...desc, configurable: true} : undefined; - }, - }); - } - } catch { - // ignore - } - } - return stub; - } - } - } - return prev.call(this, id); - }; - mod.prototype.require = Object.assign(req, prev); - try { - return prev.call(parent, request); - } finally { - mod.prototype.require = prev; - for (const id of global ? Object.keys(cache) : [sut]) { - if (!id.endsWith('.node')) delete cache[id]; - } - for (const [id, m] of saved) { - if (m && (keepCache || id !== sut)) cache[id] = m; - } - } -} - -// eslint-disable-next-line @typescript-eslint/no-namespace -namespace proxyquire { - export const load = proxyquire; - export const noCallThru = () => ((skipCallThru = true), proxyquire); - export const callThru = () => ((skipCallThru = false), proxyquire); - export const noPreserveCache = () => ((keepCache = false), proxyquire); - export const preserveCache = () => ((keepCache = true), proxyquire); -} - -export = proxyquire; diff --git a/handwritten/bigtable/test/row.ts b/handwritten/bigtable/test/row.ts index ab2cba41c8a8..e03bfb56ffc8 100644 --- a/handwritten/bigtable/test/row.ts +++ b/handwritten/bigtable/test/row.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {afterEach, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {Mutation} from '../src/mutation.js'; import * as rw from '../src/row'; diff --git a/handwritten/bigtable/test/table.ts b/handwritten/bigtable/test/table.ts index 5a7d54d67d07..6406de9e25e9 100644 --- a/handwritten/bigtable/test/table.ts +++ b/handwritten/bigtable/test/table.ts @@ -15,7 +15,7 @@ import * as promisify from '@google-cloud/promisify'; import * as assert from 'assert'; import {afterEach, before, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as pumpify from 'pumpify'; import * as sinon from 'sinon'; import {PassThrough, Writable, Duplex} from 'stream'; diff --git a/handwritten/spanner/cloudbuild.yaml b/handwritten/spanner/cloudbuild.yaml index 5ac1335ff111..fe987edf9c5f 100644 --- a/handwritten/spanner/cloudbuild.yaml +++ b/handwritten/spanner/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: - '-c' - | - cd ../.. && corepack enable pnpm && pnpm install --filter "...{./handwritten/spanner}" + cd ../.. && corepack enable pnpm && pnpm install --filter "./core/test-utils" --filter "...{./handwritten/spanner}" --ignore-scripts && pnpm --dir core/test-utils run compile && node -e "require('./bin/run-test.cjs').ensureWorkspacePackagesLinked()" && pnpm --filter "...{./handwritten/spanner}" run prepare --if-present dir: 'handwritten/spanner' id: 'install-dependencies' diff --git a/handwritten/spanner/observability-test/batch-transaction.ts b/handwritten/spanner/observability-test/batch-transaction.ts index 4105c3d95993..3aa215842ec3 100644 --- a/handwritten/spanner/observability-test/batch-transaction.ts +++ b/handwritten/spanner/observability-test/batch-transaction.ts @@ -21,7 +21,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, after, beforeEach, afterEach, describe, it} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from '../test/proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; const { AlwaysOnSampler, diff --git a/handwritten/spanner/observability-test/database.ts b/handwritten/spanner/observability-test/database.ts index 7d4ab0b796e6..1b3a3e23df4b 100644 --- a/handwritten/spanner/observability-test/database.ts +++ b/handwritten/spanner/observability-test/database.ts @@ -30,7 +30,7 @@ import { } from '../src/transaction'; import {util} from '@google-cloud/common'; import {Transform} from 'stream'; -import * as proxyquire from '../test/proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; const {SpanStatusCode} = require('@opentelemetry/api'); const { diff --git a/handwritten/spanner/observability-test/table.ts b/handwritten/spanner/observability-test/table.ts index 3506f10e4eeb..5f76ba14bf59 100644 --- a/handwritten/spanner/observability-test/table.ts +++ b/handwritten/spanner/observability-test/table.ts @@ -18,7 +18,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from '../test/proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import * as through from 'through2'; diff --git a/handwritten/spanner/observability-test/transaction.ts b/handwritten/spanner/observability-test/transaction.ts index 351427dd0b9b..642e1066b1b3 100644 --- a/handwritten/spanner/observability-test/transaction.ts +++ b/handwritten/spanner/observability-test/transaction.ts @@ -17,7 +17,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {EventEmitter} from 'events'; -import * as proxyquire from '../test/proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {codec} from '../src/codec'; diff --git a/handwritten/spanner/package.json b/handwritten/spanner/package.json index e26166d92aa7..04702a7dc5c6 100644 --- a/handwritten/spanner/package.json +++ b/handwritten/spanner/package.json @@ -107,7 +107,7 @@ "dedent": "^1.5.3", "execa": "^5.0.0", "gapic-tools": "^2.0.0", - "google-test-utils": "^0.2.0", + "google-test-utils": "^0.3.0", "grpc-health-check": "^2.1.0", "gts": "^6.0.2", "jsdoc": "^4.0.4", diff --git a/handwritten/spanner/test/backup.ts b/handwritten/spanner/test/backup.ts index e8de46ecbed3..9ef283045d36 100644 --- a/handwritten/spanner/test/backup.ts +++ b/handwritten/spanner/test/backup.ts @@ -17,7 +17,7 @@ import * as assert from 'assert'; import {EventEmitter} from 'events'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {util} from '@google-cloud/common'; import * as pfy from '@google-cloud/promisify'; diff --git a/handwritten/spanner/test/batch-transaction.ts b/handwritten/spanner/test/batch-transaction.ts index f8cd9043ae7a..d9945b34d615 100644 --- a/handwritten/spanner/test/batch-transaction.ts +++ b/handwritten/spanner/test/batch-transaction.ts @@ -20,7 +20,7 @@ import {util} from '@google-cloud/common'; import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {Session, Database, Spanner, protos} from '../src'; diff --git a/handwritten/spanner/test/common/service-object.ts b/handwritten/spanner/test/common/service-object.ts index 489863c87d5a..208c3f148d39 100644 --- a/handwritten/spanner/test/common/service-object.ts +++ b/handwritten/spanner/test/common/service-object.ts @@ -19,7 +19,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, describe, it} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from '../proxyquire'; +import {proxyquire} from 'google-test-utils'; let promisified = false; const fakePfy = Object.assign({}, pfy, { diff --git a/handwritten/spanner/test/common/service.ts b/handwritten/spanner/test/common/service.ts index c073c31cd095..05fc2d0d0a58 100644 --- a/handwritten/spanner/test/common/service.ts +++ b/handwritten/spanner/test/common/service.ts @@ -20,7 +20,7 @@ import {before, beforeEach, after, afterEach, describe, it} from 'mocha'; import * as duplexify from 'duplexify'; import * as extend from 'extend'; import {grpc, GrpcClient} from 'google-gax'; -import * as proxyquire from '../proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as retryRequest from 'retry-request'; import * as sn from 'sinon'; import {PassThrough} from 'stream'; diff --git a/handwritten/spanner/test/database.ts b/handwritten/spanner/test/database.ts index eebe303775c2..d5b66fae4bce 100644 --- a/handwritten/spanner/test/database.ts +++ b/handwritten/spanner/test/database.ts @@ -21,7 +21,7 @@ import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {EventEmitter} from 'events'; import * as extend from 'extend'; import {ApiError, util} from '@google-cloud/common'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {Transform, Duplex} from 'stream'; import * as through from 'through2'; diff --git a/handwritten/spanner/test/index.ts b/handwritten/spanner/test/index.ts index 3bbcbc34f054..ea9fa0c6ca9a 100644 --- a/handwritten/spanner/test/index.ts +++ b/handwritten/spanner/test/index.ts @@ -20,7 +20,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import * as extend from 'extend'; import * as path from 'path'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as through from 'through2'; import {util} from '@google-cloud/common'; import {PreciseDate} from '@google-cloud/precise-date'; diff --git a/handwritten/spanner/test/instance-config.ts b/handwritten/spanner/test/instance-config.ts index 528ca1e875e2..b755ff8033b4 100644 --- a/handwritten/spanner/test/instance-config.ts +++ b/handwritten/spanner/test/instance-config.ts @@ -18,7 +18,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {grpc} from 'google-gax'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as pfy from '@google-cloud/promisify'; import * as sinon from 'sinon'; diff --git a/handwritten/spanner/test/instance.ts b/handwritten/spanner/test/instance.ts index ef6d21788c6c..263d9bb9621f 100644 --- a/handwritten/spanner/test/instance.ts +++ b/handwritten/spanner/test/instance.ts @@ -20,7 +20,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {ApiError} from '@google-cloud/common'; import {grpc} from 'google-gax'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as pfy from '@google-cloud/promisify'; import * as sinon from 'sinon'; import snakeCase = require('lodash.snakecase'); diff --git a/handwritten/spanner/test/partial-result-stream.ts b/handwritten/spanner/test/partial-result-stream.ts index beceec7e1521..05d2fa8e1a45 100644 --- a/handwritten/spanner/test/partial-result-stream.ts +++ b/handwritten/spanner/test/partial-result-stream.ts @@ -19,7 +19,6 @@ import {before, beforeEach, afterEach, describe, it} from 'mocha'; // eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires const concat = require('concat-stream'); -import * as proxyquire from './proxyquire'; import * as sinon from 'sinon'; import {Transform, finished} from 'stream'; import * as through from 'through2'; @@ -28,7 +27,7 @@ import {codec} from '../src/codec'; import {PreciseDate} from '@google-cloud/precise-date'; import * as prs from '../src/partial-result-stream'; import {grpc} from 'google-gax'; -import {assertArraySubclassStrictEqual} from 'google-test-utils'; +import {assertArraySubclassStrictEqual, proxyquire} from 'google-test-utils'; import {Row} from '../src/partial-result-stream'; function toRawValue(value: any): any { diff --git a/handwritten/spanner/test/proxyquire.ts b/handwritten/spanner/test/proxyquire.ts deleted file mode 100644 index 7db5ba67b401..000000000000 --- a/handwritten/spanner/test/proxyquire.ts +++ /dev/null @@ -1,138 +0,0 @@ -// 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. - -import * as Module from 'module'; -import * as path from 'path'; - -delete require.cache[require.resolve(__filename)]; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type Stubs = Record; -const mod = Module as unknown as { - prototype: {require: NodeRequire}; - _resolveFilename: (request: string, parent: NodeModule) => string; -}; -const {hasOwnProperty} = Object.prototype; -const isObj = (o: unknown) => - Boolean(o && (typeof o === 'object' || typeof o === 'function')); -const hasOwn = (o: unknown, k: string) => isObj(o) && hasOwnProperty.call(o, k); -const isGlobal = (s: unknown) => - hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); -const resolveFrom = (base: string, req: string) => { - try { - return require.resolve(req, {paths: [path.dirname(base)]}); - } catch { - return req[0] === '.' ? path.resolve(path.dirname(base), req) : req; - } -}; - -const parent = module.parent || module; -let skipCallThru = false; -let keepCache = true; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function proxyquire(request: string, stubs: Stubs = {}): T { - const sut = mod._resolveFilename(request, parent); - const resolved: Stubs = {}; - let global = false; - for (const [k, v] of Object.entries(stubs)) { - if (isGlobal(v)) global = true; - resolved[resolveFrom(sut, k)] = v; - } - const cache = require.cache; - const keys = global - ? Object.keys(cache).filter(id => !id.endsWith('.node')) - : [sut]; - const saved = keys.map(id => [id, cache[id]] as const); - for (const id of keys) delete cache[id]; - const prev = mod.prototype.require; - const req = function (this: NodeModule, id: string) { - const isSut = this?.filename === sut; - if (this?.filename && (isSut || global)) { - const hit = (isSut || !id.startsWith('.')) && hasOwn(stubs, id); - const rId = hit ? '' : resolveFrom(this.filename, id); - if (hit || hasOwn(resolved, rId)) { - const stub = hit ? stubs[id] : resolved[rId]; - if (isSut || isGlobal(stub)) { - if (stub === null) { - const e = new Error(`Cannot find module '${id}'`); - throw Object.assign(e, {code: 'MODULE_NOT_FOUND'}); - } - const skip = hasOwn(stub, '@noCallThru') - ? stub['@noCallThru'] - : skipCallThru; - if (!skip && stub && ['object', 'function'].includes(typeof stub)) { - try { - const real = prev.call(this, id); - if (real && ['object', 'function'].includes(typeof real)) { - return new Proxy(stub, { - get(target, prop) { - if (prop in target) { - return Reflect.get(target, prop); - } - return Reflect.get(real, prop); - }, - has(target, prop) { - return prop in target || prop in real; - }, - ownKeys(target) { - return Array.from( - new Set([ - ...Reflect.ownKeys(target), - ...Reflect.ownKeys(real), - ]), - ); - }, - getOwnPropertyDescriptor(target, prop) { - const desc = - Reflect.getOwnPropertyDescriptor(target, prop) || - Reflect.getOwnPropertyDescriptor(real, prop); - return desc ? {...desc, configurable: true} : undefined; - }, - }); - } - } catch { - // ignore - } - } - return stub; - } - } - } - return prev.call(this, id); - }; - mod.prototype.require = Object.assign(req, prev); - try { - return prev.call(parent, request); - } finally { - mod.prototype.require = prev; - for (const id of global ? Object.keys(cache) : [sut]) { - if (!id.endsWith('.node')) delete cache[id]; - } - for (const [id, m] of saved) { - if (m && (keepCache || id !== sut)) cache[id] = m; - } - } -} - -// eslint-disable-next-line @typescript-eslint/no-namespace -namespace proxyquire { - export const load = proxyquire; - export const noCallThru = () => ((skipCallThru = true), proxyquire); - export const callThru = () => ((skipCallThru = false), proxyquire); - export const noPreserveCache = () => ((keepCache = false), proxyquire); - export const preserveCache = () => ((keepCache = true), proxyquire); -} - -export = proxyquire; diff --git a/handwritten/spanner/test/session.ts b/handwritten/spanner/test/session.ts index fb5cfb734054..6e8e48c36afc 100644 --- a/handwritten/spanner/test/session.ts +++ b/handwritten/spanner/test/session.ts @@ -19,7 +19,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, describe, it} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import { CLOUD_RESOURCE_HEADER, LEADER_AWARE_ROUTING_HEADER, diff --git a/handwritten/spanner/test/table.ts b/handwritten/spanner/test/table.ts index cd4d2a744532..b492b7a8d7a2 100644 --- a/handwritten/spanner/test/table.ts +++ b/handwritten/spanner/test/table.ts @@ -17,7 +17,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {Transform} from 'stream'; import * as through from 'through2'; diff --git a/handwritten/spanner/test/transaction-runner.ts b/handwritten/spanner/test/transaction-runner.ts index ee5c8764bcbc..d258340da1d3 100644 --- a/handwritten/spanner/test/transaction-runner.ts +++ b/handwritten/spanner/test/transaction-runner.ts @@ -18,7 +18,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {EventEmitter} from 'events'; import {grpc} from 'google-gax'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import * as through from 'through2'; import {RunTransactionOptions} from '../src/transaction-runner'; diff --git a/handwritten/spanner/test/transaction.ts b/handwritten/spanner/test/transaction.ts index c3ae51eff5ac..55706e14605c 100644 --- a/handwritten/spanner/test/transaction.ts +++ b/handwritten/spanner/test/transaction.ts @@ -19,7 +19,7 @@ import * as assert from 'assert'; import {before, beforeEach, afterEach, describe, it} from 'mocha'; import {EventEmitter} from 'events'; import {common as p} from 'protobufjs'; -import * as proxyquire from './proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {codec} from '../src/codec'; diff --git a/handwritten/storage/cloudbuild.yaml b/handwritten/storage/cloudbuild.yaml index c0d5e374ea4a..57e5f1eb1536 100644 --- a/handwritten/storage/cloudbuild.yaml +++ b/handwritten/storage/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: - '-c' - | - cd ../.. && corepack enable pnpm && pnpm install --filter "...{./handwritten/storage}" + cd ../.. && corepack enable pnpm && pnpm install --filter "./core/test-utils" --filter "...{./handwritten/storage}" --ignore-scripts && pnpm --dir core/test-utils run compile && node -e "require('./bin/run-test.cjs').ensureWorkspacePackagesLinked()" && pnpm --filter "...{./handwritten/storage}" run prepare --if-present dir: 'handwritten/storage' id: 'install-dependencies' diff --git a/handwritten/storage/package.json b/handwritten/storage/package.json index 679530c84430..ee196e30138c 100644 --- a/handwritten/storage/package.json +++ b/handwritten/storage/package.json @@ -110,6 +110,7 @@ "cross-env": "^7.0.3", "form-data": "^4.0.4", "gapic-tools": "^2.0.1", + "google-test-utils": "^0.3.0", "gts": "^5.0.0", "jsdoc": "^4.0.4", "jsdoc-fresh": "^6.0.0", diff --git a/handwritten/storage/test/acl.ts b/handwritten/storage/test/acl.ts index 6d35f9398488..ba6c7d876ceb 100644 --- a/handwritten/storage/test/acl.ts +++ b/handwritten/storage/test/acl.ts @@ -15,7 +15,7 @@ import {DecorateRequestOptions, util} from '../src/nodejs-common/index.js'; import assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import {Storage} from '../src/storage.js'; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/handwritten/storage/test/bucket.ts b/handwritten/storage/test/bucket.ts index dc39164881c3..91eafa1e2263 100644 --- a/handwritten/storage/test/bucket.ts +++ b/handwritten/storage/test/bucket.ts @@ -26,7 +26,7 @@ import {describe, it, before, beforeEach, after, afterEach} from 'mocha'; import mime from 'mime'; import pLimit from 'p-limit'; import * as path from 'path'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import * as stream from 'stream'; import {Bucket, Channel, Notification, CRC32C} from '../src/index.js'; diff --git a/handwritten/storage/test/channel.ts b/handwritten/storage/test/channel.ts index 715f8adc1321..b9c5039d1599 100644 --- a/handwritten/storage/test/channel.ts +++ b/handwritten/storage/test/channel.ts @@ -24,7 +24,7 @@ import { } from '../src/nodejs-common/index.js'; import assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; let promisified = false; const fakePromisify = { diff --git a/handwritten/storage/test/file.ts b/handwritten/storage/test/file.ts index 04dd3bb069ef..962bf4dbad78 100644 --- a/handwritten/storage/test/file.ts +++ b/handwritten/storage/test/file.ts @@ -36,7 +36,7 @@ import * as crypto from 'crypto'; import duplexify from 'duplexify'; import * as fs from 'fs'; import * as path from 'path'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import * as resumableUpload from '../src/resumable-upload.js'; import * as sinon from 'sinon'; import * as tmp from 'tmp'; diff --git a/handwritten/storage/test/headers.ts b/handwritten/storage/test/headers.ts index 4b3080293308..fc1f437c4a97 100644 --- a/handwritten/storage/test/headers.ts +++ b/handwritten/storage/test/headers.ts @@ -14,7 +14,7 @@ import * as assert from 'assert'; import {describe, it} from 'mocha'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; const error = Error('not implemented'); diff --git a/handwritten/storage/test/hmacKey.ts b/handwritten/storage/test/hmacKey.ts index 01d8d73c4167..f8a3da7e723b 100644 --- a/handwritten/storage/test/hmacKey.ts +++ b/handwritten/storage/test/hmacKey.ts @@ -13,7 +13,7 @@ // limitations under the License. import * as sinon from 'sinon'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import assert from 'assert'; import {describe, it, beforeEach, afterEach} from 'mocha'; import {util, ServiceObject} from '../src/nodejs-common/index.js'; diff --git a/handwritten/storage/test/iam.ts b/handwritten/storage/test/iam.ts index 9907c2981075..d4b8440faf89 100644 --- a/handwritten/storage/test/iam.ts +++ b/handwritten/storage/test/iam.ts @@ -15,7 +15,7 @@ import {DecorateRequestOptions, util} from '../src/nodejs-common/index.js'; import assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import {IAMExceptionMessages} from '../src/iam.js'; describe('storage/iam', () => { diff --git a/handwritten/storage/test/index.ts b/handwritten/storage/test/index.ts index a3d3f9fc0b04..d3d8360fab7b 100644 --- a/handwritten/storage/test/index.ts +++ b/handwritten/storage/test/index.ts @@ -22,7 +22,7 @@ import { import {PromisifyAllOptions} from '@google-cloud/promisify'; import assert from 'assert'; import {describe, it, before, beforeEach, after, afterEach} from 'mocha'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import {Bucket, CRC32C_DEFAULT_VALIDATOR_GENERATOR} from '../src/index.js'; import {GetFilesOptions} from '../src/bucket.js'; diff --git a/handwritten/storage/test/nodejs-common/service-object.ts b/handwritten/storage/test/nodejs-common/service-object.ts index 58b7128964bb..159949ebe6e7 100644 --- a/handwritten/storage/test/nodejs-common/service-object.ts +++ b/handwritten/storage/test/nodejs-common/service-object.ts @@ -21,7 +21,7 @@ import { } from '@google-cloud/promisify'; import assert from 'assert'; import {describe, it, beforeEach, afterEach} from 'mocha'; -import proxyquire from '../proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import type { OptionsWithUri, Request as TeenyRequest, diff --git a/handwritten/storage/test/nodejs-common/service.ts b/handwritten/storage/test/nodejs-common/service.ts index e153b25a7de8..131dcbf4122f 100644 --- a/handwritten/storage/test/nodejs-common/service.ts +++ b/handwritten/storage/test/nodejs-common/service.ts @@ -16,7 +16,7 @@ import assert from 'assert'; import {describe, it, before, beforeEach, after} from 'mocha'; -import proxyquire from '../proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import {Request} from 'teeny-request'; import {AuthClient, GoogleAuth, OAuth2Client} from 'google-auth-library'; diff --git a/handwritten/storage/test/nodejs-common/util.ts b/handwritten/storage/test/nodejs-common/util.ts index 4b43852fd4f7..e764734227e7 100644 --- a/handwritten/storage/test/nodejs-common/util.ts +++ b/handwritten/storage/test/nodejs-common/util.ts @@ -27,7 +27,7 @@ import { OAuth2Client, } from 'google-auth-library'; import * as nock from 'nock'; -import proxyquire from '../proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import retryRequest from 'retry-request'; import * as sinon from 'sinon'; import * as stream from 'stream'; diff --git a/handwritten/storage/test/notification.ts b/handwritten/storage/test/notification.ts index 2635c34acf04..f562ec14fd92 100644 --- a/handwritten/storage/test/notification.ts +++ b/handwritten/storage/test/notification.ts @@ -21,7 +21,7 @@ import { } from '../src/nodejs-common/index.js'; import assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import proxyquire from './proxyquire.js'; +import {proxyquire} from 'google-test-utils'; import {Bucket} from '../src/index.js'; diff --git a/handwritten/storage/test/proxyquire.ts b/handwritten/storage/test/proxyquire.ts deleted file mode 100644 index a9b403b01536..000000000000 --- a/handwritten/storage/test/proxyquire.ts +++ /dev/null @@ -1,138 +0,0 @@ -// 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. - -import Module from 'module'; -import path from 'path'; - -delete require.cache[require.resolve(__filename)]; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type Stubs = Record; -const mod = Module as unknown as { - prototype: {require: NodeRequire}; - _resolveFilename: (request: string, parent: NodeModule) => string; -}; -const {hasOwnProperty} = Object.prototype; -const isObj = (o: unknown) => - Boolean(o && (typeof o === 'object' || typeof o === 'function')); -const hasOwn = (o: unknown, k: string) => isObj(o) && hasOwnProperty.call(o, k); -const isGlobal = (s: unknown) => - hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); -const resolveFrom = (base: string, req: string) => { - try { - return require.resolve(req, {paths: [path.dirname(base)]}); - } catch { - return req[0] === '.' ? path.resolve(path.dirname(base), req) : req; - } -}; - -const parent = module.parent || module; -let skipCallThru = false; -let keepCache = true; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function proxyquire(request: string, stubs: Stubs = {}): T { - const sut = mod._resolveFilename(request, parent); - const resolved: Stubs = {}; - let global = false; - for (const [k, v] of Object.entries(stubs)) { - if (isGlobal(v)) global = true; - resolved[resolveFrom(sut, k)] = v; - } - const cache = require.cache; - const keys = global - ? Object.keys(cache).filter(id => !id.endsWith('.node')) - : [sut]; - const saved = keys.map(id => [id, cache[id]] as const); - for (const id of keys) delete cache[id]; - const prev = mod.prototype.require; - const req = function (this: NodeModule, id: string) { - const isSut = this?.filename === sut; - if (this?.filename && (isSut || global)) { - const hit = (isSut || !id.startsWith('.')) && hasOwn(stubs, id); - const rId = hit ? '' : resolveFrom(this.filename, id); - if (hit || hasOwn(resolved, rId)) { - const stub = hit ? stubs[id] : resolved[rId]; - if (isSut || isGlobal(stub)) { - if (stub === null) { - const e = new Error(`Cannot find module '${id}'`); - throw Object.assign(e, {code: 'MODULE_NOT_FOUND'}); - } - const skip = hasOwn(stub, '@noCallThru') - ? stub['@noCallThru'] - : skipCallThru; - if (!skip && stub && ['object', 'function'].includes(typeof stub)) { - try { - const real = prev.call(this, id); - if (real && ['object', 'function'].includes(typeof real)) { - return new Proxy(stub, { - get(target, prop) { - if (prop in target) { - return Reflect.get(target, prop); - } - return Reflect.get(real, prop); - }, - has(target, prop) { - return prop in target || prop in real; - }, - ownKeys(target) { - return Array.from( - new Set([ - ...Reflect.ownKeys(target), - ...Reflect.ownKeys(real), - ]) - ); - }, - getOwnPropertyDescriptor(target, prop) { - const desc = - Reflect.getOwnPropertyDescriptor(target, prop) || - Reflect.getOwnPropertyDescriptor(real, prop); - return desc ? {...desc, configurable: true} : undefined; - }, - }); - } - } catch { - // ignore - } - } - return stub; - } - } - } - return prev.call(this, id); - }; - mod.prototype.require = Object.assign(req, prev); - try { - return prev.call(parent, request); - } finally { - mod.prototype.require = prev; - for (const id of global ? Object.keys(cache) : [sut]) { - if (!id.endsWith('.node')) delete cache[id]; - } - for (const [id, m] of saved) { - if (m && (keepCache || id !== sut)) cache[id] = m; - } - } -} - -// eslint-disable-next-line @typescript-eslint/no-namespace -namespace proxyquire { - export const load = proxyquire; - export const noCallThru = () => ((skipCallThru = true), proxyquire); - export const callThru = () => ((skipCallThru = false), proxyquire); - export const noPreserveCache = () => ((keepCache = false), proxyquire); - export const preserveCache = () => ((keepCache = true), proxyquire); -} - -export default proxyquire; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d3e796667663..5cc7e10574bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1934,6 +1934,9 @@ importers: gapic-tools: specifier: ^2.0.0 version: 2.0.1(protobufjs@8.8.0) + google-test-utils: + specifier: ^0.3.0 + version: 0.3.0 gts: specifier: ^6.0.2 version: 6.0.2(typescript@5.9.3) @@ -3217,8 +3220,8 @@ importers: specifier: ^2.0.0 version: 2.0.1(protobufjs@7.5.9) google-test-utils: - specifier: ^0.2.0 - version: 0.2.0 + specifier: ^0.3.0 + version: 0.3.0 grpc-health-check: specifier: ^2.1.0 version: 2.1.0 @@ -3435,6 +3438,9 @@ importers: gapic-tools: specifier: ^2.0.1 version: 2.0.1(protobufjs@8.8.0) + google-test-utils: + specifier: ^0.3.0 + version: 0.3.0 gts: specifier: ^5.0.0 version: 5.3.1(typescript@5.9.3) @@ -20775,10 +20781,6 @@ packages: resolution: {integrity: sha512-fbvUWVNh3rKGlVFWwlUNF7Sk1Tah0a89q02rLTXsWjbKKHGeuURAa8tl9rvu5TyWny1YiSIo5eDqF260Tai6vQ==} engines: {node: '>=22'} - google-test-utils@0.2.0: - resolution: {integrity: sha512-OP7B5iS65EjAlRctfq3uUdGWXN5AvIGRYQSx5luCVqkfh3uaGvVzuY1y3hCyxLk6MGf5DYkJ3rZxgJ6JsMmSog==} - engines: {node: '>=22'} - google-test-utils@0.3.0: resolution: {integrity: sha512-atGLcriC2MS6gWCJnCNrxLqYh4Xs5WkgaS9+lwxF/3rZdzt5eFv/Bc2VIeCCl5DGzGP3So8dj5f7pfBUNRhzXw==} engines: {node: '>=22'} @@ -32065,8 +32067,6 @@ snapshots: protobufjs: 7.5.9 walkdir: 0.4.1 - google-test-utils@0.2.0: {} - google-test-utils@0.3.0: {} googleapis-common@4.4.3(encoding@0.1.13):