Skip to content

Commit 8d03c54

Browse files
committed
Merge pull request #277 from drew-gross/schemas-post
Implement POST /schemas
2 parents 9a14f53 + b9bc904 commit 8d03c54

File tree

2 files changed

+226
-31
lines changed

2 files changed

+226
-31
lines changed

schemas.js

+43-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// schemas.js
22

33
var express = require('express'),
4-
PromiseRouter = require('./PromiseRouter');
4+
Parse = require('parse/node').Parse,
5+
PromiseRouter = require('./PromiseRouter'),
6+
Schema = require('./Schema');
57

68
var router = new PromiseRouter();
79

@@ -54,7 +56,7 @@ function getAllSchemas(req) {
5456
if (!req.auth.isMaster) {
5557
return Promise.resolve({
5658
status: 401,
57-
response: {error: 'unauthorized'},
59+
response: {error: 'master key not specified'},
5860
});
5961
}
6062
return req.config.database.collection('_SCHEMA')
@@ -83,7 +85,46 @@ function getOneSchema(req) {
8385
}));
8486
}
8587

88+
function createSchema(req) {
89+
if (!req.auth.isMaster) {
90+
return Promise.resolve({
91+
status: 401,
92+
response: {error: 'master key not specified'},
93+
});
94+
}
95+
if (req.params.className && req.body.className) {
96+
if (req.params.className != req.body.className) {
97+
return Promise.resolve({
98+
status: 400,
99+
response: {
100+
code: Parse.Error.INVALID_CLASS_NAME,
101+
error: 'class name mismatch between ' + req.body.className + ' and ' + req.params.className,
102+
},
103+
});
104+
}
105+
}
106+
var className = req.params.className || req.body.className;
107+
if (!className) {
108+
return Promise.resolve({
109+
status: 400,
110+
response: {
111+
code: 135,
112+
error: 'POST ' + req.path + ' needs class name',
113+
},
114+
});
115+
}
116+
return req.config.database.loadSchema()
117+
.then(schema => schema.addClassIfNotExists(className, req.body.fields))
118+
.then(result => ({ response: mongoSchemaToSchemaAPIResponse(result) }))
119+
.catch(error => ({
120+
status: 400,
121+
response: error,
122+
}));
123+
}
124+
86125
router.route('GET', '/schemas', getAllSchemas);
87126
router.route('GET', '/schemas/:className', getOneSchema);
127+
router.route('POST', '/schemas', createSchema);
128+
router.route('POST', '/schemas/:className', createSchema);
88129

89130
module.exports = router;

spec/schemas.spec.js

+183-29
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
var Parse = require('parse/node').Parse;
12
var request = require('request');
23
var dd = require('deep-diff');
4+
35
var hasAllPODobject = () => {
46
var obj = new Parse.Object('HasAllPOD');
57
obj.set('aNumber', 5);
@@ -16,7 +18,7 @@ var hasAllPODobject = () => {
1618
return obj;
1719
}
1820

19-
var expectedResponseForHasAllPOD = {
21+
var plainOldDataSchema = {
2022
className: 'HasAllPOD',
2123
fields: {
2224
//Default fields
@@ -36,7 +38,7 @@ var expectedResponseForHasAllPOD = {
3638
},
3739
};
3840

39-
var expectedResponseforHasPointersAndRelations = {
41+
var pointersAndRelationsSchema = {
4042
className: 'HasPointersAndRelations',
4143
fields: {
4244
//Default fields
@@ -56,17 +58,30 @@ var expectedResponseforHasPointersAndRelations = {
5658
},
5759
}
5860

61+
var noAuthHeaders = {
62+
'X-Parse-Application-Id': 'test',
63+
};
64+
65+
var restKeyHeaders = {
66+
'X-Parse-Application-Id': 'test',
67+
'X-Parse-REST-API-Key': 'rest',
68+
};
69+
70+
var masterKeyHeaders = {
71+
'X-Parse-Application-Id': 'test',
72+
'X-Parse-Master-Key': 'test',
73+
};
74+
5975
describe('schemas', () => {
6076
it('requires the master key to get all schemas', (done) => {
6177
request.get({
6278
url: 'http://localhost:8378/1/schemas',
6379
json: true,
64-
headers: {
65-
'X-Parse-Application-Id': 'test',
66-
'X-Parse-REST-API-Key': 'rest',
67-
},
80+
headers: noAuthHeaders,
6881
}, (error, response, body) => {
69-
expect(response.statusCode).toEqual(401);
82+
//api.parse.com uses status code 401, but due to the lack of keys
83+
//being necessary in parse-server, 403 makes more sense
84+
expect(response.statusCode).toEqual(403);
7085
expect(body.error).toEqual('unauthorized');
7186
done();
7287
});
@@ -76,25 +91,31 @@ describe('schemas', () => {
7691
request.get({
7792
url: 'http://localhost:8378/1/schemas/SomeSchema',
7893
json: true,
79-
headers: {
80-
'X-Parse-Application-Id': 'test',
81-
'X-Parse-REST-API-Key': 'rest',
82-
},
94+
headers: restKeyHeaders,
8395
}, (error, response, body) => {
8496
expect(response.statusCode).toEqual(401);
8597
expect(body.error).toEqual('unauthorized');
8698
done();
8799
});
88100
});
89101

102+
it('asks for the master key if you use the rest key', (done) => {
103+
request.get({
104+
url: 'http://localhost:8378/1/schemas',
105+
json: true,
106+
headers: restKeyHeaders,
107+
}, (error, response, body) => {
108+
expect(response.statusCode).toEqual(401);
109+
expect(body.error).toEqual('master key not specified');
110+
done();
111+
});
112+
});
113+
90114
it('responds with empty list when there are no schemas', done => {
91115
request.get({
92116
url: 'http://localhost:8378/1/schemas',
93117
json: true,
94-
headers: {
95-
'X-Parse-Application-Id': 'test',
96-
'X-Parse-Master-Key': 'test',
97-
},
118+
headers: masterKeyHeaders,
98119
}, (error, response, body) => {
99120
expect(body.results).toEqual([]);
100121
done();
@@ -113,13 +134,10 @@ describe('schemas', () => {
113134
request.get({
114135
url: 'http://localhost:8378/1/schemas',
115136
json: true,
116-
headers: {
117-
'X-Parse-Application-Id': 'test',
118-
'X-Parse-Master-Key': 'test',
119-
},
137+
headers: masterKeyHeaders,
120138
}, (error, response, body) => {
121139
var expected = {
122-
results: [expectedResponseForHasAllPOD,expectedResponseforHasPointersAndRelations]
140+
results: [plainOldDataSchema,pointersAndRelationsSchema]
123141
};
124142
expect(body).toEqual(expected);
125143
done();
@@ -133,12 +151,9 @@ describe('schemas', () => {
133151
request.get({
134152
url: 'http://localhost:8378/1/schemas/HasAllPOD',
135153
json: true,
136-
headers: {
137-
'X-Parse-Application-Id': 'test',
138-
'X-Parse-Master-Key': 'test',
139-
},
154+
headers: masterKeyHeaders,
140155
}, (error, response, body) => {
141-
expect(body).toEqual(expectedResponseForHasAllPOD);
156+
expect(body).toEqual(plainOldDataSchema);
142157
done();
143158
});
144159
});
@@ -150,10 +165,7 @@ describe('schemas', () => {
150165
request.get({
151166
url: 'http://localhost:8378/1/schemas/HASALLPOD',
152167
json: true,
153-
headers: {
154-
'X-Parse-Application-Id': 'test',
155-
'X-Parse-Master-Key': 'test',
156-
},
168+
headers: masterKeyHeaders,
157169
}, (error, response, body) => {
158170
expect(response.statusCode).toEqual(400);
159171
expect(body).toEqual({
@@ -164,4 +176,146 @@ describe('schemas', () => {
164176
});
165177
});
166178
});
179+
180+
it('requires the master key to create a schema', done => {
181+
request.post({
182+
url: 'http://localhost:8378/1/schemas',
183+
json: true,
184+
headers: noAuthHeaders,
185+
body: {
186+
className: 'MyClass',
187+
}
188+
}, (error, response, body) => {
189+
expect(response.statusCode).toEqual(403);
190+
expect(body.error).toEqual('unauthorized');
191+
done();
192+
});
193+
});
194+
195+
it('asks for the master key if you use the rest key', done => {
196+
request.post({
197+
url: 'http://localhost:8378/1/schemas',
198+
json: true,
199+
headers: restKeyHeaders,
200+
body: {
201+
className: 'MyClass',
202+
},
203+
}, (error, response, body) => {
204+
expect(response.statusCode).toEqual(401);
205+
expect(body.error).toEqual('master key not specified');
206+
done();
207+
});
208+
});
209+
210+
it('sends an error if you use mismatching class names', done => {
211+
request.post({
212+
url: 'http://localhost:8378/1/schemas/A',
213+
headers: masterKeyHeaders,
214+
json: true,
215+
body: {
216+
className: 'B',
217+
}
218+
}, (error, response, body) => {
219+
expect(response.statusCode).toEqual(400);
220+
expect(body).toEqual({
221+
code: Parse.Error.INVALID_CLASS_NAME,
222+
error: 'class name mismatch between B and A',
223+
});
224+
done();
225+
});
226+
});
227+
228+
it('sends an error if you use no class name', done => {
229+
request.post({
230+
url: 'http://localhost:8378/1/schemas',
231+
headers: masterKeyHeaders,
232+
json: true,
233+
body: {},
234+
}, (error, response, body) => {
235+
expect(response.statusCode).toEqual(400);
236+
expect(body).toEqual({
237+
code: 135,
238+
error: 'POST /schemas needs class name',
239+
});
240+
done();
241+
})
242+
});
243+
244+
it('sends an error if you try to create the same class twice', done => {
245+
request.post({
246+
url: 'http://localhost:8378/1/schemas',
247+
headers: masterKeyHeaders,
248+
json: true,
249+
body: {
250+
className: 'A',
251+
},
252+
}, (error, response, body) => {
253+
expect(error).toEqual(null);
254+
request.post({
255+
url: 'http://localhost:8378/1/schemas',
256+
headers: masterKeyHeaders,
257+
json: true,
258+
body: {
259+
className: 'A',
260+
}
261+
}, (error, response, body) => {
262+
expect(response.statusCode).toEqual(400);
263+
expect(body).toEqual({
264+
code: Parse.Error.INVALID_CLASS_NAME,
265+
error: 'class A already exists',
266+
});
267+
done();
268+
});
269+
});
270+
});
271+
272+
it('responds with all fields when you create a class', done => {
273+
request.post({
274+
url: 'http://localhost:8378/1/schemas',
275+
headers: masterKeyHeaders,
276+
json: true,
277+
body: {
278+
className: "NewClass",
279+
fields: {
280+
foo: {type: 'Number'},
281+
ptr: {type: 'Pointer', targetClass: 'SomeClass'}
282+
}
283+
}
284+
}, (error, response, body) => {
285+
expect(body).toEqual({
286+
className: 'NewClass',
287+
fields: {
288+
ACL: {type: 'ACL'},
289+
createdAt: {type: 'Date'},
290+
updatedAt: {type: 'Date'},
291+
objectId: {type: 'String'},
292+
foo: {type: 'Number'},
293+
ptr: {type: 'Pointer', targetClass: 'SomeClass'},
294+
}
295+
});
296+
done();
297+
});
298+
});
299+
300+
it('lets you specify class name in both places', done => {
301+
request.post({
302+
url: 'http://localhost:8378/1/schemas/NewClass',
303+
headers: masterKeyHeaders,
304+
json: true,
305+
body: {
306+
className: "NewClass",
307+
}
308+
}, (error, response, body) => {
309+
expect(body).toEqual({
310+
className: 'NewClass',
311+
fields: {
312+
ACL: {type: 'ACL'},
313+
createdAt: {type: 'Date'},
314+
updatedAt: {type: 'Date'},
315+
objectId: {type: 'String'},
316+
}
317+
});
318+
done();
319+
});
320+
});
167321
});

0 commit comments

Comments
 (0)