diff --git a/.github/actions/pnpm-lockfile-check/action.yaml b/.github/actions/pnpm-lockfile-check/action.yaml index 2ac007add90b..554ba3db8864 100644 --- a/.github/actions/pnpm-lockfile-check/action.yaml +++ b/.github/actions/pnpm-lockfile-check/action.yaml @@ -39,3 +39,5 @@ runs: git checkout -- pnpm-lock.yaml pnpm --dir core/packages/gaxios run compile pnpm --dir core/packages/teeny-request run compile + pnpm --dir core/test-utils run compile + node -e "require('./bin/run-test.cjs').ensureWorkspacePackagesLinked()" diff --git a/bin/run-test.cjs b/bin/run-test.cjs index 7f31b6e3df0d..5dd86bdc1292 100755 --- a/bin/run-test.cjs +++ b/bin/run-test.cjs @@ -112,6 +112,11 @@ const WORKSPACE_LINK_PACKAGES = [ relDir: 'core/packages/teeny-request', entryPoint: 'build/src/index.js', }, + { + name: 'google-test-utils', + relDir: 'core/test-utils', + entryPoint: 'build/src/index.js', + }, ]; function linkTargetToWorkspacePackage( diff --git a/core/common/package.json b/core/common/package.json index 1957c7f8c44a..52c981a0b446 100644 --- a/core/common/package.json +++ b/core/common/package.json @@ -20,7 +20,7 @@ ], "scripts": { "docs": "jsdoc -c .jsdoc.js", - "test": "node ../../bin/run-test.cjs --proxyquire-shim build/test", + "test": "node ../../bin/run-test.cjs build/test", "prepare": "npm run compile", "pretest": "npm run compile", "compile": "tsc -p .", @@ -57,6 +57,7 @@ "@types/tmp": "^0.2.6", "c8": "^10.1.3", "codecov": "^3.8.3", + "google-test-utils": "^0.3.0", "gts": "^6.0.2", "jsdoc": "^4.0.4", "jsdoc-fresh": "^6.0.0", diff --git a/core/common/test/service-object.ts b/core/common/test/service-object.ts index 1d3ed35a5fee..a17038302bfa 100644 --- a/core/common/test/service-object.ts +++ b/core/common/test/service-object.ts @@ -20,7 +20,8 @@ import { import * as assert from 'assert'; import {describe, it, beforeEach, afterEach} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; +// eslint-disable-next-line import/namespace import * as r from 'teeny-request'; import * as sinon from 'sinon'; diff --git a/core/common/test/service.ts b/core/common/test/service.ts index 768e7db4e4a6..6c378f197b90 100644 --- a/core/common/test/service.ts +++ b/core/common/test/service.ts @@ -15,7 +15,7 @@ import * as assert from 'assert'; import {describe, it, before, beforeEach, after} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import {Request} from 'teeny-request'; import {AuthClient, GoogleAuth, OAuth2Client} from 'google-auth-library'; diff --git a/core/common/test/util.ts b/core/common/test/util.ts index 6c018afd4d3d..8c65ccb98d71 100644 --- a/core/common/test/util.ts +++ b/core/common/test/util.ts @@ -26,7 +26,8 @@ import { OAuth2Client, } from 'google-auth-library'; import * as nock from 'nock'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; +// eslint-disable-next-line import/namespace import * as r from 'teeny-request'; import * as retryRequest from 'retry-request'; import * as sinon from 'sinon'; diff --git a/core/test-utils/src/index.ts b/core/test-utils/src/index.ts index 4d06ba191b6d..23cd8724c2b9 100644 --- a/core/test-utils/src/index.ts +++ b/core/test-utils/src/index.ts @@ -28,3 +28,9 @@ export { isReadOnlyPropertyError, requiresStrictArrayPrototypeEquality, } from './runtime'; +export { + type Proxyquire, + type ProxyquireStubs, + createProxyquire, + proxyquire, +} from './proxyquire'; diff --git a/core/test-utils/src/proxyquire.ts b/core/test-utils/src/proxyquire.ts new file mode 100644 index 000000000000..bb793bd66be1 --- /dev/null +++ b/core/test-utils/src/proxyquire.ts @@ -0,0 +1,251 @@ +// 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'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type ProxyquireStubs = Record; + +export interface Proxyquire { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (request: string, stubs?: ProxyquireStubs): T; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + load(request: string, stubs?: ProxyquireStubs): T; + noCallThru(): Proxyquire; + callThru(): Proxyquire; + noPreserveCache(): Proxyquire; + preserveCache(): Proxyquire; +} + +type ModuleConstructor = { + new (id: string, parent?: NodeModule): NodeModule; + prototype: {require: NodeRequire}; + _cache?: NodeJS.Dict; + _resolveFilename: (request: string, parent: NodeModule) => string; + _nodeModulePaths: (from: string) => string[]; + createRequire?: (filename: string) => NodeRequire; +}; + +const rawMod = ((Module as unknown as {Module?: ModuleConstructor}).Module ?? + Module) as unknown as ModuleConstructor; +const {hasOwnProperty} = Object.prototype; +const isDirectMod = hasOwnProperty.call(rawMod.prototype, 'require'); +const mod: ModuleConstructor = isDirectMod + ? rawMod + : (Object.getPrototypeOf(rawMod) as ModuleConstructor); +const modProto: {require: NodeRequire} = isDirectMod + ? rawMod.prototype + : Object.getPrototypeOf(rawMod.prototype); +const isObj = (o: unknown): o is Record => + Boolean(o && (typeof o === 'object' || typeof o === 'function')); +const hasOwn = (o: unknown, k: string): boolean => + isObj(o) && hasOwnProperty.call(o, k); +const isGlobal = (s: unknown): boolean => + hasOwn(s, '@global') || hasOwn(s, '@runtimeGlobal'); + +const resolveFrom = (base: string, req: string): string => { + try { + return require.resolve(req, {paths: [path.dirname(base)]}); + } catch { + return req[0] === '.' ? path.resolve(path.dirname(base), req) : req; + } +}; + +function getCallerFile(): string | undefined { + const origPrepare = Error.prepareStackTrace; + try { + const err = new Error(); + Error.prepareStackTrace = (_, stack) => stack; + const stack = err.stack as unknown as NodeJS.CallSite[] | undefined; + if (Array.isArray(stack)) { + for (const frame of stack) { + const file = frame.getFileName?.(); + if ( + file && + file !== __filename && + !file.startsWith('node:') && + !file.startsWith('internal/') + ) { + return file; + } + } + } + } finally { + Error.prepareStackTrace = origPrepare; + } + return undefined; +} + +function resolveModule(target: NodeModule | string | undefined): NodeModule { + if (target && typeof target === 'object') { + return target; + } + const filename = + typeof target === 'string' ? target : (getCallerFile() ?? __filename); + const cached = require.cache[filename]; + if (cached) { + return cached; + } + const fallback = new mod(filename); + fallback.filename = filename; + fallback.paths = mod._nodeModulePaths(path.dirname(filename)); + return fallback; +} + +interface CallerOptions { + skipCallThru: boolean; + keepCache: boolean; +} + +/** + * Creates a runtime-agnostic `proxyquire` function compatible with both + * Node.js and Bun (`JS_RUNTIME=bun`). + * + * @param caller Optional calling `NodeModule` or `__filename` used to resolve + * relative module paths. When omitted, the caller's filename is detected + * automatically from the call stack. + */ +export function createProxyquire(caller?: NodeModule | string): Proxyquire { + const defaultOpts: CallerOptions = {skipCallThru: false, keepCache: true}; + const callerOpts = new Map(); + + const getOpts = (callerFilename: string): CallerOptions => { + if (caller !== undefined) { + return defaultOpts; + } + let opts = callerOpts.get(callerFilename); + if (!opts) { + opts = {...defaultOpts}; + callerOpts.set(callerFilename, opts); + } + return opts; + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const fn = function proxyquire( + request: string, + stubs: ProxyquireStubs = {}, + ): T { + const parent = resolveModule(caller); + const opts = getOpts(parent.filename); + const sut = mod._resolveFilename(request, parent); + const resolved: ProxyquireStubs = {}; + let global = false; + for (const [k, v] of Object.entries(stubs)) { + if (isGlobal(v)) global = true; + resolved[resolveFrom(sut, k)] = v; + } + const cache = mod._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 = modProto.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') + ? Boolean(stub['@noCallThru']) + : opts.skipCallThru; + if (!skip && isObj(stub)) { + try { + const real = prev.call(this, id); + if (isObj(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 when underlying module cannot be loaded + } + } + return stub; + } + } + } + return prev.call(this, id); + }; + modProto.require = Object.assign(req, prev); + try { + return typeof parent.require === 'function' && parent instanceof mod + ? prev.call(parent, request) + : mod.createRequire + ? mod.createRequire(parent.filename)(request) + : prev.call(parent, request); + } finally { + modProto.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 && (opts.keepCache || id !== sut)) cache[id] = m; + } + } + }; + + const updateOpts = (patch: Partial): Proxyquire => { + const parent = resolveModule(caller); + Object.assign(getOpts(parent.filename), patch); + return instance; + }; + + const instance: Proxyquire = Object.assign(fn, { + load: fn, + noCallThru: () => updateOpts({skipCallThru: true}), + callThru: () => updateOpts({skipCallThru: false}), + noPreserveCache: () => updateOpts({keepCache: false}), + preserveCache: () => updateOpts({keepCache: true}), + }); + + return instance; +} + +/** + * Shared `proxyquire` instance that resolves relative module requests and + * option flags (`noCallThru`, `noPreserveCache`, etc.) per calling test file. + */ +export const proxyquire: Proxyquire = createProxyquire(); diff --git a/core/test-utils/test/proxyquire.test.ts b/core/test-utils/test/proxyquire.test.ts new file mode 100644 index 000000000000..40b006fe8e0c --- /dev/null +++ b/core/test-utils/test/proxyquire.test.ts @@ -0,0 +1,145 @@ +// 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 +// +// https://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 fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import {createProxyquire, proxyquire} from '../src'; + +describe('proxyquire utilities (unit)', () => { + let tmpDir: string; + let depFile: string; + let sutFile: string; + let transitiveFile: string; + + beforeAll(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'proxyquire-test-')); + depFile = path.join(tmpDir, 'dep.js'); + transitiveFile = path.join(tmpDir, 'transitive.js'); + sutFile = path.join(tmpDir, 'sut.js'); + + fs.writeFileSync( + depFile, + [ + 'module.exports = {', + ' greet: () => "real-greet",', + ' extra: () => "real-extra",', + ' constant: 42,', + '};', + ].join('\n'), + ); + fs.writeFileSync( + transitiveFile, + [ + 'const dep = require("./dep");', + 'module.exports = { run: () => dep.greet() };', + ].join('\n'), + ); + fs.writeFileSync( + sutFile, + [ + 'const dep = require("./dep");', + 'const trans = require("./transitive");', + 'const pathMod = require("path");', + 'module.exports = {', + ' greet: () => dep.greet(),', + ' extra: () => dep.extra && dep.extra(),', + ' constant: dep.constant,', + ' hasExtra: "extra" in dep,', + ' keys: Object.keys(dep),', + ' transGreet: () => trans.run(),', + ' sep: pathMod.sep,', + '};', + ].join('\n'), + ); + }); + + afterAll(() => { + fs.rmSync(tmpDir, {recursive: true, force: true}); + }); + + it('stubs direct dependencies and falls through to real module via Proxy without mutating stub', () => { + const stub = {greet: () => 'stubbed-greet'}; + const loaded = proxyquire(sutFile, {'./dep': stub}); + expect(loaded.greet()).toBe('stubbed-greet'); + expect(loaded.extra()).toBe('real-extra'); + expect(loaded.constant).toBe(42); + expect(loaded.hasExtra).toBe(true); + expect(loaded.keys).toEqual( + expect.arrayContaining(['greet', 'extra', 'constant']), + ); + // Stub object itself was not mutated in place + expect('extra' in stub).toBe(false); + expect(Object.keys(stub)).toEqual(['greet']); + }); + + it('supports @noCallThru on individual stubs and noCallThru() / callThru() on instance', () => { + const pq = createProxyquire(__filename); + const stubWithNoCallThru = { + greet: () => 'isolated', + '@noCallThru': true, + }; + const loaded1 = pq.load(sutFile, {'./dep': stubWithNoCallThru}); + expect(loaded1.greet()).toBe('isolated'); + expect(loaded1.extra()).toBeUndefined(); + expect(loaded1.constant).toBeUndefined(); + + pq.noCallThru(); + const loaded2 = pq(sutFile, {'./dep': {greet: () => 'instance-no-call'}}); + expect(loaded2.greet()).toBe('instance-no-call'); + expect(loaded2.extra()).toBeUndefined(); + + pq.callThru(); + const loaded3 = pq(sutFile, {'./dep': {greet: () => 'instance-call-thru'}}); + expect(loaded3.greet()).toBe('instance-call-thru'); + expect(loaded3.extra()).toBe('real-extra'); + }); + + it('supports @global stubs across transitive dependencies', () => { + const loadedNonGlobal = proxyquire(sutFile, { + './dep': {greet: () => 'direct-only'}, + }); + expect(loadedNonGlobal.greet()).toBe('direct-only'); + + const loadedGlobal = proxyquire(sutFile, { + './dep': {greet: () => 'global-stub', '@global': true}, + }); + expect(loadedGlobal.greet()).toBe('global-stub'); + expect(loadedGlobal.transGreet()).toBe('global-stub'); + }); + + it('throws MODULE_NOT_FOUND when stub is null', () => { + expect(() => proxyquire(sutFile, {'./dep': null})).toThrow( + /Cannot find module '\.\/dep'/, + ); + }); + + it('supports noPreserveCache() and preserveCache()', () => { + const pq = createProxyquire(module); + pq.noPreserveCache(); + const loaded1 = pq(sutFile, {'./dep': {greet: () => 'a'}}); + expect(loaded1.greet()).toBe('a'); + pq.preserveCache(); + const loaded2 = pq(sutFile, {'./dep': {greet: () => 'b'}}); + expect(loaded2.greet()).toBe('b'); + }); + + it('supports bare module stubs and unresolvable relative paths', () => { + const loaded = proxyquire(sutFile, { + path: {sep: '/custom-sep'}, + './non-existent-relative': {foo: 'bar'}, + }); + expect(loaded.sep).toBe('/custom-sep'); + }); +}); diff --git a/handwritten/bigquery/cloudbuild.yaml b/handwritten/bigquery/cloudbuild.yaml index 61057c32af6d..94c291b817bf 100644 --- a/handwritten/bigquery/cloudbuild.yaml +++ b/handwritten/bigquery/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: - '-c' - | - cd ../.. && corepack enable pnpm && pnpm install --filter "...{./handwritten/bigquery}" + cd ../.. && corepack enable pnpm && pnpm install --filter "./core/test-utils" --filter "...{./handwritten/bigquery}" --ignore-scripts && pnpm --dir core/test-utils run compile && node -e "require('./bin/run-test.cjs').ensureWorkspacePackagesLinked()" && pnpm --filter "...{./handwritten/bigquery}" run prepare --if-present dir: 'handwritten/bigquery' id: 'install-dependencies' diff --git a/handwritten/bigquery/package.json b/handwritten/bigquery/package.json index f826e9b8072f..beefdd0f0594 100644 --- a/handwritten/bigquery/package.json +++ b/handwritten/bigquery/package.json @@ -36,7 +36,7 @@ "docs": "jsdoc -c .jsdoc.js", "lint": "gts check", "samples-test": "cd samples/ && pnpm link ../ && pnpm test && cd ../", - "test": "node ../../bin/run-test.cjs --proxyquire-shim build/test", + "test": "node ../../bin/run-test.cjs build/test", "system-test": "node ../../bin/run-test.cjs build/system-test --timeout 600000", "presystem-test": "pnpm run compile", "clean": "gts clean", @@ -91,6 +91,7 @@ "codecov": "^3.8.3", "discovery-tsd": "^0.3.0", "eslint-plugin-prettier": "^5.2.6", + "google-test-utils": "^0.3.0", "gts": "^6.0.2", "jsdoc": "^4.0.4", "jsdoc-fresh": "^6.0.0", diff --git a/handwritten/bigquery/test/bigquery.ts b/handwritten/bigquery/test/bigquery.ts index e5bbf0e222a1..397c9c582c23 100644 --- a/handwritten/bigquery/test/bigquery.ts +++ b/handwritten/bigquery/test/bigquery.ts @@ -24,7 +24,7 @@ import * as assert from 'assert'; import {describe, it, after, afterEach, before, beforeEach} from 'mocha'; import * as Big from 'big.js'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import * as crypto from 'crypto'; diff --git a/handwritten/bigquery/test/dataset.ts b/handwritten/bigquery/test/dataset.ts index 26bb3ff8a831..d369c322179c 100644 --- a/handwritten/bigquery/test/dataset.ts +++ b/handwritten/bigquery/test/dataset.ts @@ -22,7 +22,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as _root from '../src'; import {toArray} from '../src/util'; diff --git a/handwritten/bigquery/test/job.ts b/handwritten/bigquery/test/job.ts index afba50a3fc7a..9dcafc9f7ac5 100644 --- a/handwritten/bigquery/test/job.ts +++ b/handwritten/bigquery/test/job.ts @@ -16,7 +16,7 @@ import {DecorateRequestOptions, util} from '@google-cloud/common'; import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {describe, it, beforeEach, afterEach, before} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {BigQuery} from '../src/bigquery'; diff --git a/handwritten/bigquery/test/model.ts b/handwritten/bigquery/test/model.ts index 49c59a5cec4b..895afea43b40 100644 --- a/handwritten/bigquery/test/model.ts +++ b/handwritten/bigquery/test/model.ts @@ -15,7 +15,7 @@ import * as assert from 'assert'; import {describe, it, before, beforeEach, afterEach} from 'mocha'; import * as sinon from 'sinon'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as pfy from '@google-cloud/promisify'; import {EventEmitter} from 'events'; import {JobOptions} from '../src/job'; diff --git a/handwritten/bigquery/test/routine.ts b/handwritten/bigquery/test/routine.ts index d1cdb0966ac4..f550f2a3aecf 100644 --- a/handwritten/bigquery/test/routine.ts +++ b/handwritten/bigquery/test/routine.ts @@ -17,7 +17,7 @@ import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as _root from '../src'; diff --git a/handwritten/bigquery/test/rowQueue.ts b/handwritten/bigquery/test/rowQueue.ts index f53dbf011485..bde9198edcc0 100644 --- a/handwritten/bigquery/test/rowQueue.ts +++ b/handwritten/bigquery/test/rowQueue.ts @@ -16,7 +16,7 @@ import {util} from '@google-cloud/common'; import * as assert from 'assert'; import {describe, it, before, beforeEach, afterEach} from 'mocha'; import {Duplex, Stream} from 'stream'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import * as q from '../src/rowQueue'; import * as t from '../src/table'; diff --git a/handwritten/bigquery/test/table.ts b/handwritten/bigquery/test/table.ts index f3e59d7f3d9a..eb78c7f00d10 100644 --- a/handwritten/bigquery/test/table.ts +++ b/handwritten/bigquery/test/table.ts @@ -26,7 +26,7 @@ import {describe, it, afterEach, beforeEach, before, after} from 'mocha'; import * as Big from 'big.js'; import {EventEmitter} from 'events'; import * as extend from 'extend'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import * as stream from 'stream'; import * as crypto from 'crypto'; diff --git a/handwritten/datastore/cloudbuild.yaml b/handwritten/datastore/cloudbuild.yaml index a8a82e5f75c7..baf0365542b9 100644 --- a/handwritten/datastore/cloudbuild.yaml +++ b/handwritten/datastore/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: - '-c' - | - cd ../.. && corepack enable pnpm && pnpm install --filter "...{./handwritten/datastore}" + cd ../.. && corepack enable pnpm && pnpm install --filter "./core/test-utils" --filter "...{./handwritten/datastore}" --ignore-scripts && pnpm --dir core/test-utils run compile && node -e "require('./bin/run-test.cjs').ensureWorkspacePackagesLinked()" && pnpm --filter "...{./handwritten/datastore}" run prepare --if-present dir: 'handwritten/datastore' id: 'install-dependencies' diff --git a/handwritten/datastore/package.json b/handwritten/datastore/package.json index a031adee220d..2c94da61069a 100644 --- a/handwritten/datastore/package.json +++ b/handwritten/datastore/package.json @@ -38,7 +38,7 @@ "presystem-test": "npm run compile", "system-test": "node ../../bin/run-test.cjs build/system-test --timeout 600000", "pretest": "npm run compile", - "test": "node ../../bin/run-test.cjs --proxyquire-shim build/test", + "test": "node ../../bin/run-test.cjs build/test", "precompile": "gts clean" }, "dependencies": { @@ -68,6 +68,7 @@ "async": "^3.2.6", "c8": "^10.1.3", "gapic-tools": "^2.0.0", + "google-test-utils": "^0.3.0", "gts": "^6.0.2", "js-yaml": "^4.1.0", "jsdoc": "^4.0.4", diff --git a/handwritten/datastore/system-test/transaction-spy.ts b/handwritten/datastore/system-test/transaction-spy.ts index 23ea7a69524c..415492f8a50d 100644 --- a/handwritten/datastore/system-test/transaction-spy.ts +++ b/handwritten/datastore/system-test/transaction-spy.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 {DatastoreRequest} from '../src'; import {RequestCallback, RequestConfig} from '../src/request'; diff --git a/handwritten/datastore/test/gapic-mocks/client-initialization-testing.ts b/handwritten/datastore/test/gapic-mocks/client-initialization-testing.ts index a3a16ef7dc30..8a978d916fd9 100644 --- a/handwritten/datastore/test/gapic-mocks/client-initialization-testing.ts +++ b/handwritten/datastore/test/gapic-mocks/client-initialization-testing.ts @@ -21,7 +21,7 @@ import { DatastoreOptions, } from '../../src'; import * as assert from 'assert'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import {Callback, CallOptions} from 'google-gax'; import * as protos from '../../src/protos'; import * as ds from '../../src'; diff --git a/handwritten/datastore/test/index-class.ts b/handwritten/datastore/test/index-class.ts index 3754e4672748..db7b21339846 100644 --- a/handwritten/datastore/test/index-class.ts +++ b/handwritten/datastore/test/index-class.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 * as ds from '../src'; diff --git a/handwritten/datastore/test/index.ts b/handwritten/datastore/test/index.ts index 03fedde213ce..9ca046a1f99b 100644 --- a/handwritten/datastore/test/index.ts +++ b/handwritten/datastore/test/index.ts @@ -15,7 +15,7 @@ import * as assert from 'assert'; import {before, beforeEach, after, afterEach, describe, it} from 'mocha'; import * as gax from 'google-gax'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import {PassThrough, Readable} from 'stream'; import * as ds from '../src'; diff --git a/handwritten/datastore/test/request.ts b/handwritten/datastore/test/request.ts index 97366be73bbe..c90f71367aea 100644 --- a/handwritten/datastore/test/request.ts +++ b/handwritten/datastore/test/request.ts @@ -18,7 +18,7 @@ import {after, afterEach, before, beforeEach, describe, it} from 'mocha'; import * as extend from 'extend'; import * as gax from 'google-gax'; import * as is from 'is'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import * as sinon from 'sinon'; import {PassThrough, Transform} from 'stream'; diff --git a/handwritten/datastore/test/transaction.ts b/handwritten/datastore/test/transaction.ts index 36366c0d7e02..a69f3f84588c 100644 --- a/handwritten/datastore/test/transaction.ts +++ b/handwritten/datastore/test/transaction.ts @@ -16,7 +16,7 @@ import * as pfy from '@google-cloud/promisify'; import arrify = require('arrify'); import * as assert from 'assert'; import {afterEach, beforeEach, before, describe, it} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import { Datastore, DatastoreOptions, diff --git a/handwritten/google-cloud-dns/cloudbuild.yaml b/handwritten/google-cloud-dns/cloudbuild.yaml index 24525563b03b..2af42772c0c9 100644 --- a/handwritten/google-cloud-dns/cloudbuild.yaml +++ b/handwritten/google-cloud-dns/cloudbuild.yaml @@ -5,7 +5,7 @@ steps: args: - '-c' - | - cd ../.. && corepack enable pnpm && pnpm install --filter "...{./handwritten/google-cloud-dns}" + cd ../.. && corepack enable pnpm && pnpm install --filter "./core/test-utils" --filter "...{./handwritten/google-cloud-dns}" --ignore-scripts && pnpm --dir core/test-utils run compile && node -e "require('./bin/run-test.cjs').ensureWorkspacePackagesLinked()" && pnpm --filter "...{./handwritten/google-cloud-dns}" run prepare --if-present dir: 'handwritten/google-cloud-dns' id: 'install-dependencies' diff --git a/handwritten/google-cloud-dns/package.json b/handwritten/google-cloud-dns/package.json index 9a8744bc31b2..38fbb29c6cb8 100644 --- a/handwritten/google-cloud-dns/package.json +++ b/handwritten/google-cloud-dns/package.json @@ -33,7 +33,7 @@ "scripts": { "docs": "jsdoc -c .jsdoc.js", "lint": "gts check", - "test": "node ../../bin/run-test.cjs --proxyquire-shim build/test", + "test": "node ../../bin/run-test.cjs build/test", "samples-test": "pnpm run compile && cd samples/ && pnpm link ../ && pnpm i && pnpm test", "presystem-test": "pnpm run compile", "system-test": "pnpm run compile && node ../../bin/run-test.cjs build/system-test", @@ -64,6 +64,7 @@ "@types/tmp": "^0.2.6", "c8": "^10.1.3", "codecov": "^3.8.3", + "google-test-utils": "^0.3.0", "gts": "^6.0.2", "jsdoc": "^4.0.4", "jsdoc-fresh": "^6.0.0", diff --git a/handwritten/google-cloud-dns/test/change.ts b/handwritten/google-cloud-dns/test/change.ts index 4d37cfd21cd6..197685947841 100644 --- a/handwritten/google-cloud-dns/test/change.ts +++ b/handwritten/google-cloud-dns/test/change.ts @@ -20,7 +20,7 @@ import { 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 {Change} from '../src/change'; let promisified = false; @@ -119,7 +119,7 @@ describe('Change', () => { assert.strictEqual(change, null); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); }); @@ -145,7 +145,7 @@ describe('Change', () => { assert.strictEqual(change_, change); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); diff --git a/handwritten/google-cloud-dns/test/index.ts b/handwritten/google-cloud-dns/test/index.ts index 0c3fd27da05e..a3ef8499b7ac 100644 --- a/handwritten/google-cloud-dns/test/index.ts +++ b/handwritten/google-cloud-dns/test/index.ts @@ -22,7 +22,7 @@ import * as promisify from '@google-cloud/promisify'; import arrify = require('arrify'); import * as assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import {CoreOptions, OptionsWithUri, Response} from 'request'; import {Zone} from '../src'; @@ -135,7 +135,7 @@ describe('DNS', () => { assert.deepStrictEqual( calledWith.packageJson, // eslint-disable-next-line @typescript-eslint/no-var-requires - require('../../package.json') + require('../../package.json'), ); }); @@ -226,7 +226,7 @@ describe('DNS', () => { assert.strictEqual(zone, null); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); }); @@ -265,7 +265,7 @@ describe('DNS', () => { assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); @@ -318,7 +318,7 @@ describe('DNS', () => { err: Error, zones: Zone[], nextQuery: {}, - apiResponse_: Response + apiResponse_: Response, ) => { assert.strictEqual(err, error); assert.strictEqual(zones, null); @@ -326,7 +326,7 @@ describe('DNS', () => { assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); }); @@ -377,7 +377,7 @@ describe('DNS', () => { nextQuery, Object.assign({}, query, { pageToken: apiResponseWithNextPageToken.nextPageToken, - }) + }), ); done(); @@ -391,7 +391,7 @@ describe('DNS', () => { err: Error, zones: Zone[], nextQuery: {}, - apiResponse_: Response + apiResponse_: Response, ) => { assert.ifError(err); assert.strictEqual(zones[0], zone); @@ -399,7 +399,7 @@ describe('DNS', () => { assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); diff --git a/handwritten/google-cloud-dns/test/record.ts b/handwritten/google-cloud-dns/test/record.ts index e7174c43d546..ee2f3fb4d494 100644 --- a/handwritten/google-cloud-dns/test/record.ts +++ b/handwritten/google-cloud-dns/test/record.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'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import {Record} from '../src'; diff --git a/handwritten/google-cloud-dns/test/zone.ts b/handwritten/google-cloud-dns/test/zone.ts index 07681f51e591..cc5c517f7808 100644 --- a/handwritten/google-cloud-dns/test/zone.ts +++ b/handwritten/google-cloud-dns/test/zone.ts @@ -17,7 +17,7 @@ import * as promisify from '@google-cloud/promisify'; import arrify = require('arrify'); import * as assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; -import * as proxyquire from 'proxyquire'; +import {proxyquire} from 'google-test-utils'; import {CoreOptions, OptionsWithUri, Response} from 'request'; import * as crypto from 'crypto'; @@ -187,7 +187,7 @@ describe('Zone', () => { zone.createChange = ( options: CreateChangeRequest, - callback: Function + callback: Function, ) => { assert.strictEqual(options.add, records); callback(); @@ -215,7 +215,7 @@ describe('Zone', () => { type: crypto.randomUUID(), rrdatas: [crypto.randomUUID(), crypto.randomUUID()], }, - recordJson + recordJson, ); return { @@ -312,7 +312,7 @@ describe('Zone', () => { assert.strictEqual(err, error); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); }); @@ -342,7 +342,7 @@ describe('Zone', () => { assert.strictEqual(change_.metadata, apiResponse); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); }); @@ -390,7 +390,7 @@ describe('Zone', () => { zone.createChange = ( options: CreateChangeRequest, - callback: Function + callback: Function, ) => { assert.deepStrictEqual(options.delete, [recordsToDelete]); callback(); @@ -462,7 +462,7 @@ describe('Zone', () => { it('should delete non-NS and non-SOA records', done => { zone.deleteRecords = ( recordsToDelete: string[], - callback: Function + callback: Function, ) => { assert.deepStrictEqual(recordsToDelete, expectedRecordsToDelete); callback(); @@ -539,7 +539,7 @@ describe('Zone', () => { writeFileOverride = ( path_: string, content: string, - encoding: string + encoding: string, ) => { assert.strictEqual(path_, path); assert.strictEqual(content, expectedZonefileContents); @@ -559,7 +559,7 @@ describe('Zone', () => { path: string, content: string, encoding: string, - callback: Function + callback: Function, ) => { callback(error); }; @@ -579,7 +579,7 @@ describe('Zone', () => { path: string, content: string, encoding: string, - callback: Function + callback: Function, ) => { callback(); }; @@ -648,12 +648,12 @@ describe('Zone', () => { err: Error, changes: Change[], nextQuery: {}, - apiResponse_: Response + apiResponse_: Response, ) => { assert.strictEqual(err, error); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); }); @@ -703,14 +703,14 @@ describe('Zone', () => { err: Error, changes: Change[], nextQuery: {}, - apiResponse_: Response + apiResponse_: Response, ) => { assert.ifError(err); assert.strictEqual(changes[0], change); assert.strictEqual(changes[0].metadata, apiResponse.changes[0]); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); }); @@ -734,12 +734,12 @@ describe('Zone', () => { err: Error, changes: Change[], nextQuery: {}, - apiResponse_: Response + apiResponse_: Response, ) => { assert.strictEqual(err, error); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); }); @@ -795,13 +795,13 @@ describe('Zone', () => { err: Error, records: Record[], nextQuery: {}, - apiResponse_: Response + apiResponse_: Response, ) => { assert.ifError(err); assert.strictEqual(records[0], record); assert.strictEqual(apiResponse_, apiResponse); done(); - } + }, ); it('should not require a query', done => { @@ -866,7 +866,7 @@ describe('Zone', () => { readFileOverride = ( path: string, encoding: string, - callback: Function + callback: Function, ) => { callback(error); }; @@ -897,7 +897,7 @@ describe('Zone', () => { readFileOverride = ( path: string, encoding: string, - callback: Function + callback: Function, ) => { callback(); }; @@ -906,7 +906,7 @@ describe('Zone', () => { it('should add records', done => { zone.addRecords = ( recordsToCreate: FakeRecord[], - callback: Function + callback: Function, ) => { assert.strictEqual(recordsToCreate.length, 1); const recordToCreate = recordsToCreate[0]; @@ -998,7 +998,7 @@ describe('Zone', () => { it('should create a change', done => { zone.createChange = ( options: CreateChangeRequest, - callback: Function + callback: Function, ) => { assert.strictEqual(options.add, recordsToCreate); assert.strictEqual(options.delete, recordsToDelete); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62f973509c54..3bab231f6cbc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -129,6 +129,9 @@ importers: codecov: specifier: ^3.8.3 version: 3.8.3(encoding@0.1.13) + google-test-utils: + specifier: ^0.3.0 + version: 0.3.0 gts: specifier: ^6.0.2 version: 6.0.2(typescript@5.9.3) @@ -1662,6 +1665,9 @@ importers: eslint-plugin-prettier: specifier: ^5.2.6 version: 5.5.6(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint@8.57.1)(prettier@3.9.6) + google-test-utils: + specifier: ^0.3.0 + version: 0.3.0 gts: specifier: ^6.0.2 version: 6.0.2(typescript@5.9.3) @@ -2173,6 +2179,9 @@ importers: gapic-tools: specifier: ^2.0.0 version: 2.0.1(protobufjs@7.5.9) + google-test-utils: + specifier: ^0.3.0 + version: 0.3.0 gts: specifier: ^6.0.2 version: 6.0.2(typescript@5.9.3) @@ -2515,6 +2524,9 @@ importers: codecov: specifier: ^3.8.3 version: 3.8.3(encoding@0.1.13) + google-test-utils: + specifier: ^0.3.0 + version: 0.3.0 gts: specifier: ^6.0.2 version: 6.0.2(typescript@5.9.3) @@ -20758,6 +20770,10 @@ packages: resolution: {integrity: sha512-OP7B5iS65EjAlRctfq3uUdGWXN5AvIGRYQSx5luCVqkfh3uaGvVzuY1y3hCyxLk6MGf5DYkJ3rZxgJ6JsMmSog==} engines: {node: '>=22'} + google-test-utils@0.3.0: + resolution: {integrity: sha512-atGLcriC2MS6gWCJnCNrxLqYh4Xs5WkgaS9+lwxF/3rZdzt5eFv/Bc2VIeCCl5DGzGP3So8dj5f7pfBUNRhzXw==} + engines: {node: '>=22'} + googleapis-common@4.4.3: resolution: {integrity: sha512-W46WKCk3QtlCCfmZyQIH5zxmDOyeV5Qj+qs7nr2ox08eRkEJMWp6iwv542R/PsokXaGUSrmif4vCC4+rGzRSsQ==} engines: {node: '>=10.10.0'} @@ -28716,7 +28732,7 @@ snapshots: '@webpack-cli/configtest@3.0.1(webpack-cli@6.0.1)(webpack@5.109.0)': dependencies: - webpack: 5.109.0(uglify-js@3.19.3)(webpack-cli@6.0.1) + webpack: 5.109.0(webpack-cli@6.0.1) webpack-cli: 6.0.1(webpack@5.109.0) '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.109.0)': @@ -28726,7 +28742,7 @@ snapshots: '@webpack-cli/info@3.0.1(webpack-cli@6.0.1)(webpack@5.109.0)': dependencies: - webpack: 5.109.0(uglify-js@3.19.3)(webpack-cli@6.0.1) + webpack: 5.109.0(webpack-cli@6.0.1) webpack-cli: 6.0.1(webpack@5.109.0) '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.109.0)': @@ -28736,7 +28752,7 @@ snapshots: '@webpack-cli/serve@3.0.1(webpack-cli@6.0.1)(webpack@5.109.0)': dependencies: - webpack: 5.109.0(uglify-js@3.19.3)(webpack-cli@6.0.1) + webpack: 5.109.0(webpack-cli@6.0.1) webpack-cli: 6.0.1(webpack@5.109.0) '@xtuc/ieee754@1.2.0': {} @@ -32042,6 +32058,8 @@ snapshots: google-test-utils@0.2.0: {} + google-test-utils@0.3.0: {} + googleapis-common@4.4.3(encoding@0.1.13): dependencies: extend: 3.0.2 @@ -34372,7 +34390,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.109.0(uglify-js@3.19.3)(webpack-cli@6.0.1) + webpack: 5.109.0(webpack-cli@6.0.1) nunjucks@3.2.4(chokidar@3.6.0): dependencies: @@ -36563,7 +36581,7 @@ snapshots: picomatch: 4.0.5 source-map: 0.7.6 typescript: 5.8.3 - webpack: 5.109.0(uglify-js@3.19.3)(webpack-cli@6.0.1) + webpack: 5.109.0(webpack-cli@6.0.1) optionalDependencies: loader-utils: 2.0.4 @@ -37051,7 +37069,7 @@ snapshots: import-local: 3.2.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.109.0(uglify-js@3.19.3)(webpack-cli@6.0.1) + webpack: 5.109.0(webpack-cli@6.0.1) webpack-merge: 6.0.1 webpack-merge@4.2.2: