diff --git a/.github/sync-repo-settings.yaml b/.github/sync-repo-settings.yaml index df8c3a4dc..28901a224 100644 --- a/.github/sync-repo-settings.yaml +++ b/.github/sync-repo-settings.yaml @@ -14,9 +14,9 @@ branchProtectionRules: - "ci/kokoro: System test" - docs - lint - - test (12) - test (14) - test (16) + - test (18) - cla/google - windows - OwlBot Post Processor diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bd813c792..711957bad 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [12, 14, 16, 18] + node: [14, 16, 18, 20] steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 diff --git a/.github/workflows/conformance-test.yaml b/.github/workflows/conformance-test.yaml index f33ffbda1..47106314d 100644 --- a/.github/workflows/conformance-test.yaml +++ b/.github/workflows/conformance-test.yaml @@ -11,7 +11,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 12 + node-version: 14 - run: node --version - run: npm install - run: npm run conformance-test diff --git a/.kokoro/continuous/node12/common.cfg b/.kokoro/continuous/node12/common.cfg deleted file mode 100644 index 20314a73e..000000000 --- a/.kokoro/continuous/node12/common.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "nodejs-storage/.kokoro/trampoline_v2.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/node:12-user" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/nodejs-storage/.kokoro/test.sh" -} diff --git a/.kokoro/continuous/node12/lint.cfg b/.kokoro/continuous/node12/lint.cfg deleted file mode 100644 index 72c4ad241..000000000 --- a/.kokoro/continuous/node12/lint.cfg +++ /dev/null @@ -1,4 +0,0 @@ -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/nodejs-storage/.kokoro/lint.sh" -} diff --git a/.kokoro/continuous/node12/samples-test.cfg b/.kokoro/continuous/node12/samples-test.cfg deleted file mode 100644 index 2137cefc0..000000000 --- a/.kokoro/continuous/node12/samples-test.cfg +++ /dev/null @@ -1,12 +0,0 @@ -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/nodejs-storage/.kokoro/samples-test.sh" -} - -env_vars: { - key: "SECRET_MANAGER_KEYS" - value: "long-door-651-kokoro-system-test-service-account" -} \ No newline at end of file diff --git a/.kokoro/continuous/node12/system-test.cfg b/.kokoro/continuous/node12/system-test.cfg deleted file mode 100644 index 9ba9ad1c7..000000000 --- a/.kokoro/continuous/node12/system-test.cfg +++ /dev/null @@ -1,12 +0,0 @@ -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/nodejs-storage/.kokoro/system-test.sh" -} - -env_vars: { - key: "SECRET_MANAGER_KEYS" - value: "long-door-651-kokoro-system-test-service-account" -} \ No newline at end of file diff --git a/.kokoro/continuous/node12/test.cfg b/.kokoro/continuous/node12/test.cfg deleted file mode 100644 index e69de29bb..000000000 diff --git a/.kokoro/presubmit/node12/common.cfg b/.kokoro/presubmit/node12/common.cfg deleted file mode 100644 index 20314a73e..000000000 --- a/.kokoro/presubmit/node12/common.cfg +++ /dev/null @@ -1,24 +0,0 @@ -# Format: //devtools/kokoro/config/proto/build.proto - -# Build logs will be here -action { - define_artifacts { - regex: "**/*sponge_log.xml" - } -} - -# Download trampoline resources. -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" - -# Use the trampoline script to run in docker. -build_file: "nodejs-storage/.kokoro/trampoline_v2.sh" - -# Configure the docker image for kokoro-trampoline. -env_vars: { - key: "TRAMPOLINE_IMAGE" - value: "gcr.io/cloud-devrel-kokoro-resources/node:12-user" -} -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/nodejs-storage/.kokoro/test.sh" -} diff --git a/.kokoro/presubmit/node12/conformance-test.cfg b/.kokoro/presubmit/node12/conformance-test.cfg deleted file mode 100644 index fedfd5fa9..000000000 --- a/.kokoro/presubmit/node12/conformance-test.cfg +++ /dev/null @@ -1,4 +0,0 @@ -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/nodejs-storage/.kokoro/conformance-test.sh" -} diff --git a/.kokoro/presubmit/node12/samples-test.cfg b/.kokoro/presubmit/node12/samples-test.cfg deleted file mode 100644 index 2137cefc0..000000000 --- a/.kokoro/presubmit/node12/samples-test.cfg +++ /dev/null @@ -1,12 +0,0 @@ -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/nodejs-storage/.kokoro/samples-test.sh" -} - -env_vars: { - key: "SECRET_MANAGER_KEYS" - value: "long-door-651-kokoro-system-test-service-account" -} \ No newline at end of file diff --git a/.kokoro/presubmit/node12/system-test.cfg b/.kokoro/presubmit/node12/system-test.cfg deleted file mode 100644 index 9ba9ad1c7..000000000 --- a/.kokoro/presubmit/node12/system-test.cfg +++ /dev/null @@ -1,12 +0,0 @@ -# Download resources for system tests (service account key, etc.) -gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" - -env_vars: { - key: "TRAMPOLINE_BUILD_FILE" - value: "github/nodejs-storage/.kokoro/system-test.sh" -} - -env_vars: { - key: "SECRET_MANAGER_KEYS" - value: "long-door-651-kokoro-system-test-service-account" -} \ No newline at end of file diff --git a/.kokoro/presubmit/node12/test.cfg b/.kokoro/presubmit/node12/test.cfg deleted file mode 100644 index e69de29bb..000000000 diff --git a/conformance-test/libraryMethods.ts b/conformance-test/libraryMethods.ts index eb09efb39..73a325118 100644 --- a/conformance-test/libraryMethods.ts +++ b/conformance-test/libraryMethods.ts @@ -42,7 +42,9 @@ export async function addLifecycleRuleInstancePrecondition( options: ConformanceTestOptions ) { await options.bucket!.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { age: 365 * 3, // Specified in days. }, @@ -53,7 +55,9 @@ export async function addLifecycleRule(options: ConformanceTestOptions) { if (options.preconditionRequired) { await options.bucket!.addLifecycleRule( { - action: 'delete', + action: { + type: 'Delete', + }, condition: { age: 365 * 3, // Specified in days. }, @@ -64,7 +68,9 @@ export async function addLifecycleRule(options: ConformanceTestOptions) { ); } else { await options.bucket!.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { age: 365 * 3, // Specified in days. }, @@ -104,7 +110,7 @@ export async function combine(options: ConformanceTestOptions) { await allFiles.save('allfiles contents'); if (options.preconditionRequired) { await options.bucket!.combine(sources, allFiles, { - ifGenerationMatch: allFiles.metadata.generation, + ifGenerationMatch: allFiles.metadata.generation!, }); } else { await options.bucket!.combine(sources, allFiles); @@ -468,7 +474,9 @@ export async function copy(options: ConformanceTestOptions) { if (options.preconditionRequired) { await options.file!.copy('a-different-file.png', { - preconditionOpts: {ifGenerationMatch: newFile.metadata.generation}, + preconditionOpts: { + ifGenerationMatch: newFile.metadata.generation!, + }, }); } else { await options.file!.copy('a-different-file.png'); diff --git a/package.json b/package.json index 7a297d666..d03637bc1 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "author": "Google Inc.", "engines": { - "node": ">=12" + "node": ">=14" }, "repository": "googleapis/nodejs-storage", "main": "./build/src/index.js", @@ -58,15 +58,14 @@ "compressible": "^2.0.12", "duplexify": "^4.0.0", "ent": "^2.2.0", - "extend": "^3.0.2", "fast-xml-parser": "^4.2.2", - "gaxios": "^5.0.0", - "google-auth-library": "^8.0.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.0.0", "mime": "^3.0.0", "mime-types": "^2.0.8", "p-limit": "^3.0.1", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0", + "retry-request": "^6.0.0", + "teeny-request": "^9.0.0", "uuid": "^8.0.0" }, "devDependencies": { @@ -76,22 +75,21 @@ "@types/async-retry": "^1.4.3", "@types/compressible": "^2.0.0", "@types/ent": "^2.2.1", - "@types/extend": "^3.0.0", "@types/mime": "^3.0.0", "@types/mime-types": "^2.1.0", "@types/mocha": "^9.1.1", "@types/mockery": "^1.4.29", - "@types/node": "^18.0.0", + "@types/node": "^20.4.4", "@types/node-fetch": "^2.1.3", "@types/proxyquire": "^1.3.28", "@types/request": "^2.48.4", - "@types/sinon": "^10.0.0", + "@types/sinon": "^10.0.15", "@types/tmp": "0.2.3", "@types/uuid": "^8.0.0", "@types/yargs": "^17.0.10", "c8": "^8.0.0", "form-data": "^4.0.0", - "gts": "^3.1.0", + "gts": "^3.1.1", "jsdoc": "^4.0.0", "jsdoc-fresh": "^2.0.0", "jsdoc-region-tag": "^2.0.0", @@ -101,9 +99,9 @@ "nock": "~13.3.0", "node-fetch": "^2.6.7", "proxyquire": "^2.1.3", - "sinon": "^15.0.0", + "sinon": "^15.2.0", "tmp": "^0.2.0", - "typescript": "^4.6.4", + "typescript": "^5.1.6", "yargs": "^17.3.1" } } diff --git a/samples/addBucketLabel.js b/samples/addBucketLabel.js index 68ecd9204..bb814a831 100644 --- a/samples/addBucketLabel.js +++ b/samples/addBucketLabel.js @@ -48,7 +48,7 @@ function main( }; async function addBucketLabel() { - await storage.bucket(bucketName).setLabels(labels); + await storage.bucket(bucketName).setMetadata({labels}); console.log(`Added label to bucket ${bucketName}`); } diff --git a/samples/enableBucketLifecycleManagement.js b/samples/enableBucketLifecycleManagement.js index b125f157c..ce1130986 100644 --- a/samples/enableBucketLifecycleManagement.js +++ b/samples/enableBucketLifecycleManagement.js @@ -35,7 +35,9 @@ function main(bucketName = 'my-bucket') { async function enableBucketLifecycleManagement() { const [metadata] = await storage.bucket(bucketName).addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: {age: 100}, }); diff --git a/samples/removeBucketLabel.js b/samples/removeBucketLabel.js index 17d70eb07..8df82f012 100644 --- a/samples/removeBucketLabel.js +++ b/samples/removeBucketLabel.js @@ -19,7 +19,7 @@ // description: Removes bucket label. // usage: node removeBucketLabel.js labelone) -function main(bucketName = 'my-bucket', labelKey = ['label1', 'label2']) { +function main(bucketName = 'my-bucket', labelKey = 'labelone') { // [START storage_remove_bucket_label] /** * TODO(developer): Uncomment the following lines before running the sample. @@ -37,7 +37,10 @@ function main(bucketName = 'my-bucket', labelKey = ['label1', 'label2']) { const storage = new Storage(); async function removeBucketLabel() { - await storage.bucket(bucketName).deleteLabels(labelKey); + const labels = {}; + // To remove a label set the value of the key to null. + labels[labelKey] = null; + await storage.bucket(bucketName).setMetadata({labels}); console.log(`Removed labels from bucket ${bucketName}`); } diff --git a/samples/system-test/bucketLifecycle.test.js b/samples/system-test/bucketLifecycle.test.js index f97c9898a..8b1adf9b5 100644 --- a/samples/system-test/bucketLifecycle.test.js +++ b/samples/system-test/bucketLifecycle.test.js @@ -57,7 +57,7 @@ describe('Bucket lifecycle management', () => { it('should disable all lifecycle rules', async () => { // Add a lifecycle rule in order for the sample to delete. await bucket.addLifecycleRule({ - action: 'delete', + action: {type: 'Delete'}, condition: {age: 100}, }); diff --git a/src/acl.ts b/src/acl.ts index ad67bbaa9..0e593f756 100644 --- a/src/acl.ts +++ b/src/acl.ts @@ -15,7 +15,7 @@ import { BodyResponseCallback, DecorateRequestOptions, - Metadata, + BaseMetadata, } from './nodejs-common'; import {promisifyAll} from '@google-cloud/promisify'; @@ -29,13 +29,13 @@ export interface AclOptions { export type GetAclResponse = [ AccessControlObject | AccessControlObject[], - Metadata + AclMetadata ]; export interface GetAclCallback { ( err: Error | null, acl?: AccessControlObject | AccessControlObject[] | null, - apiResponse?: Metadata + apiResponse?: AclMetadata ): void; } export interface GetAclOptions { @@ -50,12 +50,12 @@ export interface UpdateAclOptions { generation?: number; userProject?: string; } -export type UpdateAclResponse = [AccessControlObject, Metadata]; +export type UpdateAclResponse = [AccessControlObject, AclMetadata]; export interface UpdateAclCallback { ( err: Error | null, acl?: AccessControlObject | null, - apiResponse?: Metadata + apiResponse?: AclMetadata ): void; } @@ -65,17 +65,17 @@ export interface AddAclOptions { generation?: number; userProject?: string; } -export type AddAclResponse = [AccessControlObject, Metadata]; +export type AddAclResponse = [AccessControlObject, AclMetadata]; export interface AddAclCallback { ( err: Error | null, acl?: AccessControlObject | null, - apiResponse?: Metadata + apiResponse?: AclMetadata ): void; } -export type RemoveAclResponse = [Metadata]; +export type RemoveAclResponse = [AclMetadata]; export interface RemoveAclCallback { - (err: Error | null, apiResponse?: Metadata): void; + (err: Error | null, apiResponse?: AclMetadata): void; } export interface RemoveAclOptions { entity: string; @@ -94,6 +94,21 @@ export interface AccessControlObject { projectTeam: string; } +export interface AclMetadata extends BaseMetadata { + bucket?: string; + domain?: string; + entity?: string; + entityId?: string; + generation?: string; + object?: string; + projectTeam?: { + projectNumber?: string; + team?: 'editors' | 'owners' | 'viewers'; + }; + role?: 'OWNER' | 'READER' | 'WRITER' | 'FULL_CONTROL'; + [key: string]: unknown; +} + /** * Attach functionality to a {@link Storage.acl} instance. This will add an * object for each role group (owners, readers, and writers), with each object diff --git a/src/bucket.ts b/src/bucket.ts index 25227f9ea..14b081842 100644 --- a/src/bucket.ts +++ b/src/bucket.ts @@ -19,42 +19,40 @@ import { DeleteCallback, ExistsCallback, GetConfig, - Metadata, MetadataCallback, - ResponseBody, ServiceObject, SetMetadataResponse, util, } from './nodejs-common'; +import {RequestResponse} from './nodejs-common/service-object'; import {paginator} from '@google-cloud/paginator'; import {promisifyAll} from '@google-cloud/promisify'; -import * as extend from 'extend'; import * as fs from 'fs'; import * as http from 'http'; import * as mime from 'mime-types'; import * as path from 'path'; -import pLimit = require('p-limit'); +import * as pLimit from 'p-limit'; import {promisify} from 'util'; import retry = require('async-retry'); import {convertObjKeysToSnakeCase} from './util'; -import {Acl} from './acl'; +import {Acl, AclMetadata} from './acl'; import {Channel} from './channel'; import { File, FileOptions, CreateResumableUploadOptions, CreateWriteStreamOptions, + FileMetadata, } from './file'; import {Iam} from './iam'; -import {Notification} from './notification'; +import {Notification, NotificationMetadata} from './notification'; import { Storage, Cors, PreconditionOptions, IdempotencyStrategy, BucketOptions, - ExceptionMessages, } from './storage'; import { GetSignedUrlResponse, @@ -66,7 +64,7 @@ import { import {Readable} from 'stream'; import {CRC32CValidatorGenerator} from './crc32c'; import {URL} from 'url'; -import {SetMetadataOptions} from './nodejs-common/service-object'; +import {BaseMetadata, SetMetadataOptions} from './nodejs-common/service-object'; interface SourceObject { name: string; @@ -80,19 +78,19 @@ interface CreateNotificationQuery { interface MetadataOptions { predefinedAcl: string; userProject?: string; - ifGenerationMatch?: number; - ifGenerationNotMatch?: number; - ifMetagenerationMatch?: number; - ifMetagenerationNotMatch?: number; + ifGenerationMatch?: number | string; + ifGenerationNotMatch?: number | string; + ifMetagenerationMatch?: number | string; + ifMetagenerationNotMatch?: number | string; } -export type GetFilesResponse = [File[], {}, Metadata]; +export type GetFilesResponse = [File[], {}, unknown]; export interface GetFilesCallback { ( err: Error | null, files?: File[], nextQuery?: {}, - apiResponse?: Metadata + apiResponse?: unknown ): void; } @@ -110,11 +108,47 @@ export interface AddLifecycleRuleOptions extends PreconditionOptions { append?: boolean; } -export interface LifecycleRule { - action: {type: string; storageClass?: string} | string; - condition: {[key: string]: boolean | Date | number | string | string[]}; +export interface LifecycleAction { + type: 'Delete' | 'SetStorageClass' | 'AbortIncompleteMultipartUpload'; storageClass?: string; } +export interface LifecycleCondition { + age?: number; + createdBefore?: Date | string; + customTimeBefore?: Date | string; + daysSinceCustomTime?: number; + daysSinceNoncurrentTime?: number; + isLive?: boolean; + matchesPrefix?: string[]; + matchesSuffix?: string[]; + matchesStorageClass?: string[]; + noncurrentTimeBefore?: Date | string; + numNewerVersions?: number; +} + +export interface LifecycleRule { + action: LifecycleAction; + condition: LifecycleCondition; +} + +export interface LifecycleCondition { + age?: number; + createdBefore?: Date | string; + customTimeBefore?: Date | string; + daysSinceCustomTime?: number; + daysSinceNoncurrentTime?: number; + isLive?: boolean; + matchesPrefix?: string[]; + matchesSuffix?: string[]; + matchesStorageClass?: string[]; + noncurrentTimeBefore?: Date | string; + numNewerVersions?: number; +} + +export interface LifecycleRule { + action: LifecycleAction; + condition: LifecycleCondition; +} export interface EnableLoggingOptions extends PreconditionOptions { bucket?: string | Bucket; @@ -142,10 +176,10 @@ export interface CombineOptions extends PreconditionOptions { } export interface CombineCallback { - (err: Error | null, newFile: File | null, apiResponse: Metadata): void; + (err: Error | null, newFile: File | null, apiResponse: unknown): void; } -export type CombineResponse = [File, Metadata]; +export type CombineResponse = [File, unknown]; export interface CreateChannelConfig extends WatchAllOptions { address: string; @@ -155,10 +189,10 @@ export interface CreateChannelOptions { userProject?: string; } -export type CreateChannelResponse = [Channel, Metadata]; +export type CreateChannelResponse = [Channel, unknown]; export interface CreateChannelCallback { - (err: Error | null, channel: Channel | null, apiResponse: Metadata): void; + (err: Error | null, channel: Channel | null, apiResponse: unknown): void; } export interface CreateNotificationOptions { @@ -173,21 +207,21 @@ export interface CreateNotificationCallback { ( err: Error | null, notification: Notification | null, - apiResponse: Metadata + apiResponse: unknown ): void; } -export type CreateNotificationResponse = [Notification, Metadata]; +export type CreateNotificationResponse = [Notification, unknown]; export interface DeleteBucketOptions { ignoreNotFound?: boolean; userProject?: string; } -export type DeleteBucketResponse = [Metadata]; +export type DeleteBucketResponse = [unknown]; export interface DeleteBucketCallback extends DeleteCallback { - (err: Error | null, apiResponse: Metadata): void; + (err: Error | null, apiResponse: unknown): void; } export interface DeleteFilesOptions @@ -200,7 +234,7 @@ export interface DeleteFilesCallback { (err: Error | Error[] | null, apiResponse?: object): void; } -export type DeleteLabelsResponse = [Metadata]; +export type DeleteLabelsResponse = [unknown]; export type DeleteLabelsCallback = SetLabelsCallback; @@ -208,16 +242,16 @@ export type DeleteLabelsOptions = PreconditionOptions; export type DisableRequesterPaysOptions = PreconditionOptions; -export type DisableRequesterPaysResponse = [Metadata]; +export type DisableRequesterPaysResponse = [unknown]; export interface DisableRequesterPaysCallback { (err?: Error | null, apiResponse?: object): void; } -export type EnableRequesterPaysResponse = [Metadata]; +export type EnableRequesterPaysResponse = [unknown]; export interface EnableRequesterPaysCallback { - (err?: Error | null, apiResponse?: Metadata): void; + (err?: Error | null, apiResponse?: unknown): void; } export type EnableRequesterPaysOptions = PreconditionOptions; @@ -233,29 +267,91 @@ export interface GetBucketOptions extends GetConfig { userProject?: string; } -export type GetBucketResponse = [Bucket, Metadata]; +export type GetBucketResponse = [Bucket, unknown]; export interface GetBucketCallback { - (err: ApiError | null, bucket: Bucket | null, apiResponse: Metadata): void; + (err: ApiError | null, bucket: Bucket | null, apiResponse: unknown): void; } export interface GetLabelsOptions { userProject?: string; } -export type GetLabelsResponse = [Metadata]; +export type GetLabelsResponse = [unknown]; export interface GetLabelsCallback { (err: Error | null, labels: object | null): void; } -export type GetBucketMetadataResponse = [Metadata, Metadata]; +export interface BucketMetadata extends BaseMetadata { + acl?: AclMetadata[] | null; + autoclass?: { + enabled?: boolean; + toggleTime?: string; + }; + billing?: { + requesterPays?: boolean; + }; + cors?: Cors[]; + customPlacementConfig?: { + dataLocations?: string[]; + }; + defaultEventBasedHold?: boolean; + defaultObjectAcl?: AclMetadata[]; + encryption?: { + defaultKmsKeyName?: string; + } | null; + iamConfiguration?: { + publicAccessPrevention?: string; + uniformBucketLevelAccess?: { + enabled?: boolean; + lockedTime?: string; + }; + }; + labels?: { + [key: string]: string | null; + }; + lifecycle?: { + rule?: LifecycleRule[]; + } | null; + location?: string; + locationType?: string; + logging?: { + logBucket?: string; + logObjectPrefix?: string; + }; + metageneration?: string; + name?: string; + owner?: { + entity?: string; + entityId?: string; + }; + projectNumber?: string | number; + retentionPolicy?: { + effectiveTime?: string; + isLocked?: boolean; + retentionPeriod?: string | number; + } | null; + rpo?: string; + storageClass?: string; + timeCreated?: string; + updated?: string; + versioning?: { + enabled?: boolean; + }; + website?: { + mainPageSuffix?: string; + notFoundPage?: string; + }; +} + +export type GetBucketMetadataResponse = [BucketMetadata, unknown]; export interface GetBucketMetadataCallback { ( err: ApiError | null, - metadata: Metadata | null, - apiResponse: Metadata + metadata: BucketMetadata | null, + apiResponse: unknown ): void; } @@ -290,16 +386,16 @@ export interface GetNotificationsCallback { ( err: Error | null, notifications: Notification[] | null, - apiResponse: Metadata + apiResponse: unknown ): void; } -export type GetNotificationsResponse = [Notification[], Metadata]; +export type GetNotificationsResponse = [Notification[], unknown]; export interface MakeBucketPrivateOptions { includeFiles?: boolean; force?: boolean; - metadata?: Metadata; + metadata?: BucketMetadata; userProject?: string; preconditionOpts?: PreconditionOptions; } @@ -329,17 +425,17 @@ export interface SetBucketMetadataOptions extends PreconditionOptions { userProject?: string; } -export type SetBucketMetadataResponse = [Metadata]; +export type SetBucketMetadataResponse = [BucketMetadata]; export interface SetBucketMetadataCallback { - (err?: Error | null, metadata?: Metadata): void; + (err?: Error | null, metadata?: BucketMetadata): void; } export interface BucketLockCallback { - (err?: Error | null, apiResponse?: Metadata): void; + (err?: Error | null, apiResponse?: unknown): void; } -export type BucketLockResponse = [Metadata]; +export type BucketLockResponse = [unknown]; export interface Labels { [key: string]: string; @@ -349,10 +445,10 @@ export interface SetLabelsOptions extends PreconditionOptions { userProject?: string; } -export type SetLabelsResponse = [Metadata]; +export type SetLabelsResponse = [unknown]; export interface SetLabelsCallback { - (err?: Error | null, metadata?: Metadata): void; + (err?: Error | null, metadata?: unknown): void; } export interface SetBucketStorageClassOptions extends PreconditionOptions { @@ -363,10 +459,10 @@ export interface SetBucketStorageClassCallback { (err?: Error | null): void; } -export type UploadResponse = [File, Metadata]; +export type UploadResponse = [File, unknown]; export interface UploadCallback { - (err: Error | null, file?: File | null, apiResponse?: Metadata): void; + (err: Error | null, file?: File | null, apiResponse?: unknown): void; } export interface UploadOptions @@ -396,7 +492,6 @@ export enum BucketExceptionMessages { PROVIDE_SOURCE_FILE = 'You must provide at least one source file.', DESTINATION_FILE_NOT_SPECIFIED = 'A destination file must be specified.', CHANNEL_ID_REQUIRED = 'An ID is required to create a channel.', - CHANNEL_ADDRESS_REQUIRED = 'An address is required to create a channel.', TOPIC_NAME_REQUIRED = 'A valid topic name is required.', CONFIGURATION_OBJECT_PREFIX_REQUIRED = 'A configuration object with a prefix is required.', SPECIFY_FILE_NAME = 'A file name must be specified.', @@ -681,8 +776,7 @@ export enum BucketExceptionMessages { * const bucket = storage.bucket('albums'); * ``` */ -class Bucket extends ServiceObject { - metadata: Metadata; +class Bucket extends ServiceObject { name: string; /** @@ -722,10 +816,10 @@ class Bucket extends ServiceObject { const requestQueryObject: { userProject?: string; - ifGenerationMatch?: number; - ifGenerationNotMatch?: number; - ifMetagenerationMatch?: number; - ifMetagenerationNotMatch?: number; + ifGenerationMatch?: number | string; + ifGenerationNotMatch?: number | string; + ifMetagenerationMatch?: number | string; + ifMetagenerationNotMatch?: number | string; } = {}; if (options?.preconditionOpts?.ifGenerationMatch) { @@ -1344,68 +1438,45 @@ class Bucket extends ServiceObject { options = options || {}; const rules = Array.isArray(rule) ? rule : [rule]; - - const newLifecycleRules = rules.map(rule => { - if (typeof rule.action === 'object') { - // This is a raw-formatted rule object, the way the API expects. - // Just pass it through as-is. - return rule; + for (const curRule of rules) { + if (curRule.condition.createdBefore instanceof Date) { + curRule.condition.createdBefore = curRule.condition.createdBefore + .toISOString() + .replace(/T.+$/, ''); } - - const apiFormattedRule = {} as LifecycleRule; - - apiFormattedRule.condition = {}; - apiFormattedRule.action = { - type: rule.action.charAt(0).toUpperCase() + rule.action.slice(1), - }; - - if (rule.storageClass) { - apiFormattedRule.action.storageClass = rule.storageClass; + if (curRule.condition.customTimeBefore instanceof Date) { + curRule.condition.customTimeBefore = curRule.condition.customTimeBefore + .toISOString() + .replace(/T.+$/, ''); } - - for (const condition in rule.condition) { - if (rule.condition[condition] instanceof Date) { - apiFormattedRule.condition[condition] = ( - rule.condition[condition] as Date - ) + if (curRule.condition.noncurrentTimeBefore instanceof Date) { + curRule.condition.noncurrentTimeBefore = + curRule.condition.noncurrentTimeBefore .toISOString() .replace(/T.+$/, ''); - } else { - apiFormattedRule.condition[condition] = rule.condition[condition]; - } } - - return apiFormattedRule; - }); + } if (options.append === false) { - this.setMetadata( - {lifecycle: {rule: newLifecycleRules}}, - options, - callback! - ); + this.setMetadata({lifecycle: {rule: rules}}, options, callback!); return; } // The default behavior appends the previously-defined lifecycle rules with // the new ones just passed in by the user. - this.getMetadata((err: ApiError | null, metadata: Metadata) => { + this.getMetadata((err: ApiError | null, metadata: BucketMetadata) => { if (err) { callback!(err); return; } - const currentLifecycleRules = Array.isArray( - metadata.lifecycle && metadata.lifecycle.rule - ) - ? metadata.lifecycle && metadata.lifecycle.rule + const currentLifecycleRules = Array.isArray(metadata.lifecycle?.rule) + ? metadata.lifecycle?.rule : []; this.setMetadata( { - lifecycle: { - rule: currentLifecycleRules.concat(newLifecycleRules), - }, + lifecycle: {rule: currentLifecycleRules!.concat(rules)}, }, options as AddLifecycleRuleOptions, callback! @@ -1579,7 +1650,9 @@ class Bucket extends ServiceObject { } as SourceObject; if (source.metadata && source.metadata.generation) { - sourceObject.generation = source.metadata.generation; + sourceObject.generation = parseInt( + source.metadata.generation.toString() + ); } return sourceObject; @@ -1716,10 +1789,6 @@ class Bucket extends ServiceObject { throw new Error(BucketExceptionMessages.CHANNEL_ID_REQUIRED); } - if (typeof config.address !== 'string') { - throw new Error(BucketExceptionMessages.CHANNEL_ADDRESS_REQUIRED); - } - let options: CreateChannelOptions = {}; if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; @@ -2083,15 +2152,18 @@ class Bucket extends ServiceObject { callback: DeleteLabelsCallback ): void; /** + * @deprecated * @typedef {array} DeleteLabelsResponse * @property {object} 0 The full API response. */ /** + * @deprecated * @callback DeleteLabelsCallback * @param {?Error} err Request error, if any. * @param {object} metadata Bucket's metadata. */ /** + * @deprecated Use setMetadata directly * Delete one or more labels from this bucket. * * @param {string|string[]} [labels] The labels to delete. If no labels are @@ -2342,9 +2414,12 @@ class Bucket extends ServiceObject { ); } - const logBucket = config.bucket - ? (config.bucket as Bucket).id || config.bucket - : this.id; + let logBucket = this.id; + if (config.bucket && config.bucket instanceof Bucket) { + logBucket = config.bucket.id; + } else if (config.bucket && typeof config.bucket === 'string') { + logBucket = config.bucket; + } const options: PreconditionOptions = {}; if (config?.ifMetagenerationMatch) { @@ -2709,7 +2784,7 @@ class Bucket extends ServiceObject { } const itemsArray = resp.items ? resp.items : []; - const files = itemsArray.map((file: Metadata) => { + const files = itemsArray.map((file: FileMetadata) => { const options = {} as FileOptions; if (query.versions) { @@ -2720,7 +2795,7 @@ class Bucket extends ServiceObject { options.kmsKeyName = file.kmsKeyName; } - const fileInstance = this.file(file.name, options); + const fileInstance = this.file(file.name!, options); fileInstance.metadata = file; return fileInstance; @@ -2742,20 +2817,24 @@ class Bucket extends ServiceObject { getLabels(callback: GetLabelsCallback): void; getLabels(options: GetLabelsOptions, callback: GetLabelsCallback): void; /** + * @deprecated * @typedef {object} GetLabelsOptions Configuration options for Bucket#getLabels(). * @param {string} [userProject] The ID of the project which will be * billed for the request. */ /** + * @deprecated * @typedef {array} GetLabelsResponse * @property {object} 0 Object of labels currently set on this bucket. */ /** + * @deprecated * @callback GetLabelsCallback * @param {?Error} err Request error, if any. * @param {object} labels Object of labels currently set on this bucket. */ /** + * @deprecated Use getMetadata directly. * Get the labels currently set on this bucket. * * @param {object} [options] Configuration options. @@ -2802,13 +2881,13 @@ class Bucket extends ServiceObject { this.getMetadata( options, - (err: ApiError | null, metadata: Metadata | null) => { + (err: ApiError | null, metadata: BucketMetadata | undefined) => { if (err) { callback!(err, null); return; } - callback!(null, metadata.labels || {}); + callback!(null, metadata?.labels || {}); } ); } @@ -2896,11 +2975,13 @@ class Bucket extends ServiceObject { return; } const itemsArray = resp.items ? resp.items : []; - const notifications = itemsArray.map((notification: Metadata) => { - const notificationInstance = this.notification(notification.id); - notificationInstance.metadata = notification; - return notificationInstance; - }); + const notifications = itemsArray.map( + (notification: NotificationMetadata) => { + const notificationInstance = this.notification(notification.id!); + notificationInstance.metadata = notification; + return notificationInstance; + } + ); callback!(null, notifications, resp); } @@ -3043,9 +3124,6 @@ class Bucket extends ServiceObject { callback?: GetSignedUrlCallback ): void | Promise { const method = BucketActionToHTTPMethod[cfg.action]; - if (!method) { - throw new Error(ExceptionMessages.INVALID_ACTION); - } const signConfig = { method, @@ -3270,9 +3348,9 @@ class Bucket extends ServiceObject { // You aren't allowed to set both predefinedAcl & acl properties on a bucket // so acl must explicitly be nullified. - const metadata = extend({}, options.metadata, {acl: null}); + const metadata = {...options.metadata, acl: null}; - this.setMetadata(metadata, query, err => { + this.setMetadata(metadata, query, (err: Error | null | undefined) => { if (err) { callback!(err); } @@ -3400,7 +3478,7 @@ class Bucket extends ServiceObject { callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : callback; - const req = extend(true, {public: true}, options); + const req = {public: true, ...options}; this.acl .add({ @@ -3496,7 +3574,7 @@ class Bucket extends ServiceObject { ); } - request(reqOpts: DecorateRequestOptions): Promise<[ResponseBody, Metadata]>; + request(reqOpts: DecorateRequestOptions): Promise; request( reqOpts: DecorateRequestOptions, callback: BodyResponseCallback @@ -3512,9 +3590,9 @@ class Bucket extends ServiceObject { request( reqOpts: DecorateRequestOptions, callback?: BodyResponseCallback - ): void | Promise<[ResponseBody, Metadata]> { + ): void | Promise { if (this.userProject && (!reqOpts.qs || !reqOpts.qs.userProject)) { - reqOpts.qs = extend(reqOpts.qs, {userProject: this.userProject}); + reqOpts.qs = {...reqOpts.qs, userProject: this.userProject}; } return super.request(reqOpts, callback!); } @@ -3530,20 +3608,24 @@ class Bucket extends ServiceObject { callback: SetLabelsCallback ): void; /** + * @deprecated * @typedef {array} SetLabelsResponse * @property {object} 0 The bucket metadata. */ /** + * @deprecated * @callback SetLabelsCallback * @param {?Error} err Request error, if any. * @param {object} metadata The bucket metadata. */ /** + * @deprecated * @typedef {object} SetLabelsOptions Configuration options for Bucket#setLabels(). * @property {string} [userProject] The ID of the project which will be * billed for the request. */ /** + * @deprecated Use setMetadata directly. * Set labels on the bucket. * * This makes an underlying call to {@link Bucket#setMetadata}, which @@ -3598,25 +3680,28 @@ class Bucket extends ServiceObject { } setMetadata( - metadata: Metadata, + metadata: BucketMetadata, options?: SetMetadataOptions - ): Promise; - setMetadata(metadata: Metadata, callback: MetadataCallback): void; + ): Promise>; + setMetadata( + metadata: BucketMetadata, + callback: MetadataCallback + ): void; setMetadata( - metadata: Metadata, + metadata: BucketMetadata, options: SetMetadataOptions, - callback: MetadataCallback + callback: MetadataCallback ): void; setMetadata( - metadata: Metadata, - optionsOrCallback: SetMetadataOptions | MetadataCallback, - cb?: MetadataCallback - ): Promise | void { + metadata: BucketMetadata, + optionsOrCallback: SetMetadataOptions | MetadataCallback, + cb?: MetadataCallback + ): Promise> | void { const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; cb = typeof optionsOrCallback === 'function' - ? (optionsOrCallback as MetadataCallback) + ? (optionsOrCallback as MetadataCallback) : cb; this.disableAutoRetryConditionallyIdempotent_( @@ -3697,7 +3782,7 @@ class Bucket extends ServiceObject { this.setMetadata( { retentionPolicy: { - retentionPeriod: duration, + retentionPeriod: duration.toString(), }, }, options, @@ -3893,7 +3978,7 @@ class Bucket extends ServiceObject { const methodConfig = this.methods[method]; if (typeof methodConfig === 'object') { if (typeof methodConfig.reqOpts === 'object') { - extend(methodConfig.reqOpts.qs, {userProject}); + Object.assign(methodConfig.reqOpts.qs, {userProject}); } else { methodConfig.reqOpts = { qs: {userProject}, diff --git a/src/channel.ts b/src/channel.ts index f0dff46dd..ec4db1a35 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {Metadata, ServiceObject, util} from './nodejs-common'; +import {BaseMetadata, ServiceObject, util} from './nodejs-common'; import {promisifyAll} from '@google-cloud/promisify'; import {Storage} from './storage'; export interface StopCallback { - (err: Error | null, apiResponse?: Metadata): void; + (err: Error | null, apiResponse?: unknown): void; } /** @@ -38,7 +38,7 @@ export interface StopCallback { * const channel = storage.channel('id', 'resource-id'); * ``` */ -class Channel extends ServiceObject { +class Channel extends ServiceObject { constructor(storage: Storage, id: string, resourceId: string) { const config = { parent: storage, @@ -60,7 +60,7 @@ class Channel extends ServiceObject { this.metadata.resourceId = resourceId; } - stop(): Promise; + stop(): Promise; stop(callback: StopCallback): void; /** * @typedef {array} StopResponse @@ -96,7 +96,7 @@ class Channel extends ServiceObject { * }); * ``` */ - stop(callback?: StopCallback): Promise | void { + stop(callback?: StopCallback): Promise | void { callback = callback || util.noop; this.request( { diff --git a/src/file.ts b/src/file.ts index 1237fd01d..d949106a0 100644 --- a/src/file.ts +++ b/src/file.ts @@ -17,7 +17,6 @@ import { DecorateRequestOptions, GetConfig, Interceptor, - Metadata, MetadataCallback, ServiceObject, SetMetadataResponse, @@ -27,7 +26,6 @@ import {promisifyAll} from '@google-cloud/promisify'; import compressible = require('compressible'); import * as crypto from 'crypto'; -import * as extend from 'extend'; import * as fs from 'fs'; import * as mime from 'mime'; import * as resumableUpload from './resumable-upload'; @@ -42,7 +40,7 @@ import { Storage, } from './storage'; import {AvailableServiceObjectMethods, Bucket} from './bucket'; -import {Acl} from './acl'; +import {Acl, AclMetadata} from './acl'; import { GetSignedUrlResponse, SigningError, @@ -72,8 +70,10 @@ import {URL} from 'url'; import retry = require('async-retry'); import { + BaseMetadata, DeleteCallback, DeleteOptions, + RequestResponse, SetMetadataOptions, } from './nodejs-common/service-object'; import * as r from 'teeny-request'; @@ -83,7 +83,7 @@ export interface GetExpirationDateCallback { ( err: Error | null, expirationDate?: Date | null, - apiResponse?: Metadata + apiResponse?: unknown ): void; } @@ -152,20 +152,20 @@ export interface GetFileMetadataOptions { userProject?: string; } -export type GetFileMetadataResponse = [Metadata, Metadata]; +export type GetFileMetadataResponse = [FileMetadata, unknown]; export interface GetFileMetadataCallback { - (err: Error | null, metadata?: Metadata, apiResponse?: Metadata): void; + (err: Error | null, metadata?: FileMetadata, apiResponse?: unknown): void; } export interface GetFileOptions extends GetConfig { userProject?: string; } -export type GetFileResponse = [File, Metadata]; +export type GetFileResponse = [File, unknown]; export interface GetFileCallback { - (err: Error | null, file?: File, apiResponse?: Metadata): void; + (err: Error | null, file?: File, apiResponse?: unknown): void; } export interface FileExistsOptions { @@ -183,10 +183,10 @@ export interface DeleteFileOptions { userProject?: string; } -export type DeleteFileResponse = [Metadata]; +export type DeleteFileResponse = [unknown]; export interface DeleteFileCallback { - (err: Error | null, apiResponse?: Metadata): void; + (err: Error | null, apiResponse?: unknown): void; } export type PredefinedAcl = @@ -200,7 +200,7 @@ export type PredefinedAcl = export interface CreateResumableUploadOptions { chunkSize?: number; highWaterMark?: number; - metadata?: Metadata; + metadata?: FileMetadata; origin?: string; offset?: number; predefinedAcl?: PredefinedAcl; @@ -226,13 +226,13 @@ export interface CreateWriteStreamOptions extends CreateResumableUploadOptions { } export interface MakeFilePrivateOptions { - metadata?: Metadata; + metadata?: FileMetadata; strict?: boolean; userProject?: string; preconditionOpts?: PreconditionOptions; } -export type MakeFilePrivateResponse = [Metadata]; +export type MakeFilePrivateResponse = [unknown]; export type MakeFilePrivateCallback = SetFileMetadataCallback; @@ -242,19 +242,19 @@ export interface IsPublicCallback { export type IsPublicResponse = [boolean]; -export type MakeFilePublicResponse = [Metadata]; +export type MakeFilePublicResponse = [unknown]; export interface MakeFilePublicCallback { - (err?: Error | null, apiResponse?: Metadata): void; + (err?: Error | null, apiResponse?: unknown): void; } -export type MoveResponse = [Metadata]; +export type MoveResponse = [unknown]; export interface MoveCallback { ( err: Error | null, destinationFile?: File | null, - apiResponse?: Metadata + apiResponse?: unknown ): void; } @@ -311,17 +311,17 @@ export interface CopyOptions { contentType?: string; contentDisposition?: string; destinationKmsKeyName?: string; - metadata?: Metadata; + metadata?: FileMetadata; predefinedAcl?: string; token?: string; userProject?: string; preconditionOpts?: PreconditionOptions; } -export type CopyResponse = [File, Metadata]; +export type CopyResponse = [File, unknown]; export interface CopyCallback { - (err: Error | null, file?: File | null, apiResponse?: Metadata): void; + (err: Error | null, file?: File | null, apiResponse?: unknown): void; } export type DownloadResponse = [Buffer]; @@ -341,10 +341,10 @@ interface CopyQuery { userProject?: string; destinationKmsKeyName?: string; destinationPredefinedAcl?: string; - ifGenerationMatch?: number; - ifGenerationNotMatch?: number; - ifMetagenerationMatch?: number; - ifMetagenerationNotMatch?: number; + ifGenerationMatch?: number | string; + ifGenerationNotMatch?: number | string; + ifMetagenerationMatch?: number | string; + ifMetagenerationNotMatch?: number | string; } interface FileQuery { @@ -375,24 +375,59 @@ export interface SetFileMetadataOptions { } export interface SetFileMetadataCallback { - (err?: Error | null, apiResponse?: Metadata): void; + (err?: Error | null, apiResponse?: unknown): void; } -export type SetFileMetadataResponse = [Metadata]; +export type SetFileMetadataResponse = [unknown]; -export type SetStorageClassResponse = [Metadata]; +export type SetStorageClassResponse = [unknown]; export interface SetStorageClassOptions { userProject?: string; preconditionOpts?: PreconditionOptions; } -interface SetStorageClassRequest extends SetStorageClassOptions { - storageClass?: string; +export interface SetStorageClassCallback { + (err?: Error | null, apiResponse?: unknown): void; } -export interface SetStorageClassCallback { - (err?: Error | null, apiResponse?: Metadata): void; +export interface FileMetadata extends BaseMetadata { + acl?: AclMetadata[] | null; + bucket?: string; + cacheControl?: string; + componentCount?: number; + contentDisposition?: string; + contentEncoding?: string; + contentLanguage?: string; + contentType?: string; + crc32c?: string; + customerEncryption?: { + encryptionAlgorithm?: string; + keySha256?: string; + }; + customTime?: string; + eventBasedHold?: boolean | null; + generation?: string | number; + kmsKeyName?: string; + md5Hash?: string; + mediaLink?: string; + metadata?: { + [key: string]: string; + }; + metageneration?: string | number; + name?: string; + owner?: { + entity?: string; + entityId?: string; + }; + retentionExpirationTime?: string; + size?: string | number; + storageClass?: string; + temporaryHold?: boolean | null; + timeCreated?: string; + timeDeleted?: string; + timeStorageClassUpdated?: string; + updated?: string; } export class RequestError extends Error { @@ -427,7 +462,7 @@ export enum FileExceptionMessages { * * @class */ -class File extends ServiceObject { +class File extends ServiceObject { acl: Acl; crc32cGenerator: CRC32CValidatorGenerator; bucket: Bucket; @@ -435,7 +470,6 @@ class File extends ServiceObject { kmsKeyName?: string; userProject?: string; signer?: URLSigner; - metadata: Metadata; name: string; generation?: number; @@ -1158,10 +1192,9 @@ class File extends ServiceObject { if (typeof optionsOrCallback === 'function') { callback = optionsOrCallback; } else if (optionsOrCallback) { - options = optionsOrCallback; + options = {...optionsOrCallback}; } - options = extend(true, {}, options); callback = callback || util.noop; let destBucket: Bucket; @@ -1432,11 +1465,11 @@ class File extends ServiceObject { const onResponse = ( err: Error | null, _body: ResponseBody, - rawResponseStream: Metadata + rawResponseStream: unknown ) => { if (err) { // Get error message from the body. - this.getBufferFromReadable(rawResponseStream).then(body => { + this.getBufferFromReadable(rawResponseStream as Readable).then(body => { err.message = body.toString('utf8'); throughStream.destroy(err); }); @@ -1444,7 +1477,7 @@ class File extends ServiceObject { return; } - const headers = rawResponseStream.toJSON().headers; + const headers = (rawResponseStream as ResponseBody).toJSON().headers; const isCompressed = headers['content-encoding'] === 'gzip'; const hashes: {crc32c?: string; md5?: string} = {}; @@ -1499,7 +1532,7 @@ class File extends ServiceObject { } pipeline( - rawResponseStream, + rawResponseStream as Readable, ...(transformStreams as [Transform]), throughStream, onComplete @@ -1848,30 +1881,30 @@ class File extends ServiceObject { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any createWriteStream(options: CreateWriteStreamOptions = {}): Writable { - options = extend(true, {metadata: {}}, options); + options.metadata ??= {}; if (options.contentType) { - options.metadata.contentType = options.contentType; + options!.metadata!.contentType = options.contentType; } if ( - !options.metadata.contentType || - options.metadata.contentType === 'auto' + !options!.metadata!.contentType || + options!.metadata!.contentType === 'auto' ) { const detectedContentType = mime.getType(this.name); if (detectedContentType) { - options.metadata.contentType = detectedContentType; + options!.metadata!.contentType = detectedContentType; } } let gzip = options.gzip; if (gzip === 'auto') { - gzip = compressible(options.metadata.contentType); + gzip = compressible(options!.metadata!.contentType || ''); } if (gzip) { - options.metadata.contentEncoding = 'gzip'; + options!.metadata!.contentEncoding = 'gzip'; } let crc32c = true; @@ -2232,7 +2265,7 @@ class File extends ServiceObject { callback?: GetExpirationDateCallback ): void | Promise { this.getMetadata( - (err: ApiError | null, metadata: Metadata, apiResponse: Metadata) => { + (err: ApiError | null, metadata: FileMetadata, apiResponse: unknown) => { if (err) { callback!(err, null, apiResponse); return; @@ -2833,9 +2866,6 @@ class File extends ServiceObject { callback?: GetSignedUrlCallback ): void | Promise { const method = ActionToHTTPMethod[cfg.action]; - if (!method) { - throw new Error(ExceptionMessages.INVALID_ACTION); - } const extensionHeaders = objectKeyToLowercase(cfg.extensionHeaders || {}); if (cfg.action === 'resumable') { extensionHeaders['x-goog-resumable'] = 'start'; @@ -3069,7 +3099,7 @@ class File extends ServiceObject { // You aren't allowed to set both predefinedAcl & acl properties on a file, // so acl must explicitly be nullified, destroying all previous acls on the // file. - const metadata = extend({}, options.metadata, {acl: null}); + const metadata = {...options.metadata, acl: null}; this.setMetadata(metadata, query, callback!); } @@ -3439,7 +3469,7 @@ class File extends ServiceObject { this.move(destinationFile, options, callback); } - request(reqOpts: DecorateRequestOptions): Promise<[ResponseBody, Metadata]>; + request(reqOpts: DecorateRequestOptions): Promise; request( reqOpts: DecorateRequestOptions, callback: BodyResponseCallback @@ -3455,7 +3485,7 @@ class File extends ServiceObject { request( reqOpts: DecorateRequestOptions, callback?: BodyResponseCallback - ): void | Promise<[ResponseBody, Metadata]> { + ): void | Promise { return this.parent.request.call(this, reqOpts, callback!); } @@ -3653,25 +3683,28 @@ class File extends ServiceObject { } setMetadata( - metadata: Metadata, + metadata: FileMetadata, options?: SetMetadataOptions - ): Promise; - setMetadata(metadata: Metadata, callback: MetadataCallback): void; + ): Promise>; setMetadata( - metadata: Metadata, + metadata: FileMetadata, + callback: MetadataCallback + ): void; + setMetadata( + metadata: FileMetadata, options: SetMetadataOptions, - callback: MetadataCallback + callback: MetadataCallback ): void; setMetadata( - metadata: Metadata, - optionsOrCallback: SetMetadataOptions | MetadataCallback, - cb?: MetadataCallback - ): Promise | void { + metadata: FileMetadata, + optionsOrCallback: SetMetadataOptions | MetadataCallback, + cb?: MetadataCallback + ): Promise> | void { const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; cb = typeof optionsOrCallback === 'function' - ? (optionsOrCallback as MetadataCallback) + ? (optionsOrCallback as MetadataCallback) : cb; this.disableAutoRetryConditionallyIdempotent_( @@ -3757,19 +3790,17 @@ class File extends ServiceObject { typeof optionsOrCallback === 'function' ? optionsOrCallback : callback; const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; - const req = extend( - true, - {}, - options - ); - // In case we get input like `storageClass`, convert to `storage_class`. - req.storageClass = storageClass - .replace(/-/g, '_') - .replace(/([a-z])([A-Z])/g, (_, low, up) => { - return low + '_' + up; - }) - .toUpperCase(); + const req = { + ...options, + // In case we get input like `storageClass`, convert to `storage_class`. + storageClass: storageClass + .replace(/-/g, '_') + .replace(/([a-z])([A-Z])/g, (_, low, up) => { + return low + '_' + up; + }) + .toUpperCase(), + }; this.copy(this, req, (err, file, apiResponse) => { if (err) { @@ -3813,20 +3844,14 @@ class File extends ServiceObject { */ startResumableUpload_( dup: Duplexify, - options: CreateResumableUploadOptions + options: CreateResumableUploadOptions = {} ): void { - options = extend( - true, - { - metadata: {}, - }, - options - ); + options.metadata ??= {}; const retryOptions = this.storage.retryOptions; if ( !this.shouldRetryBasedOnPreconditionAndIdempotencyStrat( - options?.preconditionOpts + options.preconditionOpts ) ) { retryOptions.autoRetry = false; @@ -3884,14 +3909,11 @@ class File extends ServiceObject { * * @private */ - startSimpleUpload_(dup: Duplexify, options?: CreateWriteStreamOptions): void { - options = extend( - true, - { - metadata: {}, - }, - options - ); + startSimpleUpload_( + dup: Duplexify, + options: CreateWriteStreamOptions = {} + ): void { + options.metadata ??= {}; const apiEndpoint = this.storage.apiEndpoint; const bucketName = this.bucket.name; diff --git a/src/hmacKey.ts b/src/hmacKey.ts index 33f11431a..4621fef5e 100644 --- a/src/hmacKey.ts +++ b/src/hmacKey.ts @@ -13,13 +13,12 @@ // limitations under the License. import { - Metadata, ServiceObject, Methods, MetadataCallback, SetMetadataResponse, } from './nodejs-common'; -import {SetMetadataOptions} from './nodejs-common/service-object'; +import {BaseMetadata, SetMetadataOptions} from './nodejs-common/service-object'; import {IdempotencyStrategy, Storage} from './storage'; import {promisifyAll} from '@google-cloud/promisify'; @@ -27,10 +26,9 @@ export interface HmacKeyOptions { projectId?: string; } -export interface HmacKeyMetadata { - accessId: string; +export interface HmacKeyMetadata extends BaseMetadata { + accessId?: string; etag?: string; - id?: string; projectId?: string; serviceAccountEmail?: string; state?: string; @@ -51,10 +49,10 @@ export interface SetHmacKeyMetadata { } export interface HmacKeyMetadataCallback { - (err: Error | null, metadata?: HmacKeyMetadata, apiResponse?: Metadata): void; + (err: Error | null, metadata?: HmacKeyMetadata, apiResponse?: unknown): void; } -export type HmacKeyMetadataResponse = [HmacKeyMetadata, Metadata]; +export type HmacKeyMetadataResponse = [HmacKeyMetadata, unknown]; /** * The API-formatted resource description of the HMAC key. @@ -74,8 +72,7 @@ export type HmacKeyMetadataResponse = [HmacKeyMetadata, Metadata]; * * @class */ -export class HmacKey extends ServiceObject { - metadata: HmacKeyMetadata | undefined; +export class HmacKey extends ServiceObject { /** * A reference to the {@link Storage} associated with this {@link HmacKey} * instance. @@ -370,20 +367,23 @@ export class HmacKey extends ServiceObject { * @param {object} callback.apiResponse - The full API response. */ setMetadata( - metadata: Metadata, + metadata: HmacKeyMetadata, options?: SetMetadataOptions - ): Promise; - setMetadata(metadata: Metadata, callback: MetadataCallback): void; + ): Promise>; setMetadata( - metadata: Metadata, + metadata: HmacKeyMetadata, + callback: MetadataCallback + ): void; + setMetadata( + metadata: HmacKeyMetadata, options: SetMetadataOptions, - callback: MetadataCallback + callback: MetadataCallback ): void; setMetadata( - metadata: Metadata, - optionsOrCallback: SetMetadataOptions | MetadataCallback, - cb?: MetadataCallback - ): Promise | void { + metadata: HmacKeyMetadata, + optionsOrCallback: SetMetadataOptions | MetadataCallback, + cb?: MetadataCallback + ): Promise> | void { // ETag preconditions are not currently supported. Retries should be disabled if the idempotency strategy is not set to RetryAlways if ( this.storage.retryOptions.idempotencyStrategy !== @@ -395,7 +395,7 @@ export class HmacKey extends ServiceObject { typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; cb = typeof optionsOrCallback === 'function' - ? (optionsOrCallback as MetadataCallback) + ? (optionsOrCallback as MetadataCallback) : cb; super diff --git a/src/iam.ts b/src/iam.ts index 1f27622d7..fc65aeac0 100644 --- a/src/iam.ts +++ b/src/iam.ts @@ -12,11 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { - BodyResponseCallback, - DecorateRequestOptions, - Metadata, -} from './nodejs-common'; +import {BodyResponseCallback, DecorateRequestOptions} from './nodejs-common'; import {promisifyAll} from '@google-cloud/promisify'; import {Bucket} from './bucket'; @@ -27,7 +23,7 @@ export interface GetPolicyOptions { requestedPolicyVersion?: number; } -export type GetPolicyResponse = [Policy, Metadata]; +export type GetPolicyResponse = [Policy, unknown]; /** * @callback GetPolicyCallback @@ -36,7 +32,7 @@ export type GetPolicyResponse = [Policy, Metadata]; * @param {object} apiResponse The full API response. */ export interface GetPolicyCallback { - (err?: Error | null, acl?: Policy, apiResponse?: Metadata): void; + (err?: Error | null, acl?: Policy, apiResponse?: unknown): void; } /** @@ -53,7 +49,7 @@ export interface SetPolicyOptions { * @property {object} 0 The policy. * @property {object} 1 The full API response. */ -export type SetPolicyResponse = [Policy, Metadata]; +export type SetPolicyResponse = [Policy, unknown]; /** * @callback SetPolicyCallback @@ -88,7 +84,7 @@ export interface Expr { * @property {object} 0 A subset of permissions that the caller is allowed. * @property {object} 1 The full API response. */ -export type TestIamPermissionsResponse = [{[key: string]: boolean}, Metadata]; +export type TestIamPermissionsResponse = [{[key: string]: boolean}, unknown]; /** * @callback TestIamPermissionsCallback @@ -100,7 +96,7 @@ export interface TestIamPermissionsCallback { ( err?: Error | null, acl?: {[key: string]: boolean} | null, - apiResponse?: Metadata + apiResponse?: unknown ): void; } diff --git a/src/index.ts b/src/index.ts index c2e6b9df6..e8e9af9cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -57,6 +57,7 @@ * Full quickstart example: */ export { + AclMetadata, AccessControlObject, AclOptions, AddAclCallback, @@ -79,6 +80,7 @@ export { BucketExistsResponse, BucketLockCallback, BucketLockResponse, + BucketMetadata, CombineCallback, CombineOptions, CombineResponse, @@ -157,6 +159,7 @@ export { FileExistsCallback, FileExistsOptions, FileExistsResponse, + FileMetadata, FileOptions, GetExpirationDateCallback, GetExpirationDateResponse, diff --git a/src/nodejs-common/index.ts b/src/nodejs-common/index.ts index f90c514ba..293105b95 100644 --- a/src/nodejs-common/index.ts +++ b/src/nodejs-common/index.ts @@ -23,12 +23,12 @@ export { } from './service'; export { + BaseMetadata, DeleteCallback, ExistsCallback, GetConfig, InstanceResponseCallback, Interceptor, - Metadata, MetadataCallback, MetadataResponse, Methods, diff --git a/src/nodejs-common/service-object.ts b/src/nodejs-common/service-object.ts index afc5491c8..bab980134 100644 --- a/src/nodejs-common/service-object.ts +++ b/src/nodejs-common/service-object.ts @@ -15,7 +15,6 @@ */ import {promisifyAll} from '@google-cloud/promisify'; import {EventEmitter} from 'events'; -import * as extend from 'extend'; import * as r from 'teeny-request'; import {StreamRequestOptions} from '.'; @@ -27,7 +26,7 @@ import { util, } from './util'; -export type RequestResponse = [Metadata, r.Response]; +export type RequestResponse = [unknown, r.Response]; export interface ServiceObjectParent { interceptors: Interceptor[]; @@ -45,12 +44,10 @@ export interface Interceptor { export type GetMetadataOptions = object; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export type Metadata = any; -export type MetadataResponse = [Metadata, r.Response]; -export type MetadataCallback = ( +export type MetadataResponse = [K, r.Response]; +export type MetadataCallback = ( err: Error | null, - metadata?: Metadata, + metadata?: K, apiResponse?: r.Response ) => void; @@ -114,10 +111,10 @@ export interface CreateCallback { export type DeleteOptions = { ignoreNotFound?: boolean; - ifGenerationMatch?: number; - ifGenerationNotMatch?: number; - ifMetagenerationMatch?: number; - ifMetagenerationNotMatch?: number; + ifGenerationMatch?: number | string; + ifGenerationNotMatch?: number | string; + ifMetagenerationMatch?: number | string; + ifMetagenerationNotMatch?: number | string; } & object; export interface DeleteCallback { (err: Error | null, apiResponse?: r.Response): void; @@ -129,16 +126,24 @@ export interface GetConfig { */ autoCreate?: boolean; } -type GetOrCreateOptions = GetConfig & CreateOptions; +export type GetOrCreateOptions = GetConfig & CreateOptions; export type GetResponse = [T, r.Response]; export interface ResponseCallback { (err?: Error | null, apiResponse?: r.Response): void; } -export type SetMetadataResponse = [Metadata]; +export type SetMetadataResponse = [K]; export type SetMetadataOptions = object; +export interface BaseMetadata { + id?: string; + kind?: string; + etag?: string; + selfLink?: string; + [key: string]: unknown; +} + /** * ServiceObject is a base class, meant to be inherited from by a "service * object," like a BigQuery dataset or Storage bucket. @@ -151,8 +156,8 @@ export type SetMetadataOptions = object; * object requires specific behavior. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any -class ServiceObject extends EventEmitter { - metadata: Metadata; +class ServiceObject extends EventEmitter { + metadata: K; baseUrl?: string; parent: ServiceObjectParent; id?: string; @@ -181,7 +186,7 @@ class ServiceObject extends EventEmitter { */ constructor(config: ServiceObjectConfig) { super(); - this.metadata = {}; + this.metadata = {} as K; this.baseUrl = config.baseUrl; this.parent = config.parent; // Parent class. this.id = config.id; // Name or ID (e.g. dataset ID, bucket name, etc). @@ -249,7 +254,7 @@ class ServiceObject extends EventEmitter { // Wrap the callback to return *this* instance of the object, not the // newly-created one. // tslint: disable-next-line no-any - function onCreate(...args: [Error, ServiceObject]) { + function onCreate(...args: [Error, ServiceObject]) { const [err, instance] = args; if (!err) { self.metadata = instance.metadata; @@ -290,31 +295,28 @@ class ServiceObject extends EventEmitter { const methodConfig = (typeof this.methods.delete === 'object' && this.methods.delete) || {}; - const reqOpts = extend( - true, - { - method: 'DELETE', - uri: '', + const reqOpts = { + method: 'DELETE', + uri: '', + ...methodConfig.reqOpts, + qs: { + ...methodConfig.reqOpts?.qs, + ...options, }, - methodConfig.reqOpts, - { - qs: options, - } - ); + }; // The `request` method may have been overridden to hold any special // behavior. Ensure we call the original `request` method. ServiceObject.prototype.request.call( this, reqOpts, - (err: ApiError | null, ...args) => { + (err: ApiError | null, body?: ResponseBody, res?: r.Response) => { if (err) { if (err.code === 404 && ignoreNotFound) { err = null; } } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callback(err, ...(args as any)); + callback(err, res); } ); } @@ -409,10 +411,10 @@ class ServiceObject extends EventEmitter { self.create(...args); return; } - callback!(err, null, metadata as r.Response); + callback!(err, null, metadata as unknown as r.Response); return; } - callback!(null, self as {} as T, metadata as r.Response); + callback!(null, self as {} as T, metadata as unknown as r.Response); }); } @@ -424,32 +426,30 @@ class ServiceObject extends EventEmitter { * @param {object} callback.metadata - The metadata for this object. * @param {object} callback.apiResponse - The full API response. */ - getMetadata(options?: GetMetadataOptions): Promise; - getMetadata(options: GetMetadataOptions, callback: MetadataCallback): void; - getMetadata(callback: MetadataCallback): void; + getMetadata(options?: GetMetadataOptions): Promise>; + getMetadata(options: GetMetadataOptions, callback: MetadataCallback): void; + getMetadata(callback: MetadataCallback): void; getMetadata( - optionsOrCallback: GetMetadataOptions | MetadataCallback, - cb?: MetadataCallback - ): Promise | void { + optionsOrCallback: GetMetadataOptions | MetadataCallback, + cb?: MetadataCallback + ): Promise> | void { const [options, callback] = util.maybeOptionsOrCallback< GetMetadataOptions, - MetadataCallback + MetadataCallback >(optionsOrCallback, cb); const methodConfig = (typeof this.methods.getMetadata === 'object' && this.methods.getMetadata) || {}; - const reqOpts = extend( - true, - { - uri: '', + const reqOpts = { + uri: '', + ...methodConfig.reqOpts, + qs: { + ...methodConfig.reqOpts?.qs, + ...options, }, - methodConfig.reqOpts, - { - qs: options, - } - ); + }; // The `request` method may have been overridden to hold any special // behavior. Ensure we call the original `request` method. @@ -484,42 +484,42 @@ class ServiceObject extends EventEmitter { * @param {object} callback.apiResponse - The full API response. */ setMetadata( - metadata: Metadata, + metadata: K, options?: SetMetadataOptions - ): Promise; - setMetadata(metadata: Metadata, callback: MetadataCallback): void; + ): Promise>; + setMetadata(metadata: K, callback: MetadataCallback): void; setMetadata( - metadata: Metadata, + metadata: K, options: SetMetadataOptions, - callback: MetadataCallback + callback: MetadataCallback ): void; setMetadata( - metadata: Metadata, - optionsOrCallback: SetMetadataOptions | MetadataCallback, - cb?: MetadataCallback - ): Promise | void { + metadata: K, + optionsOrCallback: SetMetadataOptions | MetadataCallback, + cb?: MetadataCallback + ): Promise> | void { const [options, callback] = util.maybeOptionsOrCallback< SetMetadataOptions, - MetadataCallback + MetadataCallback >(optionsOrCallback, cb); const methodConfig = (typeof this.methods.setMetadata === 'object' && this.methods.setMetadata) || {}; - const reqOpts = extend( - true, - {}, - { - method: 'PATCH', - uri: '', + const reqOpts = { + method: 'PATCH', + uri: '', + ...methodConfig.reqOpts, + json: { + ...methodConfig.reqOpts?.json, + ...metadata, }, - methodConfig.reqOpts, - { - json: metadata, - qs: options, - } - ); + qs: { + ...methodConfig.reqOpts?.qs, + ...options, + }, + }; // The `request` method may have been overridden to hold any special // behavior. Ensure we call the original `request` method. @@ -551,7 +551,7 @@ class ServiceObject extends EventEmitter { reqOpts: DecorateRequestOptions | StreamRequestOptions, callback?: BodyResponseCallback ): void | r.Request { - reqOpts = extend(true, {}, reqOpts); + reqOpts = {...reqOpts}; if (this.projectId) { reqOpts.projectId = this.projectId; @@ -582,7 +582,6 @@ class ServiceObject extends EventEmitter { if (reqOpts.shouldReturnStream) { return this.parent.requestStream(reqOpts); } - this.parent.request(reqOpts, callback!); } @@ -612,7 +611,7 @@ class ServiceObject extends EventEmitter { * @param {string} reqOpts.uri - A URI relative to the baseUrl. */ requestStream(reqOpts: DecorateRequestOptions): r.Request { - const opts = extend(true, reqOpts, {shouldReturnStream: true}); + const opts = {...reqOpts, shouldReturnStream: true}; return this.request_(opts as StreamRequestOptions); } } diff --git a/src/nodejs-common/service.ts b/src/nodejs-common/service.ts index 8ef28c8db..d20d19735 100644 --- a/src/nodejs-common/service.ts +++ b/src/nodejs-common/service.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import * as extend from 'extend'; import {AuthClient, GoogleAuth, GoogleAuthOptions} from 'google-auth-library'; import * as r from 'teeny-request'; import * as uuid from 'uuid'; @@ -112,15 +111,16 @@ export class Service { this.projectIdRequired = config.projectIdRequired !== false; this.providedUserAgent = options.userAgent; - const reqCfg = extend({}, config, { + const reqCfg = { + ...config, projectIdRequired: this.projectIdRequired, projectId: this.projectId, - authClient: options.authClient, + authClient: options.authClient || config.authClient, credentials: options.credentials, keyFile: options.keyFilename, email: options.email, token: options.token, - }); + }; this.makeAuthenticatedRequest = util.makeAuthenticatedRequestFactory(reqCfg); @@ -193,7 +193,7 @@ export class Service { reqOpts: DecorateRequestOptions | StreamRequestOptions, callback?: BodyResponseCallback ): void | r.Request { - reqOpts = extend(true, {}, reqOpts, {timeout: this.timeout}); + reqOpts = {...reqOpts, timeout: this.timeout}; const isAbsoluteUrl = reqOpts.uri.indexOf('http') === 0; const uriComponents = [this.baseUrl]; @@ -245,12 +245,13 @@ export class Service { if (this.providedUserAgent) { userAgent = `${this.providedUserAgent} ${userAgent}`; } - reqOpts.headers = extend({}, reqOpts.headers, { + reqOpts.headers = { + ...reqOpts.headers, 'User-Agent': userAgent, 'x-goog-api-client': `${getRuntimeTrackingString()} gccl/${ pkg.version } gccl-invocation-id/${uuid.v4()}`, - }); + }; if (reqOpts.shouldReturnStream) { return this.makeAuthenticatedRequest(reqOpts) as {} as r.Request; @@ -280,7 +281,7 @@ export class Service { * @param {string} reqOpts.uri - A URI relative to the baseUrl. */ requestStream(reqOpts: DecorateRequestOptions): r.Request { - const opts = extend(true, reqOpts, {shouldReturnStream: true}); + const opts = {...reqOpts, shouldReturnStream: true}; return (Service.prototype.request_ as Function).call(this, opts); } } diff --git a/src/nodejs-common/util.ts b/src/nodejs-common/util.ts index 970eac1b5..0d4fc6374 100644 --- a/src/nodejs-common/util.ts +++ b/src/nodejs-common/util.ts @@ -23,7 +23,6 @@ import { MissingProjectIdError, } from '@google-cloud/projectify'; import * as ent from 'ent'; -import * as extend from 'extend'; import {AuthClient, GoogleAuth, GoogleAuthOptions} from 'google-auth-library'; import {CredentialBody} from 'google-auth-library'; import * as r from 'teeny-request'; @@ -401,12 +400,12 @@ export class Util { ) { callback = callback || util.noop; - const parsedResp = extend( - true, - {err: err || null}, - resp && util.parseHttpRespMessage(resp), - body && util.parseHttpRespBody(body) - ); + const parsedResp = { + err: err || null, + ...(resp && util.parseHttpRespMessage(resp)), + ...(body && util.parseHttpRespBody(body)), + }; + // Assign the parsed body to resp.body, even if { json: false } was passed // as a request option. // We assume that nobody uses the previously unparsed value of resp.body. @@ -514,7 +513,13 @@ export class Util { const metadata = options.metadata || {}; - const reqOpts = extend(true, defaultReqOpts, options.request, { + const reqOpts = { + ...defaultReqOpts, + ...options.request, + qs: { + ...defaultReqOpts.qs, + ...options.request?.qs, + }, multipart: [ { 'Content-Type': 'application/json', @@ -525,7 +530,7 @@ export class Util { body: writeStream, }, ], - }) as r.OptionsWithUri; + } as {} as r.OptionsWithUri; options.makeAuthenticatedRequest(reqOpts, { onAuthenticated(err, authenticatedReqOpts) { @@ -602,7 +607,7 @@ export class Util { makeAuthenticatedRequestFactory( config: MakeAuthenticatedRequestFactoryConfig ) { - const googleAutoAuthConfig = extend({}, config); + const googleAutoAuthConfig = {...config}; if (googleAutoAuthConfig.projectId === DEFAULT_PROJECT_ID_TOKEN) { delete googleAutoAuthConfig.projectId; } @@ -648,7 +653,7 @@ export class Util { ): void | Abortable | Duplexify { let stream: Duplexify; let projectId: string; - const reqConfig = extend({}, config); + const reqConfig = {...config}; let activeRequest_: void | Abortable | null; if (!optionsOrCallback) { diff --git a/src/notification.ts b/src/notification.ts index aff9d315c..c24bf0c39 100644 --- a/src/notification.ts +++ b/src/notification.ts @@ -12,13 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { - ApiError, - Metadata, - MetadataCallback, - ServiceObject, - util, -} from './nodejs-common'; +import {BaseMetadata, ServiceObject} from './nodejs-common'; import {ResponseBody} from './nodejs-common/util'; import {promisifyAll} from '@google-cloud/promisify'; @@ -37,7 +31,7 @@ export interface GetNotificationMetadataOptions { * @property {object} 0 The notification metadata. * @property {object} 1 The full API response. */ -export type GetNotificationMetadataResponse = [ResponseBody, Metadata]; +export type GetNotificationMetadataResponse = [ResponseBody, unknown]; /** * @callback GetNotificationMetadataCallback @@ -46,7 +40,7 @@ export type GetNotificationMetadataResponse = [ResponseBody, Metadata]; * @param {object} apiResponse The full API response. */ export interface GetNotificationMetadataCallback { - (err: Error | null, metadata?: ResponseBody, apiResponse?: Metadata): void; + (err: Error | null, metadata?: ResponseBody, apiResponse?: unknown): void; } /** @@ -54,7 +48,7 @@ export interface GetNotificationMetadataCallback { * @property {Notification} 0 The {@link Notification} * @property {object} 1 The full API response. */ -export type GetNotificationResponse = [Notification, Metadata]; +export type GetNotificationResponse = [Notification, unknown]; export interface GetNotificationOptions { /** @@ -78,7 +72,7 @@ export interface GetNotificationCallback { ( err: Error | null, notification?: Notification | null, - apiResponse?: Metadata + apiResponse?: unknown ): void; } @@ -88,7 +82,17 @@ export interface GetNotificationCallback { * @param {object} apiResponse The full API response. */ export interface DeleteNotificationCallback { - (err: Error | null, apiResponse?: Metadata): void; + (err: Error | null, apiResponse?: unknown): void; +} + +export interface NotificationMetadata extends BaseMetadata { + custom_attributes?: { + [key: string]: string; + }; + event_types?: string[]; + object_name_prefix?: string; + payload_format?: 'JSON_API_V1' | 'NONE'; + topic?: string; } /** @@ -122,8 +126,15 @@ export interface DeleteNotificationCallback { * const notification = myBucket.notification('1'); * ``` */ -class Notification extends ServiceObject { +class Notification extends ServiceObject { constructor(bucket: Bucket, id: string) { + const requestQueryObject: { + ifGenerationMatch?: number; + ifGenerationNotMatch?: number; + ifMetagenerationMatch?: number; + ifMetagenerationNotMatch?: number; + } = {}; + const methods = { /** * Creates a notification subscription for the bucket. @@ -169,6 +180,127 @@ class Notification extends ServiceObject { */ create: true, + /** + * @typedef {array} DeleteNotificationResponse + * @property {object} 0 The full API response. + */ + /** + * Permanently deletes a notification subscription. + * + * See {@link https://cloud.google.com/storage/docs/json_api/v1/notifications/delete| Notifications: delete API Documentation} + * + * @param {object} [options] Configuration options. + * @param {string} [options.userProject] The ID of the project which will be + * billed for the request. + * @param {DeleteNotificationCallback} [callback] Callback function. + * @returns {Promise} + * + * @example + * ``` + * const {Storage} = require('@google-cloud/storage'); + * const storage = new Storage(); + * const myBucket = storage.bucket('my-bucket'); + * const notification = myBucket.notification('1'); + * + * notification.delete(function(err, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * notification.delete().then(function(data) { + * const apiResponse = data[0]; + * }); + * + * ``` + * @example include:samples/deleteNotification.js + * region_tag:storage_delete_bucket_notification + * Another example: + */ + delete: { + reqOpts: { + qs: requestQueryObject, + }, + }, + + /** + * Get a notification and its metadata if it exists. + * + * See {@link https://cloud.google.com/storage/docs/json_api/v1/notifications/get| Notifications: get API Documentation} + * + * @param {object} [options] Configuration options. + * See {@link Bucket#createNotification} for create options. + * @param {boolean} [options.autoCreate] Automatically create the object if + * it does not exist. Default: `false`. + * @param {string} [options.userProject] The ID of the project which will be + * billed for the request. + * @param {GetNotificationCallback} [callback] Callback function. + * @return {Promise} + * + * @example + * ``` + * const {Storage} = require('@google-cloud/storage'); + * const storage = new Storage(); + * const myBucket = storage.bucket('my-bucket'); + * const notification = myBucket.notification('1'); + * + * notification.get(function(err, notification, apiResponse) { + * // `notification.metadata` has been populated. + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * notification.get().then(function(data) { + * const notification = data[0]; + * const apiResponse = data[1]; + * }); + * ``` + */ + get: { + reqOpts: { + qs: requestQueryObject, + }, + }, + + /** + * Get the notification's metadata. + * + * See {@link https://cloud.google.com/storage/docs/json_api/v1/notifications/get| Notifications: get API Documentation} + * + * @param {object} [options] Configuration options. + * @param {string} [options.userProject] The ID of the project which will be + * billed for the request. + * @param {GetNotificationMetadataCallback} [callback] Callback function. + * @returns {Promise} + * + * @example + * ``` + * const {Storage} = require('@google-cloud/storage'); + * const storage = new Storage(); + * const myBucket = storage.bucket('my-bucket'); + * const notification = myBucket.notification('1'); + * + * notification.getMetadata(function(err, metadata, apiResponse) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * notification.getMetadata().then(function(data) { + * const metadata = data[0]; + * const apiResponse = data[1]; + * }); + * + * ``` + * @example include:samples/getMetadataNotifications.js + * region_tag:storage_print_pubsub_bucket_notification + * Another example: + */ + getMetadata: { + reqOpts: { + qs: requestQueryObject, + }, + }, + /** * @typedef {array} NotificationExistsResponse * @property {boolean} 0 Whether the notification exists or not. @@ -213,222 +345,6 @@ class Notification extends ServiceObject { methods, }); } - - delete(options?: DeleteNotificationOptions): Promise<[Metadata]>; - delete( - options: DeleteNotificationOptions, - callback: DeleteNotificationCallback - ): void; - delete(callback: DeleteNotificationCallback): void; - /** - * @typedef {array} DeleteNotificationResponse - * @property {object} 0 The full API response. - */ - /** - * Permanently deletes a notification subscription. - * - * See {@link https://cloud.google.com/storage/docs/json_api/v1/notifications/delete| Notifications: delete API Documentation} - * - * @param {object} [options] Configuration options. - * @param {string} [options.userProject] The ID of the project which will be - * billed for the request. - * @param {DeleteNotificationCallback} [callback] Callback function. - * @returns {Promise} - * - * @example - * ``` - * const {Storage} = require('@google-cloud/storage'); - * const storage = new Storage(); - * const myBucket = storage.bucket('my-bucket'); - * const notification = myBucket.notification('1'); - * - * notification.delete(function(err, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * notification.delete().then(function(data) { - * const apiResponse = data[0]; - * }); - * - * ``` - * @example include:samples/deleteNotification.js - * region_tag:storage_delete_bucket_notification - * Another example: - */ - delete( - optionsOrCallback?: DeleteNotificationOptions | DeleteNotificationCallback, - callback?: DeleteNotificationCallback - ): void | Promise<[Metadata]> { - const options = - typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; - callback = - typeof optionsOrCallback === 'function' ? optionsOrCallback : callback; - this.request( - { - method: 'DELETE', - uri: '', - qs: options, - }, - callback || util.noop - ); - } - - get(options?: GetNotificationOptions): Promise; - get(options: GetNotificationOptions, callback: GetNotificationCallback): void; - get(callback: GetNotificationCallback): void; - /** - * Get a notification and its metadata if it exists. - * - * See {@link https://cloud.google.com/storage/docs/json_api/v1/notifications/get| Notifications: get API Documentation} - * - * @param {object} [options] Configuration options. - * See {@link Bucket#createNotification} for create options. - * @param {boolean} [options.autoCreate] Automatically create the object if - * it does not exist. Default: `false`. - * @param {string} [options.userProject] The ID of the project which will be - * billed for the request. - * @param {GetNotificationCallback} [callback] Callback function. - * @return {Promise} - * - * @example - * ``` - * const {Storage} = require('@google-cloud/storage'); - * const storage = new Storage(); - * const myBucket = storage.bucket('my-bucket'); - * const notification = myBucket.notification('1'); - * - * notification.get(function(err, notification, apiResponse) { - * // `notification.metadata` has been populated. - * }); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * notification.get().then(function(data) { - * const notification = data[0]; - * const apiResponse = data[1]; - * }); - * ``` - */ - get( - optionsOrCallback?: GetNotificationOptions | GetNotificationCallback, - callback?: GetNotificationCallback - ): void | Promise { - const options = - typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; - callback = - typeof optionsOrCallback === 'function' ? optionsOrCallback : callback; - - const autoCreate = options.autoCreate; - delete options.autoCreate; - - const onCreate = ( - err: ApiError | null, - notification: Notification, - apiResponse: Metadata - ) => { - if (err) { - if (err.code === 409) { - this.get(options, callback!); - return; - } - - callback!(err, null, apiResponse); - return; - } - - callback!(null, notification, apiResponse); - }; - - this.getMetadata(options, (err, metadata) => { - if (err) { - if ((err as ApiError).code === 404 && autoCreate) { - const args = [] as object[]; - - if (Object.keys(options).length > 0) { - args.push(options); - } - - args.push(onCreate); - - // eslint-disable-next-line - this.create.apply(this, args as any); - return; - } - - callback!(err, null, metadata); - return; - } - - callback!(null, this, metadata); - }); - } - - getMetadata( - options?: GetNotificationMetadataOptions - ): Promise; - getMetadata( - options: GetNotificationMetadataOptions, - callback: MetadataCallback - ): void; - getMetadata(callback: MetadataCallback): void; - /** - * Get the notification's metadata. - * - * See {@link https://cloud.google.com/storage/docs/json_api/v1/notifications/get| Notifications: get API Documentation} - * - * @param {object} [options] Configuration options. - * @param {string} [options.userProject] The ID of the project which will be - * billed for the request. - * @param {GetNotificationMetadataCallback} [callback] Callback function. - * @returns {Promise} - * - * @example - * ``` - * const {Storage} = require('@google-cloud/storage'); - * const storage = new Storage(); - * const myBucket = storage.bucket('my-bucket'); - * const notification = myBucket.notification('1'); - * - * notification.getMetadata(function(err, metadata, apiResponse) {}); - * - * //- - * // If the callback is omitted, we'll return a Promise. - * //- - * notification.getMetadata().then(function(data) { - * const metadata = data[0]; - * const apiResponse = data[1]; - * }); - * - * ``` - * @example include:samples/getMetadataNotifications.js - * region_tag:storage_print_pubsub_bucket_notification - * Another example: - */ - getMetadata( - optionsOrCallback?: GetNotificationMetadataOptions | MetadataCallback, - callback?: MetadataCallback - ): void | Promise { - const options = - typeof optionsOrCallback === 'object' ? optionsOrCallback : {}; - callback = - typeof optionsOrCallback === 'function' ? optionsOrCallback : callback; - this.request( - { - uri: '', - qs: options, - }, - (err, resp) => { - if (err) { - callback!(err, null, resp); - return; - } - this.metadata = resp; - callback!(null, this.metadata, resp); - } - ); - } } /*! Developer Documentation diff --git a/src/resumable-upload.ts b/src/resumable-upload.ts index d7487f75e..521b6586f 100644 --- a/src/resumable-upload.ts +++ b/src/resumable-upload.ts @@ -14,7 +14,6 @@ import AbortController from 'abort-controller'; import {createHash} from 'crypto'; -import * as extend from 'extend'; import { GaxiosOptions, GaxiosPromise, @@ -38,6 +37,7 @@ export const PROTOCOL_REGEX = /^(\w*):\/\//; export interface ErrorWithCode extends Error { code: number; + status?: number | string; } export type CreateUriCallback = (err: Error | null, uri?: string) => void; @@ -963,12 +963,15 @@ export class Upload extends Writable { ); }; - const combinedReqOpts = extend( - true, - {}, - this.customRequestOptions, - reqOpts - ); + const combinedReqOpts = { + ...this.customRequestOptions, + ...reqOpts, + headers: { + ...this.customRequestOptions.headers, + ...reqOpts.headers, + }, + }; + const res = await this.authClient.request<{error?: object}>( combinedReqOpts ); @@ -990,12 +993,14 @@ export class Upload extends Writable { reqOpts.signal = controller.signal; reqOpts.validateStatus = () => true; - const combinedReqOpts = extend( - true, - {}, - this.customRequestOptions, - reqOpts - ); + const combinedReqOpts = { + ...this.customRequestOptions, + ...reqOpts, + headers: { + ...this.customRequestOptions.headers, + ...reqOpts.headers, + }, + }; const res = await this.authClient.request(combinedReqOpts); const successfulRequest = this.onResponse(res); this.removeListener('error', errorCallback); diff --git a/src/storage.ts b/src/storage.ts index ba77ee164..3996132bb 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {ApiError, Metadata, Service, ServiceOptions} from './nodejs-common'; +import {ApiError, Service, ServiceOptions} from './nodejs-common'; import {paginator} from '@google-cloud/paginator'; import {promisifyAll} from '@google-cloud/promisify'; import {Readable} from 'stream'; -import {Bucket} from './bucket'; +import {Bucket, BucketMetadata} from './bucket'; import {Channel} from './channel'; import {File} from './file'; import {normalize} from './util'; @@ -33,12 +33,12 @@ export interface GetServiceAccountOptions { export interface ServiceAccount { emailAddress?: string; } -export type GetServiceAccountResponse = [ServiceAccount, Metadata]; +export type GetServiceAccountResponse = [ServiceAccount, unknown]; export interface GetServiceAccountCallback { ( err: Error | null, serviceAccount?: ServiceAccount, - apiResponse?: Metadata + apiResponse?: unknown ): void; } @@ -64,10 +64,10 @@ export interface RetryOptions { } export interface PreconditionOptions { - ifGenerationMatch?: number; - ifGenerationNotMatch?: number; - ifMetagenerationMatch?: number; - ifMetagenerationNotMatch?: number; + ifGenerationMatch?: number | string; + ifGenerationNotMatch?: number | string; + ifMetagenerationMatch?: number | string; + ifMetagenerationNotMatch?: number | string; } export interface StorageOptions extends ServiceOptions { @@ -130,19 +130,19 @@ export interface CreateBucketRequest { versioning?: Versioning; } -export type CreateBucketResponse = [Bucket, Metadata]; +export type CreateBucketResponse = [Bucket, unknown]; export interface BucketCallback { - (err: Error | null, bucket?: Bucket | null, apiResponse?: Metadata): void; + (err: Error | null, bucket?: Bucket | null, apiResponse?: unknown): void; } -export type GetBucketsResponse = [Bucket[], {}, Metadata]; +export type GetBucketsResponse = [Bucket[], {}, unknown]; export interface GetBucketsCallback { ( err: Error | null, buckets: Bucket[], nextQuery?: {}, - apiResponse?: Metadata + apiResponse?: unknown ): void; } export interface GetBucketsRequest { @@ -192,14 +192,13 @@ export interface GetHmacKeysCallback { err: Error | null, hmacKeys: HmacKey[] | null, nextQuery?: {}, - apiResponse?: Metadata + apiResponse?: unknown ): void; } export enum ExceptionMessages { EXPIRATION_DATE_INVALID = 'The expiration date provided was invalid.', EXPIRATION_DATE_PAST = 'An expiration date cannot be in the past.', - INVALID_ACTION = 'The action is not provided or invalid.', } export enum StorageExceptionMessages { @@ -1156,7 +1155,7 @@ export class Storage extends Service { } const metadata = resp.metadata; - const hmacKey = this.hmacKey(metadata.accessId, { + const hmacKey = this.hmacKey(metadata.accessId!, { projectId: metadata.projectId, }); hmacKey.metadata = resp.metadata; @@ -1276,8 +1275,8 @@ export class Storage extends Service { } const itemsArray = resp.items ? resp.items : []; - const buckets = itemsArray.map((bucket: Metadata) => { - const bucketInstance = this.bucket(bucket.id); + const buckets = itemsArray.map((bucket: BucketMetadata) => { + const bucketInstance = this.bucket(bucket.id!); bucketInstance.metadata = bucket; return bucketInstance; }); @@ -1399,7 +1398,7 @@ export class Storage extends Service { const itemsArray = resp.items ? resp.items : []; const hmacKeys = itemsArray.map((hmacKey: HmacKeyMetadata) => { - const hmacKeyInstance = this.hmacKey(hmacKey.accessId, { + const hmacKeyInstance = this.hmacKey(hmacKey.accessId!, { projectId: hmacKey.projectId, }); hmacKeyInstance.metadata = hmacKey; diff --git a/src/transfer-manager.ts b/src/transfer-manager.ts index deab9082d..550fb8aec 100644 --- a/src/transfer-manager.ts +++ b/src/transfer-manager.ts @@ -18,7 +18,6 @@ import {Bucket, UploadOptions, UploadResponse} from './bucket'; import {DownloadOptions, DownloadResponse, File} from './file'; import * as pLimit from 'p-limit'; import * as path from 'path'; -import * as extend from 'extend'; import {createReadStream, promises as fsp} from 'fs'; import {CRC32C} from './crc32c'; import {GoogleAuth} from 'google-auth-library'; @@ -257,7 +256,7 @@ class XMLMultiPartUploadHelper implements MultiPartUploadHelper { const sortedMap = new Map( [...this.partsMap.entries()].sort((a, b) => a[0] - b[0]) ); - const parts = []; + const parts: {}[] = []; for (const entry of sortedMap.entries()) { parts.push({PartNumber: entry[0], ETag: entry[1]}); } @@ -375,7 +374,7 @@ export class TransferManager { const limit = pLimit( options.concurrencyLimit || DEFAULT_PARALLEL_UPLOAD_LIMIT ); - const promises = []; + const promises: Promise[] = []; let allPaths: string[] = []; if (!Array.isArray(filePathsOrDirectory)) { for await (const curPath of this.getPathsFromDirectory( @@ -392,11 +391,11 @@ export class TransferManager { if (stat.isDirectory()) { continue; } - const passThroughOptionsCopy: UploadOptions = extend( - true, - {}, - options.passthroughOptions - ); + + const passThroughOptionsCopy: UploadOptions = { + ...options.passthroughOptions, + }; + passThroughOptionsCopy.destination = filePath; if (options.prefix) { passThroughOptionsCopy.destination = path.join( @@ -405,7 +404,9 @@ export class TransferManager { ); } promises.push( - limit(() => this.bucket.upload(filePath, passThroughOptionsCopy)) + limit(() => + this.bucket.upload(filePath, passThroughOptionsCopy as UploadOptions) + ) ); } @@ -461,7 +462,7 @@ export class TransferManager { const limit = pLimit( options.concurrencyLimit || DEFAULT_PARALLEL_DOWNLOAD_LIMIT ); - const promises = []; + const promises: Promise[] = []; let files: File[] = []; if (!Array.isArray(filesOrFolder)) { @@ -484,11 +485,10 @@ export class TransferManager { const regex = new RegExp(stripRegexString, 'g'); for (const file of files) { - const passThroughOptionsCopy = extend( - true, - {}, - options.passthroughOptions - ); + const passThroughOptionsCopy = { + ...options.passthroughOptions, + }; + if (options.prefix) { passThroughOptionsCopy.destination = path.join( options.prefix || '', @@ -553,7 +553,7 @@ export class TransferManager { : fileOrName; const fileInfo = await file.get(); - const size = parseInt(fileInfo[0].metadata.size); + const size = parseInt(fileInfo[0].metadata.size!.toString()); // If the file size does not meet the threshold download it as a single chunk. if (size < DOWNLOAD_IN_CHUNKS_FILE_SIZE_THRESHOLD) { limit = pLimit(1); @@ -662,7 +662,7 @@ export class TransferManager { options.partsMap ); let partNumber = 1; - let promises = []; + let promises: Promise[] = []; try { if (options.uploadId === undefined) { await mpuHelper.initiateUpload(); diff --git a/system-test/kitchen.ts b/system-test/kitchen.ts index 8d687ecc2..0126b81f8 100644 --- a/system-test/kitchen.ts +++ b/system-test/kitchen.ts @@ -174,7 +174,8 @@ describe('resumable-upload', () => { }) ) .on('error', (err: ErrorWithCode) => { - assert.strictEqual(err.code, '400'); + console.log(err); + assert.strictEqual(err.status, 400); done(); }); }); diff --git a/system-test/storage.ts b/system-test/storage.ts index 90014aac6..5dcc62834 100644 --- a/system-test/storage.ts +++ b/system-test/storage.ts @@ -18,7 +18,7 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import fetch from 'node-fetch'; import * as FormData from 'form-data'; -import pLimit = require('p-limit'); +import * as pLimit from 'p-limit'; import {promisify} from 'util'; import * as path from 'path'; import * as tmp from 'tmp'; @@ -426,10 +426,9 @@ describe('storage', () => { resumable: false, }); const [metadata] = await file.getMetadata(); - assert.strictEqual( - metadata.customerEncryption.encryptionAlgorithm, - 'AES256' - ); + const encyrptionAlgorithm = + metadata.customerEncryption?.encryptionAlgorithm; + assert.strictEqual(encyrptionAlgorithm, 'AES256'); }); it('should set custom encryption in a resumable upload', async () => { @@ -439,10 +438,9 @@ describe('storage', () => { resumable: true, }); const [metadata] = await file.getMetadata(); - assert.strictEqual( - metadata.customerEncryption.encryptionAlgorithm, - 'AES256' - ); + const encyrptionAlgorithm = + metadata.customerEncryption?.encryptionAlgorithm; + assert.strictEqual(encyrptionAlgorithm, 'AES256'); }); it('should make a file public during the upload', async () => { @@ -631,7 +629,7 @@ describe('storage', () => { ); const [bucketMetadata] = await bucket.getMetadata(); const publicAccessPreventionStatus = - bucketMetadata.iamConfiguration.publicAccessPrevention; + bucketMetadata!.iamConfiguration!.publicAccessPrevention; return assert.strictEqual( publicAccessPreventionStatus, PUBLIC_ACCESS_PREVENTION_ENFORCED @@ -680,7 +678,7 @@ describe('storage', () => { ); const [bucketMetadata] = await bucket.getMetadata(); const publicAccessPreventionStatus = - bucketMetadata.iamConfiguration.publicAccessPrevention; + bucketMetadata!.iamConfiguration!.publicAccessPrevention; return assert.strictEqual( publicAccessPreventionStatus, PUBLIC_ACCESS_PREVENTION_INHERITED @@ -705,7 +703,7 @@ describe('storage', () => { it('UBLA modification on PAP bucket does not affect pap setting', async () => { const [bucketMetadata] = await bucket.getMetadata(); const publicAccessPreventionStatus = - bucketMetadata.iamConfiguration.publicAccessPrevention; + bucketMetadata!.iamConfiguration!.publicAccessPrevention; await bucket.setMetadata({ iamConfiguration: { uniformBucketLevelAccess: { @@ -715,7 +713,7 @@ describe('storage', () => { }); const [updatedBucketMetadata] = await bucket.getMetadata(); return assert.strictEqual( - updatedBucketMetadata.iamConfiguration.publicAccessPrevention, + updatedBucketMetadata!.iamConfiguration!.publicAccessPrevention, publicAccessPreventionStatus ); }); @@ -730,14 +728,15 @@ describe('storage', () => { }); const [bucketMetadata] = await bucket.getMetadata(); const ublaSetting = - bucketMetadata.iamConfiguration.uniformBucketLevelAccess.enabled; + bucketMetadata!.iamConfiguration!.uniformBucketLevelAccess!.enabled; await setPublicAccessPrevention( bucket, PUBLIC_ACCESS_PREVENTION_INHERITED ); const [updatedBucketMetadata] = await bucket.getMetadata(); return assert.strictEqual( - updatedBucketMetadata.iamConfiguration.uniformBucketLevelAccess.enabled, + updatedBucketMetadata!.iamConfiguration!.uniformBucketLevelAccess! + .enabled, ublaSetting ); }); @@ -1062,33 +1061,33 @@ describe('storage', () => { }, }); let [metadata] = await bucket.getMetadata(); - const timestampEnabled = metadata.autoclass.toggleTime; - assert.strictEqual(metadata.autoclass.enabled, true); + const timestampEnabled = metadata!.autoclass!.toggleTime; + assert.strictEqual(metadata!.autoclass!.enabled, true); [metadata] = await bucket.setMetadata({ autoclass: { enabled: false, }, }); - const timestampDisabled = metadata.autoclass.toggleTime; - assert.strictEqual(metadata.autoclass.enabled, false); - assert.strictEqual(timestampDisabled > timestampEnabled, true); + const timestampDisabled = metadata!.autoclass!.toggleTime; + assert.strictEqual(metadata!.autoclass!.enabled, false); + assert.strictEqual(timestampDisabled! > timestampEnabled!, true); }); describe('locationType', () => { const types = ['multi-region', 'region', 'dual-region']; beforeEach(() => { - delete bucket.metadata; + bucket.metadata = {}; }); it('should be available from getting a bucket', async () => { const [metadata] = await bucket.getMetadata(); - assert(types.includes(metadata.locationType)); + assert(types.includes(metadata.locationType!)); }); it('should be available from creating a bucket', async () => { const [bucket] = await storage.createBucket(generateName()); - assert(types.includes(bucket.metadata.locationType)); + assert(types.includes(bucket.metadata.locationType!)); return bucket.delete(); }); @@ -1098,19 +1097,19 @@ describe('storage', () => { assert(buckets.length > 0); buckets.forEach(bucket => { - assert(types.includes(bucket.metadata.locationType)); + assert(types.includes(bucket.metadata.locationType!)); }); }); it('should be available from setting retention policy', async () => { await bucket.setRetentionPeriod(RETENTION_DURATION_SECONDS); - assert(types.includes(bucket.metadata.locationType)); + assert(types.includes(bucket.metadata.locationType!)); await bucket.removeRetentionPeriod(); }); it('should be available from updating a bucket', async () => { - await bucket.setLabels({a: 'b'}); - assert(types.includes(bucket.metadata.locationType)); + await bucket.setMetadata({labels: {a: 'b'}}); + assert(types.includes(bucket.metadata.locationType!)); }); }); @@ -1121,23 +1120,33 @@ describe('storage', () => { }; beforeEach(async () => { - await bucket.deleteLabels(); + const [metadata] = await bucket.getMetadata(); + const labels: {[index: string]: string | null} = {}; + if (metadata.labels) { + for (const curLabel of Object.keys(metadata.labels)) { + labels[curLabel] = null; + } + await bucket.setMetadata({labels}); + } }); it('should set labels', async () => { - await bucket.setLabels(LABELS); - const [labels] = await bucket.getLabels(); - assert.deepStrictEqual(labels, LABELS); + await bucket.setMetadata({labels: LABELS}); + const [metadata] = await bucket.getMetadata(); + assert.deepStrictEqual(metadata.labels, LABELS); }); it('should update labels', async () => { const newLabels = { siblinglabel: 'labelvalue', }; - await bucket.setLabels(LABELS); - await bucket.setLabels(newLabels); - const [labels] = await bucket.getLabels(); - assert.deepStrictEqual(labels, Object.assign({}, LABELS, newLabels)); + await bucket.setMetadata({labels: LABELS}); + await bucket.setMetadata({labels: newLabels}); + const [metadata] = await bucket.getMetadata(); + assert.deepStrictEqual( + metadata.labels, + Object.assign({}, LABELS, newLabels) + ); }); it('should delete a single label', async () => { @@ -1146,19 +1155,29 @@ describe('storage', () => { } const labelKeyToDelete = Object.keys(LABELS)[0]; - await bucket.setLabels(LABELS); - await bucket.deleteLabels(labelKeyToDelete); - const [labels] = await bucket.getLabels(); + await bucket.setMetadata({labels: LABELS}); + const labelsToDelete = { + [labelKeyToDelete]: null, + }; + await bucket.setMetadata({labels: labelsToDelete}); + const [metadata] = await bucket.getMetadata(); const expectedLabels = Object.assign({}, LABELS); delete (expectedLabels as {[index: string]: {}})[labelKeyToDelete]; - assert.deepStrictEqual(labels, expectedLabels); + assert.deepStrictEqual(metadata.labels, expectedLabels); }); it('should delete all labels', async () => { - await bucket.deleteLabels(); - const [labels] = await bucket.getLabels(); - assert.deepStrictEqual(labels, {}); + let [metadata] = await bucket.getMetadata(); + if (metadata.labels) { + const labels: {[index: string]: string | null} = {}; + for (const curLabel of Object.keys(metadata.labels)) { + labels[curLabel] = null; + } + await bucket.setMetadata({labels}); + } + [metadata] = await bucket.getMetadata(); + assert.deepStrictEqual(metadata.labels, undefined); }); }); }); @@ -1166,14 +1185,16 @@ describe('storage', () => { describe('bucket object lifecycle management', () => { it('should add a rule', async () => { await bucket.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { age: 30, isLive: true, }, }); - const rules = [].slice.call(bucket.metadata.lifecycle.rule); + const rules = [].slice.call(bucket.metadata.lifecycle?.rule); assert.deepStrictEqual(rules.pop(), { action: { type: 'Delete', @@ -1187,40 +1208,46 @@ describe('storage', () => { it('should append a new rule', async () => { const numExistingRules = - (bucket.metadata.lifecycle && bucket.metadata.lifecycle.rule.length) || + (bucket.metadata.lifecycle && bucket.metadata.lifecycle.rule!.length) || 0; await bucket.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { age: 30, isLive: true, }, }); await bucket.addLifecycleRule({ - action: 'setStorageClass', + action: { + type: 'SetStorageClass', + storageClass: 'coldline', + }, condition: { age: 60, isLive: true, }, - storageClass: 'coldline', }); assert.strictEqual( - bucket.metadata.lifecycle.rule.length, + bucket.metadata.lifecycle!.rule!.length, numExistingRules + 2 ); }); it('should add a prefix rule', async () => { await bucket.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { matchesPrefix: [TESTS_PREFIX], }, }); assert( - bucket.metadata.lifecycle.rule.some( + bucket.metadata.lifecycle!.rule!.some( (rule: LifecycleRule) => typeof rule.action === 'object' && rule.action.type === 'Delete' && @@ -1233,14 +1260,16 @@ describe('storage', () => { it('should add a suffix rule', async () => { await bucket.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { matchesSuffix: [TESTS_PREFIX, 'test_suffix'], }, }); assert( - bucket.metadata.lifecycle.rule.some( + bucket.metadata.lifecycle!.rule!.some( (rule: LifecycleRule) => typeof rule.action === 'object' && rule.action.type === 'Delete' && @@ -1251,12 +1280,14 @@ describe('storage', () => { it('should convert a rule with createdBefore to a date in string', async () => { await bucket.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { createdBefore: new Date('2018'), }, }); - const rules = [].slice.call(bucket.metadata.lifecycle.rule); + const rules = [].slice.call(bucket.metadata.lifecycle?.rule); assert.deepStrictEqual(rules.pop(), { action: { type: 'Delete', @@ -1271,7 +1302,9 @@ describe('storage', () => { const NONCURRENT_TIME_BEFORE = '2020-01-01'; await bucket.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { noncurrentTimeBefore: new Date(NONCURRENT_TIME_BEFORE), daysSinceNoncurrentTime: 100, @@ -1279,7 +1312,7 @@ describe('storage', () => { }); assert( - bucket.metadata.lifecycle.rule.some( + bucket.metadata.lifecycle!.rule!.some( (rule: LifecycleRule) => typeof rule.action === 'object' && rule.action.type === 'Delete' && @@ -1293,7 +1326,9 @@ describe('storage', () => { const CUSTOM_TIME_BEFORE = '2020-01-01'; await bucket.addLifecycleRule({ - action: 'delete', + action: { + type: 'Delete', + }, condition: { customTimeBefore: new Date(CUSTOM_TIME_BEFORE), daysSinceCustomTime: 100, @@ -1301,7 +1336,7 @@ describe('storage', () => { }); assert( - bucket.metadata.lifecycle.rule.some( + bucket.metadata.lifecycle!.rule!.some( (rule: LifecycleRule) => typeof rule.action === 'object' && rule.action.type === 'Delete' && @@ -1376,7 +1411,7 @@ describe('storage', () => { }, }); await bucket.getMetadata(); - assert.strictEqual(bucket.metadata.versioning.enabled, true); + assert.strictEqual(bucket.metadata!.versioning!.enabled, true); }); it('should by default create a bucket without versioning set', async () => { @@ -1399,7 +1434,7 @@ describe('storage', () => { }); await bucket.getMetadata(); assert.strictEqual( - bucket.metadata.retentionPolicy.retentionPeriod, + bucket.metadata!.retentionPolicy!.retentionPeriod, `${RETENTION_DURATION_SECONDS}` ); }); @@ -1410,7 +1445,7 @@ describe('storage', () => { await bucket.setRetentionPeriod(RETENTION_DURATION_SECONDS); await bucket.getMetadata(); assert.strictEqual( - bucket.metadata.retentionPolicy.retentionPeriod, + bucket.metadata!.retentionPolicy!.retentionPeriod, `${RETENTION_DURATION_SECONDS}` ); }); @@ -1421,7 +1456,7 @@ describe('storage', () => { await bucket.setRetentionPeriod(RETENTION_DURATION_SECONDS); await bucket.getMetadata(); - await bucket.lock(bucket.metadata.metageneration); + await bucket.lock(bucket.metadata!.metageneration!.toString()); await assert.rejects( bucket.setRetentionPeriod(RETENTION_DURATION_SECONDS / 2), (err: ApiError) => { @@ -1436,7 +1471,7 @@ describe('storage', () => { await bucket.setRetentionPeriod(RETENTION_DURATION_SECONDS); await bucket.getMetadata(); assert.strictEqual( - bucket.metadata.retentionPolicy.retentionPeriod, + bucket.metadata!.retentionPolicy!.retentionPeriod, `${RETENTION_DURATION_SECONDS}` ); @@ -1586,7 +1621,7 @@ describe('storage', () => { it('should have enabled requesterPays functionality', async () => { const [metadata] = await bucket.getMetadata(); - assert.strictEqual(metadata.billing.requesterPays, true); + assert.strictEqual(metadata.billing!.requesterPays, true); }); // These tests will verify that the requesterPays functionality works from @@ -2099,7 +2134,7 @@ describe('storage', () => { bucket.upload(FILES.big.path, (err: Error | null, file?: File | null) => { assert.ifError(err); - const fileSize = file!.metadata.size; + const fileSize = parseInt(file!.metadata.size!.toString()); const byteRange = { start: Math.floor((fileSize * 1) / 3), end: Math.floor((fileSize * 2) / 3), @@ -2494,7 +2529,7 @@ describe('storage', () => { // Strip the project ID, as it could be the placeholder locally, but // the real value upstream. const projectIdRegExp = /^.+\/locations/; - const actualKmsKeyName = metadata.kmsKeyName.replace( + const actualKmsKeyName = metadata!.kmsKeyName!.replace( projectIdRegExp, '' ); @@ -2514,7 +2549,7 @@ describe('storage', () => { // Strip the project ID, as it could be the placeholder locally, // but the real value upstream. const projectIdRegExp = /^.+\/locations/; - const actualKmsKeyName = metadata.kmsKeyName.replace( + const actualKmsKeyName = metadata!.kmsKeyName!.replace( projectIdRegExp, '' ); @@ -2580,7 +2615,10 @@ describe('storage', () => { // the real value upstream. const projectIdRegExp = /^.+\/locations/; const actualKmsKeyName = - metadata.encryption.defaultKmsKeyName.replace(projectIdRegExp, ''); + metadata!.encryption!.defaultKmsKeyName!.replace( + projectIdRegExp, + '' + ); const expectedKmsKeyName = kmsKeyName.replace(projectIdRegExp, ''); assert.strictEqual(actualKmsKeyName, expectedKmsKeyName); }); @@ -2608,7 +2646,7 @@ describe('storage', () => { assert.strictEqual( fileMetadata.kmsKeyName, - `${metadata.encryption.defaultKmsKeyName}/cryptoKeyVersions/1` + `${metadata!.encryption!.defaultKmsKeyName}/cryptoKeyVersions/1` ); }); }); @@ -2635,10 +2673,10 @@ describe('storage', () => { const [copiedFile] = await file.copy('CloudLogoCopy', copyOpts); const [metadata] = await copiedFile.getMetadata(); assert.strictEqual( - typeof metadata.metadata.originalProperty, + typeof metadata!.metadata!.originalProperty, 'undefined' ); - assert.strictEqual(metadata.metadata.newProperty, 'true'); + assert.strictEqual(metadata!.metadata!.newProperty, 'true'); await Promise.all([file.delete, copiedFile.delete()]); }); @@ -3637,7 +3675,7 @@ describe('storage', () => { const [metadata] = await file.getMetadata(); assert.equal(metadata.crc32c, expected); - assert(crc32c.validate(metadata.crc32c)); + assert(crc32c.validate(metadata.crc32c!)); } }); }); diff --git a/test/acl.ts b/test/acl.ts index 9339b6377..bee7fe2e4 100644 --- a/test/acl.ts +++ b/test/acl.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {DecorateRequestOptions, Metadata, util} from '../src/nodejs-common'; +import {DecorateRequestOptions, util} from '../src/nodejs-common'; import * as assert from 'assert'; import {describe, it, before, beforeEach} from 'mocha'; import * as proxyquire from 'proxyquire'; @@ -147,7 +147,7 @@ describe('storage/acl', () => { acl.add( {entity: ENTITY, role: ROLE}, - (err: Error, acls: {}, apiResponse: Metadata) => { + (err: Error, acls: {}, apiResponse: unknown) => { assert.deepStrictEqual(resp, apiResponse); done(); } @@ -214,7 +214,7 @@ describe('storage/acl', () => { callback(null, resp); }; - acl.delete({entity: ENTITY}, (err: Error, apiResponse: Metadata) => { + acl.delete({entity: ENTITY}, (err: Error, apiResponse: unknown) => { assert.deepStrictEqual(resp, apiResponse); done(); }); @@ -351,7 +351,7 @@ describe('storage/acl', () => { callback(null, resp); }; - acl.get((err: Error, acls: Array<{}>, apiResponse: Metadata) => { + acl.get((err: Error, acls: Array<{}>, apiResponse: unknown) => { assert.deepStrictEqual(resp, apiResponse); done(); }); @@ -441,7 +441,7 @@ describe('storage/acl', () => { const config = {entity: ENTITY, role: ROLE}; acl.update( config, - (err: Error, acls: Array<{}>, apiResponse: Metadata) => { + (err: Error, acls: Array<{}>, apiResponse: unknown) => { assert.deepStrictEqual(resp, apiResponse); done(); } diff --git a/test/bucket.ts b/test/bucket.ts index 3951a1c52..164cd99f2 100644 --- a/test/bucket.ts +++ b/test/bucket.ts @@ -13,14 +13,13 @@ // limitations under the License. import { + BaseMetadata, DecorateRequestOptions, - Metadata, ServiceObject, ServiceObjectConfig, util, } from '../src/nodejs-common'; import * as assert from 'assert'; -import * as extend from 'extend'; import * as fs from 'fs'; import {describe, it, before, beforeEach, after, afterEach} from 'mocha'; import * as mime from 'mime-types'; @@ -35,6 +34,7 @@ import { File, SetFileMetadataOptions, FileOptions, + FileMetadata, } from '../src/file'; import {PromisifyAllOptions} from '@google-cloud/promisify'; import { @@ -45,12 +45,14 @@ import { GetBucketSignedUrlConfig, AvailableServiceObjectMethods, BucketExceptionMessages, + BucketMetadata, + LifecycleRule, } from '../src/bucket'; import {AddAclOptions} from '../src/acl'; import {Policy} from '../src/iam'; import sinon = require('sinon'); import {Transform} from 'stream'; -import {ExceptionMessages, IdempotencyStrategy} from '../src/storage'; +import {IdempotencyStrategy} from '../src/storage'; import {convertObjKeysToSnakeCase} from '../src/util'; class FakeFile { @@ -58,7 +60,7 @@ class FakeFile { bucket: Bucket; name: string; options: FileOptions; - metadata: {}; + metadata: FileMetadata; createWriteStream: Function; delete: Function; isSameFile = () => false; @@ -71,7 +73,7 @@ class FakeFile { this.metadata = {}; this.createWriteStream = (options: CreateWriteStreamOptions) => { - this.metadata = options.metadata; + this.metadata = options.metadata!; const ws = new stream.Writable(); ws.write = () => { ws.emit('complete'); @@ -97,11 +99,12 @@ class FakeNotification { } let fsStatOverride: Function | null; -const fakeFs = extend(true, {}, fs, { +const fakeFs = { + ...fs, stat: (filePath: string, callback: Function) => { return (fsStatOverride || fs.stat)(filePath, callback); }, -}); +}; let pLimitOverride: Function | null; const fakePLimit = (limit: number) => (pLimitOverride || pLimit)(limit); @@ -160,7 +163,7 @@ class FakeIam { } } -class FakeServiceObject extends ServiceObject { +class FakeServiceObject extends ServiceObject { calledWith_: IArguments; constructor(config: ServiceObjectConfig) { super(config); @@ -470,54 +473,8 @@ describe('Bucket', () => { condition: {}, }; - bucket.setMetadata = (metadata: Metadata) => { - assert.deepStrictEqual(metadata.lifecycle.rule, [rule]); - done(); - }; - - bucket.addLifecycleRule(rule, assert.ifError); - }); - - it('should properly capitalize rule action', done => { - const rule = { - action: 'delete', - condition: {}, - }; - - bucket.setMetadata = (metadata: Metadata) => { - assert.deepStrictEqual(metadata.lifecycle.rule, [ - { - action: { - type: rule.action.charAt(0).toUpperCase() + rule.action.slice(1), - }, - condition: rule.condition, - }, - ]); - - done(); - }; - - bucket.addLifecycleRule(rule, assert.ifError); - }); - - it('should properly set the storage class', done => { - const rule = { - action: 'setStorageClass', - storageClass: 'storage class', - condition: {}, - }; - - bucket.setMetadata = (metadata: Metadata) => { - assert.deepStrictEqual(metadata.lifecycle.rule, [ - { - action: { - type: rule.action.charAt(0).toUpperCase() + rule.action.slice(1), - storageClass: rule.storageClass, - }, - condition: rule.condition, - }, - ]); - + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.deepStrictEqual(metadata.lifecycle!.rule, [rule]); done(); }; @@ -526,17 +483,19 @@ describe('Bucket', () => { it('should properly set condition', done => { const rule = { - action: 'delete', + action: { + type: 'Delete', + }, condition: { age: 30, }, }; - bucket.setMetadata = (metadata: Metadata) => { - assert.deepStrictEqual(metadata.lifecycle.rule, [ + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.deepStrictEqual(metadata.lifecycle?.rule, [ { action: { - type: rule.action.charAt(0).toUpperCase() + rule.action.slice(1), + type: 'Delete', }, condition: rule.condition, }, @@ -551,16 +510,18 @@ describe('Bucket', () => { const date = new Date(); const rule = { - action: 'delete', + action: { + type: 'Delete', + }, condition: { createdBefore: date, }, }; - bucket.setMetadata = (metadata: Metadata) => { + bucket.setMetadata = (metadata: BucketMetadata) => { const expectedDateString = date.toISOString().replace(/T.+$/, ''); - const rule = metadata.lifecycle.rule[0]; + const rule = metadata!.lifecycle!.rule![0]; assert.strictEqual(rule.condition.createdBefore, expectedDateString); done(); @@ -585,9 +546,9 @@ describe('Bucket', () => { done(new Error('Metadata should not be refreshed.')); }; - bucket.setMetadata = (metadata: Metadata) => { - assert.strictEqual(metadata.lifecycle.rule.length, 1); - assert.deepStrictEqual(metadata.lifecycle.rule, [rule]); + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.strictEqual(metadata!.lifecycle!.rule!.length, 1); + assert.deepStrictEqual(metadata.lifecycle?.rule, [rule]); done(); }; @@ -595,16 +556,16 @@ describe('Bucket', () => { }); it('should combine rule with existing rules by default', done => { - const existingRule = { + const existingRule: LifecycleRule = { action: { - type: 'type', + type: 'Delete', }, condition: {}, }; - const newRule = { + const newRule: LifecycleRule = { action: { - type: 'type', + type: 'Delete', }, condition: {}, }; @@ -613,9 +574,9 @@ describe('Bucket', () => { callback(null, {lifecycle: {rule: [existingRule]}}, {}); }; - bucket.setMetadata = (metadata: Metadata) => { - assert.strictEqual(metadata.lifecycle.rule.length, 2); - assert.deepStrictEqual(metadata.lifecycle.rule, [ + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.strictEqual(metadata!.lifecycle!.rule!.length, 2); + assert.deepStrictEqual(metadata.lifecycle?.rule, [ existingRule, newRule, ]); @@ -626,23 +587,23 @@ describe('Bucket', () => { }); it('should accept multiple rules', done => { - const existingRule = { + const existingRule: LifecycleRule = { action: { - type: 'type', + type: 'Delete', }, condition: {}, }; - const newRules = [ + const newRules: LifecycleRule[] = [ { action: { - type: 'type', + type: 'Delete', }, condition: {}, }, { action: { - type: 'type2', + type: 'Delete', }, condition: {}, }, @@ -652,9 +613,9 @@ describe('Bucket', () => { callback(null, {lifecycle: {rule: [existingRule]}}, {}); }; - bucket.setMetadata = (metadata: Metadata) => { - assert.strictEqual(metadata.lifecycle.rule.length, 3); - assert.deepStrictEqual(metadata.lifecycle.rule, [ + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.strictEqual(metadata!.lifecycle!.rule!.length, 3); + assert.deepStrictEqual(metadata.lifecycle?.rule, [ existingRule, newRules[0], newRules[1], @@ -962,12 +923,6 @@ describe('Bucket', () => { }); }); - it('should throw if an address is not provided', () => { - assert.throws(() => { - bucket.createChannel(ID, {}); - }, /An address is required to create a channel\./); - }); - it('should make the correct request', done => { const config = Object.assign({}, CONFIG, { a: 'b', @@ -1649,7 +1604,7 @@ describe('Bucket', () => { }); it('should update the logging metadata configuration', done => { - bucket.setMetadata = (metadata: Metadata) => { + bucket.setMetadata = (metadata: BucketMetadata) => { assert.deepStrictEqual(metadata.logging, { logBucket: bucket.id, logObjectPrefix: PREFIX, @@ -1664,8 +1619,8 @@ describe('Bucket', () => { it('should allow a custom bucket to be provided', done => { const bucketName = 'bucket-name'; - bucket.setMetadata = (metadata: Metadata) => { - assert.deepStrictEqual(metadata.logging.logBucket, bucketName); + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.deepStrictEqual(metadata!.logging!.logBucket, bucketName); setImmediate(done); return Promise.resolve([]); }; @@ -1682,8 +1637,11 @@ describe('Bucket', () => { it('should accept a Bucket object', done => { const bucketForLogging = new Bucket(STORAGE, 'bucket-name'); - bucket.setMetadata = (metadata: Metadata) => { - assert.deepStrictEqual(metadata.logging.logBucket, bucketForLogging.id); + bucket.setMetadata = (metadata: BucketMetadata) => { + assert.deepStrictEqual( + metadata!.logging!.logBucket, + bucketForLogging.id + ); setImmediate(done); return Promise.resolve([]); }; @@ -2172,37 +2130,6 @@ describe('Bucket', () => { } ); }); - - it('should error if action is null', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - SIGNED_URL_CONFIG.action = null as any; - - assert.throws(() => { - bucket.getSignedUrl(SIGNED_URL_CONFIG, () => {}), - ExceptionMessages.INVALID_ACTION; - }); - }); - - it('should error if action is undefined', () => { - const urlConfig = { - ...SIGNED_URL_CONFIG, - } as Partial; - delete urlConfig.action; - assert.throws(() => { - bucket.getSignedUrl(urlConfig, () => {}), - ExceptionMessages.INVALID_ACTION; - }); - }); - - it('should error for an invalid action', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - SIGNED_URL_CONFIG.action = 'watch' as any; - - assert.throws(() => { - bucket.getSignedUrl(SIGNED_URL_CONFIG, () => {}), - ExceptionMessages.INVALID_ACTION; - }); - }); }); describe('lock', () => { @@ -2513,7 +2440,7 @@ describe('Bucket', () => { it('should correctly call setMetadata', done => { const labels = {}; bucket.setMetadata = ( - metadata: Metadata, + metadata: BucketMetadata, _callbackOrOptions: {}, callback: Function ) => { @@ -2545,7 +2472,7 @@ describe('Bucket', () => { ) => { assert.deepStrictEqual(metadata, { retentionPolicy: { - retentionPeriod: duration, + retentionPeriod: `${duration}`, }, }); @@ -2582,7 +2509,7 @@ describe('Bucket', () => { const CALLBACK = util.noop; it('should convert camelCase to snake_case', done => { - bucket.setMetadata = (metadata: Metadata) => { + bucket.setMetadata = (metadata: BucketMetadata) => { assert.strictEqual(metadata.storageClass, 'CAMEL_CASE'); done(); }; @@ -2591,7 +2518,7 @@ describe('Bucket', () => { }); it('should convert hyphenate to snake_case', done => { - bucket.setMetadata = (metadata: Metadata) => { + bucket.setMetadata = (metadata: BucketMetadata) => { assert.strictEqual(metadata.storageClass, 'HYPHENATED_CLASS'); done(); }; @@ -2601,7 +2528,7 @@ describe('Bucket', () => { it('should call setMetdata correctly', () => { bucket.setMetadata = ( - metadata: Metadata, + metadata: BucketMetadata, options: {}, callback: Function ) => { @@ -2663,7 +2590,7 @@ describe('Bucket', () => { }; beforeEach(() => { - bucket.file = (name: string, metadata: Metadata) => { + bucket.file = (name: string, metadata: FileMetadata) => { return new FakeFile(bucket, name, metadata); }; }); @@ -2999,7 +2926,7 @@ describe('Bucket', () => { ws.write = () => true; setImmediate(() => { assert.strictEqual( - options.metadata.contentType, + options!.metadata!.contentType, metadata.contentType ); done(); diff --git a/test/channel.ts b/test/channel.ts index e14d4ed1f..b671f0ae2 100644 --- a/test/channel.ts +++ b/test/channel.ts @@ -17,6 +17,7 @@ */ import { + BaseMetadata, DecorateRequestOptions, ServiceObject, ServiceObjectConfig, @@ -34,7 +35,7 @@ const fakePromisify = { }, }; -class FakeServiceObject extends ServiceObject { +class FakeServiceObject extends ServiceObject { calledWith_: IArguments; constructor(config: ServiceObjectConfig) { super(config); diff --git a/test/file.ts b/test/file.ts index 14b493f22..aa050254e 100644 --- a/test/file.ts +++ b/test/file.ts @@ -16,7 +16,6 @@ import { ApiError, BodyResponseCallback, DecorateRequestOptions, - Metadata, MetadataCallback, ServiceObject, ServiceObjectConfig, @@ -28,7 +27,6 @@ import {Readable, PassThrough, Stream, Duplex, Transform} from 'stream'; import * as assert from 'assert'; import * as crypto from 'crypto'; import * as duplexify from 'duplexify'; -import * as extend from 'extend'; import * as fs from 'fs'; import * as proxyquire from 'proxyquire'; import * as resumableUpload from '../src/resumable-upload'; @@ -53,10 +51,14 @@ import { STORAGE_POST_POLICY_BASE_URL, MoveOptions, FileExceptionMessages, + FileMetadata, } from '../src/file'; import {ExceptionMessages, IdempotencyStrategy} from '../src/storage'; import {formatAsUTCISO} from '../src/util'; -import {SetMetadataOptions} from '../src/nodejs-common/service-object'; +import { + BaseMetadata, + SetMetadataOptions, +} from '../src/nodejs-common/service-object'; class HTTPError extends Error { code: number; @@ -105,20 +107,21 @@ const fakePromisify = { }, }; -const fsCached = extend(true, {}, fs); -const fakeFs = extend(true, {}, fsCached); +const fsCached = fs; +const fakeFs = {...fsCached}; -const zlibCached = extend(true, {}, zlib); +const zlibCached = zlib; let createGunzipOverride: Function | null; -const fakeZlib = extend(true, {}, zlib, { +const fakeZlib = { + ...zlib, createGunzip(...args: Array<{}>) { return (createGunzipOverride || zlibCached.createGunzip)(...args); }, -}); +}; // eslint-disable-next-line @typescript-eslint/no-var-requires -const osCached = extend(true, {}, require('os')); -const fakeOs = extend(true, {}, osCached); +const osCached = require('os'); +const fakeOs = {...osCached}; // eslint-disable-next-line @typescript-eslint/no-explicit-any let resumableUploadOverride: any; @@ -150,7 +153,7 @@ Object.assign(fakeResumableUpload, { }, }); -class FakeServiceObject extends ServiceObject { +class FakeServiceObject extends ServiceObject { calledWith_: IArguments; constructor(config: ServiceObjectConfig) { super(config); @@ -210,8 +213,8 @@ describe('File', () => { }); beforeEach(() => { - extend(true, fakeFs, fsCached); - extend(true, fakeOs, osCached); + Object.assign(fakeFs, fsCached); + Object.assign(fakeOs, osCached); // eslint-disable-next-line @typescript-eslint/no-explicit-any FakeServiceObject.prototype.request = util.noop as any; @@ -2084,19 +2087,6 @@ describe('File', () => { writable.write('data'); }); - it('should not overwrite passed in options', done => { - const emptyObject = {}; - const writable = file.createWriteStream(emptyObject); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - file.startResumableUpload_ = (stream: {}, options: any) => { - assert.strictEqual(options.metadata.contentType, 'image/png'); - assert.deepStrictEqual(emptyObject, {}); - done(); - }; - - writable.write('data'); - }); - it('should not set a contentType if mime lookup failed', done => { const file = new File('file-without-ext'); const writable = file.createWriteStream(); @@ -3562,35 +3552,6 @@ describe('File', () => { }); }); - it('should error if action is null', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - SIGNED_URL_CONFIG.action = null as any; - - assert.throws(() => { - file.getSignedUrl(SIGNED_URL_CONFIG, () => {}), - ExceptionMessages.INVALID_ACTION; - }); - }); - - it('should error if action is undefined', () => { - const urlConfig = {...SIGNED_URL_CONFIG} as Partial; - delete urlConfig.action; - assert.throws(() => { - file.getSignedUrl(urlConfig, () => {}), - ExceptionMessages.INVALID_ACTION; - }); - }); - - it('should error for an invalid action', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - SIGNED_URL_CONFIG.action = 'watch' as any; - - assert.throws(() => { - file.getSignedUrl(SIGNED_URL_CONFIG, () => {}), - ExceptionMessages.INVALID_ACTION; - }); - }); - it('should add "x-goog-resumable: start" header if action is resumable', done => { SIGNED_URL_CONFIG.action = 'resumable'; SIGNED_URL_CONFIG.extensionHeaders = { @@ -3683,9 +3644,9 @@ describe('File', () => { const apiResponse = {}; file.setMetadata = ( - metadata: Metadata, - optionsOrCallback: SetMetadataOptions | MetadataCallback, - cb: MetadataCallback + metadata: FileMetadata, + optionsOrCallback: SetMetadataOptions | MetadataCallback, + cb: MetadataCallback ) => { Promise.resolve([apiResponse]).then(resp => cb(null, ...resp)); }; diff --git a/test/hmacKey.ts b/test/hmacKey.ts index 9ceb342a4..1f5b8b156 100644 --- a/test/hmacKey.ts +++ b/test/hmacKey.ts @@ -16,8 +16,8 @@ import * as sinon from 'sinon'; import * as proxyquire from 'proxyquire'; import * as assert from 'assert'; import {describe, it, beforeEach, afterEach} from 'mocha'; -import {util, ServiceObject, Metadata} from '../src/nodejs-common'; -import {IdempotencyStrategy} from '../src'; +import {util, ServiceObject} from '../src/nodejs-common'; +import {HmacKeyMetadata, IdempotencyStrategy} from '../src'; const sandbox = sinon.createSandbox(); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -98,7 +98,7 @@ describe('HmacKey', () => { }); it('should correctly call setMetadata', done => { - hmacKey.setMetadata = (metadata: Metadata, callback: Function) => { + hmacKey.setMetadata = (metadata: HmacKeyMetadata, callback: Function) => { assert.deepStrictEqual(metadata.accessId, ACCESS_ID); Promise.resolve([]).then(resp => callback(null, ...resp)); }; diff --git a/test/index.ts b/test/index.ts index 3b8b07c30..7b592abf4 100644 --- a/test/index.ts +++ b/test/index.ts @@ -15,7 +15,6 @@ import { ApiError, DecorateRequestOptions, - Metadata, Service, ServiceConfig, util, @@ -835,7 +834,7 @@ describe('Storage', () => { }; storage.createBucket( BUCKET_NAME, - (err: Error, bucket: Bucket, apiResponse: Metadata) => { + (err: Error, bucket: Bucket, apiResponse: unknown) => { assert.strictEqual(resp, apiResponse); done(); } @@ -1026,7 +1025,7 @@ describe('Storage', () => { storage.getBuckets( {}, - (err: Error, buckets: Bucket[], nextQuery: {}, resp: Metadata) => { + (err: Error, buckets: Bucket[], nextQuery: {}, resp: unknown) => { assert.strictEqual(err, error); assert.strictEqual(buckets, null); assert.strictEqual(nextQuery, null); @@ -1193,7 +1192,7 @@ describe('Storage', () => { storage.getHmacKeys( {}, - (err: Error, hmacKeys: HmacKey[], nextQuery: {}, resp: Metadata) => { + (err: Error, hmacKeys: HmacKey[], nextQuery: {}, resp: unknown) => { assert.strictEqual(err, error); assert.strictEqual(hmacKeys, null); assert.strictEqual(nextQuery, null); @@ -1244,7 +1243,7 @@ describe('Storage', () => { }); storage.getHmacKeys( - (err: Error, _hmacKeys: [], _nextQuery: {}, apiResponse: Metadata) => { + (err: Error, _hmacKeys: [], _nextQuery: {}, apiResponse: unknown) => { assert.ifError(err); assert.deepStrictEqual(resp, apiResponse); done(); @@ -1310,7 +1309,7 @@ describe('Storage', () => { it('should return the error and apiResponse', done => { storage.getServiceAccount( - (err: Error, serviceAccount: {}, apiResponse: Metadata) => { + (err: Error, serviceAccount: {}, apiResponse: unknown) => { assert.strictEqual(err, ERROR); assert.strictEqual(serviceAccount, null); assert.strictEqual(apiResponse, API_RESPONSE); diff --git a/test/nodejs-common/service-object.ts b/test/nodejs-common/service-object.ts index 518c08a5e..b2c30c45b 100644 --- a/test/nodejs-common/service-object.ts +++ b/test/nodejs-common/service-object.ts @@ -21,7 +21,6 @@ import { } from '@google-cloud/promisify'; import * as assert from 'assert'; import {describe, it, beforeEach, afterEach} from 'mocha'; -import * as extend from 'extend'; import * as proxyquire from 'proxyquire'; import * as r from 'teeny-request'; import * as sinon from 'sinon'; @@ -63,12 +62,14 @@ interface InternalServiceObject { interceptors: SO.Interceptor[]; } -function asInternal(serviceObject: SO.ServiceObject) { +function asInternal( + serviceObject: SO.ServiceObject +) { return serviceObject as {} as InternalServiceObject; } describe('ServiceObject', () => { - let serviceObject: SO.ServiceObject; + let serviceObject: SO.ServiceObject; const sandbox = sinon.createSandbox(); const CONFIG = { @@ -117,7 +118,7 @@ describe('ServiceObject', () => { it('should localize the methods', () => { const methods = {}; - const config = extend({}, CONFIG, {methods}); + const config = {...CONFIG, methods}; const serviceObject = new ServiceObject(config); assert.deepStrictEqual(asInternal(serviceObject).methods, methods); }); @@ -127,11 +128,12 @@ describe('ServiceObject', () => { }); it('should clear out methods that are not asked for', () => { - const config = extend({}, CONFIG, { + const config = { + ...CONFIG, methods: { create: true, }, - }); + }; const serviceObject = new ServiceObject(config); assert.strictEqual(typeof serviceObject.create, 'function'); assert.strictEqual(serviceObject.delete, undefined); @@ -139,14 +141,14 @@ describe('ServiceObject', () => { it('should always expose the request method', () => { const methods = {}; - const config = extend({}, CONFIG, {methods}); + const config = {...CONFIG, methods}; const serviceObject = new ServiceObject(config); assert.strictEqual(typeof serviceObject.request, 'function'); }); it('should always expose the getRequestInterceptors method', () => { const methods = {}; - const config = extend({}, CONFIG, {methods}); + const config = {...CONFIG, methods}; const serviceObject = new ServiceObject(config); assert.strictEqual( typeof serviceObject.getRequestInterceptors, @@ -157,9 +159,7 @@ describe('ServiceObject', () => { describe('create', () => { it('should call createMethod', done => { - const config = extend({}, CONFIG, { - createMethod, - }); + const config = {...CONFIG, createMethod}; const options = {}; function createMethod( @@ -177,9 +177,7 @@ describe('ServiceObject', () => { }); it('should not require options', done => { - const config = extend({}, CONFIG, { - createMethod, - }); + const config = {...CONFIG, createMethod}; function createMethod(id: string, options: Function, callback: Function) { assert.strictEqual(id, config.id); @@ -193,9 +191,7 @@ describe('ServiceObject', () => { }); it('should update id with metadata id', done => { - const config = extend({}, CONFIG, { - createMethod, - }); + const config = {...CONFIG, createMethod}; const options = {}; function createMethod( @@ -215,7 +211,7 @@ describe('ServiceObject', () => { }); it('should pass error to callback', done => { - const config = extend({}, CONFIG, {createMethod}); + const config = {...CONFIG, createMethod}; const options = {}; const error = new Error('Error.'); const apiResponse = {}; @@ -236,9 +232,7 @@ describe('ServiceObject', () => { }); it('should return instance and apiResponse to callback', async () => { - const config = extend({}, CONFIG, { - createMethod, - }); + const config = {...CONFIG, createMethod}; const options = {}; const apiResponse = {}; function createMethod(id: string, options_: {}, callback: Function) { @@ -252,9 +246,7 @@ describe('ServiceObject', () => { }); it('should assign metadata', async () => { - const config = extend({}, CONFIG, { - createMethod, - }); + const config = {...CONFIG, createMethod}; const options = {}; const instance = { metadata: {}, @@ -268,9 +260,7 @@ describe('ServiceObject', () => { }); it('should execute callback with any amount of arguments', done => { - const config = extend({}, CONFIG, { - createMethod, - }); + const config = {...CONFIG, createMethod}; const options = {}; const args = ['a', 'b', 'c', 'd', 'e', 'f']; @@ -293,10 +283,12 @@ describe('ServiceObject', () => { sandbox .stub(ServiceObject.prototype, 'request') .callsFake((reqOpts, callback) => { - assert.strictEqual(reqOpts.method, 'DELETE'); - assert.strictEqual(reqOpts.uri, ''); + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; + assert.strictEqual(opts.method, 'DELETE'); + assert.strictEqual(opts.uri, ''); done(); - callback(null, null, {} as r.Response); + cb(null, null, {} as r.Response); }); serviceObject.delete(assert.ifError); }); @@ -306,9 +298,11 @@ describe('ServiceObject', () => { sandbox .stub(ServiceObject.prototype, 'request') .callsFake((reqOpts, callback) => { - assert.deepStrictEqual(reqOpts.qs, options); + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; + assert.deepStrictEqual(opts.qs, options); done(); - callback(null, null, {} as r.Response); + cb(null, null, {} as r.Response); }); serviceObject.delete(options, assert.ifError); }); @@ -321,19 +315,21 @@ describe('ServiceObject', () => { }, }; - const cachedMethodConfig = extend(true, {}, methodConfig); + const cachedMethodConfig = {reqOpts: {...methodConfig.reqOpts}}; sandbox .stub(ServiceObject.prototype, 'request') - .callsFake((reqOpts_, callback) => { + .callsFake((reqOpts, callback) => { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; assert.deepStrictEqual( serviceObject.methods.delete, cachedMethodConfig ); - assert.deepStrictEqual(reqOpts_.uri, 'v2'); - assert.deepStrictEqual(reqOpts_.method, 'PATCH'); + assert.deepStrictEqual(opts.uri, 'v2'); + assert.deepStrictEqual(opts.method, 'PATCH'); done(); - callback(null, null, null!); + cb(null, null, null!); }); const serviceObject = new ServiceObject(CONFIG) as FakeServiceObject; @@ -368,9 +364,11 @@ describe('ServiceObject', () => { sandbox .stub(ServiceObject.prototype, 'request') .callsFake((reqOpts, callback) => { - assert.strictEqual(reqOpts.qs.ignoreNotFound, undefined); + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; + assert.strictEqual(opts.qs.ignoreNotFound, undefined); done(); - callback(null, null, {} as r.Response); + cb(null, null, {} as r.Response); }); serviceObject.delete(options, assert.ifError); }); @@ -385,22 +383,24 @@ describe('ServiceObject', () => { }, }; - const cachedMethodConfig = extend(true, {}, methodConfig); + const cachedMethodConfig = {reqOpts: {qs: {...methodConfig.reqOpts.qs}}}; sandbox .stub(ServiceObject.prototype, 'request') - .callsFake((reqOpts_, callback) => { + .callsFake((reqOpts, callback) => { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; assert.deepStrictEqual( serviceObject.methods.delete, cachedMethodConfig ); - assert.deepStrictEqual(reqOpts_.qs, { + assert.deepStrictEqual(opts.qs, { defaultProperty: true, optionalProperty: true, thisPropertyWasOverridden: true, }); done(); - callback(null, null, null!); + cb(null, null, null!); }); const serviceObject = new ServiceObject(CONFIG) as FakeServiceObject; @@ -442,10 +442,12 @@ describe('ServiceObject', () => { const options = {queryOptionProperty: true}; sandbox .stub(ServiceObject.prototype, 'get') - .callsFake((options_, callback) => { - assert.deepStrictEqual(options_, options); + .callsFake((reqOpts, callback) => { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; + assert.deepStrictEqual(opts, options); done(); - callback(null, null, {} as r.Response); + cb(null, null, {} as r.Response); }); serviceObject.exists(options, assert.ifError); }); @@ -511,10 +513,13 @@ describe('ServiceObject', () => { it('should execute callback with error & metadata', done => { const error = new Error('Error.'); - const metadata = {} as SO.Metadata; + const metadata = {} as SO.BaseMetadata; serviceObject.getMetadata = promisify( - (options: SO.GetMetadataOptions, callback: SO.MetadataCallback) => { + ( + options: SO.GetMetadataOptions, + callback: SO.MetadataCallback + ) => { callback(error, metadata); } ); @@ -529,10 +534,13 @@ describe('ServiceObject', () => { }); it('should execute callback with instance & metadata', done => { - const metadata = {} as SO.Metadata; + const metadata = {} as SO.BaseMetadata; serviceObject.getMetadata = promisify( - (options: SO.GetMetadataOptions, callback: SO.MetadataCallback) => { + ( + options: SO.GetMetadataOptions, + callback: SO.MetadataCallback + ) => { callback(null, metadata); } ); @@ -552,7 +560,7 @@ describe('ServiceObject', () => { const ERROR = new ApiError('bad'); ERROR.code = 404; - const METADATA = {} as SO.Metadata; + const METADATA = {} as SO.BaseMetadata; beforeEach(() => { AUTO_CREATE_CONFIG = { @@ -560,7 +568,10 @@ describe('ServiceObject', () => { }; serviceObject.getMetadata = promisify( - (options: SO.GetMetadataOptions, callback: SO.MetadataCallback) => { + ( + options: SO.GetMetadataOptions, + callback: SO.MetadataCallback + ) => { callback(ERROR, METADATA); } ); @@ -583,7 +594,7 @@ describe('ServiceObject', () => { it('should pass config to create if it was provided', done => { const expectedConfig = {maxResults: 5} as SO.GetConfig; - const config = extend({}, AUTO_CREATE_CONFIG, expectedConfig); + const config = {...AUTO_CREATE_CONFIG, ...expectedConfig}; sandbox.stub(serviceObject, 'create').callsFake(config_ => { assert.deepStrictEqual(config_, expectedConfig); @@ -644,11 +655,17 @@ describe('ServiceObject', () => { it('should make the correct request', done => { sandbox .stub(ServiceObject.prototype, 'request') - .callsFake(function (this: SO.ServiceObject, reqOpts, callback) { + .callsFake(function ( + this: SO.ServiceObject, + reqOpts, + callback + ) { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; assert.strictEqual(this, serviceObject); - assert.strictEqual(reqOpts.uri, ''); + assert.strictEqual(opts.uri, ''); done(); - callback(null, null, {} as r.Response); + cb(null, null, {} as r.Response); }); serviceObject.getMetadata(() => {}); }); @@ -658,9 +675,11 @@ describe('ServiceObject', () => { sandbox .stub(ServiceObject.prototype, 'request') .callsFake((reqOpts, callback) => { - assert.deepStrictEqual(reqOpts.qs, options); + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; + assert.deepStrictEqual(opts.qs, options); done(); - callback(null, null, {} as r.Response); + cb(null, null, {} as r.Response); }); serviceObject.getMetadata(options, assert.ifError); }); @@ -672,18 +691,20 @@ describe('ServiceObject', () => { }, }; - const cachedMethodConfig = extend(true, {}, methodConfig); + const cachedMethodConfig = {reqOpts: {...methodConfig.reqOpts}}; sandbox .stub(ServiceObject.prototype, 'request') - .callsFake((reqOpts_, callback) => { + .callsFake((reqOpts, callback) => { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; assert.deepStrictEqual( serviceObject.methods.getMetadata, cachedMethodConfig ); - assert.deepStrictEqual(reqOpts_.uri, 'v2'); + assert.deepStrictEqual(opts.uri, 'v2'); done(); - callback(null, null, null!); + cb(null, null, null!); }); const serviceObject = new ServiceObject(CONFIG) as FakeServiceObject; @@ -701,22 +722,24 @@ describe('ServiceObject', () => { }, }; - const cachedMethodConfig = extend(true, {}, methodConfig); + const cachedMethodConfig = {reqOpts: {qs: {...methodConfig.reqOpts.qs}}}; sandbox .stub(ServiceObject.prototype, 'request') - .callsFake((reqOpts_, callback) => { + .callsFake((reqOpts, callback) => { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; assert.deepStrictEqual( serviceObject.methods.getMetadata, cachedMethodConfig ); - assert.deepStrictEqual(reqOpts_.qs, { + assert.deepStrictEqual(opts.qs, { defaultProperty: true, optionalProperty: true, thisPropertyWasOverridden: true, }); done(); - callback(null, null, null!); + cb(null, null, null!); }); const serviceObject = new ServiceObject(CONFIG) as FakeServiceObject; @@ -860,13 +883,19 @@ describe('ServiceObject', () => { const metadata = {metadataProperty: true}; sandbox .stub(ServiceObject.prototype, 'request') - .callsFake(function (this: SO.ServiceObject, reqOpts, callback) { + .callsFake(function ( + this: SO.ServiceObject, + reqOpts, + callback + ) { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; assert.strictEqual(this, serviceObject); - assert.strictEqual(reqOpts.method, 'PATCH'); - assert.strictEqual(reqOpts.uri, ''); - assert.deepStrictEqual(reqOpts.json, metadata); + assert.strictEqual(opts.method, 'PATCH'); + assert.strictEqual(opts.uri, ''); + assert.deepStrictEqual(opts.json, metadata); done(); - callback(null, null, {} as r.Response); + cb(null, null, {} as r.Response); }); serviceObject.setMetadata(metadata, () => {}); }); @@ -877,9 +906,11 @@ describe('ServiceObject', () => { sandbox .stub(ServiceObject.prototype, 'request') .callsFake((reqOpts, callback) => { - assert.deepStrictEqual(reqOpts.qs, options); + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; + assert.deepStrictEqual(opts.qs, options); done(); - callback(null, null, {} as r.Response); + cb(null, null, {} as r.Response); }); serviceObject.setMetadata(metadata, options, () => {}); }); @@ -891,19 +922,21 @@ describe('ServiceObject', () => { method: 'PUT', }, }; - const cachedMethodConfig = extend(true, {}, methodConfig); + const cachedMethodConfig = {reqOpts: {...methodConfig.reqOpts}}; sandbox .stub(ServiceObject.prototype, 'request') - .callsFake((reqOpts_, callback) => { + .callsFake((reqOpts, callback) => { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; assert.deepStrictEqual( serviceObject.methods.setMetadata, cachedMethodConfig ); - assert.deepStrictEqual(reqOpts_.uri, 'v2'); - assert.deepStrictEqual(reqOpts_.method, 'PUT'); + assert.deepStrictEqual(opts.uri, 'v2'); + assert.deepStrictEqual(opts.method, 'PUT'); done(); - callback(null, null, null!); + cb(null, null, null!); }); const serviceObject = new ServiceObject(CONFIG) as FakeServiceObject; @@ -920,22 +953,24 @@ describe('ServiceObject', () => { }, }, }; - const cachedMethodConfig = extend(true, {}, methodConfig); + const cachedMethodConfig = {reqOpts: {qs: {...methodConfig.reqOpts.qs}}}; sandbox .stub(ServiceObject.prototype, 'request') - .callsFake((reqOpts_, callback) => { + .callsFake((reqOpts, callback) => { + const opts = reqOpts as r.OptionsWithUri; + const cb = callback as BodyResponseCallback; assert.deepStrictEqual( serviceObject.methods.setMetadata, cachedMethodConfig ); - assert.deepStrictEqual(reqOpts_.qs, { + assert.deepStrictEqual(opts.qs, { defaultProperty: true, optionalProperty: true, thisPropertyWasOverridden: true, }); done(); - callback(null, null, null!); + cb(null, null, null!); }); const serviceObject = new ServiceObject(CONFIG) as FakeServiceObject; @@ -1070,9 +1105,7 @@ describe('ServiceObject', () => { }, }); - const child = new ServiceObject( - extend({}, CONFIG, {parent}) - ) as FakeServiceObject; + const child = new ServiceObject({...CONFIG, parent}) as FakeServiceObject; child.interceptors.push({ request(reqOpts: DecorateRequestOptions) { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -1082,7 +1115,10 @@ describe('ServiceObject', () => { }); sandbox - .stub(parent.parent as SO.ServiceObject, 'request') + .stub( + parent.parent as SO.ServiceObject, + 'request' + ) .callsFake((reqOpts, callback) => { assert.deepStrictEqual( reqOpts.interceptors_![0].request({} as DecorateRequestOptions), @@ -1140,7 +1176,7 @@ describe('ServiceObject', () => { return fakeObj as r.Request; }; - const opts = extend(true, reqOpts, {shouldReturnStream: true}); + const opts = {...reqOpts, shouldReturnStream: true}; const res = asInternal(serviceObject).request_(opts); assert.strictEqual(res, fakeObj); }); diff --git a/test/nodejs-common/service.ts b/test/nodejs-common/service.ts index 1ae57f9c7..a131d6489 100644 --- a/test/nodejs-common/service.ts +++ b/test/nodejs-common/service.ts @@ -16,7 +16,6 @@ import * as assert from 'assert'; import {describe, it, before, beforeEach, after} from 'mocha'; -import * as extend from 'extend'; import * as proxyquire from 'proxyquire'; import {Request} from 'teeny-request'; import {AuthClient, GoogleAuth, OAuth2Client} from 'google-auth-library'; @@ -102,7 +101,8 @@ describe('Service', () => { makeAuthenticatedRequestFactoryOverride = ( config: MakeAuthenticatedRequestFactoryConfig ) => { - const expectedConfig = extend({}, CONFIG, { + const expectedConfig = { + ...CONFIG, authClient: OPTIONS.authClient, credentials: OPTIONS.credentials, keyFile: OPTIONS.keyFilename, @@ -110,7 +110,7 @@ describe('Service', () => { projectIdRequired: CONFIG.projectIdRequired, projectId: OPTIONS.projectId, token: OPTIONS.token, - }); + }; assert.deepStrictEqual(config, expectedConfig); @@ -186,7 +186,7 @@ describe('Service', () => { it('should localize the timeout', () => { const timeout = 10000; - const options = extend({}, OPTIONS, {timeout}); + const options = {...OPTIONS, timeout}; const service = new Service(fakeCfg, options); assert.strictEqual(service.timeout, timeout); }); @@ -212,7 +212,7 @@ describe('Service', () => { it('should preserve the original global interceptors', () => { const globalInterceptors: Interceptor[] = []; - const options = extend({}, OPTIONS); + const options = {...OPTIONS}; options.interceptors_ = globalInterceptors; const service = new Service(fakeCfg, options); assert.strictEqual(service.globalInterceptors, globalInterceptors); @@ -460,8 +460,8 @@ describe('Service', () => { it('should set reqOpt.timeout', done => { const timeout = 10000; - const config = extend({}, CONFIG); - const options = extend({}, OPTIONS, {timeout}); + const config = {...CONFIG}; + const options = {...OPTIONS, timeout}; const service = new Service(config, options); service.makeAuthenticatedRequest = (reqOpts_: DecorateRequestOptions) => { @@ -529,7 +529,7 @@ describe('Service', () => { describe('projectIdRequired', () => { describe('false', () => { it('should include the projectId', done => { - const config = extend({}, CONFIG, {projectIdRequired: false}); + const config = {...CONFIG, projectIdRequired: false}; const service = new Service(config, OPTIONS); const expectedUri = [service.baseUrl, reqOpts.uri].join('/'); @@ -548,7 +548,7 @@ describe('Service', () => { describe('true', () => { it('should not include the projectId', done => { - const config = extend({}, CONFIG, {projectIdRequired: true}); + const config = {...CONFIG, projectIdRequired: true}; const service = new Service(config, OPTIONS); const expectedUri = [ @@ -570,7 +570,7 @@ describe('Service', () => { }); it('should use projectId override', done => { - const config = extend({}, CONFIG, {projectIdRequired: true}); + const config = {...CONFIG, projectIdRequired: true}; const service = new Service(config, OPTIONS); const projectOverride = 'turing'; @@ -727,7 +727,7 @@ describe('Service', () => { const fakeStream = {}; Service.prototype.request_ = async (reqOpts: DecorateRequestOptions) => { - assert.strictEqual(reqOpts, fakeOpts); + assert.deepStrictEqual(reqOpts, {shouldReturnStream: true}); return fakeStream; }; diff --git a/test/nodejs-common/util.ts b/test/nodejs-common/util.ts index 732cc078e..f8269bcf4 100644 --- a/test/nodejs-common/util.ts +++ b/test/nodejs-common/util.ts @@ -20,7 +20,6 @@ import { } from '@google-cloud/projectify'; import * as assert from 'assert'; import {describe, it, before, beforeEach, afterEach} from 'mocha'; -import * as extend from 'extend'; import { AuthClient, GoogleAuth, @@ -907,13 +906,14 @@ describe('common/util', () => { describe('authentication', () => { it('should pass correct args to authorizeRequest', done => { - const fake = extend(true, authClient, { + const fake = { + ...authClient, authorizeRequest: async (rOpts: {}) => { assert.deepStrictEqual(rOpts, fakeReqOpts); setImmediate(done); return rOpts; }, - }); + }; retryRequestOverride = () => { return new stream.PassThrough(); }; @@ -924,11 +924,12 @@ describe('common/util', () => { it('should return a stream if callback is missing', () => { sandbox.stub(fakeGoogleAuth, 'GoogleAuth').callsFake(() => { - return extend(true, authClient, { + return { + ...authClient, authorizeRequest: async (rOpts: {}) => { return rOpts; }, - }); + }; }); retryRequestOverride = () => { return new stream.PassThrough(); @@ -1367,7 +1368,6 @@ describe('common/util', () => { return (reqOpts_: DecorateRequestOptions, config: MakeRequestConfig) => { assert.strictEqual(reqOpts_, reqOpts); assert.strictEqual(config.retries, 3); - extend({}, config, customRetryRequestFunctionConfig); const error = new Error(errorMessage); stub('parseHttpRespMessage', () => { @@ -1601,11 +1601,10 @@ describe('common/util', () => { it('should allow request options to control retry setting', done => { retryRequestOverride = testCustomRetryRequestConfig(done); - const reqOptsWithRetrySettings = extend( - {}, - reqOpts, - customRetryRequestConfig - ); + const reqOptsWithRetrySettings = { + ...reqOpts, + ...customRetryRequestConfig, + }; util.makeRequest( reqOptsWithRetrySettings, noRetryRequestConfig, diff --git a/test/notification.ts b/test/notification.ts index e986f69db..184761289 100644 --- a/test/notification.ts +++ b/test/notification.ts @@ -13,6 +13,7 @@ // limitations under the License. import { + BaseMetadata, DecorateRequestOptions, ServiceObject, ServiceObjectConfig, @@ -24,7 +25,7 @@ import * as proxyquire from 'proxyquire'; import {Bucket} from '../src'; -class FakeServiceObject extends ServiceObject { +class FakeServiceObject extends ServiceObject { calledWith_: IArguments; constructor(config: ServiceObjectConfig) { super(config); @@ -51,6 +52,10 @@ describe('Notification', () => { const BUCKET = { createNotification: fakeUtil.noop, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + request(_reqOpts: DecorateRequestOptions, _callback: Function) { + return fakeUtil.noop(); + }, }; const ID = '123'; @@ -67,6 +72,7 @@ describe('Notification', () => { beforeEach(() => { BUCKET.createNotification = fakeUtil.noop = () => {}; + BUCKET.request = fakeUtil.noop = () => {}; notification = new Notification(BUCKET, ID); }); @@ -86,6 +92,21 @@ describe('Notification', () => { assert.deepStrictEqual(calledWith.methods, { create: true, + delete: { + reqOpts: { + qs: {}, + }, + }, + get: { + reqOpts: { + qs: {}, + }, + }, + getMetadata: { + reqOpts: { + qs: {}, + }, + }, exists: true, }); }); @@ -117,13 +138,13 @@ describe('Notification', () => { it('should make the correct request', done => { const options = {}; - notification.request = ( + BUCKET.request = ( reqOpts: DecorateRequestOptions, callback: Function ) => { assert.strictEqual(reqOpts.method, 'DELETE'); - assert.strictEqual(reqOpts.uri, ''); - assert.strictEqual(reqOpts.qs, options); + assert.strictEqual(reqOpts.uri, 'notificationConfigs/123'); + assert.deepStrictEqual(reqOpts.qs, options); callback(); // the done fn }; @@ -131,7 +152,7 @@ describe('Notification', () => { }); it('should optionally accept options', done => { - notification.request = ( + BUCKET.request = ( reqOpts: DecorateRequestOptions, callback: Function ) => { @@ -143,16 +164,14 @@ describe('Notification', () => { }); it('should optionally accept a callback', done => { - fakeUtil.noop = done; - - notification.request = ( - reqOpts: DecorateRequestOptions, + BUCKET.request = ( + _reqOpts: DecorateRequestOptions, callback: Function ) => { callback(); // the done fn }; - notification.delete(); + notification.delete(done); }); }); @@ -169,7 +188,7 @@ describe('Notification', () => { const options = {}; notification.getMetadata = (options_: {}) => { - assert.strictEqual(options_, options); + assert.deepStrictEqual(options_, options); done(); }; @@ -180,7 +199,7 @@ describe('Notification', () => { const error = new Error('Error.'); const metadata = {}; - notification.getMetadata = (options: {}, callback: Function) => { + notification.getMetadata = (_options: {}, callback: Function) => { callback(error, metadata); }; @@ -196,7 +215,7 @@ describe('Notification', () => { it('should execute callback with instance & metadata', done => { const metadata = {}; - notification.getMetadata = (options: {}, callback: Function) => { + notification.getMetadata = (_options: {}, callback: Function) => { callback(null, metadata); }; @@ -221,22 +240,25 @@ describe('Notification', () => { autoCreate: true, }; - notification.getMetadata = (options: {}, callback: Function) => { + notification.getMetadata = (_options: {}, callback: Function) => { callback(ERROR, METADATA); }; }); it('should pass config to create if it was provided', done => { - const config = Object.assign({}, AUTO_CREATE_CONFIG, { - maxResults: 5, - }); - - notification.create = (config_: {}) => { - assert.strictEqual(config_, config); + const config = Object.assign( + {}, + { + maxResults: 5, + } + ); + + notification.get = (config_: {}) => { + assert.deepStrictEqual(config_, config); done(); }; - notification.get(config, assert.ifError); + notification.get(config); }); it('should pass only a callback to create if no config', done => { @@ -296,9 +318,9 @@ describe('Notification', () => { it('should make the correct request', done => { const options = {}; - notification.request = (reqOpts: DecorateRequestOptions) => { - assert.strictEqual(reqOpts.uri, ''); - assert.strictEqual(reqOpts.qs, options); + BUCKET.request = (reqOpts: DecorateRequestOptions) => { + assert.strictEqual(reqOpts.uri, 'notificationConfigs/123'); + assert.deepStrictEqual(reqOpts.qs, options); done(); }; @@ -306,7 +328,7 @@ describe('Notification', () => { }); it('should optionally accept options', done => { - notification.request = (reqOpts: DecorateRequestOptions) => { + BUCKET.request = (reqOpts: DecorateRequestOptions) => { assert.deepStrictEqual(reqOpts.qs, {}); done(); }; @@ -318,16 +340,16 @@ describe('Notification', () => { const error = new Error('err'); const response = {}; - notification.request = ( - reqOpts: DecorateRequestOptions, + BUCKET.request = ( + _reqOpts: DecorateRequestOptions, callback: Function ) => { - callback(error, response); + callback(error, response, response); }; notification.getMetadata((err: Error, metadata: {}, resp: {}) => { assert.strictEqual(err, error); - assert.strictEqual(metadata, null); + assert.strictEqual(metadata, response); assert.strictEqual(resp, response); done(); }); @@ -336,11 +358,11 @@ describe('Notification', () => { it('should set and return the metadata', done => { const response = {}; - notification.request = ( - reqOpts: DecorateRequestOptions, + BUCKET.request = ( + _reqOpts: DecorateRequestOptions, callback: Function ) => { - callback(null, response); + callback(null, response, response); }; notification.getMetadata((err: Error, metadata: {}, resp: {}) => { diff --git a/test/resumable-upload.ts b/test/resumable-upload.ts index 6e3200d95..943140fe6 100644 --- a/test/resumable-upload.ts +++ b/test/resumable-upload.ts @@ -1618,7 +1618,7 @@ describe('resumable-upload', () => { const scope = nock(REQ_OPTS.url!).get(queryPath).reply(500, {error}); await assert.rejects(up.makeRequest(REQ_OPTS), (err: GaxiosError) => { scope.done(); - assert.strictEqual(err.code, '500'); + assert.strictEqual(err.status, 500); return true; }); }); @@ -1635,7 +1635,7 @@ describe('resumable-upload', () => { const scope = nock(REQ_OPTS.url!).get(queryPath).reply(500, {error}); await assert.rejects(up.makeRequest(REQ_OPTS), (err: GaxiosError) => { scope.done(); - assert.deepStrictEqual(err.code, '500'); + assert.deepStrictEqual(err.status, 500); return true; }); }); diff --git a/test/transfer-manager.ts b/test/transfer-manager.ts index e713d000c..ab9efb4c4 100644 --- a/test/transfer-manager.ts +++ b/test/transfer-manager.ts @@ -15,7 +15,12 @@ */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import {ServiceObject, ServiceObjectConfig, util} from '../src/nodejs-common'; +import { + BaseMetadata, + ServiceObject, + ServiceObjectConfig, + util, +} from '../src/nodejs-common'; import * as pLimit from 'p-limit'; import * as proxyquire from 'proxyquire'; import { @@ -33,15 +38,15 @@ import { import * as assert from 'assert'; import * as path from 'path'; import * as stream from 'stream'; -import * as extend from 'extend'; import * as fs from 'fs'; import * as sinon from 'sinon'; import {GaxiosResponse} from 'gaxios'; +import {FileMetadata} from '../src/file'; const fakeUtil = Object.assign({}, util); fakeUtil.noop = util.noop; -class FakeServiceObject extends ServiceObject { +class FakeServiceObject extends ServiceObject { calledWith_: IArguments; constructor(config: ServiceObjectConfig) { super(config); @@ -62,7 +67,7 @@ class FakeFile { bucket: Bucket; name: string; options: FileOptions; - metadata: {}; + metadata: FileMetadata; createWriteStream: Function; isSameFile = () => false; constructor(bucket: Bucket, name: string, options?: FileOptions) { @@ -74,7 +79,7 @@ class FakeFile { this.metadata = {}; this.createWriteStream = (options: CreateWriteStreamOptions) => { - this.metadata = options.metadata; + this.metadata = options.metadata!; const ws = new stream.Writable(); ws.write = () => { ws.emit('complete'); @@ -96,11 +101,8 @@ class HTTPError extends Error { let pLimitOverride: Function | null; const fakePLimit = (limit: number) => (pLimitOverride || pLimit)(limit); -const fakeFs = extend(true, {}, fs, { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - createReadStream(path: string, _options: {}): stream.Stream { - return new stream.PassThrough(); - }, +const fakeFs = { + ...fs, get promises() { return { open: () => { @@ -120,7 +122,7 @@ const fakeFs = extend(true, {}, fs, { }, }; }, -}); +}; describe('Transfer Manager', () => { let TransferManager: any; @@ -348,7 +350,7 @@ describe('Transfer Manager', () => { sandbox = sinon.createSandbox(); readStreamStub = sandbox .stub(fakeFs, 'createReadStream') - .returns(pThrough); + .returns(pThrough as unknown as fs.ReadStream); mockGeneratorFunction = (bucket, fileName, uploadId, partsMap) => { fakeHelper = sandbox.createStubInstance(FakeXMLHelper); fakeHelper.uploadId = uploadId || ''; diff --git a/tsconfig.json b/tsconfig.json index f010f8b3d..bfc500c8b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "rootDir": ".", "outDir": "build", "resolveJsonModule": true, + "sourceMap": false }, "include": [ "src/*.ts",