diff --git a/spec/providers/storage.spec.ts b/spec/providers/storage.spec.ts index 1b049a1fc..6afee6368 100644 --- a/spec/providers/storage.spec.ts +++ b/spec/providers/storage.spec.ts @@ -70,38 +70,52 @@ describe('storage.FunctionBuilder', () => { expect(() => storage.bucket('bad/bucket/format')).to.throw(Error); }); - it('should filter out spurious deploy-time events', () => { - let functionRan = false; - let cloudFunction = storage.object().onChange(() => { - functionRan = true; - return null; + it('should correct nested media links using a single literal slash', () => { + let cloudFunction = storage.object().onChange((event) => { + return event.data.mediaLink; }); + let badMediaLinkEvent = { + data: { + mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder/myobject.file?generation=12345&alt=media', + }, + }; + return cloudFunction(badMediaLinkEvent).then(result => { + expect(result).equals( + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fmyobject.file?generation=12345&alt=media'); + }); + }); - let spuriousEvent = { - timestamp: '2017-03-06T20:02:05.192Z', - eventType: 'providers/cloud.storage/eventTypes/object.change', - resource: 'projects/_/buckets/rjh-20170306.appspot.com/objects/#0', + it('should correct nested media links using multiple literal slashes', () => { + let cloudFunction = storage.object().onChange((event) => { + return event.data.mediaLink; + }); + let badMediaLinkEvent = { + data: { + mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder/anotherfolder/myobject.file?generation=12345&alt=media', + }, + }; + return cloudFunction(badMediaLinkEvent).then(result => { + expect(result).equals( + 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media'); + }); + }); + + it('should not mess with media links using non-literal slashes', () => { + let cloudFunction = storage.object().onChange((event) => { + return event.data.mediaLink; + }); + let goodMediaLinkEvent = { data: { - kind: 'storage#object', - resourceState: 'exists', - id: 'rjh-20170306.appspot.com//0', - selfLink: 'https://www.googleapis.com/storage/v1/b/rjh-20170306.appspot.com/o/', - bucket: 'rjh-20170306.appspot.com', - generation: '0', - metageneration: '0', - contentType: '', - timeCreated: '1970-01-01T00:00:00.000Z', - updated: '1970-01-01T00:00:00.000Z', - size: '0', - md5Hash: '', - mediaLink: 'https://www.googleapis.com/storage/v1/b/rjh-20170306.appspot.com/o/?generation=0&alt=media', - crc32c: 'AAAAAA==', + mediaLink: 'https://www.googleapis.com/storage/v1/b/mybucket.appspot.com' + + '/o/nestedfolder%2Fanotherfolder%2Fmyobject.file?generation=12345&alt=media', }, - params: {}, }; - return cloudFunction(spuriousEvent).then((result) => { - expect(result).equals(null); - expect(functionRan).equals(false); + return cloudFunction(goodMediaLinkEvent).then(result => { + expect(result).equals(goodMediaLinkEvent.data.mediaLink); }); }); }); diff --git a/src/providers/storage.ts b/src/providers/storage.ts index 63356da04..75f657b66 100644 --- a/src/providers/storage.ts +++ b/src/providers/storage.ts @@ -52,6 +52,9 @@ export class BucketBuilder { } } +// A RegExp that matches on a GCS media link that points at a _nested_ object (one that uses a path/with/slashes). +const nestedMediaLinkRE = new RegExp('https://www.googleapis.com/storage/v1/b/([^/]+)/o/(.*)'); + export class ObjectBuilder { /** @internal */ constructor(private resource) { } @@ -60,19 +63,21 @@ export class ObjectBuilder { * Handle any change to any object. */ onChange(handler: (event: Event) => PromiseLike | any): CloudFunction { - // This is a temporary shim to filter out spurious events due to Functions deployments. - // BUG(34123812): clean this up when backend fix for bug is deployed. - let filterDeployEvents = (event: Event) => { - if (event.data.timeCreated === '1970-01-01T00:00:00.000Z' && event.data.crc32c === 'AAAAAA==') { - console.log( - 'Function triggered unneccessarily by Cloud Function deployment; function execution elided. ' + - 'This is a spurious event that will go away soon, sorry for the spam!'); - return null; + // This is a temporary shim to fix the 'mediaLink' for nested objects. + // BUG(37962789): clean this up when backend fix for bug is deployed. + let correctMediaLink = (event: Event) => { + let deconstructedNestedLink = event.data.mediaLink.match(nestedMediaLinkRE); + if (deconstructedNestedLink != null) { + // The media link for a nested object uses an illegal URL (using literal slashes instead of "%2F". + // Fix up the URL. + let bucketName = deconstructedNestedLink[1]; + let fixedTail = deconstructedNestedLink[2].replace(/\//g, '%2F'); // "/\//g" means "all forward slashes". + event.data.mediaLink = 'https://www.googleapis.com/storage/v1/b/' + bucketName + '/o/' + fixedTail; } return handler(event); }; return makeCloudFunction( - { provider, handler: filterDeployEvents, resource: this.resource, eventType: 'object.change' }); + { provider, handler: correctMediaLink, resource: this.resource, eventType: 'object.change' }); } }