From f797f5a634a62b1914663aef8bdd236977a7dfe6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:25:45 +0000 Subject: [PATCH 01/37] feat(pqc): add post quantum cryptography safe showcase integration tests Adds gRPC and HTTP/REST Fallback post quantum cryptography safe integration tests to the test-application package. Configures ShowcaseServer to support TLS, specific ports, and CA certificate output files. Asserts that X25519MLKEM768 is correctly negotiated. Co-authored-by: danieljbruce <8935272+danieljbruce@users.noreply.github.com> --- .../gax/test/showcase-server/src/index.ts | 28 +++- .../gax/test/test-application/src/index.ts | 134 ++++++++++++++++++ 2 files changed, 159 insertions(+), 3 deletions(-) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index a7f904cc6bff..c9dfda05d1d4 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -31,9 +31,11 @@ function sleep(timeoutMs: number) { export class ShowcaseServer { server: execa.ExecaChildProcess | undefined; + originalCwd: string | undefined; - async start() { - const testDir = path.join(process.cwd(), '.showcase-server-dir'); + async start(opts?: { tls?: boolean; port?: string; caCertOutputFile?: string }) { + this.originalCwd = process.cwd(); + const testDir = path.join(this.originalCwd, '.showcase-server-dir'); const platform = process.platform; const arch = process.arch === 'x64' ? 'amd64' : process.arch; const showcaseVersion = process.env['SHOWCASE_VERSION'] || '0.36.2'; @@ -41,6 +43,11 @@ export class ShowcaseServer { const fallbackServerUrl = `https://github.com/googleapis/gapic-showcase/releases/download/v${showcaseVersion}/${tarballFilename}`; const binaryName = './gapic-showcase'; + let resolvedCaCertPath = ''; + if (opts?.caCertOutputFile) { + resolvedCaCertPath = path.resolve(this.originalCwd, opts.caCertOutputFile); + } + await fsp.rm(testDir, {recursive: true, force: true}); await mkdir(testDir); process.chdir(testDir); @@ -48,7 +55,19 @@ export class ShowcaseServer { await download(fallbackServerUrl, testDir); await execa('tar', ['xzf', tarballFilename]); - const childProcess = execa(binaryName, ['run'], { + + const args = ['run']; + if (opts?.tls) { + args.push('--tls'); + } + if (opts?.port) { + args.push('--port', opts.port); + } + if (resolvedCaCertPath) { + args.push('--ca-cert-output-file', resolvedCaCertPath); + } + + const childProcess = execa(binaryName, args, { cwd: testDir, stdio: 'inherit', }); @@ -75,6 +94,9 @@ export class ShowcaseServer { throw new Error("Cannot kill the server, it's not started."); } this.server.kill(); + if (this.originalCwd) { + process.chdir(this.originalCwd); + } } } diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index d6ab4154e17b..c6727845a124 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -19,6 +19,8 @@ import {EchoClient, SequenceServiceClient, protos} from 'showcase-echo-client'; import {ShowcaseServer} from 'showcase-server'; import * as assert from 'assert'; import {promises as fsp} from 'fs'; +import * as fs from 'fs'; +import * as https from 'https'; import * as path from 'path'; import { protobuf, @@ -2902,6 +2904,112 @@ async function testStreamingErrorAfterDataNoBufferNoRetry( }); } +async function testPqc(pemPath: string, port: number) { + console.log('Running Post Quantum Cryptography (PQC) Integration Tests...'); + + // Verify the CA certificate file exists + let pemExists = false; + for (let i = 0; i < 15; i++) { + if (fs.existsSync(pemPath)) { + pemExists = true; + break; + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } + if (!pemExists) { + throw new Error(`CA Certificate file not found at ${pemPath}`); + } + + const pemBuffer = fs.readFileSync(pemPath); + + // --- 1. gRPC PQC Test --- + console.log('Testing PQC via gRPC...'); + let negotiatedGroupGrpc: string | undefined; + + const interceptor = (options: any, nextCall: any) => { + return new grpc.InterceptingCall(nextCall(options), { + start: (metadata: any, listener: any, next: any) => { + next(metadata, { + onReceiveMetadata: (receivedMetadata: any, nextListener: any) => { + const group = receivedMetadata.get('x-showcase-tls-group'); + if (group && group.length > 0) { + negotiatedGroupGrpc = group[0].toString(); + } + nextListener(receivedMetadata); + }, + }); + }, + }); + }; + + const grpcClientOpts = { + grpc, + sslCreds: grpc.credentials.createSsl(pemBuffer), + servicePath: 'localhost', + port: port, + }; + + const grpcClient = new EchoClient(grpcClientOpts); + + const [responseGrpc] = await grpcClient.echo( + { content: 'grpc-pqc-test' }, + { + otherArgs: { + options: { + interceptors: [interceptor], + }, + }, + } + ); + + assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); + console.log(`gRPC TLS negotiated group: ${negotiatedGroupGrpc}`); + assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); + assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); + + // --- 2. HTTP/REST Fallback PQC Test --- + console.log('Testing PQC via HTTP/REST Fallback...'); + let negotiatedGroupRest: string | undefined; + + const auth = new GoogleAuth({ + authClient: new googleAuthLibrary.PassThroughClient(), + }); + + const originalFetch = auth.fetch.bind(auth); + (auth as any).fetch = async (url: string, opts: any) => { + if (url.startsWith('https:')) { + opts.agent = new https.Agent({ + ca: pemBuffer, + keepAlive: true, + }); + } + const res = await originalFetch(url, opts); + const group = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-group') : (res.headers as any)['x-showcase-tls-group']; + if (group) { + negotiatedGroupRest = group; + } + return res; + }; + + const restClientOpts = { + fallback: true, + protocol: 'https', + servicePath: 'localhost', + port: port, + auth: auth, + }; + + const restClient = new EchoClient(restClientOpts); + const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); + + assert.strictEqual(responseRest.content, 'rest-pqc-test'); + console.log(`REST TLS negotiated group: ${negotiatedGroupRest}`); + assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); + assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); + + console.log('All PQC Integration Tests Passed Successfully!'); +} + async function main() { const showcaseServer = new ShowcaseServer(); try { @@ -2910,6 +3018,32 @@ async function main() { } finally { showcaseServer.stop(); } + + console.log('Starting PQC test with TLS-enabled showcase server...'); + process.env['SHOWCASE_VERSION'] = '0.41.1'; + const showcaseServerTls = new ShowcaseServer(); + const tlsPort = 7443; + const caCertOutputFile = 'showcase.pem'; + const pemPath = path.join(process.cwd(), caCertOutputFile); + + try { + if (fs.existsSync(pemPath)) { + fs.unlinkSync(pemPath); + } + + await showcaseServerTls.start({ + tls: true, + port: `:${tlsPort}`, + caCertOutputFile: caCertOutputFile, + }); + + await testPqc(pemPath, tlsPort); + } finally { + showcaseServerTls.stop(); + if (fs.existsSync(pemPath)) { + fs.unlinkSync(pemPath); + } + } } main(); From 754f7af60a32f07c0bffaf0095e0fa444735d037 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 14 Jul 2026 15:25:54 +0000 Subject: [PATCH 02/37] feat(pqc): add post quantum cryptography safe showcase integration tests Adds gRPC and HTTP/REST Fallback post quantum cryptography safe integration tests to the test-application package. Configures ShowcaseServer to support TLS, specific ports, and CA certificate output files. Asserts that X25519MLKEM768 is correctly negotiated. Co-authored-by: danieljbruce <8935272+danieljbruce@users.noreply.github.com> --- core/packages/gax/test/test-application/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index c6727845a124..417d8b1cd3aa 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -3031,7 +3031,7 @@ async function main() { fs.unlinkSync(pemPath); } - await showcaseServerTls.start({ + await (showcaseServerTls as any).start({ tls: true, port: `:${tlsPort}`, caCertOutputFile: caCertOutputFile, From 9413c6a00dc7d2ce9ff3c1659af9eaad07b6eb89 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Tue, 14 Jul 2026 11:34:14 -0400 Subject: [PATCH 03/37] Comment out gapic tests --- core/packages/gax/test/test-application/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index 417d8b1cd3aa..cbdbb98f1cdf 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -3011,6 +3011,7 @@ async function testPqc(pemPath: string, port: number) { } async function main() { + /* const showcaseServer = new ShowcaseServer(); try { await showcaseServer.start(); @@ -3018,6 +3019,7 @@ async function main() { } finally { showcaseServer.stop(); } + */ console.log('Starting PQC test with TLS-enabled showcase server...'); process.env['SHOWCASE_VERSION'] = '0.41.1'; From 2a3c9914d0c0f3bb8e5661913f221646cf4c5510 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Tue, 14 Jul 2026 15:14:21 -0400 Subject: [PATCH 04/37] Make the server private --- core/packages/gax/test/showcase-server/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index c9dfda05d1d4..8e325345f550 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -31,7 +31,7 @@ function sleep(timeoutMs: number) { export class ShowcaseServer { server: execa.ExecaChildProcess | undefined; - originalCwd: string | undefined; + private originalCwd: string | undefined; async start(opts?: { tls?: boolean; port?: string; caCertOutputFile?: string }) { this.originalCwd = process.cwd(); From c050585b20b8ca9e147d83cabf3e739b3b5ea459 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Tue, 14 Jul 2026 15:22:12 -0400 Subject: [PATCH 05/37] Refactor into PQC compliance tests --- .../gax/test/test-application/src/index.ts | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index cbdbb98f1cdf..c588260f1f75 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -3010,17 +3010,7 @@ async function testPqc(pemPath: string, port: number) { console.log('All PQC Integration Tests Passed Successfully!'); } -async function main() { - /* - const showcaseServer = new ShowcaseServer(); - try { - await showcaseServer.start(); - await testShowcase(); - } finally { - showcaseServer.stop(); - } - */ - +async function runPqcComplianceTests() { console.log('Starting PQC test with TLS-enabled showcase server...'); process.env['SHOWCASE_VERSION'] = '0.41.1'; const showcaseServerTls = new ShowcaseServer(); @@ -3048,4 +3038,17 @@ async function main() { } } +async function main() { + /* + const showcaseServer = new ShowcaseServer(); + try { + await showcaseServer.start(); + await testShowcase(); + } finally { + showcaseServer.stop(); + } + */ + runPqcComplianceTests() +} + main(); From debed486e11e03d163a2af581138e05258c344bd Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 09:48:12 -0400 Subject: [PATCH 06/37] Add some JS doc comments --- core/packages/gax/test/showcase-server/src/index.ts | 10 ++++++++++ .../packages/gax/test/test-application/src/index.ts | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index 8e325345f550..d24f7f6d158f 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -33,7 +33,15 @@ export class ShowcaseServer { server: execa.ExecaChildProcess | undefined; private originalCwd: string | undefined; + /** + * Starts the gapic-showcase server. + * @param opts Optional configuration for the server: + * - tls: If true, starts the server with TLS enabled. + * - port: The port to bind the server to (e.g. ":7443"). + * - caCertOutputFile: Path where the server should write its CA cert (when TLS is enabled). + */ async start(opts?: { tls?: boolean; port?: string; caCertOutputFile?: string }) { + // Keep track of the original working directory to restore it on stop this.originalCwd = process.cwd(); const testDir = path.join(this.originalCwd, '.showcase-server-dir'); const platform = process.platform; @@ -57,6 +65,7 @@ export class ShowcaseServer { await execa('tar', ['xzf', tarballFilename]); const args = ['run']; + // Pass additional options to gapic-showcase based on opts if (opts?.tls) { args.push('--tls'); } @@ -94,6 +103,7 @@ export class ShowcaseServer { throw new Error("Cannot kill the server, it's not started."); } this.server.kill(); + // Restore the original working directory if we changed it if (this.originalCwd) { process.chdir(this.originalCwd); } diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index c588260f1f75..baafdf0e6568 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -2904,6 +2904,12 @@ async function testStreamingErrorAfterDataNoBufferNoRetry( }); } +/** + * Tests Post Quantum Cryptography (PQC) using the specified CA cert and port. + * It verifies both gRPC and HTTP/REST clients by inspecting the negotiated TLS group. + * @param pemPath Path to the generated CA certificate file. + * @param port The port the TLS showcase server is listening on. + */ async function testPqc(pemPath: string, port: number) { console.log('Running Post Quantum Cryptography (PQC) Integration Tests...'); @@ -2926,6 +2932,7 @@ async function testPqc(pemPath: string, port: number) { console.log('Testing PQC via gRPC...'); let negotiatedGroupGrpc: string | undefined; + // Interceptor to capture the 'x-showcase-tls-group' metadata from the response const interceptor = (options: any, nextCall: any) => { return new grpc.InterceptingCall(nextCall(options), { start: (metadata: any, listener: any, next: any) => { @@ -2975,6 +2982,8 @@ async function testPqc(pemPath: string, port: number) { authClient: new googleAuthLibrary.PassThroughClient(), }); + // Override fetch to capture the 'x-showcase-tls-group' header from the response + // and inject the CA certificate via https.Agent for localhost TLS verification. const originalFetch = auth.fetch.bind(auth); (auth as any).fetch = async (url: string, opts: any) => { if (url.startsWith('https:')) { @@ -3010,6 +3019,10 @@ async function testPqc(pemPath: string, port: number) { console.log('All PQC Integration Tests Passed Successfully!'); } +/** + * Spins up a TLS-enabled showcase server and runs the PQC compliance tests. + * Cleans up the generated certificate file after the tests complete. + */ async function runPqcComplianceTests() { console.log('Starting PQC test with TLS-enabled showcase server...'); process.env['SHOWCASE_VERSION'] = '0.41.1'; From 5f1a58444194d175ba66a5d3421f31f3b29d0369 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 09:57:24 -0400 Subject: [PATCH 07/37] Add line for the reason we change the directory --- core/packages/gax/test/showcase-server/src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index d24f7f6d158f..5e1c69309e31 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -58,6 +58,8 @@ export class ShowcaseServer { await fsp.rm(testDir, {recursive: true, force: true}); await mkdir(testDir); + // Change the working directory to testDir so that the tar extraction + // and the gapic-showcase server execution happen in an isolated environment. process.chdir(testDir); console.log(`Server will be run from ${testDir}.`); From f458d3d8fa630eff02a98821a1b3a0df6b0c09dd Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 10:12:28 -0400 Subject: [PATCH 08/37] Add a comment explaining why we wait --- core/packages/gax/test/test-application/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index baafdf0e6568..f1d62937eb06 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -2916,6 +2916,9 @@ async function testPqc(pemPath: string, port: number) { // Verify the CA certificate file exists let pemExists = false; for (let i = 0; i < 15; i++) { + // We loop up to 15 times (waiting 1 second each time) because gapic-showcase + // generates the CA certificate asynchronously on startup. This ensures the file + // is completely written to disk before we attempt to use it for our PQC tests. if (fs.existsSync(pemPath)) { pemExists = true; break; From 287333a67bce24617809c3f8018a88a00116c263 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 10:23:57 -0400 Subject: [PATCH 09/37] Move the pqc compliance tests to separate file --- .../gax/test/test-application/src/index.ts | 146 +---------------- .../gax/test/test-application/src/pqc-test.ts | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+), 145 deletions(-) create mode 100644 core/packages/gax/test/test-application/src/pqc-test.ts diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index f1d62937eb06..bb38ca70d803 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -19,8 +19,7 @@ import {EchoClient, SequenceServiceClient, protos} from 'showcase-echo-client'; import {ShowcaseServer} from 'showcase-server'; import * as assert from 'assert'; import {promises as fsp} from 'fs'; -import * as fs from 'fs'; -import * as https from 'https'; +import {runPqcComplianceTests} from './pqc-test'; import * as path from 'path'; import { protobuf, @@ -2910,149 +2909,6 @@ async function testStreamingErrorAfterDataNoBufferNoRetry( * @param pemPath Path to the generated CA certificate file. * @param port The port the TLS showcase server is listening on. */ -async function testPqc(pemPath: string, port: number) { - console.log('Running Post Quantum Cryptography (PQC) Integration Tests...'); - - // Verify the CA certificate file exists - let pemExists = false; - for (let i = 0; i < 15; i++) { - // We loop up to 15 times (waiting 1 second each time) because gapic-showcase - // generates the CA certificate asynchronously on startup. This ensures the file - // is completely written to disk before we attempt to use it for our PQC tests. - if (fs.existsSync(pemPath)) { - pemExists = true; - break; - } - await new Promise(resolve => setTimeout(resolve, 1000)); - } - if (!pemExists) { - throw new Error(`CA Certificate file not found at ${pemPath}`); - } - - const pemBuffer = fs.readFileSync(pemPath); - - // --- 1. gRPC PQC Test --- - console.log('Testing PQC via gRPC...'); - let negotiatedGroupGrpc: string | undefined; - - // Interceptor to capture the 'x-showcase-tls-group' metadata from the response - const interceptor = (options: any, nextCall: any) => { - return new grpc.InterceptingCall(nextCall(options), { - start: (metadata: any, listener: any, next: any) => { - next(metadata, { - onReceiveMetadata: (receivedMetadata: any, nextListener: any) => { - const group = receivedMetadata.get('x-showcase-tls-group'); - if (group && group.length > 0) { - negotiatedGroupGrpc = group[0].toString(); - } - nextListener(receivedMetadata); - }, - }); - }, - }); - }; - - const grpcClientOpts = { - grpc, - sslCreds: grpc.credentials.createSsl(pemBuffer), - servicePath: 'localhost', - port: port, - }; - - const grpcClient = new EchoClient(grpcClientOpts); - - const [responseGrpc] = await grpcClient.echo( - { content: 'grpc-pqc-test' }, - { - otherArgs: { - options: { - interceptors: [interceptor], - }, - }, - } - ); - - assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); - console.log(`gRPC TLS negotiated group: ${negotiatedGroupGrpc}`); - assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); - assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); - - // --- 2. HTTP/REST Fallback PQC Test --- - console.log('Testing PQC via HTTP/REST Fallback...'); - let negotiatedGroupRest: string | undefined; - - const auth = new GoogleAuth({ - authClient: new googleAuthLibrary.PassThroughClient(), - }); - - // Override fetch to capture the 'x-showcase-tls-group' header from the response - // and inject the CA certificate via https.Agent for localhost TLS verification. - const originalFetch = auth.fetch.bind(auth); - (auth as any).fetch = async (url: string, opts: any) => { - if (url.startsWith('https:')) { - opts.agent = new https.Agent({ - ca: pemBuffer, - keepAlive: true, - }); - } - const res = await originalFetch(url, opts); - const group = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-group') : (res.headers as any)['x-showcase-tls-group']; - if (group) { - negotiatedGroupRest = group; - } - return res; - }; - - const restClientOpts = { - fallback: true, - protocol: 'https', - servicePath: 'localhost', - port: port, - auth: auth, - }; - - const restClient = new EchoClient(restClientOpts); - const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); - - assert.strictEqual(responseRest.content, 'rest-pqc-test'); - console.log(`REST TLS negotiated group: ${negotiatedGroupRest}`); - assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); - assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); - - console.log('All PQC Integration Tests Passed Successfully!'); -} - -/** - * Spins up a TLS-enabled showcase server and runs the PQC compliance tests. - * Cleans up the generated certificate file after the tests complete. - */ -async function runPqcComplianceTests() { - console.log('Starting PQC test with TLS-enabled showcase server...'); - process.env['SHOWCASE_VERSION'] = '0.41.1'; - const showcaseServerTls = new ShowcaseServer(); - const tlsPort = 7443; - const caCertOutputFile = 'showcase.pem'; - const pemPath = path.join(process.cwd(), caCertOutputFile); - - try { - if (fs.existsSync(pemPath)) { - fs.unlinkSync(pemPath); - } - - await (showcaseServerTls as any).start({ - tls: true, - port: `:${tlsPort}`, - caCertOutputFile: caCertOutputFile, - }); - - await testPqc(pemPath, tlsPort); - } finally { - showcaseServerTls.stop(); - if (fs.existsSync(pemPath)) { - fs.unlinkSync(pemPath); - } - } -} async function main() { /* diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts new file mode 100644 index 000000000000..cf954d1b7887 --- /dev/null +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -0,0 +1,151 @@ +import * as fs from 'fs'; +import * as https from 'https'; +import * as path from 'path'; +import * as assert from 'assert'; +import {grpc, GoogleAuth, googleAuthLibrary} from 'google-gax'; +import {EchoClient} from 'showcase-echo-client'; +import {ShowcaseServer} from 'showcase-server'; + +async function testPqc(pemPath: string, port: number) { + console.log('Running Post Quantum Cryptography (PQC) Integration Tests...'); + + // Verify the CA certificate file exists + let pemExists = false; + for (let i = 0; i < 15; i++) { + // We loop up to 15 times (waiting 1 second each time) because gapic-showcase + // generates the CA certificate asynchronously on startup. This ensures the file + // is completely written to disk before we attempt to use it for our PQC tests. + if (fs.existsSync(pemPath)) { + pemExists = true; + break; + } + await new Promise(resolve => setTimeout(resolve, 1000)); + } + if (!pemExists) { + throw new Error(`CA Certificate file not found at ${pemPath}`); + } + + const pemBuffer = fs.readFileSync(pemPath); + + // --- 1. gRPC PQC Test --- + console.log('Testing PQC via gRPC...'); + let negotiatedGroupGrpc: string | undefined; + + // Interceptor to capture the 'x-showcase-tls-group' metadata from the response + const interceptor = (options: any, nextCall: any) => { + return new grpc.InterceptingCall(nextCall(options), { + start: (metadata: any, listener: any, next: any) => { + next(metadata, { + onReceiveMetadata: (receivedMetadata: any, nextListener: any) => { + const group = receivedMetadata.get('x-showcase-tls-group'); + if (group && group.length > 0) { + negotiatedGroupGrpc = group[0].toString(); + } + nextListener(receivedMetadata); + }, + }); + }, + }); + }; + + const grpcClientOpts = { + grpc, + sslCreds: grpc.credentials.createSsl(pemBuffer), + servicePath: 'localhost', + port: port, + }; + + const grpcClient = new EchoClient(grpcClientOpts); + + const [responseGrpc] = await grpcClient.echo( + { content: 'grpc-pqc-test' }, + { + otherArgs: { + options: { + interceptors: [interceptor], + }, + }, + } + ); + + assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); + console.log(`gRPC TLS negotiated group: ${negotiatedGroupGrpc}`); + assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); + assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); + + // --- 2. HTTP/REST Fallback PQC Test --- + console.log('Testing PQC via HTTP/REST Fallback...'); + let negotiatedGroupRest: string | undefined; + + const auth = new GoogleAuth({ + authClient: new googleAuthLibrary.PassThroughClient(), + }); + + // Override fetch to capture the 'x-showcase-tls-group' header from the response + // and inject the CA certificate via https.Agent for localhost TLS verification. + const originalFetch = auth.fetch.bind(auth); + (auth as any).fetch = async (url: string, opts: any) => { + if (url.startsWith('https:')) { + opts.agent = new https.Agent({ + ca: pemBuffer, + keepAlive: true, + }); + } + const res = await originalFetch(url, opts); + const group = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-group') : (res.headers as any)['x-showcase-tls-group']; + if (group) { + negotiatedGroupRest = group; + } + return res; + }; + + const restClientOpts = { + fallback: true, + protocol: 'https', + servicePath: 'localhost', + port: port, + auth: auth, + }; + + const restClient = new EchoClient(restClientOpts); + const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); + + assert.strictEqual(responseRest.content, 'rest-pqc-test'); + console.log(`REST TLS negotiated group: ${negotiatedGroupRest}`); + assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); + assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); + + console.log('All PQC Integration Tests Passed Successfully!'); +} + +/** + * Spins up a TLS-enabled showcase server and runs the PQC compliance tests. + * Cleans up the generated certificate file after the tests complete. + */ +export async function runPqcComplianceTests() { + console.log('Starting PQC test with TLS-enabled showcase server...'); + process.env['SHOWCASE_VERSION'] = '0.41.1'; + const showcaseServerTls = new ShowcaseServer(); + const tlsPort = 7443; + const caCertOutputFile = 'showcase.pem'; + const pemPath = path.join(process.cwd(), caCertOutputFile); + + try { + if (fs.existsSync(pemPath)) { + fs.unlinkSync(pemPath); + } + + await (showcaseServerTls as any).start({ + tls: true, + port: `:${tlsPort}`, + caCertOutputFile: caCertOutputFile, + }); + + await testPqc(pemPath, tlsPort); + } finally { + showcaseServerTls.stop(); + if (fs.existsSync(pemPath)) { + fs.unlinkSync(pemPath); + } + } +} From 24d096953a918403025dc7753d2e48a172e7f919 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 10:28:17 -0400 Subject: [PATCH 10/37] Add a jsdoc comment --- core/packages/gax/test/test-application/src/pqc-test.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index cf954d1b7887..8511c1d21f63 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -6,6 +6,12 @@ import {grpc, GoogleAuth, googleAuthLibrary} from 'google-gax'; import {EchoClient} from 'showcase-echo-client'; import {ShowcaseServer} from 'showcase-server'; +/** + * Tests Post Quantum Cryptography (PQC) using the specified CA cert and port. + * It verifies both gRPC and HTTP/REST clients by inspecting the negotiated TLS group. + * @param pemPath Path to the generated CA certificate file. + * @param port The port the TLS showcase server is listening on. + */ async function testPqc(pemPath: string, port: number) { console.log('Running Post Quantum Cryptography (PQC) Integration Tests...'); From 96d231f2ba73b4f3ee01fdf824d1d51573e8fd1d Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 10:28:59 -0400 Subject: [PATCH 11/37] Add license header --- .../gax/test/test-application/src/pqc-test.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 8511c1d21f63..919e1add7f4a 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -1,3 +1,19 @@ +/** + * 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 fs from 'fs'; import * as https from 'https'; import * as path from 'path'; From 3d9146f5c0284d46ca22b073c31977c26b7aa025 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 10:54:43 -0400 Subject: [PATCH 12/37] Add the jest framework to the google-gax repository --- core/packages/gax/package.json | 2 +- .../packages/gax/test/test-application/jest.config.js | 5 +++++ core/packages/gax/test/test-application/package.json | 6 +++++- .../gax/test/test-application/test/pqc.test.ts | 11 +++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 core/packages/gax/test/test-application/jest.config.js create mode 100644 core/packages/gax/test/test-application/test/pqc.test.ts diff --git a/core/packages/gax/package.json b/core/packages/gax/package.json index 42fafa24cc49..81694f625787 100644 --- a/core/packages/gax/package.json +++ b/core/packages/gax/package.json @@ -78,7 +78,7 @@ "system-test": "c8 mocha build/test/system-test --timeout 600000 && npm run test-application", "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", "browser-test": "cd test/browser-test && npm run prefetch && npm install && npm test", - "test-application": "cd test/test-application && npm run prefetch && npm install && npm start", + "test-application": "cd test/test-application && npm run prefetch && npm install && npm start && npm test", "prelint": "cd samples; npm link ../; npm install", "precompile": "gts clean", "update-protos": "cd ../tools && rm -rf node_modules build && npm i && npm run compile && cd ../gax && node ../tools/build/src/listProtos.js .", diff --git a/core/packages/gax/test/test-application/jest.config.js b/core/packages/gax/test/test-application/jest.config.js new file mode 100644 index 000000000000..50c59e495c12 --- /dev/null +++ b/core/packages/gax/test/test-application/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testMatch: ['**/*.test.ts'], +}; diff --git a/core/packages/gax/test/test-application/package.json b/core/packages/gax/test/test-application/package.json index da3cc3913348..96c2f63c8b73 100644 --- a/core/packages/gax/test/test-application/package.json +++ b/core/packages/gax/test/test-application/package.json @@ -17,10 +17,14 @@ "prefetch-showcase-server": "cd ../showcase-server && npm install && npm pack && mv showcase-server*.tgz ../test-application/showcase-server.tgz", "prefetch": "npm run prefetch-cleanup && npm run prefetch-google-gax && npm run prefetch-showcase-echo-client && npm run prefetch-showcase-server", "prepare": "npm run compile", - "start": "node build/src/index.js" + "start": "node build/src/index.js", + "test": "NODE_OPTIONS=--experimental-vm-modules jest" }, "devDependencies": { + "@types/jest": "^30.0.0", + "jest": "^30.4.2", "mocha": "^10.0.1", + "ts-jest": "^29.4.11", "typescript": "^5.1.6" }, "dependencies": { diff --git a/core/packages/gax/test/test-application/test/pqc.test.ts b/core/packages/gax/test/test-application/test/pqc.test.ts new file mode 100644 index 000000000000..a83a11ddfeba --- /dev/null +++ b/core/packages/gax/test/test-application/test/pqc.test.ts @@ -0,0 +1,11 @@ +import {runPqcComplianceTests} from '../src/pqc-test'; + +describe('PQC Compliance Tests', () => { + it( + 'should run PQC compliance tests successfully', + async () => { + await runPqcComplianceTests(); + }, + 60000 + ); +}); From 17ec0ac6c4fb5de9fbeb67ffdbb3f985c06043a7 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 11:26:17 -0400 Subject: [PATCH 13/37] Eliminate the reference to the original working dir --- core/packages/gax/test/showcase-server/src/index.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index 5e1c69309e31..265ec71d3336 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -31,7 +31,6 @@ function sleep(timeoutMs: number) { export class ShowcaseServer { server: execa.ExecaChildProcess | undefined; - private originalCwd: string | undefined; /** * Starts the gapic-showcase server. @@ -41,9 +40,8 @@ export class ShowcaseServer { * - caCertOutputFile: Path where the server should write its CA cert (when TLS is enabled). */ async start(opts?: { tls?: boolean; port?: string; caCertOutputFile?: string }) { - // Keep track of the original working directory to restore it on stop - this.originalCwd = process.cwd(); - const testDir = path.join(this.originalCwd, '.showcase-server-dir'); + const cwd = process.cwd(); + const testDir = path.join(cwd, '.showcase-server-dir'); const platform = process.platform; const arch = process.arch === 'x64' ? 'amd64' : process.arch; const showcaseVersion = process.env['SHOWCASE_VERSION'] || '0.36.2'; @@ -53,7 +51,7 @@ export class ShowcaseServer { let resolvedCaCertPath = ''; if (opts?.caCertOutputFile) { - resolvedCaCertPath = path.resolve(this.originalCwd, opts.caCertOutputFile); + resolvedCaCertPath = path.resolve(cwd, opts.caCertOutputFile); } await fsp.rm(testDir, {recursive: true, force: true}); @@ -105,10 +103,6 @@ export class ShowcaseServer { throw new Error("Cannot kill the server, it's not started."); } this.server.kill(); - // Restore the original working directory if we changed it - if (this.originalCwd) { - process.chdir(this.originalCwd); - } } } From fe5df381428a40e9712d9e97fc1cfb26ef4fda94 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 15 Jul 2026 11:29:38 -0400 Subject: [PATCH 14/37] Eliminate added comment for the main function --- core/packages/gax/test/test-application/src/index.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index bb38ca70d803..c956e50574fa 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -2903,13 +2903,6 @@ async function testStreamingErrorAfterDataNoBufferNoRetry( }); } -/** - * Tests Post Quantum Cryptography (PQC) using the specified CA cert and port. - * It verifies both gRPC and HTTP/REST clients by inspecting the negotiated TLS group. - * @param pemPath Path to the generated CA certificate file. - * @param port The port the TLS showcase server is listening on. - */ - async function main() { /* const showcaseServer = new ShowcaseServer(); From 1a4ddb4136c4e412fcbfc00007d5a1239d8003da Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 16 Jul 2026 13:21:49 -0400 Subject: [PATCH 15/37] =?UTF-8?q?Don=E2=80=99t=20comment=20out=20all=20the?= =?UTF-8?q?=20other=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/packages/gax/test/test-application/src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index c956e50574fa..34696807ea76 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -2904,7 +2904,6 @@ async function testStreamingErrorAfterDataNoBufferNoRetry( } async function main() { - /* const showcaseServer = new ShowcaseServer(); try { await showcaseServer.start(); @@ -2912,7 +2911,6 @@ async function main() { } finally { showcaseServer.stop(); } - */ runPqcComplianceTests() } From ff7c90cafe85b5e14102001240da34ef5b5d44a8 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 16 Jul 2026 13:24:59 -0400 Subject: [PATCH 16/37] Add a comment about the reason a separate showcase server test is required --- core/packages/gax/test/test-application/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index 34696807ea76..a0df438e4ca6 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -2911,6 +2911,7 @@ async function main() { } finally { showcaseServer.stop(); } + // Run PQC tests with a different showcase server because setup with a certificate is required. runPqcComplianceTests() } From ac2a91103353bd21113ca40a0b3e458c5a21fea9 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 16 Jul 2026 13:35:13 -0400 Subject: [PATCH 17/37] Remove the console logs to reduce clutter --- core/packages/gax/test/test-application/src/pqc-test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 919e1add7f4a..dbb6bc727171 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -29,7 +29,6 @@ import {ShowcaseServer} from 'showcase-server'; * @param port The port the TLS showcase server is listening on. */ async function testPqc(pemPath: string, port: number) { - console.log('Running Post Quantum Cryptography (PQC) Integration Tests...'); // Verify the CA certificate file exists let pemExists = false; @@ -50,7 +49,6 @@ async function testPqc(pemPath: string, port: number) { const pemBuffer = fs.readFileSync(pemPath); // --- 1. gRPC PQC Test --- - console.log('Testing PQC via gRPC...'); let negotiatedGroupGrpc: string | undefined; // Interceptor to capture the 'x-showcase-tls-group' metadata from the response @@ -91,12 +89,10 @@ async function testPqc(pemPath: string, port: number) { ); assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); - console.log(`gRPC TLS negotiated group: ${negotiatedGroupGrpc}`); assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); // --- 2. HTTP/REST Fallback PQC Test --- - console.log('Testing PQC via HTTP/REST Fallback...'); let negotiatedGroupRest: string | undefined; const auth = new GoogleAuth({ @@ -133,11 +129,8 @@ async function testPqc(pemPath: string, port: number) { const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); assert.strictEqual(responseRest.content, 'rest-pqc-test'); - console.log(`REST TLS negotiated group: ${negotiatedGroupRest}`); assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); - - console.log('All PQC Integration Tests Passed Successfully!'); } /** @@ -145,7 +138,6 @@ async function testPqc(pemPath: string, port: number) { * Cleans up the generated certificate file after the tests complete. */ export async function runPqcComplianceTests() { - console.log('Starting PQC test with TLS-enabled showcase server...'); process.env['SHOWCASE_VERSION'] = '0.41.1'; const showcaseServerTls = new ShowcaseServer(); const tlsPort = 7443; From 9612e844983ba1b2d3108fa72b41e700dfc02daf Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 16 Jul 2026 14:07:26 -0400 Subject: [PATCH 18/37] Add headers --- .../gax/test/test-application/jest.config.js | 16 ++++++++++++++++ .../gax/test/test-application/test/pqc.test.ts | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/core/packages/gax/test/test-application/jest.config.js b/core/packages/gax/test/test-application/jest.config.js index 50c59e495c12..9ea5cefd79b2 100644 --- a/core/packages/gax/test/test-application/jest.config.js +++ b/core/packages/gax/test/test-application/jest.config.js @@ -1,3 +1,19 @@ +/** + * 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. + */ + module.exports = { preset: 'ts-jest', testEnvironment: 'node', diff --git a/core/packages/gax/test/test-application/test/pqc.test.ts b/core/packages/gax/test/test-application/test/pqc.test.ts index a83a11ddfeba..08ecc7ace0f4 100644 --- a/core/packages/gax/test/test-application/test/pqc.test.ts +++ b/core/packages/gax/test/test-application/test/pqc.test.ts @@ -1,3 +1,19 @@ +/** + * 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 {runPqcComplianceTests} from '../src/pqc-test'; describe('PQC Compliance Tests', () => { From b1b54cce52a44a2d256e5df17fbfb93d4bfeb988 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 16 Jul 2026 14:11:00 -0400 Subject: [PATCH 19/37] Test against exact cipher and protocol --- .../gax/test/test-application/src/pqc-test.ts | 184 ++++++++++-------- 1 file changed, 107 insertions(+), 77 deletions(-) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index dbb6bc727171..3332ef99d880 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -22,6 +22,8 @@ import {grpc, GoogleAuth, googleAuthLibrary} from 'google-gax'; import {EchoClient} from 'showcase-echo-client'; import {ShowcaseServer} from 'showcase-server'; +import * as tls from 'tls'; + /** * Tests Post Quantum Cryptography (PQC) using the specified CA cert and port. * It verifies both gRPC and HTTP/REST clients by inspecting the negotiated TLS group. @@ -48,89 +50,117 @@ async function testPqc(pemPath: string, port: number) { const pemBuffer = fs.readFileSync(pemPath); - // --- 1. gRPC PQC Test --- - let negotiatedGroupGrpc: string | undefined; - - // Interceptor to capture the 'x-showcase-tls-group' metadata from the response - const interceptor = (options: any, nextCall: any) => { - return new grpc.InterceptingCall(nextCall(options), { - start: (metadata: any, listener: any, next: any) => { - next(metadata, { - onReceiveMetadata: (receivedMetadata: any, nextListener: any) => { - const group = receivedMetadata.get('x-showcase-tls-group'); - if (group && group.length > 0) { - negotiatedGroupGrpc = group[0].toString(); - } - nextListener(receivedMetadata); - }, - }); - }, - }); - }; + const originalTlsConnect = tls.connect; + let grpcSocket: tls.TLSSocket | undefined; + let restSocket: tls.TLSSocket | undefined; - const grpcClientOpts = { - grpc, - sslCreds: grpc.credentials.createSsl(pemBuffer), - servicePath: 'localhost', - port: port, - }; + let currentTestType: 'grpc' | 'rest' | 'none' = 'none'; - const grpcClient = new EchoClient(grpcClientOpts); + (tls as any).connect = function (...args: any[]) { + const socket = originalTlsConnect.apply(this, args as any); + socket.on('secureConnect', () => { + if (currentTestType === 'grpc') { + grpcSocket = socket as tls.TLSSocket; + } else if (currentTestType === 'rest') { + restSocket = socket as tls.TLSSocket; + } + }); + return socket; + }; - const [responseGrpc] = await grpcClient.echo( - { content: 'grpc-pqc-test' }, - { - otherArgs: { - options: { - interceptors: [interceptor], + try { + // --- 1. gRPC PQC Test --- + currentTestType = 'grpc'; + let negotiatedGroupGrpc: string | undefined; + + const interceptor = (options: any, nextCall: any) => { + return new grpc.InterceptingCall(nextCall(options), { + start: (metadata: any, listener: any, next: any) => { + next(metadata, { + onReceiveMetadata: (receivedMetadata: any, nextListener: any) => { + const group = receivedMetadata.get('x-showcase-tls-group'); + if (group && group.length > 0) { + negotiatedGroupGrpc = group[0].toString(); + } + nextListener(receivedMetadata); + }, + }); }, - }, - } - ); - - assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); - assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); - assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); - - // --- 2. HTTP/REST Fallback PQC Test --- - let negotiatedGroupRest: string | undefined; - - const auth = new GoogleAuth({ - authClient: new googleAuthLibrary.PassThroughClient(), - }); - - // Override fetch to capture the 'x-showcase-tls-group' header from the response - // and inject the CA certificate via https.Agent for localhost TLS verification. - const originalFetch = auth.fetch.bind(auth); - (auth as any).fetch = async (url: string, opts: any) => { - if (url.startsWith('https:')) { - opts.agent = new https.Agent({ - ca: pemBuffer, - keepAlive: true, }); - } - const res = await originalFetch(url, opts); - const group = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-group') : (res.headers as any)['x-showcase-tls-group']; - if (group) { - negotiatedGroupRest = group; - } - return res; - }; - - const restClientOpts = { - fallback: true, - protocol: 'https', - servicePath: 'localhost', - port: port, - auth: auth, - }; - - const restClient = new EchoClient(restClientOpts); - const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); + }; + + const grpcClientOpts = { + grpc, + sslCreds: grpc.credentials.createSsl(pemBuffer), + servicePath: 'localhost', + port: port, + }; + + const grpcClient = new EchoClient(grpcClientOpts); + + const [responseGrpc] = await grpcClient.echo( + { content: 'grpc-pqc-test' }, + { + otherArgs: { + options: { + interceptors: [interceptor], + }, + }, + } + ); + + assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); + assert.ok(grpcSocket, 'Expected to intercept gRPC TLS socket'); + assert.strictEqual(grpcSocket.getProtocol(), 'TLSv1.3'); + assert.strictEqual(grpcSocket.getCipher()?.name, 'TLS_AES_128_GCM_SHA256', 'Expected specific negotiated cipher'); + assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); + assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); + + // --- 2. HTTP/REST Fallback PQC Test --- + currentTestType = 'rest'; + let negotiatedGroupRest: string | undefined; + + const auth = new GoogleAuth({ + authClient: new googleAuthLibrary.PassThroughClient(), + }); - assert.strictEqual(responseRest.content, 'rest-pqc-test'); - assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); - assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); + const originalFetch = auth.fetch.bind(auth); + (auth as any).fetch = async (url: string, opts: any) => { + if (url.startsWith('https:')) { + opts.agent = new https.Agent({ + ca: pemBuffer, + keepAlive: true, + }); + } + const res = await originalFetch(url, opts); + const group = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-group') : (res.headers as any)['x-showcase-tls-group']; + if (group) { + negotiatedGroupRest = group; + } + return res; + }; + + const restClientOpts = { + fallback: true, + protocol: 'https', + servicePath: 'localhost', + port: port, + auth: auth, + }; + + const restClient = new EchoClient(restClientOpts); + const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); + + assert.strictEqual(responseRest.content, 'rest-pqc-test'); + assert.ok(restSocket, 'Expected to intercept REST TLS socket'); + assert.strictEqual(restSocket.getProtocol(), 'TLSv1.3'); + assert.strictEqual(restSocket.getCipher()?.name, 'TLS_AES_128_GCM_SHA256', 'Expected specific negotiated cipher'); + assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); + assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); + } finally { + // Restore the original tls.connect + (tls as any).connect = originalTlsConnect; + } } /** From d24604b816f4c4c6a22335f2aaa24b069a27a3cd Mon Sep 17 00:00:00 2001 From: danieljbruce Date: Thu, 16 Jul 2026 14:15:42 -0400 Subject: [PATCH 20/37] Update core/packages/gax/test/test-application/src/pqc-test.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- core/packages/gax/test/test-application/src/pqc-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index dbb6bc727171..fab87c658ad2 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -149,7 +149,7 @@ export async function runPqcComplianceTests() { fs.unlinkSync(pemPath); } - await (showcaseServerTls as any).start({ + await showcaseServerTls.start({ tls: true, port: `:${tlsPort}`, caCertOutputFile: caCertOutputFile, From 7bf096f1279c3d317a4ca0ed45e0a5e81f8830c9 Mon Sep 17 00:00:00 2001 From: danieljbruce Date: Thu, 16 Jul 2026 14:15:59 -0400 Subject: [PATCH 21/37] Update core/packages/gax/test/test-application/src/index.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- core/packages/gax/test/test-application/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/packages/gax/test/test-application/src/index.ts b/core/packages/gax/test/test-application/src/index.ts index a0df438e4ca6..c0035e1de447 100644 --- a/core/packages/gax/test/test-application/src/index.ts +++ b/core/packages/gax/test/test-application/src/index.ts @@ -2912,7 +2912,7 @@ async function main() { showcaseServer.stop(); } // Run PQC tests with a different showcase server because setup with a certificate is required. - runPqcComplianceTests() + await runPqcComplianceTests() } main(); From 06adb75ace2d35f722b9da6f37a4688a7ce2951c Mon Sep 17 00:00:00 2001 From: danieljbruce Date: Thu, 16 Jul 2026 14:17:31 -0400 Subject: [PATCH 22/37] Update core/packages/gax/test/showcase-server/src/index.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- core/packages/gax/test/showcase-server/src/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index 265ec71d3336..8e15bc0924e9 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -56,13 +56,10 @@ export class ShowcaseServer { await fsp.rm(testDir, {recursive: true, force: true}); await mkdir(testDir); - // Change the working directory to testDir so that the tar extraction - // and the gapic-showcase server execution happen in an isolated environment. - process.chdir(testDir); console.log(`Server will be run from ${testDir}.`); await download(fallbackServerUrl, testDir); - await execa('tar', ['xzf', tarballFilename]); + await execa('tar', ['xzf', tarballFilename], {cwd: testDir}); const args = ['run']; // Pass additional options to gapic-showcase based on opts From 305b50c7721eaffc17c2f9d8469c5e0d2d0b6f05 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 16 Jul 2026 14:28:28 -0400 Subject: [PATCH 23/37] Compile jest and node --- core/packages/gax/test/test-application/tsconfig.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/packages/gax/test/test-application/tsconfig.json b/core/packages/gax/test/test-application/tsconfig.json index ac741f352757..74d5563241ea 100644 --- a/core/packages/gax/test/test-application/tsconfig.json +++ b/core/packages/gax/test/test-application/tsconfig.json @@ -7,6 +7,10 @@ "lib": [ "es2018", "dom" + ], + "types": [ + "jest", + "node" ] }, "include": [ From a2ccbd4d2b1a47f00972bdde49def7a6bb018b54 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 10:28:25 -0400 Subject: [PATCH 24/37] Check the tls-client-supported-groups metadata too --- .../gax/test/test-application/src/pqc-test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 67f25c3c9826..e06311194813 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -72,6 +72,7 @@ async function testPqc(pemPath: string, port: number) { // --- 1. gRPC PQC Test --- currentTestType = 'grpc'; let negotiatedGroupGrpc: string | undefined; + let clientSupportedGroupsGrpc: string | undefined; const interceptor = (options: any, nextCall: any) => { return new grpc.InterceptingCall(nextCall(options), { @@ -82,6 +83,10 @@ async function testPqc(pemPath: string, port: number) { if (group && group.length > 0) { negotiatedGroupGrpc = group[0].toString(); } + const supportedGroups = receivedMetadata.get('x-showcase-tls-client-supported-groups'); + if (supportedGroups && supportedGroups.length > 0) { + clientSupportedGroupsGrpc = supportedGroups[0].toString(); + } nextListener(receivedMetadata); }, }); @@ -115,10 +120,13 @@ async function testPqc(pemPath: string, port: number) { assert.strictEqual(grpcSocket.getCipher()?.name, 'TLS_AES_128_GCM_SHA256', 'Expected specific negotiated cipher'); assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); + assert.ok(clientSupportedGroupsGrpc, 'Expected client supported groups in gRPC response metadata'); + assert.ok(clientSupportedGroupsGrpc.includes('X25519MLKEM768'), 'Expected client to include X25519MLKEM768 in supported groups'); // --- 2. HTTP/REST Fallback PQC Test --- currentTestType = 'rest'; let negotiatedGroupRest: string | undefined; + let clientSupportedGroupsRest: string | undefined; const auth = new GoogleAuth({ authClient: new googleAuthLibrary.PassThroughClient(), @@ -137,6 +145,10 @@ async function testPqc(pemPath: string, port: number) { if (group) { negotiatedGroupRest = group; } + const supportedGroups = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-client-supported-groups') : (res.headers as any)['x-showcase-tls-client-supported-groups']; + if (supportedGroups) { + clientSupportedGroupsRest = supportedGroups; + } return res; }; @@ -157,6 +169,8 @@ async function testPqc(pemPath: string, port: number) { assert.strictEqual(restSocket.getCipher()?.name, 'TLS_AES_128_GCM_SHA256', 'Expected specific negotiated cipher'); assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); + assert.ok(clientSupportedGroupsRest, 'Expected client supported groups in REST response headers'); + assert.ok(clientSupportedGroupsRest.includes('X25519MLKEM768'), 'Expected client to include X25519MLKEM768 in supported groups'); } finally { // Restore the original tls.connect (tls as any).connect = originalTlsConnect; From 3320e5d8e9a31411ba446a74c58f75c810e1c052 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 15:08:33 -0400 Subject: [PATCH 25/37] Remove cipher comparison --- core/packages/gax/test/test-application/src/pqc-test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index e06311194813..314e4c93c4e6 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -117,7 +117,6 @@ async function testPqc(pemPath: string, port: number) { assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); assert.ok(grpcSocket, 'Expected to intercept gRPC TLS socket'); assert.strictEqual(grpcSocket.getProtocol(), 'TLSv1.3'); - assert.strictEqual(grpcSocket.getCipher()?.name, 'TLS_AES_128_GCM_SHA256', 'Expected specific negotiated cipher'); assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); assert.ok(clientSupportedGroupsGrpc, 'Expected client supported groups in gRPC response metadata'); @@ -166,7 +165,6 @@ async function testPqc(pemPath: string, port: number) { assert.strictEqual(responseRest.content, 'rest-pqc-test'); assert.ok(restSocket, 'Expected to intercept REST TLS socket'); assert.strictEqual(restSocket.getProtocol(), 'TLSv1.3'); - assert.strictEqual(restSocket.getCipher()?.name, 'TLS_AES_128_GCM_SHA256', 'Expected specific negotiated cipher'); assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); assert.ok(clientSupportedGroupsRest, 'Expected client supported groups in REST response headers'); From 09b0c7e328fb2f7db3b8049c74e8aa20274f9ef1 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 15:13:20 -0400 Subject: [PATCH 26/37] Skip this test conditionally --- core/packages/gax/test/test-application/src/pqc-test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 314e4c93c4e6..060133db5b84 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -180,6 +180,14 @@ async function testPqc(pemPath: string, port: number) { * Cleans up the generated certificate file after the tests complete. */ export async function runPqcComplianceTests() { + const [major, minor] = process.version.replace('v', '').split('.').map(Number); + if (major < 22 || (major === 22 && minor < 20)) { + console.log( + `skipping PQC compliance tests because node version is ${process.version}` + ); + return; + } + process.env['SHOWCASE_VERSION'] = '0.41.1'; const showcaseServerTls = new ShowcaseServer(); const tlsPort = 7443; From fdd3586c729e60f5ca0fa0f1d80bad7c5c1db917 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 15:39:18 -0400 Subject: [PATCH 27/37] Move the pem code to the server --- .../gax/test/showcase-server/src/index.ts | 19 ++++++++++++ .../gax/test/test-application/src/pqc-test.ts | 30 +++++-------------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index 8e15bc0924e9..e77e6c3426d9 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -93,6 +93,25 @@ export class ShowcaseServer { ); this.server = childProcess; + + if (resolvedCaCertPath) { + let pemExists = false; + for (let i = 0; i < 15; i++) { + // We loop up to 15 times (waiting 1 second each time) because gapic-showcase + // generates the CA certificate asynchronously on startup. This ensures the file + // is completely written to disk before we attempt to use it for our PQC tests. + if (fs.existsSync(resolvedCaCertPath)) { + pemExists = true; + break; + } + await sleep(1000); + } + if (!pemExists) { + throw new Error(`CA Certificate file not found at ${resolvedCaCertPath}`); + } + + return fs.readFileSync(resolvedCaCertPath); + } } stop() { diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 060133db5b84..591b590b32d2 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -27,28 +27,10 @@ import * as tls from 'tls'; /** * Tests Post Quantum Cryptography (PQC) using the specified CA cert and port. * It verifies both gRPC and HTTP/REST clients by inspecting the negotiated TLS group. - * @param pemPath Path to the generated CA certificate file. + * @param pemBuffer The CA certificate buffer. * @param port The port the TLS showcase server is listening on. */ -async function testPqc(pemPath: string, port: number) { - - // Verify the CA certificate file exists - let pemExists = false; - for (let i = 0; i < 15; i++) { - // We loop up to 15 times (waiting 1 second each time) because gapic-showcase - // generates the CA certificate asynchronously on startup. This ensures the file - // is completely written to disk before we attempt to use it for our PQC tests. - if (fs.existsSync(pemPath)) { - pemExists = true; - break; - } - await new Promise(resolve => setTimeout(resolve, 1000)); - } - if (!pemExists) { - throw new Error(`CA Certificate file not found at ${pemPath}`); - } - - const pemBuffer = fs.readFileSync(pemPath); +async function testPqc(pemBuffer: Buffer, port: number) { const originalTlsConnect = tls.connect; let grpcSocket: tls.TLSSocket | undefined; @@ -199,13 +181,17 @@ export async function runPqcComplianceTests() { fs.unlinkSync(pemPath); } - await showcaseServerTls.start({ + const pemBuffer = await showcaseServerTls.start({ tls: true, port: `:${tlsPort}`, caCertOutputFile: caCertOutputFile, }); - await testPqc(pemPath, tlsPort); + if (!pemBuffer) { + throw new Error('Expected showcase server to return CA certificate buffer'); + } + + await testPqc(pemBuffer, tlsPort); } finally { showcaseServerTls.stop(); if (fs.existsSync(pemPath)) { From bfc17efa2d7e7b9c963ae8d09aa82b8b85c88956 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 15:53:47 -0400 Subject: [PATCH 28/37] Add assert statement for the socket port --- core/packages/gax/test/test-application/src/pqc-test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 591b590b32d2..322ed31dc5d2 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -41,6 +41,7 @@ async function testPqc(pemBuffer: Buffer, port: number) { (tls as any).connect = function (...args: any[]) { const socket = originalTlsConnect.apply(this, args as any); socket.on('secureConnect', () => { + assert.strictEqual(socket.remotePort, port); if (currentTestType === 'grpc') { grpcSocket = socket as tls.TLSSocket; } else if (currentTestType === 'rest') { From 5b9e265d368fdd5d0e5dc7039587f0a967013f77 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 15:55:41 -0400 Subject: [PATCH 29/37] Assertion statement added --- core/packages/gax/test/test-application/src/pqc-test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 322ed31dc5d2..d9c115479d76 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -41,6 +41,8 @@ async function testPqc(pemBuffer: Buffer, port: number) { (tls as any).connect = function (...args: any[]) { const socket = originalTlsConnect.apply(this, args as any); socket.on('secureConnect', () => { + // Verify the socket is connecting to the expected test server port + // to ensure this mock doesn't accidentally affect other tests. assert.strictEqual(socket.remotePort, port); if (currentTestType === 'grpc') { grpcSocket = socket as tls.TLSSocket; From 2c920c8db53bce223db069d50809ebdf716bd255 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 16:00:49 -0400 Subject: [PATCH 30/37] Remove the addition of jest --- core/packages/gax/package.json | 2 +- .../gax/test/test-application/jest.config.js | 21 --------------- .../gax/test/test-application/package.json | 6 +---- .../test/test-application/test/pqc.test.ts | 27 ------------------- .../gax/test/test-application/tsconfig.json | 4 --- 5 files changed, 2 insertions(+), 58 deletions(-) delete mode 100644 core/packages/gax/test/test-application/jest.config.js delete mode 100644 core/packages/gax/test/test-application/test/pqc.test.ts diff --git a/core/packages/gax/package.json b/core/packages/gax/package.json index 81694f625787..42fafa24cc49 100644 --- a/core/packages/gax/package.json +++ b/core/packages/gax/package.json @@ -78,7 +78,7 @@ "system-test": "c8 mocha build/test/system-test --timeout 600000 && npm run test-application", "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", "browser-test": "cd test/browser-test && npm run prefetch && npm install && npm test", - "test-application": "cd test/test-application && npm run prefetch && npm install && npm start && npm test", + "test-application": "cd test/test-application && npm run prefetch && npm install && npm start", "prelint": "cd samples; npm link ../; npm install", "precompile": "gts clean", "update-protos": "cd ../tools && rm -rf node_modules build && npm i && npm run compile && cd ../gax && node ../tools/build/src/listProtos.js .", diff --git a/core/packages/gax/test/test-application/jest.config.js b/core/packages/gax/test/test-application/jest.config.js deleted file mode 100644 index 9ea5cefd79b2..000000000000 --- a/core/packages/gax/test/test-application/jest.config.js +++ /dev/null @@ -1,21 +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. - */ - -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testMatch: ['**/*.test.ts'], -}; diff --git a/core/packages/gax/test/test-application/package.json b/core/packages/gax/test/test-application/package.json index 96c2f63c8b73..da3cc3913348 100644 --- a/core/packages/gax/test/test-application/package.json +++ b/core/packages/gax/test/test-application/package.json @@ -17,14 +17,10 @@ "prefetch-showcase-server": "cd ../showcase-server && npm install && npm pack && mv showcase-server*.tgz ../test-application/showcase-server.tgz", "prefetch": "npm run prefetch-cleanup && npm run prefetch-google-gax && npm run prefetch-showcase-echo-client && npm run prefetch-showcase-server", "prepare": "npm run compile", - "start": "node build/src/index.js", - "test": "NODE_OPTIONS=--experimental-vm-modules jest" + "start": "node build/src/index.js" }, "devDependencies": { - "@types/jest": "^30.0.0", - "jest": "^30.4.2", "mocha": "^10.0.1", - "ts-jest": "^29.4.11", "typescript": "^5.1.6" }, "dependencies": { diff --git a/core/packages/gax/test/test-application/test/pqc.test.ts b/core/packages/gax/test/test-application/test/pqc.test.ts deleted file mode 100644 index 08ecc7ace0f4..000000000000 --- a/core/packages/gax/test/test-application/test/pqc.test.ts +++ /dev/null @@ -1,27 +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 {runPqcComplianceTests} from '../src/pqc-test'; - -describe('PQC Compliance Tests', () => { - it( - 'should run PQC compliance tests successfully', - async () => { - await runPqcComplianceTests(); - }, - 60000 - ); -}); diff --git a/core/packages/gax/test/test-application/tsconfig.json b/core/packages/gax/test/test-application/tsconfig.json index 74d5563241ea..ac741f352757 100644 --- a/core/packages/gax/test/test-application/tsconfig.json +++ b/core/packages/gax/test/test-application/tsconfig.json @@ -7,10 +7,6 @@ "lib": [ "es2018", "dom" - ], - "types": [ - "jest", - "node" ] }, "include": [ From bcd6632d34de919010a28873d4901a3adc06e6d1 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 16:24:10 -0400 Subject: [PATCH 31/37] Implement environment variable and temp dir suggestions --- .../gax/test/test-application/src/pqc-test.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index d9c115479d76..807829db8eda 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -22,6 +22,7 @@ import {grpc, GoogleAuth, googleAuthLibrary} from 'google-gax'; import {EchoClient} from 'showcase-echo-client'; import {ShowcaseServer} from 'showcase-server'; +import * as os from 'os'; import * as tls from 'tls'; /** @@ -173,11 +174,13 @@ export async function runPqcComplianceTests() { return; } + const originalShowcaseVersion = process.env['SHOWCASE_VERSION']; process.env['SHOWCASE_VERSION'] = '0.41.1'; const showcaseServerTls = new ShowcaseServer(); const tlsPort = 7443; - const caCertOutputFile = 'showcase.pem'; - const pemPath = path.join(process.cwd(), caCertOutputFile); + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pqc-test-')); + const caCertOutputFile = path.join(tempDir, 'showcase.pem'); + const pemPath = caCertOutputFile; try { if (fs.existsSync(pemPath)) { @@ -197,8 +200,13 @@ export async function runPqcComplianceTests() { await testPqc(pemBuffer, tlsPort); } finally { showcaseServerTls.stop(); - if (fs.existsSync(pemPath)) { - fs.unlinkSync(pemPath); + if (originalShowcaseVersion === undefined) { + delete process.env['SHOWCASE_VERSION']; + } else { + process.env['SHOWCASE_VERSION'] = originalShowcaseVersion; + } + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, {recursive: true, force: true}); } } } From 6a4aa1d23a1e9b3dc3cefc59c25ec94e0ea5b8b3 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 16:45:31 -0400 Subject: [PATCH 32/37] remove the TLS check --- .../gax/test/test-application/src/pqc-test.ts | 34 +------------------ 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 807829db8eda..1424ee9e70b0 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -33,30 +33,7 @@ import * as tls from 'tls'; */ async function testPqc(pemBuffer: Buffer, port: number) { - const originalTlsConnect = tls.connect; - let grpcSocket: tls.TLSSocket | undefined; - let restSocket: tls.TLSSocket | undefined; - - let currentTestType: 'grpc' | 'rest' | 'none' = 'none'; - - (tls as any).connect = function (...args: any[]) { - const socket = originalTlsConnect.apply(this, args as any); - socket.on('secureConnect', () => { - // Verify the socket is connecting to the expected test server port - // to ensure this mock doesn't accidentally affect other tests. - assert.strictEqual(socket.remotePort, port); - if (currentTestType === 'grpc') { - grpcSocket = socket as tls.TLSSocket; - } else if (currentTestType === 'rest') { - restSocket = socket as tls.TLSSocket; - } - }); - return socket; - }; - - try { - // --- 1. gRPC PQC Test --- - currentTestType = 'grpc'; + // --- 1. gRPC PQC Test --- let negotiatedGroupGrpc: string | undefined; let clientSupportedGroupsGrpc: string | undefined; @@ -101,15 +78,12 @@ async function testPqc(pemBuffer: Buffer, port: number) { ); assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); - assert.ok(grpcSocket, 'Expected to intercept gRPC TLS socket'); - assert.strictEqual(grpcSocket.getProtocol(), 'TLSv1.3'); assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); assert.ok(clientSupportedGroupsGrpc, 'Expected client supported groups in gRPC response metadata'); assert.ok(clientSupportedGroupsGrpc.includes('X25519MLKEM768'), 'Expected client to include X25519MLKEM768 in supported groups'); // --- 2. HTTP/REST Fallback PQC Test --- - currentTestType = 'rest'; let negotiatedGroupRest: string | undefined; let clientSupportedGroupsRest: string | undefined; @@ -149,16 +123,10 @@ async function testPqc(pemBuffer: Buffer, port: number) { const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); assert.strictEqual(responseRest.content, 'rest-pqc-test'); - assert.ok(restSocket, 'Expected to intercept REST TLS socket'); - assert.strictEqual(restSocket.getProtocol(), 'TLSv1.3'); assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); assert.ok(clientSupportedGroupsRest, 'Expected client supported groups in REST response headers'); assert.ok(clientSupportedGroupsRest.includes('X25519MLKEM768'), 'Expected client to include X25519MLKEM768 in supported groups'); - } finally { - // Restore the original tls.connect - (tls as any).connect = originalTlsConnect; - } } /** From fc1fa41739b5dde102eac187ff5fb03de444015d Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Wed, 22 Jul 2026 17:11:15 -0400 Subject: [PATCH 33/37] Add explicit return undefined --- core/packages/gax/test/showcase-server/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index e77e6c3426d9..ef373519a148 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -112,6 +112,7 @@ export class ShowcaseServer { return fs.readFileSync(resolvedCaCertPath); } + return undefined; } stop() { From e27adefc1608e64c935b5f75e0e1154a1339b113 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 23 Jul 2026 09:53:12 -0400 Subject: [PATCH 34/37] Add a watcher instead of a loop --- .../gax/test/showcase-server/src/index.ts | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index ef373519a148..aaf677af1e29 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -95,17 +95,28 @@ export class ShowcaseServer { this.server = childProcess; if (resolvedCaCertPath) { - let pemExists = false; - for (let i = 0; i < 15; i++) { - // We loop up to 15 times (waiting 1 second each time) because gapic-showcase - // generates the CA certificate asynchronously on startup. This ensures the file - // is completely written to disk before we attempt to use it for our PQC tests. + const dir = path.dirname(resolvedCaCertPath); + const filename = path.basename(resolvedCaCertPath); + + const pemExists = await new Promise((resolve) => { if (fs.existsSync(resolvedCaCertPath)) { - pemExists = true; - break; + return resolve(true); } - await sleep(1000); - } + + const timeoutId = setTimeout(() => { + watcher.close(); + resolve(false); + }, 15000); + + const watcher = fs.watch(dir, (eventType, triggeredFilename) => { + if (triggeredFilename === filename || fs.existsSync(resolvedCaCertPath)) { + clearTimeout(timeoutId); + watcher.close(); + resolve(true); + } + }); + }); + if (!pemExists) { throw new Error(`CA Certificate file not found at ${resolvedCaCertPath}`); } From 03cb81b8795d707159e22d4984212550d1568130 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 23 Jul 2026 10:22:28 -0400 Subject: [PATCH 35/37] separate grpc and rest functions --- .../gax/test/test-application/src/pqc-test.ts | 220 ++++++++++-------- 1 file changed, 125 insertions(+), 95 deletions(-) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index 1424ee9e70b0..e495c47360af 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -25,6 +25,129 @@ import {ShowcaseServer} from 'showcase-server'; import * as os from 'os'; import * as tls from 'tls'; +async function grpcPqcTest(pemBuffer: Buffer, port: number) { + let negotiatedGroupGrpc: string | undefined; + let clientSupportedGroupsGrpc: string | undefined; + + const interceptor = (options: any, nextCall: any) => { + return new grpc.InterceptingCall(nextCall(options), { + start: (metadata: any, listener: any, next: any) => { + next(metadata, { + onReceiveMetadata: (receivedMetadata: any, nextListener: any) => { + const group = receivedMetadata.get('x-showcase-tls-group'); + if (group && group.length > 0) { + negotiatedGroupGrpc = group[0].toString(); + } + const supportedGroups = receivedMetadata.get( + 'x-showcase-tls-client-supported-groups' + ); + if (supportedGroups && supportedGroups.length > 0) { + clientSupportedGroupsGrpc = supportedGroups[0].toString(); + } + nextListener(receivedMetadata); + }, + }); + }, + }); + }; + + const grpcClientOpts = { + grpc, + sslCreds: grpc.credentials.createSsl(pemBuffer), + servicePath: 'localhost', + port: port, + }; + + const grpcClient = new EchoClient(grpcClientOpts); + + const [responseGrpc] = await grpcClient.echo( + { content: 'grpc-pqc-test' }, + { + otherArgs: { + options: { + interceptors: [interceptor], + }, + }, + } + ); + + assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); + assert.ok( + negotiatedGroupGrpc, + 'Expected negotiated TLS group in gRPC response metadata' + ); + assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); + assert.ok( + clientSupportedGroupsGrpc, + 'Expected client supported groups in gRPC response metadata' + ); + assert.ok( + clientSupportedGroupsGrpc.includes('X25519MLKEM768'), + 'Expected client to include X25519MLKEM768 in supported groups' + ); +} + +async function httpRestFallbackPqcTest(pemBuffer: Buffer, port: number) { + let negotiatedGroupRest: string | undefined; + let clientSupportedGroupsRest: string | undefined; + + const auth = new GoogleAuth({ + authClient: new googleAuthLibrary.PassThroughClient(), + }); + + const originalFetch = auth.fetch.bind(auth); + (auth as any).fetch = async (url: string, opts: any) => { + if (url.startsWith('https:')) { + opts.agent = new https.Agent({ + ca: pemBuffer, + keepAlive: true, + }); + } + const res = await originalFetch(url, opts); + const group = + typeof res.headers.get === 'function' + ? res.headers.get('x-showcase-tls-group') + : (res.headers as any)['x-showcase-tls-group']; + if (group) { + negotiatedGroupRest = group; + } + const supportedGroups = + typeof res.headers.get === 'function' + ? res.headers.get('x-showcase-tls-client-supported-groups') + : (res.headers as any)['x-showcase-tls-client-supported-groups']; + if (supportedGroups) { + clientSupportedGroupsRest = supportedGroups; + } + return res; + }; + + const restClientOpts = { + fallback: true, + protocol: 'https', + servicePath: 'localhost', + port: port, + auth: auth, + }; + + const restClient = new EchoClient(restClientOpts); + const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); + + assert.strictEqual(responseRest.content, 'rest-pqc-test'); + assert.ok( + negotiatedGroupRest, + 'Expected negotiated TLS group in REST response headers' + ); + assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); + assert.ok( + clientSupportedGroupsRest, + 'Expected client supported groups in REST response headers' + ); + assert.ok( + clientSupportedGroupsRest.includes('X25519MLKEM768'), + 'Expected client to include X25519MLKEM768 in supported groups' + ); +} + /** * Tests Post Quantum Cryptography (PQC) using the specified CA cert and port. * It verifies both gRPC and HTTP/REST clients by inspecting the negotiated TLS group. @@ -32,101 +155,8 @@ import * as tls from 'tls'; * @param port The port the TLS showcase server is listening on. */ async function testPqc(pemBuffer: Buffer, port: number) { - - // --- 1. gRPC PQC Test --- - let negotiatedGroupGrpc: string | undefined; - let clientSupportedGroupsGrpc: string | undefined; - - const interceptor = (options: any, nextCall: any) => { - return new grpc.InterceptingCall(nextCall(options), { - start: (metadata: any, listener: any, next: any) => { - next(metadata, { - onReceiveMetadata: (receivedMetadata: any, nextListener: any) => { - const group = receivedMetadata.get('x-showcase-tls-group'); - if (group && group.length > 0) { - negotiatedGroupGrpc = group[0].toString(); - } - const supportedGroups = receivedMetadata.get('x-showcase-tls-client-supported-groups'); - if (supportedGroups && supportedGroups.length > 0) { - clientSupportedGroupsGrpc = supportedGroups[0].toString(); - } - nextListener(receivedMetadata); - }, - }); - }, - }); - }; - - const grpcClientOpts = { - grpc, - sslCreds: grpc.credentials.createSsl(pemBuffer), - servicePath: 'localhost', - port: port, - }; - - const grpcClient = new EchoClient(grpcClientOpts); - - const [responseGrpc] = await grpcClient.echo( - { content: 'grpc-pqc-test' }, - { - otherArgs: { - options: { - interceptors: [interceptor], - }, - }, - } - ); - - assert.strictEqual(responseGrpc.content, 'grpc-pqc-test'); - assert.ok(negotiatedGroupGrpc, 'Expected negotiated TLS group in gRPC response metadata'); - assert.strictEqual(negotiatedGroupGrpc, 'X25519MLKEM768'); - assert.ok(clientSupportedGroupsGrpc, 'Expected client supported groups in gRPC response metadata'); - assert.ok(clientSupportedGroupsGrpc.includes('X25519MLKEM768'), 'Expected client to include X25519MLKEM768 in supported groups'); - - // --- 2. HTTP/REST Fallback PQC Test --- - let negotiatedGroupRest: string | undefined; - let clientSupportedGroupsRest: string | undefined; - - const auth = new GoogleAuth({ - authClient: new googleAuthLibrary.PassThroughClient(), - }); - - const originalFetch = auth.fetch.bind(auth); - (auth as any).fetch = async (url: string, opts: any) => { - if (url.startsWith('https:')) { - opts.agent = new https.Agent({ - ca: pemBuffer, - keepAlive: true, - }); - } - const res = await originalFetch(url, opts); - const group = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-group') : (res.headers as any)['x-showcase-tls-group']; - if (group) { - negotiatedGroupRest = group; - } - const supportedGroups = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-client-supported-groups') : (res.headers as any)['x-showcase-tls-client-supported-groups']; - if (supportedGroups) { - clientSupportedGroupsRest = supportedGroups; - } - return res; - }; - - const restClientOpts = { - fallback: true, - protocol: 'https', - servicePath: 'localhost', - port: port, - auth: auth, - }; - - const restClient = new EchoClient(restClientOpts); - const [responseRest] = await restClient.echo({ content: 'rest-pqc-test' }); - - assert.strictEqual(responseRest.content, 'rest-pqc-test'); - assert.ok(negotiatedGroupRest, 'Expected negotiated TLS group in REST response headers'); - assert.strictEqual(negotiatedGroupRest, 'X25519MLKEM768'); - assert.ok(clientSupportedGroupsRest, 'Expected client supported groups in REST response headers'); - assert.ok(clientSupportedGroupsRest.includes('X25519MLKEM768'), 'Expected client to include X25519MLKEM768 in supported groups'); + await grpcPqcTest(pemBuffer, port); + await httpRestFallbackPqcTest(pemBuffer, port); } /** From 6e12527cbd6eb7ce812dfbcf6c7fb5a4308c8572 Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 23 Jul 2026 10:31:43 -0400 Subject: [PATCH 36/37] Add one line comment --- core/packages/gax/test/test-application/src/pqc-test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/core/packages/gax/test/test-application/src/pqc-test.ts b/core/packages/gax/test/test-application/src/pqc-test.ts index e495c47360af..e506b0b8767c 100644 --- a/core/packages/gax/test/test-application/src/pqc-test.ts +++ b/core/packages/gax/test/test-application/src/pqc-test.ts @@ -104,6 +104,7 @@ async function httpRestFallbackPqcTest(pemBuffer: Buffer, port: number) { }); } const res = await originalFetch(url, opts); + // res.headers is a Headers instance (with .get()) in standard Fetch API and a plain object in other HTTP clients. const group = typeof res.headers.get === 'function' ? res.headers.get('x-showcase-tls-group') From 0d13a4c6c2705684ed95cdcbb9b0aff7848239ce Mon Sep 17 00:00:00 2001 From: Daniel Bruce Date: Thu, 23 Jul 2026 10:37:28 -0400 Subject: [PATCH 37/37] Add delay for watcher --- core/packages/gax/test/showcase-server/src/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/packages/gax/test/showcase-server/src/index.ts b/core/packages/gax/test/showcase-server/src/index.ts index aaf677af1e29..8f3a5110fff3 100644 --- a/core/packages/gax/test/showcase-server/src/index.ts +++ b/core/packages/gax/test/showcase-server/src/index.ts @@ -112,7 +112,10 @@ export class ShowcaseServer { if (triggeredFilename === filename || fs.existsSync(resolvedCaCertPath)) { clearTimeout(timeoutId); watcher.close(); - resolve(true); + // Wait a few milliseconds to ensure showcase has finished writing the file. + setTimeout(() => { + resolve(true); + }, 500); } }); });