Merge remote-tracking branch 'ParsePlatform/master' into user-roles
This commit is contained in:
307
spec/OAuth.spec.js
Normal file
307
spec/OAuth.spec.js
Normal file
@@ -0,0 +1,307 @@
|
||||
var OAuth = require("../src/oauth/OAuth1Client");
|
||||
var request = require('request');
|
||||
|
||||
describe('OAuth', function() {
|
||||
|
||||
it("Nonce should have right length", (done) => {
|
||||
jequal(OAuth.nonce().length, 30);
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString({c:1, a:2, b:3})
|
||||
jequal(string, "a=2&b=3&c=1");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build empty parameter string", (done) => {
|
||||
var string = OAuth.buildParameterString()
|
||||
jequal(string, "");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build signature string", (done) => {
|
||||
var string = OAuth.buildSignatureString("get", "http://dummy.com", "");
|
||||
jequal(string, "GET&http%3A%2F%2Fdummy.com&");
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly generate request signature", (done) => {
|
||||
var request = {
|
||||
host: "dummy.com",
|
||||
path: "path"
|
||||
};
|
||||
|
||||
var oauth_params = {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA",
|
||||
oauth_consumer_key: "hello",
|
||||
oauth_token: "token"
|
||||
};
|
||||
|
||||
var consumer_secret = "world";
|
||||
var auth_token_secret = "secret";
|
||||
request = OAuth.signRequest(request, oauth_params, consumer_secret, auth_token_secret);
|
||||
jequal(request.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="8K95bpQcDi9Nd2GkhumTVcw4%2BXw%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"');
|
||||
done();
|
||||
});
|
||||
|
||||
it("Should properly build request", (done) => {
|
||||
var options = {
|
||||
host: "dummy.com",
|
||||
consumer_key: "hello",
|
||||
consumer_secret: "world",
|
||||
auth_token: "token",
|
||||
auth_token_secret: "secret",
|
||||
// Custom oauth params for tests
|
||||
oauth_params: {
|
||||
oauth_timestamp: 123450000,
|
||||
oauth_nonce: "AAAAAAAAAAAAAAAAA"
|
||||
}
|
||||
};
|
||||
var path = "path";
|
||||
var method = "get";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
var req = oauthClient.buildRequest(method, path, {"query": "param"});
|
||||
|
||||
jequal(req.host, options.host);
|
||||
jequal(req.path, "/"+path+"?query=param");
|
||||
jequal(req.method, "GET");
|
||||
jequal(req.headers['Content-Type'], 'application/x-www-form-urlencoded');
|
||||
jequal(req.headers['Authorization'], 'OAuth oauth_consumer_key="hello", oauth_nonce="AAAAAAAAAAAAAAAAA", oauth_signature="wNkyEkDE%2F0JZ2idmqyrgHdvC0rs%3D", oauth_signature_method="HMAC-SHA1", oauth_timestamp="123450000", oauth_token="token", oauth_version="1.0"')
|
||||
done();
|
||||
});
|
||||
|
||||
|
||||
function validateCannotAuthenticateError(data, done) {
|
||||
jequal(typeof data, "object");
|
||||
jequal(typeof data.errors, "object");
|
||||
var errors = data.errors;
|
||||
jequal(typeof errors[0], "object");
|
||||
// Cannot authenticate error
|
||||
jequal(errors[0].code, 32);
|
||||
done();
|
||||
}
|
||||
|
||||
it("Should fail a GET request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var path = "/1.1/help/configuration.json";
|
||||
var params = {"lang": "en"};
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.get(path, params).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a POST request", (done) => {
|
||||
var options = {
|
||||
host: "api.twitter.com",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/1.1/account/settings.json";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
validateCannotAuthenticateError(data, done);
|
||||
})
|
||||
});
|
||||
|
||||
it("Should fail a request", (done) => {
|
||||
var options = {
|
||||
host: "localhost",
|
||||
consumer_key: "XXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
};
|
||||
var body = {
|
||||
lang: "en"
|
||||
};
|
||||
var path = "/";
|
||||
|
||||
var oauthClient = new OAuth(options);
|
||||
oauthClient.post(path, null, body).then(function(data){
|
||||
jequal(false, true);
|
||||
done();
|
||||
}).catch(function(){
|
||||
jequal(true, true);
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
["facebook", "github", "instagram", "google", "linkedin", "meetup", "twitter"].map(function(providerName){
|
||||
it("Should validate structure of "+providerName, (done) => {
|
||||
var provider = require("../src/oauth/"+providerName);
|
||||
jequal(typeof provider.validateAuthData, "function");
|
||||
jequal(typeof provider.validateAppId, "function");
|
||||
jequal(provider.validateAuthData({}, {}).constructor, Promise.prototype.constructor);
|
||||
jequal(provider.validateAppId("app", "key", {}).constructor, Promise.prototype.constructor);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var getMockMyOauthProvider = function() {
|
||||
return {
|
||||
authData: {
|
||||
id: "12345",
|
||||
access_token: "12345",
|
||||
expiration_date: new Date().toJSON(),
|
||||
},
|
||||
shouldError: false,
|
||||
loggedOut: false,
|
||||
synchronizedUserId: null,
|
||||
synchronizedAuthToken: null,
|
||||
synchronizedExpiration: null,
|
||||
|
||||
authenticate: function(options) {
|
||||
if (this.shouldError) {
|
||||
options.error(this, "An error occurred");
|
||||
} else if (this.shouldCancel) {
|
||||
options.error(this, null);
|
||||
} else {
|
||||
options.success(this, this.authData);
|
||||
}
|
||||
},
|
||||
restoreAuthentication: function(authData) {
|
||||
if (!authData) {
|
||||
this.synchronizedUserId = null;
|
||||
this.synchronizedAuthToken = null;
|
||||
this.synchronizedExpiration = null;
|
||||
return true;
|
||||
}
|
||||
this.synchronizedUserId = authData.id;
|
||||
this.synchronizedAuthToken = authData.access_token;
|
||||
this.synchronizedExpiration = authData.expiration_date;
|
||||
return true;
|
||||
},
|
||||
getAuthType: function() {
|
||||
return "myoauth";
|
||||
},
|
||||
deauthenticate: function() {
|
||||
this.loggedOut = true;
|
||||
this.restoreAuthentication(null);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var ExtendedUser = Parse.User.extend({
|
||||
extended: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
var createOAuthUser = function(callback) {
|
||||
var jsonBody = {
|
||||
authData: {
|
||||
myoauth: getMockMyOauthProvider().authData
|
||||
}
|
||||
};
|
||||
var headers = {'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json' }
|
||||
|
||||
var options = {
|
||||
headers: {'X-Parse-Application-Id': 'test',
|
||||
'X-Parse-REST-API-Key': 'rest',
|
||||
'Content-Type': 'application/json' },
|
||||
url: 'http://localhost:8378/1/users',
|
||||
body: JSON.stringify(jsonBody)
|
||||
};
|
||||
|
||||
return request.post(options, callback);
|
||||
}
|
||||
|
||||
it("should create user with REST API", (done) => {
|
||||
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("should only create a single user with REST API", (done) => {
|
||||
var objectId;
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
objectId = b.objectId;
|
||||
|
||||
createOAuthUser((error, response, body) => {
|
||||
expect(error).toBe(null);
|
||||
var b = JSON.parse(body);
|
||||
expect(b.objectId).not.toBeNull();
|
||||
expect(b.objectId).not.toBeUndefined();
|
||||
expect(b.objectId).toBe(objectId);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it("unlink and link with custom provider", (done) => {
|
||||
var provider = getMockMyOauthProvider();
|
||||
Parse.User._registerAuthenticationProvider(provider);
|
||||
Parse.User._logInWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used the subclass.");
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("myoauth"), "User should be linked to myoauth");
|
||||
|
||||
model._unlinkFrom("myoauth", {
|
||||
success: function(model) {
|
||||
ok(!model._isLinked("myoauth"),
|
||||
"User should not be linked to myoauth");
|
||||
ok(!provider.synchronizedUserId, "User id should be cleared");
|
||||
ok(!provider.synchronizedAuthToken, "Auth token should be cleared");
|
||||
ok(!provider.synchronizedExpiration,
|
||||
"Expiration should be cleared");
|
||||
|
||||
model._linkWith("myoauth", {
|
||||
success: function(model) {
|
||||
ok(provider.synchronizedUserId, "User id should have a value");
|
||||
ok(provider.synchronizedAuthToken,
|
||||
"Auth token should have a value");
|
||||
ok(provider.synchronizedExpiration,
|
||||
"Expiration should have a value");
|
||||
ok(model._isLinked("myoauth"),
|
||||
"User should be linked to myoauth");
|
||||
done();
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "linking again should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "unlinking should succeed");
|
||||
done();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function(model, error) {
|
||||
ok(false, "linking should have worked");
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
@@ -831,9 +831,11 @@ describe('Parse.User testing', () => {
|
||||
// server-side.
|
||||
var getMockFacebookProvider = function() {
|
||||
return {
|
||||
userId: "8675309",
|
||||
authToken: "jenny",
|
||||
expiration: new Date().toJSON(),
|
||||
authData: {
|
||||
id: "8675309",
|
||||
access_token: "jenny",
|
||||
expiration_date: new Date().toJSON(),
|
||||
},
|
||||
shouldError: false,
|
||||
loggedOut: false,
|
||||
synchronizedUserId: null,
|
||||
@@ -846,11 +848,7 @@ describe('Parse.User testing', () => {
|
||||
} else if (this.shouldCancel) {
|
||||
options.error(this, null);
|
||||
} else {
|
||||
options.success(this, {
|
||||
id: this.userId,
|
||||
access_token: this.authToken,
|
||||
expiration_date: this.expiration
|
||||
});
|
||||
options.success(this, this.authData);
|
||||
}
|
||||
},
|
||||
restoreAuthentication: function(authData) {
|
||||
@@ -889,13 +887,14 @@ describe('Parse.User testing', () => {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used subclass.");
|
||||
strictEqual(provider.userId, provider.synchronizedUserId);
|
||||
strictEqual(provider.authToken, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.expiration, provider.synchronizedExpiration);
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||
done();
|
||||
},
|
||||
error: function(model, error) {
|
||||
console.error(model, error);
|
||||
ok(false, "linking should have worked");
|
||||
done();
|
||||
}
|
||||
@@ -910,9 +909,9 @@ describe('Parse.User testing', () => {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used the subclass.");
|
||||
strictEqual(provider.userId, provider.synchronizedUserId);
|
||||
strictEqual(provider.authToken, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.expiration, provider.synchronizedExpiration);
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||
|
||||
Parse.User.logOut();
|
||||
@@ -925,20 +924,22 @@ describe('Parse.User testing', () => {
|
||||
"Model should be a Parse.User");
|
||||
ok(innerModel === Parse.User.current(),
|
||||
"Returned model should be the current user");
|
||||
ok(provider.userId === provider.synchronizedUserId);
|
||||
ok(provider.authToken === provider.synchronizedAuthToken);
|
||||
ok(provider.authData.id === provider.synchronizedUserId);
|
||||
ok(provider.authData.access_token === provider.synchronizedAuthToken);
|
||||
ok(innerModel._isLinked("facebook"),
|
||||
"User should be linked to facebook");
|
||||
ok(innerModel.existed(), "User should not be newly-created");
|
||||
done();
|
||||
},
|
||||
error: function(model, error) {
|
||||
fail(error);
|
||||
ok(false, "LogIn should have worked");
|
||||
done();
|
||||
}
|
||||
});
|
||||
},
|
||||
error: function(model, error) {
|
||||
console.error(model, error);
|
||||
ok(false, "LogIn should have worked");
|
||||
done();
|
||||
}
|
||||
@@ -987,9 +988,9 @@ describe('Parse.User testing', () => {
|
||||
success: function(model) {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
strictEqual(provider.userId, provider.synchronizedUserId);
|
||||
strictEqual(provider.authToken, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.expiration, provider.synchronizedExpiration);
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("facebook"), "User should be linked");
|
||||
done();
|
||||
},
|
||||
@@ -1020,9 +1021,9 @@ describe('Parse.User testing', () => {
|
||||
success: function(model) {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
strictEqual(provider.userId, provider.synchronizedUserId);
|
||||
strictEqual(provider.authToken, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.expiration, provider.synchronizedExpiration);
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("facebook"), "User should be linked.");
|
||||
var user2 = new Parse.User();
|
||||
user2.set("username", "testLinkWithProviderToAlreadyLinkedUser2");
|
||||
@@ -1123,9 +1124,9 @@ describe('Parse.User testing', () => {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User.");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used the subclass.");
|
||||
strictEqual(provider.userId, provider.synchronizedUserId);
|
||||
strictEqual(provider.authToken, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.expiration, provider.synchronizedExpiration);
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("facebook"), "User should be linked to facebook.");
|
||||
|
||||
model._unlinkFrom("facebook", {
|
||||
@@ -1159,9 +1160,9 @@ describe('Parse.User testing', () => {
|
||||
ok(model instanceof Parse.User, "Model should be a Parse.User");
|
||||
strictEqual(Parse.User.current(), model);
|
||||
ok(model.extended(), "Should have used the subclass.");
|
||||
strictEqual(provider.userId, provider.synchronizedUserId);
|
||||
strictEqual(provider.authToken, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.expiration, provider.synchronizedExpiration);
|
||||
strictEqual(provider.authData.id, provider.synchronizedUserId);
|
||||
strictEqual(provider.authData.access_token, provider.synchronizedAuthToken);
|
||||
strictEqual(provider.authData.expiration_date, provider.synchronizedExpiration);
|
||||
ok(model._isLinked("facebook"), "User should be linked to facebook");
|
||||
|
||||
model._unlinkFrom("facebook", {
|
||||
@@ -1367,7 +1368,7 @@ describe('Parse.User testing', () => {
|
||||
var b = JSON.parse(body);
|
||||
expect(b.results.length).toEqual(1);
|
||||
var user = b.results[0];
|
||||
expect(Object.keys(user).length).toEqual(7);
|
||||
expect(Object.keys(user).length).toEqual(6);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,6 +100,25 @@ describe('rest create', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles no anonymous users config', (done) => {
|
||||
var NoAnnonConfig = Object.assign({}, config, {enableAnonymousUsers: false});
|
||||
var data1 = {
|
||||
authData: {
|
||||
anonymous: {
|
||||
id: '00000000-0000-0000-0000-000000000001'
|
||||
}
|
||||
}
|
||||
};
|
||||
rest.create(NoAnnonConfig, auth.nobody(NoAnnonConfig), '_User', data1).then(() => {
|
||||
fail("Should throw an error");
|
||||
done();
|
||||
}, (err) => {
|
||||
expect(err.code).toEqual(Parse.Error.UNSUPPORTED_SERVICE);
|
||||
expect(err.message).toEqual('This authentication method is unsupported.');
|
||||
done();
|
||||
})
|
||||
});
|
||||
|
||||
it('test facebook signup and login', (done) => {
|
||||
var data = {
|
||||
|
||||
@@ -162,6 +162,9 @@ describe('Schema', () => {
|
||||
foo: 'string',
|
||||
})
|
||||
done();
|
||||
})
|
||||
.catch(error => {
|
||||
fail('Error creating class: ' + JSON.stringify(error));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -570,4 +573,32 @@ describe('Schema', () => {
|
||||
Parse.Object.enableSingleInstance();
|
||||
});
|
||||
});
|
||||
|
||||
it('can merge schemas', done => {
|
||||
expect(Schema.buildMergedSchemaObject({
|
||||
_id: 'SomeClass',
|
||||
someType: 'number'
|
||||
}, {
|
||||
newType: {type: 'Number'}
|
||||
})).toEqual({
|
||||
someType: {type: 'Number'},
|
||||
newType: {type: 'Number'},
|
||||
});
|
||||
done();
|
||||
});
|
||||
|
||||
it('can merge deletions', done => {
|
||||
expect(Schema.buildMergedSchemaObject({
|
||||
_id: 'SomeClass',
|
||||
someType: 'number',
|
||||
outDatedType: 'string',
|
||||
},{
|
||||
newType: {type: 'GeoPoint'},
|
||||
outDatedType: {__op: 'Delete'},
|
||||
})).toEqual({
|
||||
someType: {type: 'Number'},
|
||||
newType: {type: 'GeoPoint'},
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
|
||||
var cache = require('../src/cache');
|
||||
var DatabaseAdapter = require('../src/DatabaseAdapter');
|
||||
var express = require('express');
|
||||
var facebook = require('../src/facebook');
|
||||
var facebook = require('../src/oauth/facebook');
|
||||
var ParseServer = require('../src/index').ParseServer;
|
||||
|
||||
var databaseURI = process.env.DATABASE_URI;
|
||||
@@ -22,7 +22,13 @@ var api = new ParseServer({
|
||||
restAPIKey: 'rest',
|
||||
masterKey: 'test',
|
||||
collectionPrefix: 'test_',
|
||||
fileKey: 'test'
|
||||
fileKey: 'test',
|
||||
oauth: { // Override the facebook provider
|
||||
facebook: mockFacebook(),
|
||||
myoauth: {
|
||||
module: "../spec/myoauth" // relative path as it's run from src
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var app = express();
|
||||
@@ -40,7 +46,6 @@ Parse.Promise.disableAPlusCompliant();
|
||||
|
||||
beforeEach(function(done) {
|
||||
Parse.initialize('test', 'test', 'test');
|
||||
mockFacebook();
|
||||
Parse.User.enableUnsafeCurrentUser();
|
||||
done();
|
||||
});
|
||||
@@ -175,18 +180,20 @@ function range(n) {
|
||||
}
|
||||
|
||||
function mockFacebook() {
|
||||
facebook.validateUserId = function(userId, accessToken) {
|
||||
if (userId === '8675309' && accessToken === 'jenny') {
|
||||
var facebook = {};
|
||||
facebook.validateAuthData = function(authData) {
|
||||
if (authData.id === '8675309' && authData.access_token === 'jenny') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
};
|
||||
facebook.validateAppId = function(appId, accessToken) {
|
||||
if (accessToken === 'jenny') {
|
||||
facebook.validateAppId = function(appId, authData) {
|
||||
if (authData.access_token === 'jenny') {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
};
|
||||
return facebook;
|
||||
}
|
||||
|
||||
function clearData() {
|
||||
|
||||
17
spec/myoauth.js
Normal file
17
spec/myoauth.js
Normal file
@@ -0,0 +1,17 @@
|
||||
// Custom oauth provider by module
|
||||
|
||||
// Returns a promise that fulfills iff this user id is valid.
|
||||
function validateAuthData(authData) {
|
||||
if (authData.id == "12345" && authData.access_token == "12345") {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject();
|
||||
}
|
||||
function validateAppId() {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAppId: validateAppId,
|
||||
validateAuthData: validateAuthData
|
||||
};
|
||||
@@ -94,7 +94,7 @@ describe('schemas', () => {
|
||||
headers: restKeyHeaders,
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(401);
|
||||
expect(body.error).toEqual('unauthorized');
|
||||
expect(body.error).toEqual('master key not specified');
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -318,4 +318,319 @@ describe('schemas', () => {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('requires the master key to modify schemas', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {},
|
||||
}, (error, response, body) => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||
headers: noAuthHeaders,
|
||||
json: true,
|
||||
body: {},
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(403);
|
||||
expect(body.error).toEqual('unauthorized');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects class name mis-matches in put', done => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {className: 'WrongClassName'}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
|
||||
expect(body.error).toEqual('class name mismatch between WrongClassName and NewClass');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses to add fields to non-existent classes', done => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/NoClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
newField: {type: 'String'}
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(Parse.Error.INVALID_CLASS_NAME);
|
||||
expect(body.error).toEqual('class NoClass does not exist');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses to put to existing fields, even if it would not be a change', done => {
|
||||
var obj = hasAllPODobject();
|
||||
obj.save()
|
||||
.then(() => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
aString: {type: 'String'}
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(255);
|
||||
expect(body.error).toEqual('field aString exists, cannot update');
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('refuses to delete non-existant fields', done => {
|
||||
var obj = hasAllPODobject();
|
||||
obj.save()
|
||||
.then(() => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
nonExistantKey: {__op: "Delete"},
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(255);
|
||||
expect(body.error).toEqual('field nonExistantKey does not exist, cannot delete');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses to add a geopoint to a class that already has one', done => {
|
||||
var obj = hasAllPODobject();
|
||||
obj.save()
|
||||
.then(() => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
newGeo: {type: 'GeoPoint'}
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
expect(body.error).toEqual('currently, only one GeoPoint field may exist in an object. Adding newGeo when aGeoPoint already exists.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses to add two geopoints', done => {
|
||||
var obj = new Parse.Object('NewClass');
|
||||
obj.set('aString', 'aString');
|
||||
obj.save()
|
||||
.then(() => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
newGeo1: {type: 'GeoPoint'},
|
||||
newGeo2: {type: 'GeoPoint'},
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(response.statusCode).toEqual(400);
|
||||
expect(body.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
expect(body.error).toEqual('currently, only one GeoPoint field may exist in an object. Adding newGeo2 when newGeo1 already exists.');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('allows you to delete and add a geopoint in the same request', done => {
|
||||
var obj = new Parse.Object('NewClass');
|
||||
obj.set('geo1', new Parse.GeoPoint({latitude: 0, longitude: 0}));
|
||||
obj.save()
|
||||
.then(() => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
geo2: {type: 'GeoPoint'},
|
||||
geo1: {__op: 'Delete'}
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(dd(body, {
|
||||
"className": "NewClass",
|
||||
"fields": {
|
||||
"ACL": {"type": "ACL"},
|
||||
"createdAt": {"type": "Date"},
|
||||
"objectId": {"type": "String"},
|
||||
"updatedAt": {"type": "Date"},
|
||||
"geo2": {"type": "GeoPoint"},
|
||||
}
|
||||
})).toEqual(undefined);
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('put with no modifications returns all fields', done => {
|
||||
var obj = hasAllPODobject();
|
||||
obj.save()
|
||||
.then(() => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {},
|
||||
}, (error, response, body) => {
|
||||
expect(body).toEqual(plainOldDataSchema);
|
||||
done();
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('lets you add fields', done => {
|
||||
request.post({
|
||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {},
|
||||
}, (error, response, body) => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
newField: {type: 'String'}
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(dd(body, {
|
||||
className: 'NewClass',
|
||||
fields: {
|
||||
"ACL": {"type": "ACL"},
|
||||
"createdAt": {"type": "Date"},
|
||||
"objectId": {"type": "String"},
|
||||
"updatedAt": {"type": "Date"},
|
||||
"newField": {"type": "String"},
|
||||
},
|
||||
})).toEqual(undefined);
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/schemas/NewClass',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
expect(body).toEqual({
|
||||
className: 'NewClass',
|
||||
fields: {
|
||||
ACL: {type: 'ACL'},
|
||||
createdAt: {type: 'Date'},
|
||||
updatedAt: {type: 'Date'},
|
||||
objectId: {type: 'String'},
|
||||
newField: {type: 'String'},
|
||||
}
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
it('lets you delete multiple fields and add fields', done => {
|
||||
var obj1 = hasAllPODobject();
|
||||
obj1.save()
|
||||
.then(() => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
aString: {__op: 'Delete'},
|
||||
aNumber: {__op: 'Delete'},
|
||||
aNewString: {type: 'String'},
|
||||
aNewNumber: {type: 'Number'},
|
||||
aNewRelation: {type: 'Relation', targetClass: 'HasAllPOD'},
|
||||
aNewPointer: {type: 'Pointer', targetClass: 'HasAllPOD'},
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(body).toEqual({
|
||||
className: 'HasAllPOD',
|
||||
fields: {
|
||||
//Default fields
|
||||
ACL: {type: 'ACL'},
|
||||
createdAt: {type: 'Date'},
|
||||
updatedAt: {type: 'Date'},
|
||||
objectId: {type: 'String'},
|
||||
//Custom fields
|
||||
aBool: {type: 'Boolean'},
|
||||
aDate: {type: 'Date'},
|
||||
aObject: {type: 'Object'},
|
||||
aArray: {type: 'Array'},
|
||||
aGeoPoint: {type: 'GeoPoint'},
|
||||
aFile: {type: 'File'},
|
||||
aNewNumber: {type: 'Number'},
|
||||
aNewString: {type: 'String'},
|
||||
aNewPointer: {type: 'Pointer', targetClass: 'HasAllPOD'},
|
||||
aNewRelation: {type: 'Relation', targetClass: 'HasAllPOD'},
|
||||
}
|
||||
});
|
||||
var obj2 = new Parse.Object('HasAllPOD');
|
||||
obj2.set('aNewPointer', obj1);
|
||||
var relation = obj2.relation('aNewRelation');
|
||||
relation.add(obj1);
|
||||
obj2.save().then(done); //Just need to make sure saving works on the new object.
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('will not delete any fields if the additions are invalid', done => {
|
||||
var obj = hasAllPODobject();
|
||||
obj.save()
|
||||
.then(() => {
|
||||
request.put({
|
||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
body: {
|
||||
fields: {
|
||||
fakeNewField: {type: 'fake type'},
|
||||
aString: {__op: 'Delete'}
|
||||
}
|
||||
}
|
||||
}, (error, response, body) => {
|
||||
expect(body.code).toEqual(Parse.Error.INCORRECT_TYPE);
|
||||
expect(body.error).toEqual('invalid field type: fake type');
|
||||
request.get({
|
||||
url: 'http://localhost:8378/1/schemas/HasAllPOD',
|
||||
headers: masterKeyHeaders,
|
||||
json: true,
|
||||
}, (error, response, body) => {
|
||||
expect(response.body).toEqual(plainOldDataSchema);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user