Skip to content

Commit 64f104e

Browse files
authored
feat: Add request context middleware for config and dependency injection in hooks (#8480)
1 parent e704de8 commit 64f104e

File tree

12 files changed

+219
-29
lines changed

12 files changed

+219
-29
lines changed

spec/CloudCode.spec.js

Lines changed: 114 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const mockAdapter = {
1111
name: filename,
1212
location: `http://www.somewhere.com/${filename}`,
1313
}),
14-
deleteFile: () => {},
15-
getFileData: () => {},
14+
deleteFile: () => { },
15+
getFileData: () => { },
1616
getFileLocation: (config, filename) => `http://www.somewhere.com/${filename}`,
1717
validateFilename: () => {
1818
return null;
@@ -49,7 +49,7 @@ describe('Cloud Code', () => {
4949
});
5050

5151
it('cloud code must be valid type', async () => {
52-
spyOn(console, 'error').and.callFake(() => {});
52+
spyOn(console, 'error').and.callFake(() => { });
5353
await expectAsync(reconfigureServer({ cloud: true })).toBeRejectedWith(
5454
"argument 'cloud' must either be a string or a function"
5555
);
@@ -114,7 +114,7 @@ describe('Cloud Code', () => {
114114

115115
it('show warning on duplicate cloud functions', done => {
116116
const logger = require('../lib/logger').logger;
117-
spyOn(logger, 'warn').and.callFake(() => {});
117+
spyOn(logger, 'warn').and.callFake(() => { });
118118
Parse.Cloud.define('hello', () => {
119119
return 'Hello world!';
120120
});
@@ -1672,7 +1672,7 @@ describe('Cloud Code', () => {
16721672
});
16731673

16741674
it('trivial beforeSave should not affect fetched pointers (regression test for #1238)', done => {
1675-
Parse.Cloud.beforeSave('BeforeSaveUnchanged', () => {});
1675+
Parse.Cloud.beforeSave('BeforeSaveUnchanged', () => { });
16761676

16771677
const TestObject = Parse.Object.extend('TestObject');
16781678
const NoBeforeSaveObject = Parse.Object.extend('NoBeforeSave');
@@ -1745,7 +1745,7 @@ describe('Cloud Code', () => {
17451745
});
17461746

17471747
it('beforeSave should not affect fetched pointers', done => {
1748-
Parse.Cloud.beforeSave('BeforeSaveUnchanged', () => {});
1748+
Parse.Cloud.beforeSave('BeforeSaveUnchanged', () => { });
17491749

17501750
Parse.Cloud.beforeSave('BeforeSaveChanged', function (req) {
17511751
req.object.set('foo', 'baz');
@@ -2059,15 +2059,15 @@ describe('Cloud Code', () => {
20592059
});
20602060

20612061
it('pointer should not be cleared by triggers', async () => {
2062-
Parse.Cloud.afterSave('MyObject', () => {});
2062+
Parse.Cloud.afterSave('MyObject', () => { });
20632063
const foo = await new Parse.Object('Test', { foo: 'bar' }).save();
20642064
const obj = await new Parse.Object('MyObject', { foo }).save();
20652065
const foo2 = obj.get('foo');
20662066
expect(foo2.get('foo')).toBe('bar');
20672067
});
20682068

20692069
it('can set a pointer in triggers', async () => {
2070-
Parse.Cloud.beforeSave('MyObject', () => {});
2070+
Parse.Cloud.beforeSave('MyObject', () => { });
20712071
Parse.Cloud.afterSave(
20722072
'MyObject',
20732073
async ({ object }) => {
@@ -2168,7 +2168,7 @@ describe('Cloud Code', () => {
21682168

21692169
it('should not run without master key', done => {
21702170
expect(() => {
2171-
Parse.Cloud.job('myJob', () => {});
2171+
Parse.Cloud.job('myJob', () => { });
21722172
}).not.toThrow();
21732173

21742174
request({
@@ -2354,6 +2354,14 @@ describe('cloud functions', () => {
23542354

23552355
Parse.Cloud.run('myFunction', {}).then(() => done());
23562356
});
2357+
2358+
it('should have request config', async () => {
2359+
Parse.Cloud.define('myConfigFunction', req => {
2360+
expect(req.config).toBeDefined();
2361+
return 'success';
2362+
});
2363+
await Parse.Cloud.run('myConfigFunction', {});
2364+
});
23572365
});
23582366

23592367
describe('beforeSave hooks', () => {
@@ -2377,6 +2385,16 @@ describe('beforeSave hooks', () => {
23772385
myObject.save().then(() => done());
23782386
});
23792387

2388+
it('should have request config', async () => {
2389+
Parse.Cloud.beforeSave('MyObject', req => {
2390+
expect(req.config).toBeDefined();
2391+
});
2392+
2393+
const MyObject = Parse.Object.extend('MyObject');
2394+
const myObject = new MyObject();
2395+
await myObject.save();
2396+
});
2397+
23802398
it('should respect custom object ids (#6733)', async () => {
23812399
Parse.Cloud.beforeSave('TestObject', req => {
23822400
expect(req.object.id).toEqual('test_6733');
@@ -2432,6 +2450,16 @@ describe('afterSave hooks', () => {
24322450
myObject.save().then(() => done());
24332451
});
24342452

2453+
it('should have request config', async () => {
2454+
Parse.Cloud.afterSave('MyObject', req => {
2455+
expect(req.config).toBeDefined();
2456+
});
2457+
2458+
const MyObject = Parse.Object.extend('MyObject');
2459+
const myObject = new MyObject();
2460+
await myObject.save();
2461+
});
2462+
24352463
it('should unset in afterSave', async () => {
24362464
Parse.Cloud.afterSave(
24372465
'MyObject',
@@ -2489,6 +2517,17 @@ describe('beforeDelete hooks', () => {
24892517
.then(myObj => myObj.destroy())
24902518
.then(() => done());
24912519
});
2520+
2521+
it('should have request config', async () => {
2522+
Parse.Cloud.beforeDelete('MyObject', req => {
2523+
expect(req.config).toBeDefined();
2524+
});
2525+
2526+
const MyObject = Parse.Object.extend('MyObject');
2527+
const myObject = new MyObject();
2528+
await myObject.save();
2529+
await myObject.destroy();
2530+
});
24922531
});
24932532

24942533
describe('afterDelete hooks', () => {
@@ -2517,6 +2556,17 @@ describe('afterDelete hooks', () => {
25172556
.then(myObj => myObj.destroy())
25182557
.then(() => done());
25192558
});
2559+
2560+
it('should have request config', async () => {
2561+
Parse.Cloud.afterDelete('MyObject', req => {
2562+
expect(req.config).toBeDefined();
2563+
});
2564+
2565+
const MyObject = Parse.Object.extend('MyObject');
2566+
const myObject = new MyObject();
2567+
await myObject.save();
2568+
await myObject.destroy();
2569+
});
25202570
});
25212571

25222572
describe('beforeFind hooks', () => {
@@ -2824,6 +2874,18 @@ describe('beforeFind hooks', () => {
28242874
.then(() => done());
28252875
});
28262876

2877+
it('should have request config', async () => {
2878+
Parse.Cloud.beforeFind('MyObject', req => {
2879+
expect(req.config).toBeDefined();
2880+
});
2881+
2882+
const MyObject = Parse.Object.extend('MyObject');
2883+
const myObject = new MyObject();
2884+
await myObject.save();
2885+
const query = new Parse.Query('MyObject');
2886+
query.equalTo('objectId', myObject.id);
2887+
await Promise.all([query.get(myObject.id), query.first(), query.find()]);
2888+
})
28272889
it('should run beforeFind on pointers and array of pointers from an object', async () => {
28282890
const obj1 = new Parse.Object('TestObject');
28292891
const obj2 = new Parse.Object('TestObject2');
@@ -3208,54 +3270,67 @@ describe('afterFind hooks', () => {
32083270
.catch(done.fail);
32093271
});
32103272

3273+
it('should have request config', async () => {
3274+
Parse.Cloud.afterFind('MyObject', req => {
3275+
expect(req.config).toBeDefined();
3276+
});
3277+
3278+
const MyObject = Parse.Object.extend('MyObject');
3279+
const myObject = new MyObject();
3280+
await myObject.save();
3281+
const query = new Parse.Query('MyObject');
3282+
query.equalTo('objectId', myObject.id);
3283+
await Promise.all([query.get(myObject.id), query.first(), query.find()]);
3284+
});
3285+
32113286
it('should validate triggers correctly', () => {
32123287
expect(() => {
3213-
Parse.Cloud.beforeSave('_Session', () => {});
3288+
Parse.Cloud.beforeSave('_Session', () => { });
32143289
}).toThrow('Only the afterLogout trigger is allowed for the _Session class.');
32153290
expect(() => {
3216-
Parse.Cloud.afterSave('_Session', () => {});
3291+
Parse.Cloud.afterSave('_Session', () => { });
32173292
}).toThrow('Only the afterLogout trigger is allowed for the _Session class.');
32183293
expect(() => {
3219-
Parse.Cloud.beforeSave('_PushStatus', () => {});
3294+
Parse.Cloud.beforeSave('_PushStatus', () => { });
32203295
}).toThrow('Only afterSave is allowed on _PushStatus');
32213296
expect(() => {
3222-
Parse.Cloud.afterSave('_PushStatus', () => {});
3297+
Parse.Cloud.afterSave('_PushStatus', () => { });
32233298
}).not.toThrow();
32243299
expect(() => {
3225-
Parse.Cloud.beforeLogin(() => {});
3300+
Parse.Cloud.beforeLogin(() => { });
32263301
}).not.toThrow('Only the _User class is allowed for the beforeLogin and afterLogin triggers');
32273302
expect(() => {
3228-
Parse.Cloud.beforeLogin('_User', () => {});
3303+
Parse.Cloud.beforeLogin('_User', () => { });
32293304
}).not.toThrow('Only the _User class is allowed for the beforeLogin and afterLogin triggers');
32303305
expect(() => {
3231-
Parse.Cloud.beforeLogin(Parse.User, () => {});
3306+
Parse.Cloud.beforeLogin(Parse.User, () => { });
32323307
}).not.toThrow('Only the _User class is allowed for the beforeLogin and afterLogin triggers');
32333308
expect(() => {
3234-
Parse.Cloud.beforeLogin('SomeClass', () => {});
3309+
Parse.Cloud.beforeLogin('SomeClass', () => { });
32353310
}).toThrow('Only the _User class is allowed for the beforeLogin and afterLogin triggers');
32363311
expect(() => {
3237-
Parse.Cloud.afterLogin(() => {});
3312+
Parse.Cloud.afterLogin(() => { });
32383313
}).not.toThrow('Only the _User class is allowed for the beforeLogin and afterLogin triggers');
32393314
expect(() => {
3240-
Parse.Cloud.afterLogin('_User', () => {});
3315+
Parse.Cloud.afterLogin('_User', () => { });
32413316
}).not.toThrow('Only the _User class is allowed for the beforeLogin and afterLogin triggers');
32423317
expect(() => {
3243-
Parse.Cloud.afterLogin(Parse.User, () => {});
3318+
Parse.Cloud.afterLogin(Parse.User, () => { });
32443319
}).not.toThrow('Only the _User class is allowed for the beforeLogin and afterLogin triggers');
32453320
expect(() => {
3246-
Parse.Cloud.afterLogin('SomeClass', () => {});
3321+
Parse.Cloud.afterLogin('SomeClass', () => { });
32473322
}).toThrow('Only the _User class is allowed for the beforeLogin and afterLogin triggers');
32483323
expect(() => {
3249-
Parse.Cloud.afterLogout(() => {});
3324+
Parse.Cloud.afterLogout(() => { });
32503325
}).not.toThrow();
32513326
expect(() => {
3252-
Parse.Cloud.afterLogout('_Session', () => {});
3327+
Parse.Cloud.afterLogout('_Session', () => { });
32533328
}).not.toThrow();
32543329
expect(() => {
3255-
Parse.Cloud.afterLogout('_User', () => {});
3330+
Parse.Cloud.afterLogout('_User', () => { });
32563331
}).toThrow('Only the _Session class is allowed for the afterLogout trigger.');
32573332
expect(() => {
3258-
Parse.Cloud.afterLogout('SomeClass', () => {});
3333+
Parse.Cloud.afterLogout('SomeClass', () => { });
32593334
}).toThrow('Only the _Session class is allowed for the afterLogout trigger.');
32603335
});
32613336

@@ -3695,6 +3770,7 @@ describe('beforeLogin hook', () => {
36953770
expect(req.ip).toBeDefined();
36963771
expect(req.installationId).toBeDefined();
36973772
expect(req.context).toBeDefined();
3773+
expect(req.config).toBeDefined();
36983774
});
36993775

37003776
await Parse.User.signUp('tupac', 'shakur');
@@ -3812,6 +3888,7 @@ describe('afterLogin hook', () => {
38123888
expect(req.ip).toBeDefined();
38133889
expect(req.installationId).toBeDefined();
38143890
expect(req.context).toBeDefined();
3891+
expect(req.config).toBeDefined();
38153892
});
38163893

38173894
await Parse.User.signUp('testuser', 'p@ssword');
@@ -4014,6 +4091,15 @@ describe('saveFile hooks', () => {
40144091
}
40154092
});
40164093

4094+
it('beforeSaveFile should have config', async () => {
4095+
await reconfigureServer({ filesAdapter: mockAdapter });
4096+
Parse.Cloud.beforeSave(Parse.File, req => {
4097+
expect(req.config).toBeDefined();
4098+
});
4099+
const file = new Parse.File('popeye.txt', [1, 2, 3], 'text/plain');
4100+
await file.save({ useMasterKey: true });
4101+
});
4102+
40174103
it('beforeSave(Parse.File) should change values of uploaded file by editing fileObject directly', async () => {
40184104
await reconfigureServer({ filesAdapter: mockAdapter });
40194105
const createFileSpy = spyOn(mockAdapter, 'createFile').and.callThrough();
@@ -4316,7 +4402,7 @@ describe('Parse.File hooks', () => {
43164402
beforeFind() {
43174403
throw 'unauthorized';
43184404
},
4319-
afterFind() {},
4405+
afterFind() { },
43204406
};
43214407
for (const hook in hooks) {
43224408
spyOn(hooks, hook).and.callThrough();
@@ -4344,7 +4430,7 @@ describe('Parse.File hooks', () => {
43444430
await file.save({ useMasterKey: true });
43454431
const user = await Parse.User.signUp('username', 'password');
43464432
const hooks = {
4347-
beforeFind() {},
4433+
beforeFind() { },
43484434
afterFind() {
43494435
throw 'unauthorized';
43504436
},
@@ -4563,7 +4649,7 @@ describe('sendEmail', () => {
45634649

45644650
it('cannot send email without adapter', async () => {
45654651
const logger = require('../lib/logger').logger;
4566-
spyOn(logger, 'error').and.callFake(() => {});
4652+
spyOn(logger, 'error').and.callFake(() => { });
45674653
await Parse.Cloud.sendEmail({});
45684654
expect(logger.error).toHaveBeenCalledWith(
45694655
'Failed to send email because no mail adapter is configured for Parse Server.'

spec/ParseGraphQLServer.spec.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,41 @@ describe('ParseGraphQLServer', () => {
607607
]);
608608
};
609609

610+
describe('Context', () => {
611+
it('should support dependency injection on graphql api', async () => {
612+
const requestContextMiddleware = (req, res, next) => {
613+
req.config.aCustomController = 'aCustomController';
614+
next();
615+
};
616+
617+
let called;
618+
const parseServer = await reconfigureServer({ requestContextMiddleware });
619+
await createGQLFromParseServer(parseServer);
620+
Parse.Cloud.beforeSave('_User', request => {
621+
expect(request.config.aCustomController).toEqual('aCustomController');
622+
called = true;
623+
});
624+
625+
await apolloClient.query({
626+
query: gql`
627+
mutation {
628+
createUser(input: { fields: { username: "test", password: "test" } }) {
629+
user {
630+
objectId
631+
}
632+
}
633+
}
634+
`,
635+
context: {
636+
headers: {
637+
'X-Parse-Master-Key': 'test',
638+
},
639+
}
640+
})
641+
expect(called).toBe(true);
642+
})
643+
})
644+
610645
describe('Introspection', () => {
611646
it('should have public introspection disabled by default without master key', async () => {
612647

spec/rest.spec.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,3 +1139,25 @@ describe('read-only masterKey', () => {
11391139
});
11401140
});
11411141
});
1142+
1143+
describe('rest context', () => {
1144+
it('should support dependency injection on rest api', async () => {
1145+
const requestContextMiddleware = (req, res, next) => {
1146+
req.config.aCustomController = 'aCustomController';
1147+
next();
1148+
};
1149+
1150+
let called
1151+
await reconfigureServer({ requestContextMiddleware });
1152+
Parse.Cloud.beforeSave('_User', request => {
1153+
expect(request.config.aCustomController).toEqual('aCustomController');
1154+
called = true;
1155+
});
1156+
const user = new Parse.User();
1157+
user.setUsername('test');
1158+
user.setPassword('test');
1159+
await user.signUp();
1160+
1161+
expect(called).toBe(true);
1162+
});
1163+
});

src/Controllers/HooksController.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ function wrapToHTTPRequest(hook, key) {
188188
return req => {
189189
const jsonBody = {};
190190
for (var i in req) {
191+
// Parse Server config is not serializable
192+
if (i === 'config') { continue; }
191193
jsonBody[i] = req[i];
192194
}
193195
if (req.object) {

0 commit comments

Comments
 (0)