Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b2e12e7
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
7760a7d
fix(storage): resolve system test failures after Gaxios & Node 18 mig…
thiyaguk09 May 7, 2026
d07f358
fix(storage): resolve Gaxios transport issues in conformance tests
thiyaguk09 May 12, 2026
edd2687
chore: remove system test
thiyaguk09 May 12, 2026
65f9f33
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
1ae557f
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
faf7509
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 14, 2026
bd33380
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
cc411a0
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
c5643d0
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 14, 2026
128bf0a
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
9010041
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
f1bceb6
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 15, 2026
7b6d68b
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
0e8f067
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
1c9ca6c
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 18, 2026
a2cd00b
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
72c17d7
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
c4b1b66
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 19, 2026
8eb2d72
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
eacb087
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
865e21c
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 21, 2026
683b3f4
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
0c58a9a
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
32bcc64
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 25, 2026
5f55b76
fix(storage): update storage conformance tests to correctly access re…
thiyaguk09 May 26, 2026
b5c81a1
fix(storage): standardize URL formatting and enhance transport retry
thiyaguk09 May 7, 2026
fe44861
refactor(storage): remove Service.ts and migrate logic to StorageTran…
thiyaguk09 May 14, 2026
a461869
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 26, 2026
04cc041
Merge remote-tracking branch 'upstream/storage-node-18' into node18/c…
thiyaguk09 May 27, 2026
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
233 changes: 124 additions & 109 deletions handwritten/storage/conformance-test/conformanceCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,17 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as jsonToNodeApiMapping from './test-data/retryInvocationMap.json';
import * as libraryMethods from './libraryMethods';
import {
Bucket,
File,
GaxiosOptions,
GaxiosOptionsPrepared,
HmacKey,
Notification,
Storage,
} from '../src';
import {Bucket, File, Gaxios, HmacKey, Notification, Storage} from '../src';
import * as uuid from 'uuid';
import * as assert from 'assert';
import {
StorageRequestOptions,
StorageTransport,
StorageTransportCallback,
} from '../src/storage-transport';
import {getDirName} from '../src/util';
import path from 'path';
import {GoogleAuth} from 'google-auth-library';
interface RetryCase {
instructions: String[];
}
Expand Down Expand Up @@ -60,7 +56,7 @@ interface ConformanceTestResult {

type LibraryMethodsModuleType = typeof import('./libraryMethods');
const methodMap: Map<String, String[]> = new Map(
Object.entries({}), // TODO: replace with Object.entries(jsonToNodeApiMapping)
Object.entries(jsonToNodeApiMapping),
);

const DURATION_SECONDS = 600; // 10 mins.
Expand All @@ -70,6 +66,21 @@ const TESTBENCH_HOST =
const CONF_TEST_PROJECT_ID = 'my-project-id';
const TIMEOUT_FOR_INDIVIDUAL_TEST = 20000;
const RETRY_MULTIPLIER_FOR_CONFORMANCE_TESTS = 0.01;
const SERVICE_ACCOUNT = path.join(
getDirName(),
'../../../conformance-test/fixtures/signing-service-account.json',
);

const authClient = new GoogleAuth({
keyFilename: SERVICE_ACCOUNT,
scopes: ['https://www.googleapis.com/auth/devstorage.full_control'],
}).fromJSON(require(SERVICE_ACCOUNT));

authClient.getAccessToken = async () => ({token: 'unauthenticated-test-token'});
authClient.request = async opts => {
const gaxios = new Gaxios();
return gaxios.request(opts);
};

export function executeScenario(testCase: RetryTestCase) {
for (
Expand All @@ -89,16 +100,17 @@ export function executeScenario(testCase: RetryTestCase) {
let bucket: Bucket;
let file: File;
let notification: Notification;
let creationResult: {id: string};
let creationResult: ConformanceTestCreationResult;
let storage: Storage;
let hmacKey: HmacKey;
let storageTransport: StorageTransport;

describe(`${storageMethodString}`, async () => {
beforeEach(async () => {
storageTransport = new StorageTransport({
const rawTransport = new StorageTransport({
apiEndpoint: TESTBENCH_HOST,
authClient: undefined,
authClient: authClient,
keyFilename: SERVICE_ACCOUNT,
baseUrl: TESTBENCH_HOST,
packageJson: {name: 'test-package', version: '1.0.0'},
retryOptions: {
Expand All @@ -117,175 +129,178 @@ export function executeScenario(testCase: RetryTestCase) {
timeout: DURATION_SECONDS,
});

creationResult = await createTestBenchRetryTest(
instructionSet.instructions,
jsonMethod?.name.toString(),
rawTransport,
);

// Create a Proxy around rawStorageTransport to intercept makeRequest
storageTransport = createRetryProxy(
rawTransport,
creationResult.id,
);

storage = new Storage({
apiEndpoint: TESTBENCH_HOST,
projectId: CONF_TEST_PROJECT_ID,
keyFilename: SERVICE_ACCOUNT,
authClient: authClient,
retryOptions: {
retryDelayMultiplier: RETRY_MULTIPLIER_FOR_CONFORMANCE_TESTS,
},
});

creationResult = await createTestBenchRetryTest(
instructionSet.instructions,
jsonMethod?.name.toString(),
storageTransport,
bucket = await createBucketForTest(
storage,
testCase.preconditionProvided &&
!storageMethodString.includes('combine'),
storageMethodString,
);
file = await createFileForTest(
testCase.preconditionProvided,
storageMethodString,
bucket,
);
if (storageMethodString.includes('InstancePrecondition')) {
bucket = await createBucketForTest(
storage,
testCase.preconditionProvided,
storageMethodString,
);
file = await createFileForTest(
testCase.preconditionProvided,
storageMethodString,
bucket,
);
} else {
bucket = await createBucketForTest(
storage,
false,
storageMethodString,
);
file = await createFileForTest(
false,
storageMethodString,
bucket,
);
}
notification = bucket.notification(TESTS_PREFIX);
await notification.create();

[hmacKey] = await storage.createHmacKey(
`${TESTS_PREFIX}@email.com`,
);

storage.interceptors.push({
resolved: (
requestConfig: GaxiosOptionsPrepared,
): Promise<GaxiosOptionsPrepared> => {
const config = requestConfig as GaxiosOptions;
config.headers = config.headers || {};
Object.assign(config.headers, {
'x-retry-test-id': creationResult.id,
});
return Promise.resolve(config as GaxiosOptionsPrepared);
},
rejected: error => {
return Promise.reject(error);
},
});
});

it(`${instructionNumber}`, async () => {
const methodParameters: libraryMethods.ConformanceTestOptions = {
storage: storage,
bucket: bucket,
file: file,
storageTransport: storageTransport,
notification: notification,
hmacKey: hmacKey,
storage,
bucket,
file,
storageTransport,
notification,
hmacKey,
projectId: CONF_TEST_PROJECT_ID,
preconditionRequired: testCase.preconditionProvided,
};
if (testCase.preconditionProvided) {
methodParameters.preconditionRequired = true;
}

if (testCase.expectSuccess) {
assert.ifError(await storageMethodObject(methodParameters));
await storageMethodObject(methodParameters);
const testBenchResult = await getTestBenchRetryTest(
creationResult.id,
storageTransport,
);
assert.strictEqual(testBenchResult.completed, true);
} else {
await assert.rejects(async () => {
await storageMethodObject(methodParameters);
}, undefined);
}

const testBenchResult = await getTestBenchRetryTest(
creationResult.id,
storageTransport,
);
assert.strictEqual(testBenchResult.completed, true);
}).timeout(TIMEOUT_FOR_INDIVIDUAL_TEST);
});
});
});
}
}

/**
* Creates a Proxy to automatically inject x-retry-test-id into all requests
*/
function createRetryProxy(
transport: StorageTransport,
retryId: string,
): StorageTransport {
return new Proxy(transport, {
get(target, prop, receiver) {
const original = Reflect.get(target, prop, receiver);
if (prop === 'makeRequest' && typeof original === 'function') {
return async (
reqOpts: StorageRequestOptions,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
callback?: StorageTransportCallback<any>,
) => {
reqOpts.headers = reqOpts.headers || {};

if (reqOpts.headers instanceof Headers) {
reqOpts.headers.set('x-retry-test-id', retryId);
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(reqOpts.headers as any)['x-retry-test-id'] = retryId;
}

return original.apply(target, [reqOpts, callback]);
};
}
return original;
},
});
}

async function createBucketForTest(
storage: Storage,
preconditionShouldBeOnInstance: boolean,
storageMethodString: String,
withPrecondition: boolean,
method: String,
) {
const name = generateName(storageMethodString, 'bucket');
const bucket = storage.bucket(name);
await bucket.create();
const bucket = storage.bucket(generateName(method, 'bucket'));
const [metadata] = await bucket.create();
await bucket.setRetentionPeriod(DURATION_SECONDS);

if (preconditionShouldBeOnInstance) {
if (withPrecondition) {
return new Bucket(storage, bucket.name, {
preconditionOpts: {
ifMetagenerationMatch: 2,
ifMetagenerationMatch: metadata.metageneration,
},
});
}
return bucket;
}

async function createFileForTest(
preconditionShouldBeOnInstance: boolean,
storageMethodString: String,
withPrecondition: boolean,
method: String,
bucket: Bucket,
) {
const name = generateName(storageMethodString, 'file');
const file = bucket.file(name);
await file.save(name);
if (preconditionShouldBeOnInstance) {
const file = bucket.file(generateName(method, 'file'));
await file.save('test-content');
if (withPrecondition) {
const [metadata] = await file.getMetadata();
return new File(bucket, file.name, {
preconditionOpts: {
ifMetagenerationMatch: file.metadata.metageneration,
ifGenerationMatch: file.metadata.generation,
ifMetagenerationMatch: metadata.metageneration,
ifGenerationMatch: metadata.generation,
},
});
}
return file;
}

function generateName(storageMethodString: String, bucketOrFile: string) {
return `${TESTS_PREFIX}${storageMethodString.toLowerCase()}${bucketOrFile}.${shortUUID()}`;
}

async function createTestBenchRetryTest(
instructions: String[],
methodName: string,
storageTransport: StorageTransport,
transport: StorageTransport,
): Promise<ConformanceTestCreationResult> {
const requestBody = {instructions: {[methodName]: instructions}};

const requestOptions: StorageRequestOptions = {
const response = await transport.makeRequest({
method: 'POST',
url: 'retry_test',
body: JSON.stringify(requestBody),
body: JSON.stringify({instructions: {[methodName]: instructions}}),
headers: {'Content-Type': 'application/json'},
};

const response = await storageTransport.makeRequest(requestOptions);
return response as unknown as ConformanceTestCreationResult;
});
return response.data as ConformanceTestCreationResult;
}

async function getTestBenchRetryTest(
testId: string,
storageTransport: StorageTransport,
transport: StorageTransport,
): Promise<ConformanceTestResult> {
const response = await storageTransport.makeRequest({
const response = await transport.makeRequest({
url: `retry_test/${testId}`,
method: 'GET',
retry: true,
headers: {
'x-retry-test-id': testId,
},
headers: {'x-retry-test-id': testId},
});
return response as unknown as ConformanceTestResult;
return response.data as ConformanceTestResult;
}

function generateName(method: String, type: string) {
return `${TESTS_PREFIX}${method.toLowerCase()}${type}.${shortUUID()}`;
}

function shortUUID() {
return uuid.v1().split('-').shift();
return uuid.v4().split('-').shift();
}
Loading
Loading