Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions core/packages/gax/test/showcase-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,43 @@ 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');
Comment on lines +36 to +38

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There are two issues here:

  1. TypeScript Strict Null Checks: Since this.originalCwd is typed as string | undefined, passing it directly to path.join and path.resolve will cause compilation errors under strictNullChecks. Using a local cwd variable avoids this.
  2. Error Masking & Directory Restoration Failure: If start() fails (e.g., due to download or port issues) before this.server is assigned, this.server remains undefined. When stop() is called in the finally block, it will throw an error because this.server is missing, which masks the original failure and prevents process.chdir from restoring the directory. Initializing this.server with a dummy object ensures stop() can safely restore the directory and won't mask the real error.
Suggested change
async start(opts?: { tls?: boolean; port?: string; caCertOutputFile?: string }) {
this.originalCwd = process.cwd();
const testDir = path.join(this.originalCwd, '.showcase-server-dir');
async start(opts?: { tls?: boolean; port?: string; caCertOutputFile?: string }) {
const cwd = process.cwd();
this.originalCwd = cwd;
this.server = { kill: () => {} } as any;
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';
const tarballFilename = `gapic-showcase-${showcaseVersion}-${platform}-${arch}.tar.gz`;
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);
}
Comment on lines +46 to +49

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use the local cwd variable instead of this.originalCwd to avoid TypeScript compilation errors under strictNullChecks.

Suggested change
let resolvedCaCertPath = '';
if (opts?.caCertOutputFile) {
resolvedCaCertPath = path.resolve(this.originalCwd, opts.caCertOutputFile);
}
let resolvedCaCertPath = '';
if (opts?.caCertOutputFile) {
resolvedCaCertPath = path.resolve(cwd, opts.caCertOutputFile);
}


await fsp.rm(testDir, {recursive: true, force: true});
await mkdir(testDir);
process.chdir(testDir);
console.log(`Server will be run from ${testDir}.`);

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',
});
Expand All @@ -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);
}
}
}

Expand Down
134 changes: 134 additions & 0 deletions core/packages/gax/test/test-application/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:')) {
Comment on lines +2979 to +2980

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential runtime TypeError crashes if opts is not provided (since opts is optional in the fetch signature), initialize opts to an empty object if it is falsy before accessing or setting its properties.

  (auth as any).fetch = async (url: string, opts: any) => {
    opts = opts || {};
    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 {
Expand All @@ -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 as any).start({
tls: true,
port: `:${tlsPort}`,
caCertOutputFile: caCertOutputFile,
});

await testPqc(pemPath, tlsPort);
} finally {
showcaseServerTls.stop();
if (fs.existsSync(pemPath)) {
fs.unlinkSync(pemPath);
}
}
}

main();
Loading