Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: firebase/firebase-functions-test
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.3.2
Choose a base ref
...
head repository: firebase/firebase-functions-test
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.3.3
Choose a head ref
  • 4 commits
  • 5 files changed
  • 2 contributors

Commits on Aug 16, 2021

  1. Copy the full SHA
    34f612a View commit details

Commits on Oct 6, 2021

  1. Allow passing context.app to wrapped callable functions. (#123)

    Change allows users to pass `app` property in the mocked callable context, e.g.
    
    ```js
    export myFunc = functions.https.onCall((data, context) => {
      if (context.app == undefined) {
        throw new functions.https.HttpsError(
            'failed-precondition',
            'The function must be called from an App Check verified app.')
      }
    });
    ```
    
    ### Before
    ```js
    wrap(myFunc)('data', { app: { appId: 'my-app-123' }});
    // => Options object {"auth":{"uid":""},"app":{}} has invalid key "app"
    ```
    
    ### After
    ```js
    wrap(myFunc)('data', { app: { appId: 'my-app-123' }});
    // no error
    ```
    
    The diff on `main.spec.ts` looks terrible - I'm just adding a few test cases for callable functions, and indenting the ones that existed before.
    
    Fixes #122
    taeold authored Oct 6, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1e3ac7c View commit details
  2. Add changelog for #123. (#124)

    taeold authored Oct 6, 2021

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    61d88f4 View commit details
  3. 0.3.3

    google-oss-bot committed Oct 6, 2021
    Copy the full SHA
    0a2a27f View commit details
Showing with 129 additions and 76 deletions.
  1. +2 −1 CHANGELOG.md
  2. +1 −1 package-lock.json
  3. +1 −1 package.json
  4. +119 −72 spec/main.spec.ts
  5. +6 −1 src/main.ts
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
- Fix problem with missing import in the firebase-functions SDK
* Allow setting context.app in wrapped callable functions (#123).

2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firebase-functions-test",
"version": "0.3.2",
"version": "0.3.3",
"description": "A testing companion to firebase-functions.",
"main": "lib/index.js",
"scripts": {
191 changes: 119 additions & 72 deletions spec/main.spec.ts
Original file line number Diff line number Diff line change
@@ -28,89 +28,136 @@ import { mockConfig, makeChange, _makeResourceName, wrap } from '../src/main';

describe('main', () => {
describe('#wrap', () => {
const constructCF = (eventType?: string) => {
const cloudFunction = (input) => input;
set(cloudFunction, 'run', (data, context) => {
return { data, context };
describe('background functions', () => {
const constructBackgroundCF = (eventType?: string) => {
const cloudFunction = (input) => input;
set(cloudFunction, 'run', (data, context) => {
return { data, context };
});
set(cloudFunction, '__trigger', {
eventTrigger: {
resource: 'ref/{wildcard}/nested/{anotherWildcard}',
eventType: eventType || 'event',
service: 'service',
},
});
return cloudFunction as functions.CloudFunction<any>;
};

it('should invoke the function with the supplied data', () => {
const wrapped = wrap(constructBackgroundCF());
expect(wrapped('data').data).to.equal('data');
});
set(cloudFunction, '__trigger', {
eventTrigger: {
resource: 'ref/{wildcard}/nested/{anotherWildcard}',
eventType: eventType || 'event',
service: 'service',
},

it('should generate the appropriate context if no fields specified', () => {
const context = wrap(constructBackgroundCF())('data').context;
expect(typeof context.eventId).to.equal('string');
expect(context.resource.service).to.equal('service');
expect(
/ref\/wildcard[1-9]\/nested\/anotherWildcard[1-9]/.test(
context.resource.name
)
).to.be.true;
expect(context.eventType).to.equal('event');
expect(Date.parse(context.timestamp)).to.be.greaterThan(0);
expect(context.params).to.deep.equal({});
expect(context.auth).to.be.undefined;
expect(context.authType).to.be.undefined;
});
return cloudFunction as functions.CloudFunction<any>;
};

it('should invoke the function with the supplied data', () => {
const wrapped = wrap(constructCF());
expect(wrapped('data').data).to.equal('data');
});
it('should allow specification of context fields', () => {
const wrapped = wrap(constructBackgroundCF());
const context = wrapped('data', {
eventId: '111',
timestamp: '2018-03-28T18:58:50.370Z',
}).context;
expect(context.eventId).to.equal('111');
expect(context.timestamp).to.equal('2018-03-28T18:58:50.370Z');
});

it('should generate the appropriate context if no fields specified', () => {
const context = wrap(constructCF())('data').context;
expect(typeof context.eventId).to.equal('string');
expect(context.resource.service).to.equal('service');
expect(
/ref\/wildcard[1-9]\/nested\/anotherWildcard[1-9]/.test(
context.resource.name
)
).to.be.true;
expect(context.eventType).to.equal('event');
expect(Date.parse(context.timestamp)).to.be.greaterThan(0);
expect(context.params).to.deep.equal({});
expect(context.auth).to.be.undefined;
expect(context.authType).to.be.undefined;
});
it('should generate auth and authType for database functions', () => {
const context = wrap(constructBackgroundCF('google.firebase.database.ref.write'))(
'data'
).context;
expect(context.auth).to.equal(null);
expect(context.authType).to.equal('UNAUTHENTICATED');
});

it('should allow specification of context fields', () => {
const wrapped = wrap(constructCF());
const context = wrapped('data', {
eventId: '111',
timestamp: '2018-03-28T18:58:50.370Z',
}).context;
expect(context.eventId).to.equal('111');
expect(context.timestamp).to.equal('2018-03-28T18:58:50.370Z');
});
it('should allow auth and authType to be specified for database functions', () => {
const wrapped = wrap(constructBackgroundCF('google.firebase.database.ref.write'));
const context = wrapped('data', {
auth: { uid: 'abc' },
authType: 'USER',
}).context;
expect(context.auth).to.deep.equal({ uid: 'abc' });
expect(context.authType).to.equal('USER');
});

it('should generate auth and authType for database functions', () => {
const context = wrap(constructCF('google.firebase.database.ref.write'))(
'data'
).context;
expect(context.auth).to.equal(null);
expect(context.authType).to.equal('UNAUTHENTICATED');
});
it('should throw when passed invalid options', () => {
const wrapped = wrap(constructBackgroundCF());
expect(() =>
wrapped('data', {
auth: { uid: 'abc' },
isInvalid: true,
} as any)
).to.throw();
});

it('should allow auth and authType to be specified for database functions', () => {
const wrapped = wrap(constructCF('google.firebase.database.ref.write'));
const context = wrapped('data', {
auth: { uid: 'abc' },
authType: 'USER',
}).context;
expect(context.auth).to.deep.equal({ uid: 'abc' });
expect(context.authType).to.equal('USER');
it('should generate the appropriate resource based on params', () => {
const params = {
wildcard: 'a',
anotherWildcard: 'b',
};
const wrapped = wrap(constructBackgroundCF());
const context = wrapped('data', { params }).context;
expect(context.params).to.deep.equal(params);
expect(context.resource.name).to.equal('ref/a/nested/b');
});
});

it('should throw when passed invalid options', () => {
const wrapped = wrap(constructCF());
expect(() =>
wrapped('data', {
describe('callable functions', () => {
let wrappedCF;

before(() => {
const cloudFunction = (input) => input;
set(cloudFunction, 'run', (data, context) => {
return { data, context };
});
set(cloudFunction, '__trigger', {
labels: {
'deployment-callable': 'true',
},
httpsTrigger: {},
});
wrappedCF = wrap(cloudFunction as functions.CloudFunction<any>);
});

it('should invoke the function with the supplied data', () => {
expect(wrappedCF('data').data).to.equal('data');
});

it('should allow specification of context fields', () => {
const context = wrappedCF('data', {
auth: { uid: 'abc' },
isInvalid: true,
} as any)
).to.throw();
});
app: { appId: 'efg' },
instanceIdToken: '123',
rawRequest: { body: 'hello' }
}).context;
expect(context.auth).to.deep.equal({ uid: 'abc' });
expect(context.app).to.deep.equal({ appId: 'efg' });
expect(context.instanceIdToken).to.equal('123');
expect(context.rawRequest).to.deep.equal({ body: 'hello'});
});

it('should throw when passed invalid options', () => {
expect(() =>
wrappedCF('data', {
auth: { uid: 'abc' },
isInvalid: true,
} as any)
).to.throw();
});

it('should generate the appropriate resource based on params', () => {
const params = {
wildcard: 'a',
anotherWildcard: 'b',
};
const wrapped = wrap(constructCF());
const context = wrapped('data', { params }).context;
expect(context.params).to.deep.equal(params);
expect(context.resource.name).to.equal('ref/a/nested/b');
});
});

7 changes: 6 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -64,6 +64,11 @@ export type EventContextOptions = {

/** Fields of the callable context that can be overridden/customized. */
export type CallableContextOptions = {
/**
* The result of decoding and verifying a Firebase AppCheck token.
*/
app?: any;

/**
* The result of decoding and verifying a Firebase Auth ID token.
*/
@@ -149,7 +154,7 @@ export function wrap<T>(
let context;

if (isCallableFunction) {
_checkOptionValidity(['auth', 'instanceIdToken', 'rawRequest'], options);
_checkOptionValidity(['app', 'auth', 'instanceIdToken', 'rawRequest'], options);
let callableContextOptions = options as CallableContextOptions;
context = {
...callableContextOptions,