diff --git a/schemas.js b/schemas.js index 875967cde7..6849a6a43d 100644 --- a/schemas.js +++ b/schemas.js @@ -1,7 +1,9 @@ // schemas.js var express = require('express'), - PromiseRouter = require('./PromiseRouter'); + Parse = require('parse/node').Parse, + PromiseRouter = require('./PromiseRouter'), + Schema = require('./Schema'); var router = new PromiseRouter(); @@ -54,7 +56,7 @@ function getAllSchemas(req) { if (!req.auth.isMaster) { return Promise.resolve({ status: 401, - response: {error: 'unauthorized'}, + response: {error: 'master key not specified'}, }); } return req.config.database.collection('_SCHEMA') @@ -83,7 +85,46 @@ function getOneSchema(req) { })); } +function createSchema(req) { + if (!req.auth.isMaster) { + return Promise.resolve({ + status: 401, + response: {error: 'master key not specified'}, + }); + } + if (req.params.className && req.body.className) { + if (req.params.className != req.body.className) { + return Promise.resolve({ + status: 400, + response: { + code: Parse.Error.INVALID_CLASS_NAME, + error: 'class name mismatch between ' + req.body.className + ' and ' + req.params.className, + }, + }); + } + } + var className = req.params.className || req.body.className; + if (!className) { + return Promise.resolve({ + status: 400, + response: { + code: 135, + error: 'POST ' + req.path + ' needs class name', + }, + }); + } + return req.config.database.loadSchema() + .then(schema => schema.addClassIfNotExists(className, req.body.fields)) + .then(result => ({ response: mongoSchemaToSchemaAPIResponse(result) })) + .catch(error => ({ + status: 400, + response: error, + })); +} + router.route('GET', '/schemas', getAllSchemas); router.route('GET', '/schemas/:className', getOneSchema); +router.route('POST', '/schemas', createSchema); +router.route('POST', '/schemas/:className', createSchema); module.exports = router; diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 8c7434da49..2378caf54b 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -1,5 +1,7 @@ +var Parse = require('parse/node').Parse; var request = require('request'); var dd = require('deep-diff'); + var hasAllPODobject = () => { var obj = new Parse.Object('HasAllPOD'); obj.set('aNumber', 5); @@ -16,7 +18,7 @@ var hasAllPODobject = () => { return obj; } -var expectedResponseForHasAllPOD = { +var plainOldDataSchema = { className: 'HasAllPOD', fields: { //Default fields @@ -36,7 +38,7 @@ var expectedResponseForHasAllPOD = { }, }; -var expectedResponseforHasPointersAndRelations = { +var pointersAndRelationsSchema = { className: 'HasPointersAndRelations', fields: { //Default fields @@ -56,17 +58,30 @@ var expectedResponseforHasPointersAndRelations = { }, } +var noAuthHeaders = { + 'X-Parse-Application-Id': 'test', +}; + +var restKeyHeaders = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-REST-API-Key': 'rest', +}; + +var masterKeyHeaders = { + 'X-Parse-Application-Id': 'test', + 'X-Parse-Master-Key': 'test', +}; + describe('schemas', () => { it('requires the master key to get all schemas', (done) => { request.get({ url: 'http://localhost:8378/1/schemas', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'rest', - }, + headers: noAuthHeaders, }, (error, response, body) => { - expect(response.statusCode).toEqual(401); + //api.parse.com uses status code 401, but due to the lack of keys + //being necessary in parse-server, 403 makes more sense + expect(response.statusCode).toEqual(403); expect(body.error).toEqual('unauthorized'); done(); }); @@ -76,10 +91,7 @@ describe('schemas', () => { request.get({ url: 'http://localhost:8378/1/schemas/SomeSchema', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-REST-API-Key': 'rest', - }, + headers: restKeyHeaders, }, (error, response, body) => { expect(response.statusCode).toEqual(401); expect(body.error).toEqual('unauthorized'); @@ -87,14 +99,23 @@ describe('schemas', () => { }); }); + it('asks for the master key if you use the rest key', (done) => { + request.get({ + url: 'http://localhost:8378/1/schemas', + json: true, + headers: restKeyHeaders, + }, (error, response, body) => { + expect(response.statusCode).toEqual(401); + expect(body.error).toEqual('master key not specified'); + done(); + }); + }); + it('responds with empty list when there are no schemas', done => { request.get({ url: 'http://localhost:8378/1/schemas', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, + headers: masterKeyHeaders, }, (error, response, body) => { expect(body.results).toEqual([]); done(); @@ -113,13 +134,10 @@ describe('schemas', () => { request.get({ url: 'http://localhost:8378/1/schemas', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, + headers: masterKeyHeaders, }, (error, response, body) => { var expected = { - results: [expectedResponseForHasAllPOD,expectedResponseforHasPointersAndRelations] + results: [plainOldDataSchema,pointersAndRelationsSchema] }; expect(body).toEqual(expected); done(); @@ -133,12 +151,9 @@ describe('schemas', () => { request.get({ url: 'http://localhost:8378/1/schemas/HasAllPOD', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, + headers: masterKeyHeaders, }, (error, response, body) => { - expect(body).toEqual(expectedResponseForHasAllPOD); + expect(body).toEqual(plainOldDataSchema); done(); }); }); @@ -150,10 +165,7 @@ describe('schemas', () => { request.get({ url: 'http://localhost:8378/1/schemas/HASALLPOD', json: true, - headers: { - 'X-Parse-Application-Id': 'test', - 'X-Parse-Master-Key': 'test', - }, + headers: masterKeyHeaders, }, (error, response, body) => { expect(response.statusCode).toEqual(400); expect(body).toEqual({ @@ -164,4 +176,146 @@ describe('schemas', () => { }); }); }); + + it('requires the master key to create a schema', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + json: true, + headers: noAuthHeaders, + body: { + className: 'MyClass', + } + }, (error, response, body) => { + expect(response.statusCode).toEqual(403); + expect(body.error).toEqual('unauthorized'); + done(); + }); + }); + + it('asks for the master key if you use the rest key', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + json: true, + headers: restKeyHeaders, + body: { + className: 'MyClass', + }, + }, (error, response, body) => { + expect(response.statusCode).toEqual(401); + expect(body.error).toEqual('master key not specified'); + done(); + }); + }); + + it('sends an error if you use mismatching class names', done => { + request.post({ + url: 'http://localhost:8378/1/schemas/A', + headers: masterKeyHeaders, + json: true, + body: { + className: 'B', + } + }, (error, response, body) => { + expect(response.statusCode).toEqual(400); + expect(body).toEqual({ + code: Parse.Error.INVALID_CLASS_NAME, + error: 'class name mismatch between B and A', + }); + done(); + }); + }); + + it('sends an error if you use no class name', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + headers: masterKeyHeaders, + json: true, + body: {}, + }, (error, response, body) => { + expect(response.statusCode).toEqual(400); + expect(body).toEqual({ + code: 135, + error: 'POST /schemas needs class name', + }); + done(); + }) + }); + + it('sends an error if you try to create the same class twice', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + headers: masterKeyHeaders, + json: true, + body: { + className: 'A', + }, + }, (error, response, body) => { + expect(error).toEqual(null); + request.post({ + url: 'http://localhost:8378/1/schemas', + headers: masterKeyHeaders, + json: true, + body: { + className: 'A', + } + }, (error, response, body) => { + expect(response.statusCode).toEqual(400); + expect(body).toEqual({ + code: Parse.Error.INVALID_CLASS_NAME, + error: 'class A already exists', + }); + done(); + }); + }); + }); + + it('responds with all fields when you create a class', done => { + request.post({ + url: 'http://localhost:8378/1/schemas', + headers: masterKeyHeaders, + json: true, + body: { + className: "NewClass", + fields: { + foo: {type: 'Number'}, + ptr: {type: 'Pointer', targetClass: 'SomeClass'} + } + } + }, (error, response, body) => { + expect(body).toEqual({ + className: 'NewClass', + fields: { + ACL: {type: 'ACL'}, + createdAt: {type: 'Date'}, + updatedAt: {type: 'Date'}, + objectId: {type: 'String'}, + foo: {type: 'Number'}, + ptr: {type: 'Pointer', targetClass: 'SomeClass'}, + } + }); + done(); + }); + }); + + it('lets you specify class name in both places', done => { + request.post({ + url: 'http://localhost:8378/1/schemas/NewClass', + headers: masterKeyHeaders, + json: true, + body: { + className: "NewClass", + } + }, (error, response, body) => { + expect(body).toEqual({ + className: 'NewClass', + fields: { + ACL: {type: 'ACL'}, + createdAt: {type: 'Date'}, + updatedAt: {type: 'Date'}, + objectId: {type: 'String'}, + } + }); + done(); + }); + }); });