Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/workflows/conformance-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 14
node-version: 18
- run: node --version
- run: cd handwritten/storage && npm install
- run: cd handwritten/storage && npm run conformance-test
1 change: 0 additions & 1 deletion handwritten/storage/.github/sync-repo-settings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ branchProtectionRules:
requiredStatusCheckContexts:
- "ci/kokoro: Samples test"
- "ci/kokoro: System test"
- docs
- lint
- test (18)
- test (20)
Expand Down
12 changes: 0 additions & 12 deletions handwritten/storage/.github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,3 @@ jobs:
node-version: 18
- run: npm install
- run: npm run lint
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm install
- run: npm run docs
- uses: JustinBeckwith/linkinator-action@v1
with:
paths: docs/
150 changes: 104 additions & 46 deletions handwritten/storage/conformance-test/conformanceCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 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,16 +55,31 @@ 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.
const TESTS_PREFIX = `storage.retry.tests.${shortUUID()}.`;
const TESTBENCH_HOST =
process.env.STORAGE_EMULATOR_HOST || 'http://localhost:9000/';
const CONF_TEST_PROJECT_ID = 'my-project-id';
const CONF_TEST_PROJECT_ID = 'dummy-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 +99,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 rawStorageTransport = 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,23 +128,84 @@ export function executeScenario(testCase: RetryTestCase) {
timeout: DURATION_SECONDS,
});

creationResult = await createTestBenchRetryTest(
instructionSet.instructions,
jsonMethod?.name.toString(),
rawStorageTransport,
);
if (!creationResult || !creationResult.id) {
throw new Error('Failed to get a valid test ID from test bench.');
}

/* // eslint-disable-next-line @typescript-eslint/no-explicit-any
const internalGaxios = (storageTransport as any).authClient
?.gaxiosInstance;

if (internalGaxios) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
internalGaxios.interceptors.request.use((config: any) => {
config.headers = config.headers || {};
config.headers['x-retry-test-id'] = creationResult.id;
return config;
});
} */

// Create a Proxy around rawStorageTransport to intercept makeRequest
storageTransport = new Proxy(rawStorageTransport, {
get(target, prop, receiver) {
if (prop === 'makeRequest') {
return async <T>(
reqOpts: StorageRequestOptions,
callback?: StorageTransportCallback<T>,
): Promise<void | T> => {
const config = reqOpts;
config.headers = config.headers || {};

if (creationResult && creationResult.id) {
const retryId = creationResult.id;
if (config.headers instanceof Headers) {
config.headers.set('x-retry-test-id', retryId);
} else if (
typeof config.headers === 'object' &&
config.headers !== null &&
!Array.isArray(config.headers)
) {
config.headers = {
...(config.headers as {
[key: string]: string | string[];
}),
'x-retry-test-id': retryId,
};
} else {
config.headers = {'x-retry-test-id': retryId};
}
}
return Reflect.apply(
rawStorageTransport.makeRequest,
rawStorageTransport,
[config, callback],
);
};
}
return Reflect.get(target, prop, receiver);
},
});

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,
);
if (storageMethodString.includes('InstancePrecondition')) {
bucket = await createBucketForTest(
storage,
testCase.preconditionProvided,
testCase.preconditionProvided &&
!storageMethodString.includes('combine'),
storageMethodString,
);
file = await createFileForTest(
Expand All @@ -159,22 +231,6 @@ export function executeScenario(testCase: RetryTestCase) {
[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 () => {
Expand All @@ -185,24 +241,24 @@ export function executeScenario(testCase: RetryTestCase) {
storageTransport: storageTransport,
notification: notification,
hmacKey: hmacKey,
projectId: CONF_TEST_PROJECT_ID,
};
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);
});
});
Expand Down Expand Up @@ -265,6 +321,7 @@ async function createTestBenchRetryTest(
url: 'retry_test',
body: JSON.stringify(requestBody),
headers: {'Content-Type': 'application/json'},
timeout: 10000,
};

const response = await storageTransport.makeRequest(requestOptions);
Expand All @@ -275,14 +332,15 @@ async function getTestBenchRetryTest(
testId: string,
storageTransport: StorageTransport,
): Promise<ConformanceTestResult> {
const response = await storageTransport.makeRequest({
const requestOptions: StorageRequestOptions = {
url: `retry_test/${testId}`,
method: 'GET',
retry: true,
headers: {
'x-retry-test-id': testId,
},
});
};
const response = await storageTransport.makeRequest(requestOptions);
return response as unknown as ConformanceTestResult;
}

Expand Down
Loading
Loading