Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Björn Kaiser
2016-02-12 20:37:10 +00:00
85 changed files with 3343 additions and 1287 deletions

View File

@@ -1,6 +1,65 @@
var APNS = require('../APNS');
var APNS = require('../src/APNS');
describe('APNS', () => {
it('can initialize with single cert', (done) => {
var args = {
cert: 'prodCert.pem',
key: 'prodKey.pem',
production: true,
bundleId: 'bundleId'
}
var apns = new APNS(args);
expect(apns.conns.length).toBe(1);
var apnsConnection = apns.conns[0];
expect(apnsConnection.index).toBe(0);
expect(apnsConnection.bundleId).toBe(args.bundleId);
// TODO: Remove this checking onec we inject APNS
var prodApnsOptions = apnsConnection.options;
expect(prodApnsOptions.cert).toBe(args.cert);
expect(prodApnsOptions.key).toBe(args.key);
expect(prodApnsOptions.production).toBe(args.production);
done();
});
it('can initialize with multiple certs', (done) => {
var args = [
{
cert: 'devCert.pem',
key: 'devKey.pem',
production: false,
bundleId: 'bundleId'
},
{
cert: 'prodCert.pem',
key: 'prodKey.pem',
production: true,
bundleId: 'bundleIdAgain'
}
]
var apns = new APNS(args);
expect(apns.conns.length).toBe(2);
var devApnsConnection = apns.conns[1];
expect(devApnsConnection.index).toBe(1);
var devApnsOptions = devApnsConnection.options;
expect(devApnsOptions.cert).toBe(args[0].cert);
expect(devApnsOptions.key).toBe(args[0].key);
expect(devApnsOptions.production).toBe(args[0].production);
expect(devApnsConnection.bundleId).toBe(args[0].bundleId);
var prodApnsConnection = apns.conns[0];
expect(prodApnsConnection.index).toBe(0);
// TODO: Remove this checking onec we inject APNS
var prodApnsOptions = prodApnsConnection.options;
expect(prodApnsOptions.cert).toBe(args[1].cert);
expect(prodApnsOptions.key).toBe(args[1].key);
expect(prodApnsOptions.production).toBe(args[1].production);
expect(prodApnsOptions.bundleId).toBe(args[1].bundleId);
done();
});
it('can generate APNS notification', (done) => {
//Mock request data
var data = {
@@ -29,12 +88,195 @@ describe('APNS', () => {
done();
});
it('can send APNS notification', (done) => {
var apns = new APNS();
var sender = {
pushNotification: jasmine.createSpy('send')
it('can choose conns for device without appIdentifier', (done) => {
// Mock conns
var conns = [
{
bundleId: 'bundleId'
},
{
bundleId: 'bundleIdAgain'
}
];
// Mock device
var device = {};
var qualifiedConns = APNS.chooseConns(conns, device);
expect(qualifiedConns).toEqual([0, 1]);
done();
});
it('can choose conns for device with valid appIdentifier', (done) => {
// Mock conns
var conns = [
{
bundleId: 'bundleId'
},
{
bundleId: 'bundleIdAgain'
}
];
// Mock device
var device = {
appIdentifier: 'bundleId'
};
apns.sender = sender;
var qualifiedConns = APNS.chooseConns(conns, device);
expect(qualifiedConns).toEqual([0]);
done();
});
it('can choose conns for device with invalid appIdentifier', (done) => {
// Mock conns
var conns = [
{
bundleId: 'bundleId'
},
{
bundleId: 'bundleIdAgain'
}
];
// Mock device
var device = {
appIdentifier: 'invalid'
};
var qualifiedConns = APNS.chooseConns(conns, device);
expect(qualifiedConns).toEqual([]);
done();
});
it('can handle transmission error when notification is not in cache or device is missing', (done) => {
// Mock conns
var conns = [];
var errorCode = 1;
var notification = undefined;
var device = {};
APNS.handleTransmissionError(conns, errorCode, notification, device);
var notification = {};
var device = undefined;
APNS.handleTransmissionError(conns, errorCode, notification, device);
done();
});
it('can handle transmission error when there are other qualified conns', (done) => {
// Mock conns
var conns = [
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId1'
},
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId1'
},
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId2'
},
];
var errorCode = 1;
var notification = {};
var apnDevice = {
connIndex: 0,
appIdentifier: 'bundleId1'
};
APNS.handleTransmissionError(conns, errorCode, notification, apnDevice);
expect(conns[0].pushNotification).not.toHaveBeenCalled();
expect(conns[1].pushNotification).toHaveBeenCalled();
expect(conns[2].pushNotification).not.toHaveBeenCalled();
done();
});
it('can handle transmission error when there is no other qualified conns', (done) => {
// Mock conns
var conns = [
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId1'
},
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId1'
},
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId1'
},
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId2'
},
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId1'
}
];
var errorCode = 1;
var notification = {};
var apnDevice = {
connIndex: 2,
appIdentifier: 'bundleId1'
};
APNS.handleTransmissionError(conns, errorCode, notification, apnDevice);
expect(conns[0].pushNotification).not.toHaveBeenCalled();
expect(conns[1].pushNotification).not.toHaveBeenCalled();
expect(conns[2].pushNotification).not.toHaveBeenCalled();
expect(conns[3].pushNotification).not.toHaveBeenCalled();
expect(conns[4].pushNotification).toHaveBeenCalled();
done();
});
it('can handle transmission error when device has no appIdentifier', (done) => {
// Mock conns
var conns = [
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId1'
},
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId2'
},
{
pushNotification: jasmine.createSpy('pushNotification'),
bundleId: 'bundleId3'
},
];
var errorCode = 1;
var notification = {};
var apnDevice = {
connIndex: 1,
};
APNS.handleTransmissionError(conns, errorCode, notification, apnDevice);
expect(conns[0].pushNotification).not.toHaveBeenCalled();
expect(conns[1].pushNotification).not.toHaveBeenCalled();
expect(conns[2].pushNotification).toHaveBeenCalled();
done();
});
it('can send APNS notification', (done) => {
var args = {
cert: 'prodCert.pem',
key: 'prodKey.pem',
production: true,
bundleId: 'bundleId'
}
var apns = new APNS(args);
var conn = {
pushNotification: jasmine.createSpy('send'),
bundleId: 'bundleId'
};
apns.conns = [ conn ];
// Mock data
var expirationTime = 1454571491354
var data = {
@@ -43,16 +285,23 @@ describe('APNS', () => {
'alert': 'alert'
}
}
// Mock registrationTokens
var deviceTokens = ['token'];
// Mock devices
var devices = [
{
deviceToken: '112233',
appIdentifier: 'bundleId'
}
];
var promise = apns.send(data, deviceTokens);
expect(sender.pushNotification).toHaveBeenCalled();
var args = sender.pushNotification.calls.first().args;
var promise = apns.send(data, devices);
expect(conn.pushNotification).toHaveBeenCalled();
var args = conn.pushNotification.calls.first().args;
var notification = args[0];
expect(notification.alert).toEqual(data.data.alert);
expect(notification.expiry).toEqual(data['expiration_time']);
expect(args[1]).toEqual(deviceTokens);
var apnDevice = args[1]
expect(apnDevice.connIndex).toEqual(0);
expect(apnDevice.appIdentifier).toEqual('bundleId');
done();
});
});

View File

@@ -1,4 +1,4 @@
var ExportAdapter = require('../ExportAdapter');
var ExportAdapter = require('../src/ExportAdapter');
describe('ExportAdapter', () => {
it('can be constructed', (done) => {

View File

@@ -0,0 +1,64 @@
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
var Parse = require('parse/node').Parse;
var request = require('request');
var fs = require('fs');
var LOGS_FOLDER = './test_logs/';
var deleteFolderRecursive = function(path) {
if( fs.existsSync(path) ) {
fs.readdirSync(path).forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) { // recurse
deleteFolderRecursive(curPath);
} else { // delete file
fs.unlinkSync(curPath);
}
});
fs.rmdirSync(path);
}
};
describe('info logs', () => {
afterEach((done) => {
deleteFolderRecursive(LOGS_FOLDER);
done();
});
it("Verify INFO logs", (done) => {
var fileLoggerAdapter = new FileLoggerAdapter({
logsFolder: LOGS_FOLDER
});
fileLoggerAdapter.info('testing info logs', () => {
fileLoggerAdapter.query({
size: 1,
level: 'info'
}, (results) => {
expect(results[0].message).toEqual('testing info logs');
done();
});
});
});
});
describe('error logs', () => {
afterEach((done) => {
deleteFolderRecursive(LOGS_FOLDER);
done();
});
it("Verify ERROR logs", (done) => {
var fileLoggerAdapter = new FileLoggerAdapter();
fileLoggerAdapter.error('testing error logs', () => {
fileLoggerAdapter.query({
size: 1,
level: 'error'
}, (results) => {
expect(results[0].message).toEqual('testing error logs');
done();
});
});
});
});

View File

@@ -1,6 +1,23 @@
var GCM = require('../GCM');
var GCM = require('../src/GCM');
describe('GCM', () => {
it('can initialize', (done) => {
var args = {
apiKey: 'apiKey'
};
var gcm = new GCM(args);
expect(gcm.sender.key).toBe(args.apiKey);
done();
});
it('can throw on initializing with invalid args', (done) => {
var args = 123
expect(function() {
new GCM(args);
}).toThrow();
done();
});
it('can generate GCM Payload without expiration time', (done) => {
//Mock request data
var data = {
@@ -90,7 +107,9 @@ describe('GCM', () => {
});
it('can send GCM request', (done) => {
var gcm = new GCM('apiKey');
var gcm = new GCM({
apiKey: 'apiKey'
});
// Mock gcm sender
var sender = {
send: jasmine.createSpy('send')
@@ -104,34 +123,37 @@ describe('GCM', () => {
'alert': 'alert'
}
}
// Mock registrationTokens
var registrationTokens = ['token'];
// Mock devices
var devices = [
{
deviceToken: 'token'
}
];
var promise = gcm.send(data, registrationTokens);
gcm.send(data, devices);
expect(sender.send).toHaveBeenCalled();
var args = sender.send.calls.first().args;
// It is too hard to verify message of gcm library, we just verify tokens and retry times
expect(args[1].registrationTokens).toEqual(registrationTokens);
expect(args[1].registrationTokens).toEqual(['token']);
expect(args[2]).toEqual(5);
done();
});
it('can throw on sending when we have too many registration tokens', (done) => {
var gcm = new GCM('apiKey');
// Mock gcm sender
var sender = {
send: jasmine.createSpy('send')
};
gcm.sender = sender;
// Mock registrationTokens
var registrationTokens = [];
for (var i = 0; i <= 2000; i++) {
registrationTokens.push(i.toString());
}
it('can slice devices', (done) => {
// Mock devices
var devices = [makeDevice(1), makeDevice(2), makeDevice(3), makeDevice(4)];
expect(function() {
gcm.send({}, registrationTokens);
}).toThrow();
var chunkDevices = GCM.sliceDevices(devices, 3);
expect(chunkDevices).toEqual([
[makeDevice(1), makeDevice(2), makeDevice(3)],
[makeDevice(4)]
]);
done();
});
function makeDevice(deviceToken) {
return {
deviceToken: deviceToken
};
}
});

View File

@@ -0,0 +1,55 @@
var LoggerController = require('../src/Controllers/LoggerController').LoggerController;
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
describe('LoggerController', () => {
it('can check valid master key of request', (done) => {
// Make mock request
var request = {
auth: {
isMaster: true
},
query: {}
};
var loggerController = new LoggerController(new FileLoggerAdapter());
expect(() => {
loggerController.handleGET(request);
}).not.toThrow();
done();
});
it('can check invalid construction of controller', (done) => {
// Make mock request
var request = {
auth: {
isMaster: true
},
query: {}
};
var loggerController = new LoggerController();
expect(() => {
loggerController.handleGET(request);
}).toThrow();
done();
});
it('can check invalid master key of request', (done) => {
// Make mock request
var request = {
auth: {
isMaster: false
},
query: {}
};
var loggerController = new LoggerController(new FileLoggerAdapter());
expect(() => {
loggerController.handleGET(request);
}).toThrow();
done();
});
});

View File

@@ -251,6 +251,9 @@ describe('Parse.ACL', () => {
equal(results.length, 1);
var result = results[0];
ok(result);
if (!result) {
return fail();
}
equal(result.id, object.id);
equal(result.getACL().getReadAccess(user), true);
equal(result.getACL().getWriteAccess(user), true);

View File

@@ -1,7 +1,7 @@
// A bunch of different tests are in here - it isn't very thematic.
// It would probably be better to refactor them into different files.
var DatabaseAdapter = require('../DatabaseAdapter');
var DatabaseAdapter = require('../src/DatabaseAdapter');
var request = require('request');
describe('miscellaneous', function() {

View File

@@ -33,6 +33,95 @@ describe('Parse.File testing', () => {
});
});
it('supports REST end-to-end file create, read, delete, read', done => {
var headers = {
'Content-Type': 'image/jpeg',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/testfile.txt',
body: 'check one two',
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
expect(b.name).toMatch(/_testfile.txt$/);
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*testfile.txt$/);
request.get(b.url, (error, response, body) => {
expect(error).toBe(null);
expect(body).toEqual('check one two');
request.del({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Master-Key': 'test'
},
url: 'http://localhost:8378/1/files/' + b.name
}, (error, response, body) => {
expect(error).toBe(null);
expect(response.statusCode).toEqual(200);
request.get({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
},
url: b.url
}, (error, response, body) => {
expect(error).toBe(null);
expect(response.statusCode).toEqual(404);
done();
});
});
});
});
});
it('blocks file deletions with missing or incorrect master-key header', done => {
var headers = {
'Content-Type': 'image/jpeg',
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
};
request.post({
headers: headers,
url: 'http://localhost:8378/1/files/thefile.jpg',
body: 'the file body'
}, (error, response, body) => {
expect(error).toBe(null);
var b = JSON.parse(body);
expect(b.url).toMatch(/^http:\/\/localhost:8378\/1\/files\/test\/.*thefile.jpg$/);
// missing X-Parse-Master-Key header
request.del({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
},
url: 'http://localhost:8378/1/files/' + b.name
}, (error, response, body) => {
expect(error).toBe(null);
var del_b = JSON.parse(body);
expect(response.statusCode).toEqual(403);
expect(del_b.error).toMatch(/unauthorized/);
// incorrect X-Parse-Master-Key header
request.del({
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest',
'X-Parse-Master-Key': 'tryagain'
},
url: 'http://localhost:8378/1/files/' + b.name
}, (error, response, body) => {
expect(error).toBe(null);
var del_b2 = JSON.parse(body);
expect(response.statusCode).toEqual(403);
expect(del_b2.error).toMatch(/unauthorized/);
done();
});
});
});
});
it('handles other filetypes', done => {
var headers = {
'Content-Type': 'image/jpeg',

View File

@@ -0,0 +1,81 @@
var request = require('request');
var Parse = require('parse/node').Parse;
var DatabaseAdapter = require('../src/DatabaseAdapter');
var database = DatabaseAdapter.getDatabaseConnection('test');
describe('a GlobalConfig', () => {
beforeEach(function(done) {
database.rawCollection('_GlobalConfig')
.then(coll => coll.updateOne({ '_id': 1}, { $set: { params: { companies: ['US', 'DK'] } } }, { upsert: true }))
.then(done());
});
it('can be retrieved', (done) => {
request.get({
url: 'http://localhost:8378/1/config',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
expect(body.params.companies).toEqual(['US', 'DK']);
done();
});
});
it('can be updated when a master key exists', (done) => {
request.put({
url: 'http://localhost:8378/1/config',
json: true,
body: { params: { companies: ['US', 'DK', 'SE'] } },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test'
},
}, (error, response, body) => {
expect(response.statusCode).toEqual(200);
expect(body.result).toEqual(true);
done();
});
});
it('fail to update if master key is missing', (done) => {
request.put({
url: 'http://localhost:8378/1/config',
json: true,
body: { params: { companies: [] } },
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-REST-API-Key': 'rest'
},
}, (error, response, body) => {
expect(response.statusCode).toEqual(401);
expect(body.error).toEqual('unauthorized');
done();
});
});
it('failed getting config when it is missing', (done) => {
database.rawCollection('_GlobalConfig')
.then(coll => coll.deleteOne({ '_id': 1}, {}, {}))
.then(_ => {
request.get({
url: 'http://localhost:8378/1/config',
json: true,
headers: {
'X-Parse-Application-Id': 'test',
'X-Parse-Master-Key': 'test',
},
}, (error, response, body) => {
expect(response.statusCode).toEqual(404);
expect(body.code).toEqual(Parse.Error.INVALID_KEY_NAME);
done();
});
});
});
});

View File

@@ -1,12 +1,12 @@
// These tests check the Installations functionality of the REST API.
// Ported from installation_collection_test.go
var auth = require('../Auth');
var cache = require('../cache');
var Config = require('../Config');
var DatabaseAdapter = require('../DatabaseAdapter');
var auth = require('../src/Auth');
var cache = require('../src/cache');
var Config = require('../src/Config');
var DatabaseAdapter = require('../src/DatabaseAdapter');
var Parse = require('parse/node').Parse;
var rest = require('../rest');
var rest = require('../src/rest');
var config = new Config('test');
var database = DatabaseAdapter.getDatabaseConnection('test');

View File

@@ -0,0 +1,150 @@
var ParsePushAdapter = require('../src/Adapters/Push/ParsePushAdapter');
var APNS = require('../src/APNS');
var GCM = require('../src/GCM');
describe('ParsePushAdapter', () => {
it('can be initialized', (done) => {
// Make mock config
var pushConfig = {
android: {
senderId: 'senderId',
apiKey: 'apiKey'
},
ios: [
{
cert: 'prodCert.pem',
key: 'prodKey.pem',
production: true,
bundleId: 'bundleId'
},
{
cert: 'devCert.pem',
key: 'devKey.pem',
production: false,
bundleId: 'bundleIdAgain'
}
]
};
var parsePushAdapter = new ParsePushAdapter(pushConfig);
// Check ios
var iosSender = parsePushAdapter.senderMap['ios'];
expect(iosSender instanceof APNS).toBe(true);
// Check android
var androidSender = parsePushAdapter.senderMap['android'];
expect(androidSender instanceof GCM).toBe(true);
done();
});
it('can throw on initializing with unsupported push type', (done) => {
// Make mock config
var pushConfig = {
win: {
senderId: 'senderId',
apiKey: 'apiKey'
}
};
expect(function() {
new ParsePushAdapter(pushConfig);
}).toThrow();
done();
});
it('can get valid push types', (done) => {
var parsePushAdapter = new ParsePushAdapter();
expect(parsePushAdapter.getValidPushTypes()).toEqual(['ios', 'android']);
done();
});
it('can classify installation', (done) => {
// Mock installations
var validPushTypes = ['ios', 'android'];
var installations = [
{
deviceType: 'android',
deviceToken: 'androidToken'
},
{
deviceType: 'ios',
deviceToken: 'iosToken'
},
{
deviceType: 'win',
deviceToken: 'winToken'
},
{
deviceType: 'android',
deviceToken: undefined
}
];
var deviceMap = ParsePushAdapter.classifyInstallation(installations, validPushTypes);
expect(deviceMap['android']).toEqual([makeDevice('androidToken')]);
expect(deviceMap['ios']).toEqual([makeDevice('iosToken')]);
expect(deviceMap['win']).toBe(undefined);
done();
});
it('can send push notifications', (done) => {
var parsePushAdapter = new ParsePushAdapter();
// Mock android ios senders
var androidSender = {
send: jasmine.createSpy('send')
};
var iosSender = {
send: jasmine.createSpy('send')
};
var senderMap = {
ios: iosSender,
android: androidSender
};
parsePushAdapter.senderMap = senderMap;
// Mock installations
var installations = [
{
deviceType: 'android',
deviceToken: 'androidToken'
},
{
deviceType: 'ios',
deviceToken: 'iosToken'
},
{
deviceType: 'win',
deviceToken: 'winToken'
},
{
deviceType: 'android',
deviceToken: undefined
}
];
var data = {};
parsePushAdapter.send(data, installations);
// Check android sender
expect(androidSender.send).toHaveBeenCalled();
var args = androidSender.send.calls.first().args;
expect(args[0]).toEqual(data);
expect(args[1]).toEqual([
makeDevice('androidToken')
]);
// Check ios sender
expect(iosSender.send).toHaveBeenCalled();
args = iosSender.send.calls.first().args;
expect(args[0]).toEqual(data);
expect(args[1]).toEqual([
makeDevice('iosToken')
]);
done();
});
function makeDevice(deviceToken, appIdentifier) {
return {
deviceToken: deviceToken,
appIdentifier: appIdentifier
};
}
});

View File

@@ -2056,7 +2056,7 @@ describe('Parse.Query testing', () => {
});
});
it('query match on array value', (done) => {
it('query match on array with single object', (done) => {
var target = {__type: 'Pointer', className: 'TestObject', objectId: 'abc123'};
var obj = new Parse.Object('TestObject');
obj.set('someObjs', [target]);
@@ -2072,4 +2072,20 @@ describe('Parse.Query testing', () => {
});
});
it('query match on array with multiple objects', (done) => {
var target1 = {__type: 'Pointer', className: 'TestObject', objectId: 'abc'};
var target2 = {__type: 'Pointer', className: 'TestObject', objectId: '123'};
var obj= new Parse.Object('TestObject');
obj.set('someObjs', [target1, target2]);
obj.save().then(() => {
var query = new Parse.Query('TestObject');
query.equalTo('someObjs', target1);
return query.find();
}).then((results) => {
expect(results.length).toEqual(1);
done();
}, (error) => {
console.log(error);
});
});
});

View File

@@ -6,7 +6,7 @@
// Tests that involve sending password reset emails.
var request = require('request');
var passwordCrypto = require('../password');
var passwordCrypto = require('../src/password');
describe('Parse.User testing', () => {
it("user sign up class method", (done) => {
@@ -64,6 +64,22 @@ describe('Parse.User testing', () => {
});
});
it("user login with files", (done) => {
"use strict";
let file = new Parse.File("yolo.txt", [1,2,3], "text/plain");
file.save().then((file) => {
return Parse.User.signUp("asdf", "zxcv", { "file" : file });
}).then(() => {
return Parse.User.logIn("asdf", "zxcv");
}).then((user) => {
let fileAgain = user.get('file');
ok(fileAgain.name());
ok(fileAgain.url());
done();
});
});
it("become", (done) => {
var user = null;
var sessionToken = null;
@@ -1576,5 +1592,27 @@ describe('Parse.User testing', () => {
});
});
it('ensure logout works', (done) => {
var user = null;
var sessionToken = null;
Parse.Promise.as().then(function() {
return Parse.User.signUp('log', 'out');
}).then((newUser) => {
user = newUser;
sessionToken = user.getSessionToken();
return Parse.User.logOut();
}).then(() => {
user.set('foo', 'bar');
return user.save(null, { sessionToken: sessionToken });
}).then(() => {
fail('Save should have failed.');
done();
}, (e) => {
expect(e.code).toEqual(Parse.Error.SESSION_MISSING);
done();
});
})
});

View File

@@ -1,6 +1,6 @@
var push = require('../push');
var PushController = require('../src/Controllers/PushController').PushController;
describe('push', () => {
describe('PushController', () => {
it('can check valid master key of request', (done) => {
// Make mock request
var request = {
@@ -13,7 +13,7 @@ describe('push', () => {
}
expect(() => {
push.validateMasterKey(request);
PushController.validateMasterKey(request);
}).not.toThrow();
done();
});
@@ -30,7 +30,7 @@ describe('push', () => {
}
expect(() => {
push.validateMasterKey(request);
PushController.validateMasterKey(request);
}).toThrow();
done();
});
@@ -43,7 +43,7 @@ describe('push', () => {
}
}
var where = push.getQueryCondition(request);
var where = PushController.getQueryCondition(request);
expect(where).toEqual({
'channels': {
'$in': ['Giants', 'Mets']
@@ -62,7 +62,7 @@ describe('push', () => {
}
}
var where = push.getQueryCondition(request);
var where = PushController.getQueryCondition(request);
expect(where).toEqual({
'injuryReports': true
});
@@ -77,7 +77,7 @@ describe('push', () => {
}
expect(function() {
push.getQueryCondition(request);
PushController.getQueryCondition(request);
}).toThrow();
done();
});
@@ -96,7 +96,7 @@ describe('push', () => {
}
expect(function() {
push.getQueryCondition(request);
PushController.getQueryCondition(request);
}).toThrow();
done();
});
@@ -104,10 +104,11 @@ describe('push', () => {
it('can validate device type when no device type is set', (done) => {
// Make query condition
var where = {
}
};
var validPushTypes = ['ios', 'android'];
expect(function(){
push.validateDeviceType(where);
PushController.validatePushType(where, validPushTypes);
}).not.toThrow();
done();
});
@@ -116,10 +117,11 @@ describe('push', () => {
// Make query condition
var where = {
'deviceType': 'ios'
}
};
var validPushTypes = ['ios', 'android'];
expect(function(){
push.validateDeviceType(where);
PushController.validatePushType(where, validPushTypes);
}).not.toThrow();
done();
});
@@ -130,10 +132,11 @@ describe('push', () => {
'deviceType': {
'$in': ['android', 'ios']
}
}
};
var validPushTypes = ['ios', 'android'];
expect(function(){
push.validateDeviceType(where);
PushController.validatePushType(where, validPushTypes);
}).not.toThrow();
done();
});
@@ -142,10 +145,11 @@ describe('push', () => {
// Make query condition
var where = {
'deviceType': 'osx'
}
};
var validPushTypes = ['ios', 'android'];
expect(function(){
push.validateDeviceType(where);
PushController.validatePushType(where, validPushTypes);
}).toThrow();
done();
});
@@ -154,10 +158,11 @@ describe('push', () => {
// Make query condition
var where = {
'deviceType': 'osx'
}
};
var validPushTypes = ['ios', 'android'];
expect(function(){
push.validateDeviceType(where)
PushController.validatePushType(where, validPushTypes);
}).toThrow();
done();
});
@@ -171,7 +176,7 @@ describe('push', () => {
}
}
var time = push.getExpirationTime(request);
var time = PushController.getExpirationTime(request);
expect(time).toEqual(new Date(timeStr).valueOf());
done();
});
@@ -185,7 +190,7 @@ describe('push', () => {
}
}
var time = push.getExpirationTime(request);
var time = PushController.getExpirationTime(request);
expect(time).toEqual(timeNumber * 1000);
done();
});
@@ -199,7 +204,7 @@ describe('push', () => {
}
expect(function(){
push.getExpirationTime(request);
PushController.getExpirationTime(request);
}).toThrow();
done();
});

View File

@@ -1,10 +1,10 @@
// These tests check the "create" functionality of the REST API.
var auth = require('../Auth');
var cache = require('../cache');
var Config = require('../Config');
var DatabaseAdapter = require('../DatabaseAdapter');
var auth = require('../src/Auth');
var cache = require('../src/cache');
var Config = require('../src/Config');
var DatabaseAdapter = require('../src/DatabaseAdapter');
var Parse = require('parse/node').Parse;
var rest = require('../rest');
var rest = require('../src/rest');
var request = require('request');
var config = new Config('test');
@@ -57,6 +57,50 @@ describe('rest create', () => {
});
});
it('handles anonymous user signup', (done) => {
var data1 = {
authData: {
anonymous: {
id: '00000000-0000-0000-0000-000000000001'
}
}
};
var data2 = {
authData: {
anonymous: {
id: '00000000-0000-0000-0000-000000000002'
}
}
};
var username1;
rest.create(config, auth.nobody(config), '_User', data1)
.then((r) => {
expect(typeof r.response.objectId).toEqual('string');
expect(typeof r.response.createdAt).toEqual('string');
expect(typeof r.response.sessionToken).toEqual('string');
return rest.create(config, auth.nobody(config), '_User', data1);
}).then((r) => {
expect(typeof r.response.objectId).toEqual('string');
expect(typeof r.response.createdAt).toEqual('string');
expect(typeof r.response.username).toEqual('string');
expect(typeof r.response.updatedAt).toEqual('string');
username1 = r.response.username;
return rest.create(config, auth.nobody(config), '_User', data2);
}).then((r) => {
expect(typeof r.response.objectId).toEqual('string');
expect(typeof r.response.createdAt).toEqual('string');
expect(typeof r.response.sessionToken).toEqual('string');
return rest.create(config, auth.nobody(config), '_User', data2);
}).then((r) => {
expect(typeof r.response.objectId).toEqual('string');
expect(typeof r.response.createdAt).toEqual('string');
expect(typeof r.response.username).toEqual('string');
expect(typeof r.response.updatedAt).toEqual('string');
expect(r.response.username).not.toEqual(username1);
done();
});
});
it('test facebook signup and login', (done) => {
var data = {
authData: {

View File

@@ -1,8 +1,8 @@
// These tests check the "find" functionality of the REST API.
var auth = require('../Auth');
var cache = require('../cache');
var Config = require('../Config');
var rest = require('../rest');
var auth = require('../src/Auth');
var cache = require('../src/cache');
var Config = require('../src/Config');
var rest = require('../src/rest');
var config = new Config('test');
var nobody = auth.nobody(config);

View File

@@ -1,10 +1,25 @@
// These tests check that the Schema operates correctly.
var Config = require('../Config');
var Schema = require('../Schema');
var Config = require('../src/Config');
var Schema = require('../src/Schema');
var dd = require('deep-diff');
var config = new Config('test');
var hasAllPODobject = () => {
var obj = new Parse.Object('HasAllPOD');
obj.set('aNumber', 5);
obj.set('aString', 'string');
obj.set('aBool', true);
obj.set('aDate', new Date());
obj.set('aObject', {k1: 'value', k2: true, k3: 5});
obj.set('aArray', ['contents', true, 5]);
obj.set('aGeoPoint', new Parse.GeoPoint({latitude: 0, longitude: 0}));
obj.set('aFile', new Parse.File('f.txt', { base64: 'V29ya2luZyBhdCBQYXJzZSBpcyBncmVhdCE=' }));
var objACL = new Parse.ACL();
objACL.setPublicWriteAccess(false);
obj.setACL(objACL);
return obj;
};
describe('Schema', () => {
it('can validate one object', (done) => {
config.database.loadSchema().then((schema) => {
@@ -252,7 +267,7 @@ describe('Schema', () => {
it('refuses to add fields with invalid pointer types', done => {
config.database.loadSchema()
.then(schema => schema.addClassIfNotExists('NewClass', {
foo: {type: 'Pointer'},
foo: {type: 'Pointer'}
}))
.catch(error => {
expect(error.code).toEqual(135);
@@ -398,7 +413,7 @@ describe('Schema', () => {
config.database.loadSchema()
.then(schema => schema.addClassIfNotExists('NewClass', {
geo1: {type: 'GeoPoint'},
geo2: {type: 'GeoPoint'},
geo2: {type: 'GeoPoint'}
}))
.catch(error => {
expect(error.code).toEqual(Parse.Error.INCORRECT_TYPE);
@@ -406,4 +421,153 @@ describe('Schema', () => {
done();
});
});
it('can check if a class exists', done => {
config.database.loadSchema()
.then(schema => {
return schema.addClassIfNotExists('NewClass', {})
.then(() => {
schema.hasClass('NewClass')
.then(hasClass => {
expect(hasClass).toEqual(true);
done();
})
.catch(fail);
schema.hasClass('NonexistantClass')
.then(hasClass => {
expect(hasClass).toEqual(false);
done();
})
.catch(fail);
})
.catch(error => {
fail('Couldn\'t create class');
fail(error);
});
})
.catch(error => fail('Couldn\'t load schema'));
});
it('refuses to delete fields from invalid class names', done => {
config.database.loadSchema()
.then(schema => schema.deleteField('fieldName', 'invalid class name'))
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
done();
});
});
it('refuses to delete invalid fields', done => {
config.database.loadSchema()
.then(schema => schema.deleteField('invalid field name', 'ValidClassName'))
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_KEY_NAME);
done();
});
});
it('refuses to delete the default fields', done => {
config.database.loadSchema()
.then(schema => schema.deleteField('installationId', '_Installation'))
.catch(error => {
expect(error.code).toEqual(136);
expect(error.error).toEqual('field installationId cannot be changed');
done();
});
});
it('refuses to delete fields from nonexistant classes', done => {
config.database.loadSchema()
.then(schema => schema.deleteField('field', 'NoClass'))
.catch(error => {
expect(error.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
expect(error.error).toEqual('class NoClass does not exist');
done();
});
});
it('refuses to delete fields that dont exist', done => {
hasAllPODobject().save()
.then(() => config.database.loadSchema())
.then(schema => schema.deleteField('missingField', 'HasAllPOD'))
.fail(error => {
expect(error.code).toEqual(255);
expect(error.error).toEqual('field missingField does not exist, cannot delete');
done();
});
});
it('drops related collection when deleting relation field', done => {
var obj1 = hasAllPODobject();
obj1.save()
.then(savedObj1 => {
var obj2 = new Parse.Object('HasPointersAndRelations');
obj2.set('aPointer', savedObj1);
var relation = obj2.relation('aRelation');
relation.add(obj1);
return obj2.save();
})
.then(() => {
config.database.db.collection('test__Join:aRelation:HasPointersAndRelations', { strict: true }, (err, coll) => {
expect(err).toEqual(null);
config.database.loadSchema()
.then(schema => schema.deleteField('aRelation', 'HasPointersAndRelations', config.database.db, 'test_'))
.then(() => config.database.db.collection('test__Join:aRelation:HasPointersAndRelations', { strict: true }, (err, coll) => {
expect(err).not.toEqual(null);
done();
}))
});
})
});
it('can delete string fields and resave as number field', done => {
Parse.Object.disableSingleInstance();
var obj1 = hasAllPODobject();
var obj2 = hasAllPODobject();
var p = Parse.Object.saveAll([obj1, obj2])
.then(() => config.database.loadSchema())
.then(schema => schema.deleteField('aString', 'HasAllPOD', config.database.db, 'test_'))
.then(() => new Parse.Query('HasAllPOD').get(obj1.id))
.then(obj1Reloaded => {
expect(obj1Reloaded.get('aString')).toEqual(undefined);
obj1Reloaded.set('aString', ['not a string', 'this time']);
obj1Reloaded.save()
.then(obj1reloadedAgain => {
expect(obj1reloadedAgain.get('aString')).toEqual(['not a string', 'this time']);
return new Parse.Query('HasAllPOD').get(obj2.id);
})
.then(obj2reloaded => {
expect(obj2reloaded.get('aString')).toEqual(undefined);
done();
Parse.Object.enableSingleInstance();
});
})
});
it('can delete pointer fields and resave as string', done => {
Parse.Object.disableSingleInstance();
var obj1 = new Parse.Object('NewClass');
obj1.save()
.then(() => {
obj1.set('aPointer', obj1);
return obj1.save();
})
.then(obj1 => {
expect(obj1.get('aPointer').id).toEqual(obj1.id);
})
.then(() => config.database.loadSchema())
.then(schema => schema.deleteField('aPointer', 'NewClass', config.database.db, 'test_'))
.then(() => new Parse.Query('NewClass').get(obj1.id))
.then(obj1 => {
expect(obj1.get('aPointer')).toEqual(undefined);
obj1.set('aPointer', 'Now a string');
return obj1.save();
})
.then(obj1 => {
expect(obj1.get('aPointer')).toEqual('Now a string');
done();
Parse.Object.enableSingleInstance();
});
});
});

83
spec/cryptoUtils.spec.js Normal file
View File

@@ -0,0 +1,83 @@
var cryptoUtils = require('../src/cryptoUtils');
function givesUniqueResults(fn, iterations) {
var results = {};
for (var i = 0; i < iterations; i++) {
var s = fn();
if (results[s]) {
return false;
}
results[s] = true;
}
return true;
}
describe('randomString', () => {
it('returns a string', () => {
expect(typeof cryptoUtils.randomString(10)).toBe('string');
});
it('returns result of the given length', () => {
expect(cryptoUtils.randomString(11).length).toBe(11);
expect(cryptoUtils.randomString(25).length).toBe(25);
});
it('throws if requested length is zero', () => {
expect(() => cryptoUtils.randomString(0)).toThrow();
});
it('returns unique results', () => {
expect(givesUniqueResults(() => cryptoUtils.randomString(10), 100)).toBe(true);
});
});
describe('randomHexString', () => {
it('returns a string', () => {
expect(typeof cryptoUtils.randomHexString(10)).toBe('string');
});
it('returns result of the given length', () => {
expect(cryptoUtils.randomHexString(10).length).toBe(10);
expect(cryptoUtils.randomHexString(32).length).toBe(32);
});
it('throws if requested length is zero', () => {
expect(() => cryptoUtils.randomHexString(0)).toThrow();
});
it('throws if requested length is not even', () => {
expect(() => cryptoUtils.randomHexString(11)).toThrow();
});
it('returns unique results', () => {
expect(givesUniqueResults(() => cryptoUtils.randomHexString(20), 100)).toBe(true);
});
});
describe('newObjectId', () => {
it('returns a string', () => {
expect(typeof cryptoUtils.newObjectId()).toBe('string');
});
it('returns result with at least 10 characters', () => {
expect(cryptoUtils.newObjectId().length).toBeGreaterThan(9);
});
it('returns unique results', () => {
expect(givesUniqueResults(() => cryptoUtils.newObjectId(), 100)).toBe(true);
});
});
describe('newToken', () => {
it('returns a string', () => {
expect(typeof cryptoUtils.newToken()).toBe('string');
});
it('returns result with at least 32 characters', () => {
expect(cryptoUtils.newToken().length).toBeGreaterThan(31);
});
it('returns unique results', () => {
expect(givesUniqueResults(() => cryptoUtils.newToken(), 100)).toBe(true);
});
});

View File

@@ -2,11 +2,11 @@
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
var cache = require('../cache');
var DatabaseAdapter = require('../DatabaseAdapter');
var cache = require('../src/cache');
var DatabaseAdapter = require('../src/DatabaseAdapter');
var express = require('express');
var facebook = require('../facebook');
var ParseServer = require('../index').ParseServer;
var facebook = require('../src/facebook');
var ParseServer = require('../src/index').ParseServer;
var databaseURI = process.env.DATABASE_URI;
var cloudMain = process.env.CLOUD_CODE_MAIN || './cloud/main.js';

View File

@@ -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);
@@ -14,9 +16,9 @@ var hasAllPODobject = () => {
objACL.setPublicWriteAccess(false);
obj.setACL(objACL);
return obj;
}
};
var expectedResponseForHasAllPOD = {
var plainOldDataSchema = {
className: 'HasAllPOD',
fields: {
//Default fields
@@ -33,10 +35,10 @@ var expectedResponseForHasAllPOD = {
aArray: {type: 'Array'},
aGeoPoint: {type: 'GeoPoint'},
aFile: {type: 'File'}
},
}
};
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();
});
});
});

View File

@@ -4,7 +4,7 @@
"*spec.js"
],
"helpers": [
"../node_modules/babel-core/register.js",
"helper.js"
]
}

View File

@@ -1,6 +1,6 @@
// These tests are unit tests designed to only test transform.js.
var transform = require('../transform');
var transform = require('../src/transform');
var dummySchema = {
data: {},