Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit a0ae017

Browse files
shaffeeullahddelgrosso1gcf-owl-bot[bot]
authored
test: added conformance testing scenario 7 (#2035)
* test: add conformance scenario 7 * handle a 200 response code when checking server offset * add 201 status code * fixed bucketUploadResumable test * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * deleted commented lines Co-authored-by: Denis DelGrosso <ddelgrosso@google.com> Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent e4c800d commit a0ae017

8 files changed

Lines changed: 136 additions & 26 deletions

File tree

conformance-test/conformanceCommon.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ interface RetryCase {
2828
interface Method {
2929
name: String;
3030
resources: String[];
31+
group?: String;
3132
}
3233

3334
export interface RetryTestCase {
@@ -68,7 +69,10 @@ export function executeScenario(testCase: RetryTestCase) {
6869
) {
6970
const instructionSet: RetryCase = testCase.cases[instructionNumber];
7071
testCase.methods.forEach(async jsonMethod => {
71-
const functionList = methodMap.get(jsonMethod?.name);
72+
const functionList =
73+
jsonMethod?.group !== undefined
74+
? methodMap.get(jsonMethod?.group)
75+
: methodMap.get(jsonMethod?.name);
7276
functionList?.forEach(storageMethodString => {
7377
const storageMethodObject =
7478
libraryMethods[storageMethodString as keyof LibraryMethodsModuleType];

conformance-test/libraryMethods.ts

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@
1515
import {Bucket, File, Notification, Storage, HmacKey, Policy} from '../src';
1616
import * as path from 'path';
1717
import {ApiError} from '../src/nodejs-common';
18+
import {
19+
createTestBuffer,
20+
createTestFileFromBuffer,
21+
deleteTestFile,
22+
} from './testBenchUtil';
23+
import * as uuid from 'uuid';
24+
25+
const FILE_SIZE_BYTES = 9 * 1024 * 1024;
26+
const CHUNK_SIZE_BYTES = 2 * 1024 * 1024;
1827

1928
export interface ConformanceTestOptions {
2029
bucket?: Bucket;
@@ -375,35 +384,38 @@ export async function bucketSetStorageClass(options: ConformanceTestOptions) {
375384
export async function bucketUploadResumableInstancePrecondition(
376385
options: ConformanceTestOptions
377386
) {
387+
const filePath = path.join(__dirname, `test-data/tmp-${uuid.v4()}.txt`);
388+
createTestFileFromBuffer(FILE_SIZE_BYTES, filePath);
378389
if (options.bucket!.instancePreconditionOpts) {
379390
options.bucket!.instancePreconditionOpts.ifGenerationMatch = 0;
391+
delete options.bucket!.instancePreconditionOpts.ifMetagenerationMatch;
380392
}
381-
await options.bucket!.upload(
382-
path.join(
383-
__dirname,
384-
'../../conformance-test/test-data/retryStrategyTestData.json'
385-
),
386-
{resumable: true}
387-
);
393+
await options.bucket!.upload(filePath, {
394+
resumable: true,
395+
chunkSize: CHUNK_SIZE_BYTES,
396+
metadata: {contentLength: FILE_SIZE_BYTES},
397+
});
398+
deleteTestFile(filePath);
388399
}
389400

390401
export async function bucketUploadResumable(options: ConformanceTestOptions) {
402+
const filePath = path.join(__dirname, `test-data/tmp-${uuid.v4()}.txt`);
403+
createTestFileFromBuffer(FILE_SIZE_BYTES, filePath);
391404
if (options.preconditionRequired) {
392-
await options.bucket!.upload(
393-
path.join(
394-
__dirname,
395-
'../../conformance-test/test-data/retryStrategyTestData.json'
396-
),
397-
{preconditionOpts: {ifMetagenerationMatch: 2, ifGenerationMatch: 0}}
398-
);
405+
await options.bucket!.upload(filePath, {
406+
resumable: true,
407+
chunkSize: CHUNK_SIZE_BYTES,
408+
metadata: {contentLength: FILE_SIZE_BYTES},
409+
preconditionOpts: {ifGenerationMatch: 0},
410+
});
399411
} else {
400-
await options.bucket!.upload(
401-
path.join(
402-
__dirname,
403-
'../../conformance-test/test-data/retryStrategyTestData.json'
404-
)
405-
);
412+
await options.bucket!.upload(filePath, {
413+
resumable: true,
414+
chunkSize: CHUNK_SIZE_BYTES,
415+
metadata: {contentLength: FILE_SIZE_BYTES},
416+
});
406417
}
418+
deleteTestFile(filePath);
407419
}
408420

409421
export async function bucketUploadMultipartInstancePrecondition(
@@ -590,21 +602,31 @@ export async function rotateEncryptionKey(options: ConformanceTestOptions) {
590602
export async function saveResumableInstancePrecondition(
591603
options: ConformanceTestOptions
592604
) {
593-
await options.file!.save('testdata', {resumable: true});
605+
const buf = createTestBuffer(FILE_SIZE_BYTES);
606+
await options.file!.save(buf, {
607+
resumable: true,
608+
chunkSize: CHUNK_SIZE_BYTES,
609+
metadata: {contentLength: FILE_SIZE_BYTES},
610+
});
594611
}
595612

596613
export async function saveResumable(options: ConformanceTestOptions) {
614+
const buf = createTestBuffer(FILE_SIZE_BYTES);
597615
if (options.preconditionRequired) {
598-
await options.file!.save('testdata', {
616+
await options.file!.save(buf, {
599617
resumable: true,
618+
chunkSize: CHUNK_SIZE_BYTES,
619+
metadata: {contentLength: FILE_SIZE_BYTES},
600620
preconditionOpts: {
601621
ifGenerationMatch: options.file!.metadata.generation,
602622
ifMetagenerationMatch: options.file!.metadata.metageneration,
603623
},
604624
});
605625
} else {
606-
await options.file!.save('testdata', {
626+
await options.file!.save(buf, {
607627
resumable: true,
628+
chunkSize: CHUNK_SIZE_BYTES,
629+
metadata: {contentLength: FILE_SIZE_BYTES},
608630
});
609631
}
610632
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*!
2+
* Copyright 2022 Google LLC. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import * as testFile from '../test-data/retryStrategyTestData.json';
17+
import {executeScenario, RetryTestCase} from '../conformanceCommon';
18+
import * as assert from 'assert';
19+
20+
const SCENARIO_NUMBER_TO_TEST = 7;
21+
const retryTestCase: RetryTestCase | undefined =
22+
testFile.retryStrategyTests.find(test => test.id === SCENARIO_NUMBER_TO_TEST);
23+
24+
describe(`Scenario ${SCENARIO_NUMBER_TO_TEST}`, () => {
25+
assert(retryTestCase);
26+
executeScenario(retryTestCase);
27+
});

conformance-test/test-data/retryInvocationMap.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@
7878
"saveMultipart",
7979
"createResumableUpload"
8080
],
81+
"storage.resumable.upload": [
82+
"bucketUploadResumableInstancePrecondition",
83+
"saveResumableInstancePrecondition",
84+
"bucketUploadResumable",
85+
"saveResumable"
86+
],
8187
"storage.objects.delete": [
8288
"fileDeleteInstancePrecondition",
8389
"fileDelete"

conformance-test/test-data/retryStrategyTestData.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,26 @@
402402
],
403403
"preconditionProvided": true,
404404
"expectSuccess": false
405+
},
406+
{
407+
"id": 7,
408+
"description": "resumable_uploads_handle_complex_retries",
409+
"cases": [
410+
{
411+
"instructions": ["return-reset-connection", "return-503"]
412+
},
413+
{
414+
"instructions": ["return-503-after-256K"]
415+
},
416+
{
417+
"instructions": ["return-503-after-8192K"]
418+
}
419+
],
420+
"methods": [
421+
{"name": "storage.objects.insert", "group": "storage.resumable.upload", "resources": ["BUCKET"]}
422+
],
423+
"preconditionProvided": true,
424+
"expectSuccess": true
405425
}
406426
]
407427
}

conformance-test/testBenchUtil.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
import {execSync} from 'child_process';
17+
import {unlinkSync, writeFileSync} from 'fs';
1718
import {URL} from 'url';
1819

1920
const HOST = process.env.STORAGE_EMULATOR_HOST || 'http://localhost:9000';
@@ -38,3 +39,16 @@ export async function runTestBenchDockerImage(): Promise<Buffer> {
3839
export async function stopTestBenchDockerImage(): Promise<Buffer> {
3940
return execSync(STOP_CMD);
4041
}
42+
43+
export function createTestBuffer(sizeInBytes: number): Buffer {
44+
return Buffer.alloc(sizeInBytes, 'testdata');
45+
}
46+
47+
export function createTestFileFromBuffer(sizeInMb: number, path: string): void {
48+
const buf = createTestBuffer(sizeInMb);
49+
writeFileSync(path, buf);
50+
}
51+
52+
export function deleteTestFile(path: string): void {
53+
unlinkSync(path);
54+
}

src/resumable-upload.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,20 @@ export class Upload extends Writable {
524524
}
525525

526526
protected async createURIAsync(): Promise<string> {
527-
const metadata = this.metadata;
527+
const metadata = {...this.metadata};
528+
const headers: gaxios.Headers = {};
529+
530+
// Delete content length and content type from metadata if they exist.
531+
// These are headers and should not be sent as part of the metadata.
532+
if (metadata.contentLength) {
533+
headers['X-Upload-Content-Length'] = metadata.contentLength.toString();
534+
delete metadata.contentLength;
535+
}
536+
537+
if (metadata.contentType) {
538+
headers!['X-Upload-Content-Type'] = metadata.contentType;
539+
delete metadata.contentType;
540+
}
528541

529542
// Check if headers already exist before creating new ones
530543
const reqOpts: GaxiosOptions = {
@@ -540,6 +553,7 @@ export class Upload extends Writable {
540553
data: metadata,
541554
headers: {
542555
'x-goog-api-client': `gl-node/${process.versions.node} gccl/${packageJson.version} gccl-invocation-id/${this.currentInvocationId.uri}`,
556+
...headers,
543557
},
544558
};
545559

test/resumable-upload.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -800,7 +800,10 @@ describe('resumable-upload', () => {
800800
ifGenerationMatch: GENERATION,
801801
ifMetagenerationNotMatch: PARAMS.ifMetagenerationNotMatch,
802802
});
803-
assert.strictEqual(reqOpts.data, up.metadata);
803+
const metadataNoHeaders = {...up.metadata};
804+
delete metadataNoHeaders.contentLength;
805+
delete metadataNoHeaders.contentType;
806+
assert.deepStrictEqual(reqOpts.data, metadataNoHeaders);
804807
done();
805808
return {headers: {location: '/foo'}};
806809
};

0 commit comments

Comments
 (0)